mirror of
https://github.com/zyedidia/micro.git
synced 2026-03-31 15:17:15 +09:00
Compare commits
181 Commits
v2.0.0-rc1
...
dbeef-mast
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
98ddb62af4 | ||
|
|
24a684cff2 | ||
|
|
b4e7e981f3 | ||
|
|
e73549c61a | ||
|
|
e759d4087b | ||
|
|
106ba48079 | ||
|
|
ef768e36f3 | ||
|
|
f5e1f93ee5 | ||
|
|
a52c0c2907 | ||
|
|
be7d27bc49 | ||
|
|
f6a9c482a6 | ||
|
|
6e3f38b271 | ||
|
|
8483f1da1e | ||
|
|
28ed47e358 | ||
|
|
6a1b8f4a4f | ||
|
|
dba8ef2fdd | ||
|
|
b0624cb66e | ||
|
|
09ea82c97e | ||
|
|
d94b81b8e6 | ||
|
|
bcb1947a0a | ||
|
|
b0b5d7b392 | ||
|
|
2598d8ad70 | ||
|
|
f731e422ea | ||
|
|
d326a9cddd | ||
|
|
e3131a0779 | ||
|
|
46c5a81b0d | ||
|
|
59146cabb1 | ||
|
|
35e3bddea0 | ||
|
|
016b8dcc4c | ||
|
|
03228762d4 | ||
|
|
953f5a0eff | ||
|
|
477bdb3dc8 | ||
|
|
d74f40882d | ||
|
|
d965e8de4f | ||
|
|
866b3c9238 | ||
|
|
3252324d24 | ||
|
|
8e7a016917 | ||
|
|
cf41a587a3 | ||
|
|
1dc1c65565 | ||
|
|
97ee344268 | ||
|
|
b658f94e5a | ||
|
|
0abe427026 | ||
|
|
b3e40a2644 | ||
|
|
fa4103f7aa | ||
|
|
17f0eb80cd | ||
|
|
35dfb1830b | ||
|
|
76f09adb48 | ||
|
|
289c7147e4 | ||
|
|
be34e1241c | ||
|
|
83ea8d8be9 | ||
|
|
7c90f4d6f1 | ||
|
|
61a90f7666 | ||
|
|
8d373cde6e | ||
|
|
6a465500bc | ||
|
|
f3e8413e77 | ||
|
|
f2a1e2337f | ||
|
|
c7f36f9480 | ||
|
|
955bde4abc | ||
|
|
afb03aa37f | ||
|
|
6c3814dfac | ||
|
|
d234e9ec41 | ||
|
|
c2c0325384 | ||
|
|
dfb6bc0312 | ||
|
|
0c6a7e2837 | ||
|
|
ddc8bf455e | ||
|
|
2855ae204c | ||
|
|
0bf54ff0e7 | ||
|
|
50ff45c213 | ||
|
|
eb2b546600 | ||
|
|
dc4da37908 | ||
|
|
9333354fc8 | ||
|
|
b557ed2221 | ||
|
|
021f8da6f1 | ||
|
|
6d0128059b | ||
|
|
d6dd838abd | ||
|
|
938fb7983a | ||
|
|
d9e262c394 | ||
|
|
e98be1a1e5 | ||
|
|
41fe7d090e | ||
|
|
aadf5b40ec | ||
|
|
ebf6d69f26 | ||
|
|
ebf616399e | ||
|
|
08708f79bf | ||
|
|
d7b39fe7a5 | ||
|
|
48ace1c530 | ||
|
|
fde1cc563f | ||
|
|
abf07a8357 | ||
|
|
a2f7080602 | ||
|
|
a2916c0e32 | ||
|
|
0301e3539e | ||
|
|
6632ab0a77 | ||
|
|
466c48da31 | ||
|
|
2c72a3755c | ||
|
|
92054aa649 | ||
|
|
4b5be43e60 | ||
|
|
d18864e607 | ||
|
|
4010a16784 | ||
|
|
01c6ea26b8 | ||
|
|
d83b912b3b | ||
|
|
604d78de0f | ||
|
|
9e8420aab5 | ||
|
|
dc3d6f5bc3 | ||
|
|
93431a9ddf | ||
|
|
ec6be38391 | ||
|
|
3777bcf295 | ||
|
|
3d09dfe574 | ||
|
|
532f932712 | ||
|
|
eeeb927a1d | ||
|
|
f9ce549663 | ||
|
|
5a715e7a0b | ||
|
|
e420872a27 | ||
|
|
cef32d4ac7 | ||
|
|
35375a6ea2 | ||
|
|
60eec0eccd | ||
|
|
97665573c7 | ||
|
|
f874377573 | ||
|
|
ce868faece | ||
|
|
e1c1e402a9 | ||
|
|
3b66a3364c | ||
|
|
9b03a3dc6d | ||
|
|
ff24ad5fa8 | ||
|
|
5180634947 | ||
|
|
da643a0c1f | ||
|
|
cd6765673f | ||
|
|
1b73abcfd0 | ||
|
|
f3778baaf4 | ||
|
|
cf1f9fa007 | ||
|
|
34619e111f | ||
|
|
29a5cef559 | ||
|
|
bd83c6a8a9 | ||
|
|
4b0348f64a | ||
|
|
5b52b8a60f | ||
|
|
c0e6dad88b | ||
|
|
a61616d79e | ||
|
|
8663014bbe | ||
|
|
351c7b099a | ||
|
|
c2e7fd34a7 | ||
|
|
a3e61a6e71 | ||
|
|
bd0c172667 | ||
|
|
7746039724 | ||
|
|
629f20720a | ||
|
|
2a3d7720f3 | ||
|
|
b47cf33c3b | ||
|
|
5fba432d78 | ||
|
|
b1efabaaed | ||
|
|
185d54d664 | ||
|
|
96322a6df9 | ||
|
|
42af0816d5 | ||
|
|
8368989341 | ||
|
|
9e60d468ca | ||
|
|
da32457037 | ||
|
|
2d2dbfebff | ||
|
|
6681387b47 | ||
|
|
6a4a915188 | ||
|
|
6ba66320f0 | ||
|
|
1367084a18 | ||
|
|
ec2976b069 | ||
|
|
bad8ed7473 | ||
|
|
e912b7de12 | ||
|
|
8570ff9a8c | ||
|
|
7f7ad29671 | ||
|
|
a95dab078e | ||
|
|
f8218e0648 | ||
|
|
e66d01e989 | ||
|
|
5d81fc7815 | ||
|
|
2d475fbca8 | ||
|
|
36862137db | ||
|
|
34c12e1282 | ||
|
|
8b0c858f28 | ||
|
|
daa0d67c6f | ||
|
|
aaac5466d1 | ||
|
|
89ac5d7de2 | ||
|
|
be0dcd5d10 | ||
|
|
9e0d3c7cbe | ||
|
|
f12061ea88 | ||
|
|
e87917f1e1 | ||
|
|
523592be26 | ||
|
|
a189d08c30 | ||
|
|
e33489c04f | ||
|
|
bcb8765049 | ||
|
|
1739e0c09c |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -15,3 +15,4 @@ tools/build-version
|
||||
tools/build-date
|
||||
tools/info-plist
|
||||
tools/bindata
|
||||
*.hdr
|
||||
|
||||
14
Makefile
14
Makefile
@@ -1,15 +1,14 @@
|
||||
.PHONY: runtime
|
||||
|
||||
VERSION := $(shell GOOS=$(shell go env GOHOSTOS) GOARCH=$(shell go env GOHOSTARCH) \
|
||||
VERSION = $(shell GOOS=$(shell go env GOHOSTOS) GOARCH=$(shell go env GOHOSTARCH) \
|
||||
go run tools/build-version.go)
|
||||
HASH := $(shell git rev-parse --short HEAD)
|
||||
DATE := $(shell GOOS=$(shell go env GOHOSTOS) GOARCH=$(shell go env GOHOSTARCH) \
|
||||
HASH = $(shell git rev-parse --short HEAD)
|
||||
DATE = $(shell GOOS=$(shell go env GOHOSTOS) GOARCH=$(shell go env GOHOSTARCH) \
|
||||
go run tools/build-date.go)
|
||||
ADDITIONAL_GO_LINKER_FLAGS := $(shell GOOS=$(shell go env GOHOSTOS) \
|
||||
GOARCH=$(shell go env GOHOSTARCH) \
|
||||
go run tools/info-plist.go "$(VERSION)")
|
||||
ADDITIONAL_GO_LINKER_FLAGS = $(shell GOOS=$(shell go env GOHOSTOS) \
|
||||
GOARCH=$(shell go env GOHOSTARCH))
|
||||
GOBIN ?= $(shell go env GOPATH)/bin
|
||||
GOVARS := -X github.com/zyedidia/micro/internal/util.Version=$(VERSION) -X github.com/zyedidia/micro/internal/util.CommitHash=$(HASH) -X 'github.com/zyedidia/micro/internal/util.CompileDate=$(DATE)' -X github.com/zyedidia/micro/internal/util.Debug=OFF
|
||||
GOVARS = -X github.com/zyedidia/micro/internal/util.Version=$(VERSION) -X github.com/zyedidia/micro/internal/util.CommitHash=$(HASH) -X 'github.com/zyedidia/micro/internal/util.CompileDate=$(DATE)' -X github.com/zyedidia/micro/internal/util.Debug=OFF
|
||||
|
||||
# Builds micro after checking dependencies but without updating the runtime
|
||||
build:
|
||||
@@ -39,6 +38,7 @@ install-quick:
|
||||
# Builds the runtime
|
||||
runtime:
|
||||
git submodule update --init
|
||||
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
|
||||
|
||||
68
README.md
68
README.md
@@ -29,7 +29,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)
|
||||
- [Plan9, Cygwin](#plan9-cygwin)
|
||||
- [Plan9, Cygwin, Mingw](#plan9-cygwin-mingw)
|
||||
- [Usage](#usage)
|
||||
- [Documentation and Help](#documentation-and-help)
|
||||
- [Contributing](#contributing)
|
||||
@@ -50,25 +50,20 @@ You can also check out the website for Micro at https://micro-editor.github.io.
|
||||
* Extremely good mouse support
|
||||
* This means mouse dragging to create a selection, double click to select by word, and triple click to select by line
|
||||
* Cross platform (It should work on all the platforms Go runs on)
|
||||
* Note that while Windows is supported, there are still some bugs that need to be worked out
|
||||
* Note that while Windows is supported Mingw/Cygwin is not (see below)
|
||||
* Plugin system (plugins are written in Lua)
|
||||
* Micro has a built-in plugin manager to automatically install, remove, and update all your plugins
|
||||
* Persistent undo
|
||||
* Automatic linting and error notifications
|
||||
* Syntax highlighting (for over [90 languages](runtime/syntax)!)
|
||||
* Syntax highlighting (for over [120 languages](runtime/syntax)!)
|
||||
* Colorscheme support
|
||||
* By default, micro comes with 16, 256, and true color themes.
|
||||
* True color support (set the `MICRO_TRUECOLOR` env variable to 1 to enable it)
|
||||
* Snippets
|
||||
* The snippet plugin can be installed with `> plugin install snippets`
|
||||
* True color support (set the `MICRO_TRUECOLOR` environment variable to 1 to enable it)
|
||||
* Copy and paste with the system clipboard
|
||||
* Small and simple
|
||||
* Easily configurable
|
||||
* Macros
|
||||
* Common editor things such as undo/redo, line numbers, Unicode support, softwrap...
|
||||
|
||||
Although not yet implemented, I hope to add more features such as autocompletion ([#174](https://github.com/zyedidia/micro/issues/174)) or a tree view ([#249](https://github.com/zyedidia/micro/issues/249)) in the future.
|
||||
|
||||
# Installation
|
||||
|
||||
To install micro, you can download a [prebuilt binary](https://github.com/zyedidia/micro/releases), or you can build it from source.
|
||||
@@ -106,52 +101,51 @@ You can install micro using Homebrew on Mac:
|
||||
brew install micro
|
||||
```
|
||||
|
||||
On Windows, you can install micro through [Chocolatey](https://chocolatey.org/) or [Scoop](https://github.com/lukesampson/scoop):
|
||||
|
||||
```
|
||||
choco install micro
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```
|
||||
scoop install micro
|
||||
```
|
||||
|
||||
On Linux, you can install micro through [snap](https://snapcraft.io/docs/core/install)
|
||||
On Debian Linux, you can install micro through [snap](https://snapcraft.io/docs/core/install)
|
||||
|
||||
```
|
||||
snap install micro --classic
|
||||
```
|
||||
|
||||
On OpenBSD, micro is available in the ports tree. It is also available as a binary package.
|
||||
Homebrew and snap are the two "officially" maintained package manager distributions of micro.
|
||||
|
||||
```
|
||||
pkg_add -v micro
|
||||
```
|
||||
Micro is also available through other package managers on Linux such as AUR, Nix, and package managers
|
||||
for other operating systems:
|
||||
|
||||
* Windows: [Chocolatey](https://chocolatey.org) and [Scoop](https://github.com/lukesampson/scoop)
|
||||
* `choco install micro`
|
||||
* `scoop install micro`
|
||||
* OpenBSD: Available in the ports tree and also available as a binary package
|
||||
* `pkd_add -v micro`
|
||||
|
||||
### Building from source
|
||||
|
||||
If your operating system does not have a binary release, but does run Go, you can build from source.
|
||||
|
||||
Make sure that you have Go version 1.5 or greater (Go 1.4 will work if your version supports CGO) and that your `GOPATH` env variable is set (I recommend setting it to `~/go` if you don't have one).
|
||||
Make sure that you have Go version 1.11 or greater and Go modules are enabled.
|
||||
|
||||
```
|
||||
go get -d github.com/zyedidia/micro/cmd/micro
|
||||
cd $GOPATH/src/github.com/zyedidia/micro
|
||||
make install
|
||||
git clone https://github.com/zyedidia/micro
|
||||
cd micro
|
||||
make build
|
||||
sudo mv micro /usr/local/bin # optional
|
||||
```
|
||||
|
||||
The binary will then be installed to `$GOPATH/bin` (or your `$GOBIN`).
|
||||
The binary will be placed in the current directory and can be moved to
|
||||
anywhere you like (for example `/usr/local/bin`).
|
||||
|
||||
Please make sure that when you are working with micro's code, you are working on your `GOPATH`.
|
||||
The command `make install` will install the binary to `$GOPATH/bin` or `$GOBIN`.
|
||||
|
||||
You can install directly with `go get` (`go get -u github.com/zyedidia/micro/cmd/micro`) but this isn't recommended because it doesn't build micro with version information which is useful for the plugin manager.
|
||||
You can install directly with `go get` (`go get github.com/zyedidia/micro/cmd/micro`) but this isn't
|
||||
recommended because it doesn't build micro with version information, and doesn't disable debug mode.
|
||||
|
||||
### MacOS terminal
|
||||
|
||||
If you are using MacOS, you should consider using [iTerm2](http://iterm2.com/) instead of the default Mac terminal. The iTerm2 terminal has much better mouse support as well as better handling of key events. For best keybinding behavior, choose `xterm defaults` under `Preferences->Profiles->Keys->Load Preset`. The newest versions also support true color.
|
||||
|
||||
If you still insist on using the default Mac terminal, be sure to set `Use Option key as Meta key` under
|
||||
`Preferences->Profiles->Keyboard` to use <kbd>option</kbd> as <kbd>alt</kbd>.
|
||||
|
||||
### Linux clipboard support
|
||||
|
||||
On Linux, clipboard support requires the 'xclip' or 'xsel' commands to be installed.
|
||||
@@ -167,7 +161,7 @@ If you don't have xclip or xsel, micro will use an internal clipboard for copy a
|
||||
### Colors and syntax highlighting
|
||||
|
||||
If you open micro and it doesn't seem like syntax highlighting is working, this is probably because
|
||||
you are using a terminal which does not support 256 color. Try changing the colorscheme to `simple`
|
||||
you are using a terminal which does not support 256 colors. Try changing the colorscheme to `simple`
|
||||
by pressing CtrlE in micro and typing `set colorscheme simple`.
|
||||
|
||||
If you are using the default Ubuntu terminal, to enable 256 make sure your `TERM` variable is set
|
||||
@@ -178,9 +172,11 @@ that micro's default colorscheme won't look very good. You can either set
|
||||
the colorscheme to `simple`, or download a better terminal emulator, like
|
||||
mintty.
|
||||
|
||||
### Plan9, Cygwin
|
||||
### Plan9, Cygwin, Mingw
|
||||
|
||||
Please note that micro uses the amazing [tcell library](https://github.com/gdamore/tcell), but this
|
||||
These platforms are unfortunately not supported.
|
||||
|
||||
Micro uses the amazing [tcell library](https://github.com/gdamore/tcell), but this
|
||||
means that micro is restricted to the platforms tcell supports. As a result, micro does not support
|
||||
Plan9, and Cygwin (although this may change in the future). Micro also doesn't support NaCl (but NaCl is deprecated anyways).
|
||||
|
||||
|
||||
140
cmd/micro/clean.go
Normal file
140
cmd/micro/clean.go
Normal file
@@ -0,0 +1,140 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/gob"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/zyedidia/micro/internal/buffer"
|
||||
"github.com/zyedidia/micro/internal/config"
|
||||
)
|
||||
|
||||
func shouldContinue() bool {
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
fmt.Print("Continue [Y/n]: ")
|
||||
text, err := reader.ReadString('\n')
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return false
|
||||
}
|
||||
|
||||
if len(text) <= 1 {
|
||||
// default continue
|
||||
return true
|
||||
}
|
||||
|
||||
return strings.ToLower(text)[0] == 'y'
|
||||
}
|
||||
|
||||
// CleanConfig performs cleanup in the user's configuration directory
|
||||
func CleanConfig() {
|
||||
fmt.Println("Cleaning your configuration directory at", config.ConfigDir)
|
||||
fmt.Printf("Please consider backing up %s before continuing\n", config.ConfigDir)
|
||||
|
||||
if !shouldContinue() {
|
||||
fmt.Println("Stopping early")
|
||||
return
|
||||
}
|
||||
|
||||
// detect unused options
|
||||
var unusedOptions []string
|
||||
defaultSettings := config.DefaultAllSettings()
|
||||
for k := range config.GlobalSettings {
|
||||
if _, ok := defaultSettings[k]; !ok {
|
||||
valid := false
|
||||
for _, p := range config.Plugins {
|
||||
if strings.HasPrefix(k, p.Name+".") || k == p.Name {
|
||||
valid = true
|
||||
}
|
||||
}
|
||||
if !valid {
|
||||
unusedOptions = append(unusedOptions, k)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(unusedOptions) > 0 {
|
||||
fmt.Println("The following options are unused:")
|
||||
|
||||
sort.Strings(unusedOptions)
|
||||
|
||||
for _, s := range unusedOptions {
|
||||
fmt.Printf("%s (value: %v)\n", s, config.GlobalSettings[s])
|
||||
}
|
||||
|
||||
fmt.Printf("These options will be removed from %s\n", filepath.Join(config.ConfigDir, "settings.json"))
|
||||
|
||||
if shouldContinue() {
|
||||
for _, s := range unusedOptions {
|
||||
delete(config.GlobalSettings, s)
|
||||
}
|
||||
|
||||
err := config.OverwriteSettings(filepath.Join(config.ConfigDir, "settings.json"))
|
||||
if err != nil {
|
||||
fmt.Println("Error writing settings.json file: " + err.Error())
|
||||
}
|
||||
|
||||
fmt.Println("Removed unused options")
|
||||
fmt.Print("\n\n")
|
||||
}
|
||||
}
|
||||
|
||||
// detect incorrectly formatted buffer/ files
|
||||
files, err := ioutil.ReadDir(filepath.Join(config.ConfigDir, "buffers"))
|
||||
if err == nil {
|
||||
var badFiles []string
|
||||
var buffer buffer.SerializedBuffer
|
||||
for _, f := range files {
|
||||
fname := filepath.Join(config.ConfigDir, "buffers", f.Name())
|
||||
file, err := os.Open(fname)
|
||||
defer file.Close()
|
||||
|
||||
decoder := gob.NewDecoder(file)
|
||||
err = decoder.Decode(&buffer)
|
||||
|
||||
if err != nil && f.Name() != "history" {
|
||||
badFiles = append(badFiles, fname)
|
||||
}
|
||||
}
|
||||
|
||||
if len(badFiles) > 0 {
|
||||
fmt.Printf("Detected %d files with an invalid format in %s\n", len(badFiles), filepath.Join(config.ConfigDir, "buffers"))
|
||||
fmt.Println("These files store cursor and undo history.")
|
||||
fmt.Printf("Removing badly formatted files in %s\n", filepath.Join(config.ConfigDir, "buffers"))
|
||||
|
||||
if shouldContinue() {
|
||||
for _, f := range badFiles {
|
||||
err := os.Remove(f)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println("Removed badly formatted files")
|
||||
fmt.Print("\n\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// detect plugins/ directory
|
||||
plugins := filepath.Join(config.ConfigDir, "plugins")
|
||||
if stat, err := os.Stat(plugins); err == nil && stat.IsDir() {
|
||||
fmt.Printf("Found directory %s\n", plugins)
|
||||
fmt.Printf("Plugins should now be stored in %s\n", filepath.Join(config.ConfigDir, "plug"))
|
||||
fmt.Printf("Removing %s\n", plugins)
|
||||
|
||||
if shouldContinue() {
|
||||
os.RemoveAll(plugins)
|
||||
}
|
||||
|
||||
fmt.Print("\n\n")
|
||||
}
|
||||
|
||||
fmt.Println("Done cleaning")
|
||||
}
|
||||
@@ -21,6 +21,7 @@ func init() {
|
||||
ulua.L.SetGlobal("import", luar.New(ulua.L, LuaImport))
|
||||
}
|
||||
|
||||
// LuaImport is meant to be called from lua by a plugin and will import the given micro package
|
||||
func LuaImport(pkg string) *lua.LTable {
|
||||
switch pkg {
|
||||
case "micro":
|
||||
@@ -46,6 +47,12 @@ func luaImportMicro() *lua.LTable {
|
||||
ulua.L.SetField(pkg, "InfoBar", luar.New(ulua.L, action.GetInfoBar))
|
||||
ulua.L.SetField(pkg, "Log", luar.New(ulua.L, log.Println))
|
||||
ulua.L.SetField(pkg, "SetStatusInfoFn", luar.New(ulua.L, display.SetStatusInfoFnLua))
|
||||
ulua.L.SetField(pkg, "CurPane", luar.New(ulua.L, func() action.Pane {
|
||||
return action.MainTab().CurPane()
|
||||
}))
|
||||
ulua.L.SetField(pkg, "CurTab", luar.New(ulua.L, func() *action.Tab {
|
||||
return action.MainTab()
|
||||
}))
|
||||
|
||||
return pkg
|
||||
}
|
||||
@@ -62,16 +69,20 @@ func luaImportMicroConfig() *lua.LTable {
|
||||
ulua.L.SetField(pkg, "TryBindKey", luar.New(ulua.L, action.TryBindKey))
|
||||
ulua.L.SetField(pkg, "Reload", luar.New(ulua.L, action.ReloadConfig))
|
||||
ulua.L.SetField(pkg, "AddRuntimeFileFromMemory", luar.New(ulua.L, config.PluginAddRuntimeFileFromMemory))
|
||||
ulua.L.SetField(pkg, "AddRuntimeFilesFromDirectory", luar.New(ulua.L, config.PluginAddRuntimeFileFromMemory))
|
||||
ulua.L.SetField(pkg, "AddRuntimeFilesFromDirectory", luar.New(ulua.L, config.PluginAddRuntimeFilesFromDirectory))
|
||||
ulua.L.SetField(pkg, "AddRuntimeFile", luar.New(ulua.L, config.PluginAddRuntimeFile))
|
||||
ulua.L.SetField(pkg, "ListRuntimeFiles", luar.New(ulua.L, config.PluginListRuntimeFiles))
|
||||
ulua.L.SetField(pkg, "ReadRuntimeFile", luar.New(ulua.L, config.PluginReadRuntimeFile))
|
||||
ulua.L.SetField(pkg, "NewRTFiletype", luar.New(ulua.L, config.NewRTFiletype))
|
||||
ulua.L.SetField(pkg, "RTColorscheme", luar.New(ulua.L, config.RTColorscheme))
|
||||
ulua.L.SetField(pkg, "RTSyntax", luar.New(ulua.L, config.RTSyntax))
|
||||
ulua.L.SetField(pkg, "RTHelp", luar.New(ulua.L, config.RTHelp))
|
||||
ulua.L.SetField(pkg, "RTPlugin", luar.New(ulua.L, config.RTPlugin))
|
||||
ulua.L.SetField(pkg, "RegisterCommonOption", luar.New(ulua.L, config.RegisterCommonOption))
|
||||
ulua.L.SetField(pkg, "RegisterGlobalOption", luar.New(ulua.L, config.RegisterGlobalOption))
|
||||
ulua.L.SetField(pkg, "RegisterCommonOption", luar.New(ulua.L, config.RegisterCommonOptionPlug))
|
||||
ulua.L.SetField(pkg, "RegisterGlobalOption", luar.New(ulua.L, config.RegisterGlobalOptionPlug))
|
||||
ulua.L.SetField(pkg, "GetGlobalOption", luar.New(ulua.L, config.GetGlobalOption))
|
||||
ulua.L.SetField(pkg, "SetGlobalOption", luar.New(ulua.L, action.SetGlobalOption))
|
||||
ulua.L.SetField(pkg, "SetGlobalOptionNative", luar.New(ulua.L, action.SetGlobalOptionNative))
|
||||
|
||||
return pkg
|
||||
}
|
||||
@@ -110,10 +121,15 @@ func luaImportMicroBuffer() *lua.LTable {
|
||||
ulua.L.SetField(pkg, "BTScratch", luar.New(ulua.L, buffer.BTScratch.Kind))
|
||||
ulua.L.SetField(pkg, "BTRaw", luar.New(ulua.L, buffer.BTRaw.Kind))
|
||||
ulua.L.SetField(pkg, "BTInfo", luar.New(ulua.L, buffer.BTInfo.Kind))
|
||||
ulua.L.SetField(pkg, "NewBuffer", luar.New(ulua.L, func(text, path string) *buffer.Buffer {
|
||||
return buffer.NewBufferFromString(text, path, buffer.BTDefault)
|
||||
}))
|
||||
ulua.L.SetField(pkg, "NewBufferFromFile", luar.New(ulua.L, func(path string) (*buffer.Buffer, error) {
|
||||
return buffer.NewBufferFromFile(path, buffer.BTDefault)
|
||||
}))
|
||||
ulua.L.SetField(pkg, "ByteOffset", luar.New(ulua.L, buffer.ByteOffset))
|
||||
ulua.L.SetField(pkg, "Log", luar.New(ulua.L, buffer.WriteLog))
|
||||
ulua.L.SetField(pkg, "LogBuf", luar.New(ulua.L, buffer.GetLogBuf))
|
||||
|
||||
return pkg
|
||||
}
|
||||
@@ -124,6 +140,10 @@ func luaImportMicroUtil() *lua.LTable {
|
||||
ulua.L.SetField(pkg, "RuneAt", luar.New(ulua.L, util.LuaRuneAt))
|
||||
ulua.L.SetField(pkg, "GetLeadingWhitespace", luar.New(ulua.L, util.LuaGetLeadingWhitespace))
|
||||
ulua.L.SetField(pkg, "IsWordChar", luar.New(ulua.L, util.LuaIsWordChar))
|
||||
ulua.L.SetField(pkg, "String", luar.New(ulua.L, util.String))
|
||||
ulua.L.SetField(pkg, "RuneStr", luar.New(ulua.L, func(r rune) string {
|
||||
return string(r)
|
||||
}))
|
||||
|
||||
return pkg
|
||||
}
|
||||
|
||||
@@ -5,7 +5,9 @@ import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"runtime"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/go-errors/errors"
|
||||
isatty "github.com/mattn/go-isatty"
|
||||
@@ -27,22 +29,42 @@ var (
|
||||
flagVersion = flag.Bool("version", false, "Show the version number and information")
|
||||
flagConfigDir = flag.String("config-dir", "", "Specify a custom location for the configuration directory")
|
||||
flagOptions = flag.Bool("options", false, "Show all option help")
|
||||
flagDebug = flag.Bool("debug", false, "Enable debug mode (prints debug info to ./log.txt)")
|
||||
flagPlugin = flag.String("plugin", "", "Plugin command")
|
||||
flagClean = flag.Bool("clean", false, "Clean configuration directory")
|
||||
optionFlags map[string]*string
|
||||
)
|
||||
|
||||
func InitFlags() {
|
||||
flag.Usage = func() {
|
||||
fmt.Println("Usage: micro [OPTIONS] [FILE]...")
|
||||
fmt.Println("-clean")
|
||||
fmt.Println(" \tCleans the configuration directory")
|
||||
fmt.Println("-config-dir dir")
|
||||
fmt.Println(" \tSpecify a custom location for the configuration directory")
|
||||
fmt.Println("[FILE]:LINE:COL")
|
||||
fmt.Println(" \tSpecify a line and column to start the cursor at when opening a buffer")
|
||||
fmt.Println(" \tThis can also be done by opening file:LINE:COL")
|
||||
fmt.Println("-options")
|
||||
fmt.Println(" \tShow all option help")
|
||||
fmt.Println("-debug")
|
||||
fmt.Println(" \tEnable debug mode (enables logging to ./log.txt)")
|
||||
fmt.Println("-version")
|
||||
fmt.Println(" \tShow the version number and information")
|
||||
|
||||
fmt.Print("\nMicro's plugin's can be managed at the command line with the following commands.\n")
|
||||
fmt.Println("-plugin install [PLUGIN]...")
|
||||
fmt.Println(" \tInstall plugin(s)")
|
||||
fmt.Println("-plugin remove [PLUGIN]...")
|
||||
fmt.Println(" \tRemove plugin(s)")
|
||||
fmt.Println("-plugin update [PLUGIN]...")
|
||||
fmt.Println(" \tUpdate plugin(s) (if no argument is given, updates all plugins)")
|
||||
fmt.Println("-plugin search [PLUGIN]...")
|
||||
fmt.Println(" \tSearch for a plugin")
|
||||
fmt.Println("-plugin list")
|
||||
fmt.Println(" \tList installed plugins")
|
||||
fmt.Println("-plugin available")
|
||||
fmt.Println(" \tList available plugins")
|
||||
|
||||
fmt.Print("\nMicro's options can also be set via command line arguments for quick\nadjustments. For real configuration, please use the settings.json\nfile (see 'help options').\n\n")
|
||||
fmt.Println("-option value")
|
||||
fmt.Println(" \tSet `option` to `value` for this session")
|
||||
@@ -81,6 +103,27 @@ func InitFlags() {
|
||||
}
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
if util.Debug == "OFF" && *flagDebug {
|
||||
util.Debug = "ON"
|
||||
}
|
||||
}
|
||||
|
||||
// DoPluginFlags parses and executes any flags that require LoadAllPlugins (-plugin and -clean)
|
||||
func DoPluginFlags() {
|
||||
if *flagClean || *flagPlugin != "" {
|
||||
config.LoadAllPlugins()
|
||||
|
||||
if *flagPlugin != "" {
|
||||
args := flag.Args()
|
||||
|
||||
config.PluginCommand(os.Stdout, *flagPlugin, args)
|
||||
} else if *flagClean {
|
||||
CleanConfig()
|
||||
}
|
||||
|
||||
os.Exit(0)
|
||||
}
|
||||
}
|
||||
|
||||
// LoadInput determines which files should be loaded into buffers
|
||||
@@ -143,10 +186,10 @@ func main() {
|
||||
|
||||
var err error
|
||||
|
||||
InitLog()
|
||||
|
||||
InitFlags()
|
||||
|
||||
InitLog()
|
||||
|
||||
err = config.InitConfigDir(*flagConfigDir)
|
||||
if err != nil {
|
||||
screen.TermMessage(err)
|
||||
@@ -171,22 +214,7 @@ func main() {
|
||||
}
|
||||
}
|
||||
|
||||
action.InitBindings()
|
||||
action.InitCommands()
|
||||
|
||||
err = config.InitColorscheme()
|
||||
if err != nil {
|
||||
screen.TermMessage(err)
|
||||
}
|
||||
|
||||
err = config.LoadAllPlugins()
|
||||
if err != nil {
|
||||
screen.TermMessage(err)
|
||||
}
|
||||
err = config.RunPluginFn("init")
|
||||
if err != nil {
|
||||
screen.TermMessage(err)
|
||||
}
|
||||
DoPluginFlags()
|
||||
|
||||
screen.Init()
|
||||
|
||||
@@ -207,13 +235,39 @@ func main() {
|
||||
}
|
||||
}()
|
||||
|
||||
err = config.LoadAllPlugins()
|
||||
if err != nil {
|
||||
screen.TermMessage(err)
|
||||
}
|
||||
|
||||
action.InitBindings()
|
||||
action.InitCommands()
|
||||
|
||||
err = config.InitColorscheme()
|
||||
if err != nil {
|
||||
screen.TermMessage(err)
|
||||
}
|
||||
|
||||
b := LoadInput()
|
||||
|
||||
if len(b) == 0 {
|
||||
// No buffers to open
|
||||
screen.Screen.Fini()
|
||||
runtime.Goexit()
|
||||
}
|
||||
|
||||
action.InitTabs(b)
|
||||
action.InitGlobals()
|
||||
|
||||
err = config.RunPluginFn("init")
|
||||
if err != nil {
|
||||
screen.TermMessage(err)
|
||||
}
|
||||
|
||||
events = make(chan tcell.Event)
|
||||
|
||||
// Here is the event loop which runs in a separate thread
|
||||
go func() {
|
||||
events = make(chan tcell.Event)
|
||||
for {
|
||||
screen.Lock()
|
||||
e := screen.Screen.PollEvent()
|
||||
@@ -224,6 +278,22 @@ func main() {
|
||||
}
|
||||
}()
|
||||
|
||||
// clear the drawchan so we don't redraw excessively
|
||||
// if someone requested a redraw before we started displaying
|
||||
for len(screen.DrawChan) > 0 {
|
||||
<-screen.DrawChan
|
||||
}
|
||||
|
||||
var event tcell.Event
|
||||
|
||||
// wait for initial resize event
|
||||
select {
|
||||
case event = <-events:
|
||||
action.Tabs.HandleEvent(event)
|
||||
case <-time.After(10 * time.Millisecond):
|
||||
// time out after 10ms
|
||||
}
|
||||
|
||||
for {
|
||||
// Display everything
|
||||
screen.Screen.Fill(' ', config.DefStyle)
|
||||
@@ -236,7 +306,7 @@ func main() {
|
||||
action.InfoBar.Display()
|
||||
screen.Screen.Show()
|
||||
|
||||
var event tcell.Event
|
||||
event = nil
|
||||
|
||||
// Check for new events
|
||||
select {
|
||||
|
||||
32
go.mod
32
go.mod
@@ -3,24 +3,24 @@ module github.com/zyedidia/micro
|
||||
require (
|
||||
github.com/blang/semver v3.5.1+incompatible
|
||||
github.com/dustin/go-humanize v1.0.0
|
||||
github.com/flynn/json5 v0.0.0-20160717195620-7620272ed633
|
||||
github.com/gdamore/encoding v1.0.0 // indirect
|
||||
github.com/go-errors/errors v1.0.1
|
||||
github.com/lucasb-eyer/go-colorful v1.0.3 // indirect
|
||||
github.com/mattn/go-isatty v0.0.4
|
||||
github.com/mattn/go-runewidth v0.0.4
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
|
||||
github.com/mattn/go-isatty v0.0.11
|
||||
github.com/mattn/go-runewidth v0.0.7
|
||||
github.com/mitchellh/go-homedir v1.1.0
|
||||
github.com/npat-efault/poller v2.0.0+incompatible // indirect
|
||||
github.com/sergi/go-diff v1.0.0
|
||||
github.com/stretchr/testify v1.3.0
|
||||
github.com/yuin/gopher-lua v0.0.0-20190125051437-7b9317363aa9
|
||||
github.com/zyedidia/clipboard v0.0.0-20180718195219-bd31d747117d
|
||||
github.com/sergi/go-diff v1.1.0
|
||||
github.com/stretchr/testify v1.4.0
|
||||
github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb
|
||||
github.com/zyedidia/clipboard v0.0.0-20190823154308-241f98e9b197
|
||||
github.com/zyedidia/glob v0.0.0-20170209203856-dd4023a66dc3
|
||||
github.com/zyedidia/poller v2.0.0+incompatible // indirect
|
||||
github.com/zyedidia/pty v1.1.2-0.20180126010845-30364665a244 // indirect
|
||||
github.com/zyedidia/tcell v0.0.0-20191219170756-59b50b23fa9b
|
||||
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 v1.4.2
|
||||
github.com/zyedidia/terminal v0.0.0-20180726154117-533c623e2415
|
||||
golang.org/x/text v0.3.0
|
||||
gopkg.in/yaml.v2 v2.2.2
|
||||
layeh.com/gopher-luar v1.0.4
|
||||
golang.org/x/text v0.3.2
|
||||
gopkg.in/yaml.v2 v2.2.7
|
||||
layeh.com/gopher-luar v1.0.7
|
||||
)
|
||||
|
||||
go 1.11
|
||||
|
||||
73
go.sum
73
go.sum
@@ -1,46 +1,73 @@
|
||||
github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ=
|
||||
github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/flynn/json5 v0.0.0-20160717195620-7620272ed633 h1:xJMmr4GMYIbALX5edyoDIOQpc2bOQTeJiWMeCl9lX/8=
|
||||
github.com/flynn/json5 v0.0.0-20160717195620-7620272ed633/go.mod h1:NJDK3/o7abx6PP54EOe0G0n0RLmhCo9xv61gUYpI0EY=
|
||||
github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
|
||||
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
|
||||
github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w=
|
||||
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/lucasb-eyer/go-colorful v1.0.3 h1:QIbQXiugsb+q10B+MI+7DI1oQLdmnep86tWFlaaUAac=
|
||||
github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||
github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs=
|
||||
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y=
|
||||
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGeM=
|
||||
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
|
||||
github.com/mattn/go-runewidth v0.0.7 h1:Ei8KR0497xHyKJPAv59M1dkC+rOZCMBJ+t3fZ+twI54=
|
||||
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/npat-efault/poller v2.0.0+incompatible/go.mod h1:lni01B89P8PtVpwlAhdhK1niN5rPkDGGpGGgBJzpSgo=
|
||||
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/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
|
||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
|
||||
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
||||
github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/yuin/gopher-lua v0.0.0-20190125051437-7b9317363aa9 h1:Wy3fAQLBPP0JSWdq3kBnmbFgXDHcyhtPpd+8kENV7mU=
|
||||
github.com/yuin/gopher-lua v0.0.0-20190125051437-7b9317363aa9/go.mod h1:fFiAh+CowNFr0NK5VASokuwKwkbacRmHsVA7Yb1Tqac=
|
||||
github.com/zyedidia/clipboard v0.0.0-20180718195219-bd31d747117d h1:Lhqt2eo+rgM8aswvM7nTtAMVm8ARPWzkE9n6eZDOccY=
|
||||
github.com/zyedidia/clipboard v0.0.0-20180718195219-bd31d747117d/go.mod h1:WDk3p8GiZV9+xFWlSo8qreeoLhW6Ik692rqXk+cNeRY=
|
||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
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 v0.0.0-20190823154308-241f98e9b197 h1:gYTNnAW6azuB3BbA6QYWO/H4F2ABSOjjw3Z03tlXd2c=
|
||||
github.com/zyedidia/clipboard v0.0.0-20190823154308-241f98e9b197/go.mod h1:WDk3p8GiZV9+xFWlSo8qreeoLhW6Ik692rqXk+cNeRY=
|
||||
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/poller v2.0.0+incompatible/go.mod h1:vZXJOHGDcuK08GXhF6IAY0ZFd2WcgOR5DOTp84Uk5eE=
|
||||
github.com/zyedidia/pty v1.1.2-0.20180126010845-30364665a244 h1:DZ7mZvUV5+oXeXV1E1t6ZIXRihHYyqYVIOSA+RGo88A=
|
||||
github.com/zyedidia/pty v1.1.2-0.20180126010845-30364665a244/go.mod h1:4y9l9yJZNxRa7GB/fB+mmDmGkG3CqmzLf4vUxGGotEA=
|
||||
github.com/zyedidia/tcell v0.0.0-20191219170756-59b50b23fa9b h1:cryFENlMxJJrkimVx/CUMFDCxC4vpmey2x3A3tAgTNM=
|
||||
github.com/zyedidia/tcell v0.0.0-20191219170756-59b50b23fa9b/go.mod h1:yXgdp23+aW8OMENYVBvpKoeiBtjaVWJ9HhpPDu6LBfM=
|
||||
github.com/zyedidia/highlight v0.0.0-20170330143449-201131ce5cf5 h1:Zs6mpwXvlqpF9zHl5XaN0p5V4J9XvP+WBuiuXyIgqvc=
|
||||
github.com/zyedidia/highlight v0.0.0-20170330143449-201131ce5cf5/go.mod h1:c1r+Ob9tUTPB0FKWO1+x+Hsc/zNa45WdGq7Y38Ybip0=
|
||||
github.com/zyedidia/json5 v0.0.0-20200102012142-2da050b1a98d h1:zmDMkh22zXOB7gz8jFaI4GpI7llsPgzm38/jG0UgxjE=
|
||||
github.com/zyedidia/json5 v0.0.0-20200102012142-2da050b1a98d/go.mod h1:NDJSTTYWivnza6zkRapeX2/LwhKPEMQ7bJxqgDVT78I=
|
||||
github.com/zyedidia/poller v1.0.1 h1:Tt9S3AxAjXwWGNiC2TUdRJkQDZSzCBNVQ4xXiQ7440s=
|
||||
github.com/zyedidia/poller v1.0.1/go.mod h1:vZXJOHGDcuK08GXhF6IAY0ZFd2WcgOR5DOTp84Uk5eE=
|
||||
github.com/zyedidia/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 v1.4.2 h1:JWMDs6O1saINPIR5M3kNqlWJwkfnBZeZDZszEJi3BW8=
|
||||
github.com/zyedidia/tcell v1.4.2/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/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||
golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
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/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
layeh.com/gopher-luar v1.0.4 h1:BFgt94J/CXh4HkDcE2b7A7pBaVeQKEVfHEBRKL/K/Tc=
|
||||
layeh.com/gopher-luar v1.0.4/go.mod h1:N3rev/ttQd8yVluXaYsa0M/eknzRYWe+pxZ35ZFmaaI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo=
|
||||
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
layeh.com/gopher-luar v1.0.7 h1:53iv6CCkRs5wyofZ+qVXcyAYQOIG52s6pt4xkqZdq7k=
|
||||
layeh.com/gopher-luar v1.0.7/go.mod h1:TPnIVCZ2RJBndm7ohXyaqfhzjlZ+OA2SZR/YwL8tECk=
|
||||
|
||||
@@ -7,13 +7,13 @@ import (
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
shellquote "github.com/kballard/go-shellquote"
|
||||
"github.com/zyedidia/clipboard"
|
||||
"github.com/zyedidia/micro/internal/buffer"
|
||||
"github.com/zyedidia/micro/internal/config"
|
||||
"github.com/zyedidia/micro/internal/screen"
|
||||
"github.com/zyedidia/micro/internal/shell"
|
||||
"github.com/zyedidia/micro/internal/util"
|
||||
"github.com/zyedidia/micro/pkg/shellwords"
|
||||
"github.com/zyedidia/tcell"
|
||||
)
|
||||
|
||||
@@ -23,6 +23,8 @@ func (h *BufPane) ScrollUp(n int) {
|
||||
if v.StartLine >= n {
|
||||
v.StartLine -= n
|
||||
h.SetView(v)
|
||||
} else {
|
||||
v.StartLine = 0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,7 +88,6 @@ func (h *BufPane) MousePress(e *tcell.EventMouse) bool {
|
||||
h.Cursor.AddWordToSelection()
|
||||
} else {
|
||||
h.Cursor.SetSelectionEnd(h.Cursor.Loc)
|
||||
h.Cursor.CopySelection("primary")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -282,15 +283,18 @@ func (h *BufPane) SelectWordLeft() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// StartOfLine moves the cursor to the start of the text of the line
|
||||
func (h *BufPane) StartOfText() bool {
|
||||
h.Cursor.Deselect(true)
|
||||
h.Cursor.StartOfText()
|
||||
h.Relocate()
|
||||
return true
|
||||
}
|
||||
|
||||
// StartOfLine moves the cursor to the start of the line
|
||||
func (h *BufPane) StartOfLine() bool {
|
||||
h.Cursor.Deselect(true)
|
||||
h.Cursor.StartOfText()
|
||||
// if h.Cursor.X != 0 {
|
||||
// h.Cursor.Start()
|
||||
// } else {
|
||||
// h.Cursor.StartOfText()
|
||||
// }
|
||||
h.Cursor.Start()
|
||||
h.Relocate()
|
||||
return true
|
||||
}
|
||||
@@ -310,6 +314,17 @@ func (h *BufPane) SelectLine() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// SelectToStartOfText selects to the start of the text on the current line
|
||||
func (h *BufPane) SelectToStartOfText() bool {
|
||||
if !h.Cursor.HasSelection() {
|
||||
h.Cursor.OrigSelection[0] = h.Cursor.Loc
|
||||
}
|
||||
h.Cursor.StartOfText()
|
||||
h.Cursor.SelectTo(h.Cursor.Loc)
|
||||
h.Relocate()
|
||||
return true
|
||||
}
|
||||
|
||||
// SelectToStartOfLine selects to the start of the current line
|
||||
func (h *BufPane) SelectToStartOfLine() bool {
|
||||
if !h.Cursor.HasSelection() {
|
||||
@@ -533,12 +548,14 @@ func (h *BufPane) IndentSelection() bool {
|
||||
tabsize := int(h.Buf.Settings["tabsize"].(float64))
|
||||
indentsize := len(h.Buf.IndentString(tabsize))
|
||||
for y := startY; y <= endY; y++ {
|
||||
h.Buf.Insert(buffer.Loc{X: 0, Y: y}, h.Buf.IndentString(tabsize))
|
||||
if y == startY && start.X > 0 {
|
||||
h.Cursor.SetSelectionStart(start.Move(indentsize, h.Buf))
|
||||
}
|
||||
if y == endY {
|
||||
h.Cursor.SetSelectionEnd(buffer.Loc{X: endX + indentsize + 1, Y: endY})
|
||||
if len(h.Buf.LineBytes(y)) > 0 {
|
||||
h.Buf.Insert(buffer.Loc{X: 0, Y: y}, h.Buf.IndentString(tabsize))
|
||||
if y == startY && start.X > 0 {
|
||||
h.Cursor.SetSelectionStart(start.Move(indentsize, h.Buf))
|
||||
}
|
||||
if y == endY {
|
||||
h.Cursor.SetSelectionEnd(buffer.Loc{X: endX + indentsize + 1, Y: endY})
|
||||
}
|
||||
}
|
||||
}
|
||||
h.Buf.RelocateCursors()
|
||||
@@ -610,11 +627,22 @@ func (h *BufPane) Autocomplete() bool {
|
||||
return b.Autocomplete(buffer.BufferComplete)
|
||||
}
|
||||
|
||||
// CycleAutocompleteBack cycles back in the autocomplete suggestion list
|
||||
func (h *BufPane) CycleAutocompleteBack() bool {
|
||||
if h.Cursor.HasSelection() {
|
||||
return false
|
||||
}
|
||||
|
||||
if h.Buf.HasSuggestions {
|
||||
h.Buf.CycleAutocomplete(false)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// InsertTab inserts a tab or spaces
|
||||
func (h *BufPane) InsertTab() bool {
|
||||
b := h.Buf
|
||||
l := b.LineBytes(h.Cursor.Y)
|
||||
l = util.SliceStart(l, h.Cursor.X)
|
||||
indent := b.IndentString(util.IntOpt(b.Settings["tabsize"]))
|
||||
tabBytes := len(indent)
|
||||
bytesUntilIndent := tabBytes - (h.Cursor.GetVisualX() % tabBytes)
|
||||
@@ -637,10 +665,13 @@ func (h *BufPane) Save() bool {
|
||||
if h.Buf.Path == "" {
|
||||
h.SaveAs()
|
||||
} else {
|
||||
h.saveBufToFile(h.Buf.Path)
|
||||
noPrompt := h.saveBufToFile(h.Buf.Path, "Save")
|
||||
if noPrompt {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
return false
|
||||
}
|
||||
|
||||
// SaveAs saves the buffer to disk with the given name
|
||||
@@ -648,22 +679,28 @@ func (h *BufPane) SaveAs() bool {
|
||||
InfoBar.Prompt("Filename: ", "", "Save", nil, func(resp string, canceled bool) {
|
||||
if !canceled {
|
||||
// the filename might or might not be quoted, so unquote first then join the strings.
|
||||
args, err := shellwords.Split(resp)
|
||||
filename := strings.Join(args, " ")
|
||||
args, err := shellquote.Split(resp)
|
||||
if err != nil {
|
||||
InfoBar.Error("Error parsing arguments: ", err)
|
||||
return
|
||||
}
|
||||
h.saveBufToFile(filename)
|
||||
|
||||
if len(args) == 0 {
|
||||
InfoBar.Error("No filename given")
|
||||
return
|
||||
}
|
||||
filename := strings.Join(args, " ")
|
||||
noPrompt := h.saveBufToFile(filename, "SaveAs")
|
||||
if noPrompt {
|
||||
h.completeAction("SaveAs")
|
||||
}
|
||||
}
|
||||
})
|
||||
return true
|
||||
return false
|
||||
}
|
||||
|
||||
// This function saves the buffer to `filename` and changes the buffer's path and name
|
||||
// to `filename` if the save is successful
|
||||
func (h *BufPane) saveBufToFile(filename string) {
|
||||
func (h *BufPane) saveBufToFile(filename string, action string) bool {
|
||||
err := h.Buf.SaveAs(filename)
|
||||
if err != nil {
|
||||
if strings.HasSuffix(err.Error(), "permission denied") {
|
||||
@@ -677,8 +714,10 @@ func (h *BufPane) saveBufToFile(filename string) {
|
||||
h.Buf.SetName(filename)
|
||||
InfoBar.Message("Saved " + filename)
|
||||
}
|
||||
h.completeAction(action)
|
||||
}
|
||||
})
|
||||
return false
|
||||
} else {
|
||||
InfoBar.Error(err)
|
||||
}
|
||||
@@ -687,6 +726,7 @@ func (h *BufPane) saveBufToFile(filename string) {
|
||||
h.Buf.SetName(filename)
|
||||
InfoBar.Message("Saved " + filename)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Find opens a prompt and searches forward for the input
|
||||
@@ -808,7 +848,11 @@ func (h *BufPane) Copy() bool {
|
||||
if h.Cursor.HasSelection() {
|
||||
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
|
||||
@@ -982,7 +1026,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
|
||||
@@ -1230,20 +1278,20 @@ func (h *BufPane) Quit() bool {
|
||||
}
|
||||
}
|
||||
if h.Buf.Modified() {
|
||||
if config.GlobalSettings["autosave"].(float64) > 0 {
|
||||
// autosave on means we automatically save when quitting
|
||||
h.Save()
|
||||
quit()
|
||||
} else {
|
||||
InfoBar.YNPrompt("Save changes to "+h.Buf.GetName()+" before closing? (y,n,esc)", func(yes, canceled bool) {
|
||||
if !canceled && !yes {
|
||||
quit()
|
||||
} else if !canceled && yes {
|
||||
h.Save()
|
||||
quit()
|
||||
}
|
||||
})
|
||||
}
|
||||
// if config.GlobalSettings["autosave"].(float64) > 0 {
|
||||
// autosave on means we automatically save when quitting
|
||||
// h.Save()
|
||||
// quit()
|
||||
// } else {
|
||||
InfoBar.YNPrompt("Save changes to "+h.Buf.GetName()+" before closing? (y,n,esc)", func(yes, canceled bool) {
|
||||
if !canceled && !yes {
|
||||
quit()
|
||||
} else if !canceled && yes {
|
||||
h.Save()
|
||||
quit()
|
||||
}
|
||||
})
|
||||
// }
|
||||
} else {
|
||||
quit()
|
||||
}
|
||||
@@ -1435,6 +1483,41 @@ func (h *BufPane) SpawnMultiCursor() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// SpawnMultiCursorUp creates additional cursor, at the same X (if possible), one Y less.
|
||||
func (h *BufPane) SpawnMultiCursorUp() bool {
|
||||
if h.Cursor.Y == 0 {
|
||||
return false
|
||||
} else {
|
||||
h.Cursor.GotoLoc(buffer.Loc{h.Cursor.X, h.Cursor.Y - 1})
|
||||
h.Cursor.Relocate()
|
||||
}
|
||||
|
||||
c := buffer.NewCursor(h.Buf, buffer.Loc{h.Cursor.X, h.Cursor.Y + 1})
|
||||
h.Buf.AddCursor(c)
|
||||
h.Buf.SetCurCursor(h.Buf.NumCursors() - 1)
|
||||
h.Buf.MergeCursors()
|
||||
|
||||
h.Relocate()
|
||||
return true
|
||||
}
|
||||
|
||||
// SpawnMultiCursorUp creates additional cursor, at the same X (if possible), one Y more.
|
||||
func (h *BufPane) SpawnMultiCursorDown() bool {
|
||||
if h.Cursor.Y+1 == h.Buf.LinesNum() {
|
||||
return false
|
||||
} else {
|
||||
h.Cursor.GotoLoc(buffer.Loc{h.Cursor.X, h.Cursor.Y + 1})
|
||||
h.Cursor.Relocate()
|
||||
}
|
||||
|
||||
c := buffer.NewCursor(h.Buf, buffer.Loc{h.Cursor.X, h.Cursor.Y - 1})
|
||||
h.Buf.AddCursor(c)
|
||||
h.Buf.SetCurCursor(h.Buf.NumCursors() - 1)
|
||||
h.Buf.MergeCursors()
|
||||
h.Relocate()
|
||||
return true
|
||||
}
|
||||
|
||||
// SpawnMultiCursorSelect adds a cursor at the beginning of each line of a selection
|
||||
func (h *BufPane) SpawnMultiCursorSelect() bool {
|
||||
// Avoid cases where multiple cursors already exist, that would create problems
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"github.com/flynn/json5"
|
||||
"github.com/zyedidia/json5"
|
||||
"github.com/zyedidia/micro/internal/config"
|
||||
"github.com/zyedidia/micro/internal/screen"
|
||||
"github.com/zyedidia/tcell"
|
||||
@@ -83,6 +83,7 @@ modSearch:
|
||||
k = k[5:]
|
||||
modifiers |= tcell.ModShift
|
||||
case strings.HasPrefix(k, "\x1b"):
|
||||
screen.Screen.RegisterRawSeq(k)
|
||||
return RawEvent{
|
||||
esc: k,
|
||||
}, true
|
||||
@@ -374,8 +375,6 @@ var keyEvents = map[string]tcell.Key{
|
||||
"CtrlRightSq": tcell.KeyCtrlRightSq,
|
||||
"CtrlCarat": tcell.KeyCtrlCarat,
|
||||
"CtrlUnderscore": tcell.KeyCtrlUnderscore,
|
||||
"CtrlPageUp": tcell.KeyCtrlPgUp,
|
||||
"CtrlPageDown": tcell.KeyCtrlPgDn,
|
||||
"Tab": tcell.KeyTab,
|
||||
"Esc": tcell.KeyEsc,
|
||||
"Escape": tcell.KeyEscape,
|
||||
@@ -387,107 +386,3 @@ var keyEvents = map[string]tcell.Key{
|
||||
"PgUp": tcell.KeyPgUp,
|
||||
"PgDown": tcell.KeyPgDn,
|
||||
}
|
||||
|
||||
// DefaultBindings returns a map containing micro's default keybindings
|
||||
func DefaultBindings() map[string]string {
|
||||
return map[string]string{
|
||||
"Up": "CursorUp",
|
||||
"Down": "CursorDown",
|
||||
"Right": "CursorRight",
|
||||
"Left": "CursorLeft",
|
||||
"ShiftUp": "SelectUp",
|
||||
"ShiftDown": "SelectDown",
|
||||
"ShiftLeft": "SelectLeft",
|
||||
"ShiftRight": "SelectRight",
|
||||
"AltLeft": "WordLeft",
|
||||
"AltRight": "WordRight",
|
||||
"AltUp": "MoveLinesUp",
|
||||
"AltDown": "MoveLinesDown",
|
||||
"AltShiftRight": "SelectWordRight",
|
||||
"AltShiftLeft": "SelectWordLeft",
|
||||
"CtrlLeft": "StartOfLine",
|
||||
"CtrlRight": "EndOfLine",
|
||||
"CtrlShiftLeft": "SelectToStartOfLine",
|
||||
"ShiftHome": "SelectToStartOfLine",
|
||||
"CtrlShiftRight": "SelectToEndOfLine",
|
||||
"ShiftEnd": "SelectToEndOfLine",
|
||||
"CtrlUp": "CursorStart",
|
||||
"CtrlDown": "CursorEnd",
|
||||
"CtrlShiftUp": "SelectToStart",
|
||||
"CtrlShiftDown": "SelectToEnd",
|
||||
"Alt-{": "ParagraphPrevious",
|
||||
"Alt-}": "ParagraphNext",
|
||||
"Enter": "InsertNewline",
|
||||
"CtrlH": "Backspace",
|
||||
"Backspace": "Backspace",
|
||||
"Alt-CtrlH": "DeleteWordLeft",
|
||||
"Alt-Backspace": "DeleteWordLeft",
|
||||
"Tab": "Autocomplete|IndentSelection|InsertTab",
|
||||
"Backtab": "OutdentSelection|OutdentLine",
|
||||
"CtrlO": "OpenFile",
|
||||
"CtrlS": "Save",
|
||||
"CtrlF": "Find",
|
||||
"CtrlN": "FindNext",
|
||||
"CtrlP": "FindPrevious",
|
||||
"CtrlZ": "Undo",
|
||||
"CtrlY": "Redo",
|
||||
"CtrlC": "Copy",
|
||||
"CtrlX": "Cut",
|
||||
"CtrlK": "CutLine",
|
||||
"CtrlD": "DuplicateLine",
|
||||
"CtrlV": "Paste",
|
||||
"CtrlA": "SelectAll",
|
||||
"CtrlT": "AddTab",
|
||||
"Alt,": "PreviousTab",
|
||||
"Alt.": "NextTab",
|
||||
"Home": "StartOfLine",
|
||||
"End": "EndOfLine",
|
||||
"CtrlHome": "CursorStart",
|
||||
"CtrlEnd": "CursorEnd",
|
||||
"PageUp": "CursorPageUp",
|
||||
"PageDown": "CursorPageDown",
|
||||
"CtrlPageUp": "PreviousTab",
|
||||
"CtrlPageDown": "NextTab",
|
||||
"CtrlG": "ToggleHelp",
|
||||
"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": "StartOfLine",
|
||||
"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",
|
||||
|
||||
// Mouse bindings
|
||||
"MouseWheelUp": "ScrollUp",
|
||||
"MouseWheelDown": "ScrollDown",
|
||||
"MouseLeft": "MousePress",
|
||||
"MouseMiddle": "PastePrimary",
|
||||
"Ctrl-MouseLeft": "MouseMultiCursor",
|
||||
|
||||
"Alt-n": "SpawnMultiCursor",
|
||||
"Alt-m": "SpawnMultiCursorSelect",
|
||||
"Alt-p": "RemoveMultiCursor",
|
||||
"Alt-c": "RemoveAllMultiCursors",
|
||||
"Alt-x": "SkipMultiCursor",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,6 +62,8 @@ func BufMapKey(k Event, action string) {
|
||||
break
|
||||
}
|
||||
|
||||
// TODO: fix problem when complex bindings have these
|
||||
// characters (escape them?)
|
||||
idx := strings.IndexAny(action, "&|,")
|
||||
a := action
|
||||
if idx >= 0 {
|
||||
@@ -74,7 +76,7 @@ func BufMapKey(k Event, action string) {
|
||||
}
|
||||
|
||||
var afn func(*BufPane) bool
|
||||
if strings.HasPrefix(action, "command:") {
|
||||
if strings.HasPrefix(a, "command:") {
|
||||
a = strings.SplitN(a, ":", 2)[1]
|
||||
afn = CommandAction(a)
|
||||
names = append(names, "")
|
||||
@@ -86,15 +88,22 @@ func BufMapKey(k Event, action string) {
|
||||
a = strings.SplitN(a, ":", 2)[1]
|
||||
afn = LuaAction(a)
|
||||
if afn == nil {
|
||||
screen.TermMessage("Lua Error:", action, "does not exist")
|
||||
screen.TermMessage("Lua Error:", a, "does not exist")
|
||||
continue
|
||||
}
|
||||
names = append(names, "")
|
||||
split := strings.SplitN(a, ".", 2)
|
||||
if len(split) > 1 {
|
||||
a = strings.Title(split[0]) + strings.Title(split[1])
|
||||
} else {
|
||||
a = strings.Title(a)
|
||||
}
|
||||
|
||||
names = append(names, a)
|
||||
} else if f, ok := BufKeyActions[a]; ok {
|
||||
afn = f
|
||||
names = append(names, a)
|
||||
} else {
|
||||
screen.TermMessage("Error:", action, "does not exist")
|
||||
screen.TermMessage("Error:", a, "does not exist")
|
||||
continue
|
||||
}
|
||||
actionfns = append(actionfns, afn)
|
||||
@@ -173,15 +182,17 @@ type BufPane struct {
|
||||
multiWord bool
|
||||
|
||||
splitID uint64
|
||||
tab *Tab
|
||||
|
||||
// remember original location of a search in case the search is canceled
|
||||
searchOrig buffer.Loc
|
||||
}
|
||||
|
||||
func NewBufPane(buf *buffer.Buffer, win display.BWindow) *BufPane {
|
||||
func NewBufPane(buf *buffer.Buffer, win display.BWindow, tab *Tab) *BufPane {
|
||||
h := new(BufPane)
|
||||
h.Buf = buf
|
||||
h.BWindow = win
|
||||
h.tab = tab
|
||||
|
||||
h.Cursor = h.Buf.GetActiveCursor()
|
||||
h.mouseReleased = true
|
||||
@@ -191,9 +202,23 @@ func NewBufPane(buf *buffer.Buffer, win display.BWindow) *BufPane {
|
||||
return h
|
||||
}
|
||||
|
||||
func NewBufPaneFromBuf(buf *buffer.Buffer) *BufPane {
|
||||
func NewBufPaneFromBuf(buf *buffer.Buffer, tab *Tab) *BufPane {
|
||||
w := display.NewBufWindow(0, 0, 0, 0, buf)
|
||||
return NewBufPane(buf, w)
|
||||
return NewBufPane(buf, w, tab)
|
||||
}
|
||||
|
||||
func (h *BufPane) SetTab(t *Tab) {
|
||||
h.tab = t
|
||||
}
|
||||
|
||||
func (h *BufPane) Tab() *Tab {
|
||||
return h.tab
|
||||
}
|
||||
|
||||
func (h *BufPane) ResizePane(size int) {
|
||||
n := h.tab.GetNode(h.splitID)
|
||||
n.ResizeSplit(size)
|
||||
h.tab.Resize()
|
||||
}
|
||||
|
||||
// PluginCB calls all plugin callbacks with a certain name and
|
||||
@@ -223,8 +248,6 @@ func (h *BufPane) OpenBuffer(b *buffer.Buffer) {
|
||||
h.BWindow.SetBuffer(b)
|
||||
h.Cursor = b.GetActiveCursor()
|
||||
h.Resize(h.GetView().Width, h.GetView().Height)
|
||||
v := new(display.View)
|
||||
h.SetView(v)
|
||||
h.Relocate()
|
||||
// Set mouseReleased to true because we assume the mouse is not being pressed when
|
||||
// the editor is opened
|
||||
@@ -249,12 +272,26 @@ func (h *BufPane) Name() string {
|
||||
|
||||
// HandleEvent executes the tcell event properly
|
||||
func (h *BufPane) HandleEvent(event tcell.Event) {
|
||||
if h.Buf.ExternallyModified() {
|
||||
InfoBar.YNPrompt("The file on disk has changed. Reload file? (y,n)", func(yes, canceled bool) {
|
||||
if !yes || canceled {
|
||||
h.Buf.UpdateModTime()
|
||||
} else {
|
||||
h.Buf.ReOpen()
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
switch e := event.(type) {
|
||||
case *tcell.EventRaw:
|
||||
re := RawEvent{
|
||||
esc: e.EscSeq(),
|
||||
}
|
||||
h.DoKeyEvent(re)
|
||||
case *tcell.EventPaste:
|
||||
h.paste(e.Text())
|
||||
h.Relocate()
|
||||
case *tcell.EventKey:
|
||||
ke := KeyEvent{
|
||||
code: e.Key(),
|
||||
@@ -267,14 +304,27 @@ func (h *BufPane) HandleEvent(event tcell.Event) {
|
||||
h.DoRuneInsert(e.Rune())
|
||||
}
|
||||
case *tcell.EventMouse:
|
||||
cancel := false
|
||||
switch e.Buttons() {
|
||||
case tcell.Button1:
|
||||
_, my := e.Position()
|
||||
if h.Buf.Settings["statusline"].(bool) && my >= h.GetView().Y+h.GetView().Height-1 {
|
||||
cancel = true
|
||||
}
|
||||
case tcell.ButtonNone:
|
||||
// Mouse event with no click
|
||||
if !h.mouseReleased {
|
||||
// Mouse was just released
|
||||
|
||||
mx, my := e.Position()
|
||||
mouseLoc := h.LocFromVisual(buffer.Loc{X: mx, Y: my})
|
||||
// mx, my := e.Position()
|
||||
// mouseLoc := h.LocFromVisual(buffer.Loc{X: mx, Y: my})
|
||||
|
||||
// we could finish the selection based on the release location as described
|
||||
// below but when the mouse click is within the scroll margin this will
|
||||
// cause a scroll and selection even for a simple mouse click which is
|
||||
// not good
|
||||
// for terminals that don't support mouse motion events, selection via
|
||||
// the mouse won't work but this is ok
|
||||
|
||||
// Relocating here isn't really necessary because the cursor will
|
||||
// be in the right place from the last mouse event
|
||||
@@ -282,20 +332,22 @@ func (h *BufPane) HandleEvent(event tcell.Event) {
|
||||
// events, this still allows the user to make selections, except only after they
|
||||
// release the mouse
|
||||
|
||||
if !h.doubleClick && !h.tripleClick {
|
||||
h.Cursor.Loc = mouseLoc
|
||||
h.Cursor.SetSelectionEnd(h.Cursor.Loc)
|
||||
h.Cursor.CopySelection("primary")
|
||||
}
|
||||
// if !h.doubleClick && !h.tripleClick {
|
||||
// h.Cursor.Loc = mouseLoc
|
||||
// h.Cursor.SetSelectionEnd(h.Cursor.Loc)
|
||||
// h.Cursor.CopySelection("primary")
|
||||
// }
|
||||
h.mouseReleased = true
|
||||
}
|
||||
}
|
||||
|
||||
me := MouseEvent{
|
||||
btn: e.Buttons(),
|
||||
mod: e.Modifiers(),
|
||||
if !cancel {
|
||||
me := MouseEvent{
|
||||
btn: e.Buttons(),
|
||||
mod: e.Modifiers(),
|
||||
}
|
||||
h.DoMouseEvent(me, e)
|
||||
}
|
||||
h.DoMouseEvent(me, e)
|
||||
}
|
||||
h.Buf.MergeCursors()
|
||||
|
||||
@@ -326,15 +378,15 @@ func (h *BufPane) DoKeyEvent(e Event) bool {
|
||||
}
|
||||
|
||||
func (h *BufPane) execAction(action func(*BufPane) bool, name string, cursor int) bool {
|
||||
if name != "Autocomplete" {
|
||||
if name != "Autocomplete" && name != "CycleAutocompleteBack" {
|
||||
h.Buf.HasSuggestions = false
|
||||
}
|
||||
|
||||
_, isMulti := MultiActions[name]
|
||||
if (!isMulti && cursor == 0) || isMulti {
|
||||
if h.PluginCB("pre" + name) {
|
||||
asuccess := action(h)
|
||||
psuccess := h.PluginCB("on" + name)
|
||||
success := action(h)
|
||||
success = success && h.PluginCB("on"+name)
|
||||
|
||||
if isMulti {
|
||||
if recording_macro {
|
||||
@@ -344,13 +396,17 @@ func (h *BufPane) execAction(action func(*BufPane) bool, name string, cursor int
|
||||
}
|
||||
}
|
||||
|
||||
return asuccess && psuccess
|
||||
return success
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (h *BufPane) completeAction(action string) {
|
||||
h.PluginCB("on" + action)
|
||||
}
|
||||
|
||||
func (h *BufPane) HasKeyEvent(e Event) bool {
|
||||
_, ok := BufKeyBindings[e]
|
||||
return ok
|
||||
@@ -400,22 +456,29 @@ func (h *BufPane) DoRuneInsert(r rune) {
|
||||
}
|
||||
}
|
||||
|
||||
func (h *BufPane) VSplitBuf(buf *buffer.Buffer) *BufPane {
|
||||
e := NewBufPaneFromBuf(buf)
|
||||
e.splitID = MainTab().GetNode(h.splitID).VSplit(h.Buf.Settings["splitright"].(bool))
|
||||
func (h *BufPane) VSplitIndex(buf *buffer.Buffer, right bool) *BufPane {
|
||||
e := NewBufPaneFromBuf(buf, h.tab)
|
||||
e.splitID = MainTab().GetNode(h.splitID).VSplit(right)
|
||||
MainTab().Panes = append(MainTab().Panes, e)
|
||||
MainTab().Resize()
|
||||
MainTab().SetActive(len(MainTab().Panes) - 1)
|
||||
return e
|
||||
}
|
||||
func (h *BufPane) HSplitBuf(buf *buffer.Buffer) *BufPane {
|
||||
e := NewBufPaneFromBuf(buf)
|
||||
e.splitID = MainTab().GetNode(h.splitID).HSplit(h.Buf.Settings["splitbottom"].(bool))
|
||||
func (h *BufPane) HSplitIndex(buf *buffer.Buffer, bottom bool) *BufPane {
|
||||
e := NewBufPaneFromBuf(buf, h.tab)
|
||||
e.splitID = MainTab().GetNode(h.splitID).HSplit(bottom)
|
||||
MainTab().Panes = append(MainTab().Panes, e)
|
||||
MainTab().Resize()
|
||||
MainTab().SetActive(len(MainTab().Panes) - 1)
|
||||
return e
|
||||
}
|
||||
|
||||
func (h *BufPane) VSplitBuf(buf *buffer.Buffer) *BufPane {
|
||||
return h.VSplitIndex(buf, h.Buf.Settings["splitright"].(bool))
|
||||
}
|
||||
func (h *BufPane) HSplitBuf(buf *buffer.Buffer) *BufPane {
|
||||
return h.HSplitIndex(buf, h.Buf.Settings["splitbottom"].(bool))
|
||||
}
|
||||
func (h *BufPane) Close() {
|
||||
h.Buf.Close()
|
||||
}
|
||||
@@ -464,6 +527,7 @@ var BufKeyActions = map[string]BufKeyAction{
|
||||
"DeleteWordLeft": (*BufPane).DeleteWordLeft,
|
||||
"SelectLine": (*BufPane).SelectLine,
|
||||
"SelectToStartOfLine": (*BufPane).SelectToStartOfLine,
|
||||
"SelectToStartOfText": (*BufPane).SelectToStartOfText,
|
||||
"SelectToEndOfLine": (*BufPane).SelectToEndOfLine,
|
||||
"ParagraphPrevious": (*BufPane).ParagraphPrevious,
|
||||
"ParagraphNext": (*BufPane).ParagraphNext,
|
||||
@@ -490,6 +554,7 @@ var BufKeyActions = map[string]BufKeyAction{
|
||||
"IndentSelection": (*BufPane).IndentSelection,
|
||||
"OutdentSelection": (*BufPane).OutdentSelection,
|
||||
"Autocomplete": (*BufPane).Autocomplete,
|
||||
"CycleAutocompleteBack": (*BufPane).CycleAutocompleteBack,
|
||||
"OutdentLine": (*BufPane).OutdentLine,
|
||||
"Paste": (*BufPane).Paste,
|
||||
"PastePrimary": (*BufPane).PastePrimary,
|
||||
@@ -503,6 +568,7 @@ var BufKeyActions = map[string]BufKeyAction{
|
||||
"SelectPageDown": (*BufPane).SelectPageDown,
|
||||
"HalfPageUp": (*BufPane).HalfPageUp,
|
||||
"HalfPageDown": (*BufPane).HalfPageDown,
|
||||
"StartOfText": (*BufPane).StartOfText,
|
||||
"StartOfLine": (*BufPane).StartOfLine,
|
||||
"EndOfLine": (*BufPane).EndOfLine,
|
||||
"ToggleHelp": (*BufPane).ToggleHelp,
|
||||
@@ -529,6 +595,8 @@ var BufKeyActions = map[string]BufKeyAction{
|
||||
"ScrollUp": (*BufPane).ScrollUpAction,
|
||||
"ScrollDown": (*BufPane).ScrollDownAction,
|
||||
"SpawnMultiCursor": (*BufPane).SpawnMultiCursor,
|
||||
"SpawnMultiCursorUp": (*BufPane).SpawnMultiCursorUp,
|
||||
"SpawnMultiCursorDown": (*BufPane).SpawnMultiCursorDown,
|
||||
"SpawnMultiCursorSelect": (*BufPane).SpawnMultiCursorSelect,
|
||||
"RemoveMultiCursor": (*BufPane).RemoveMultiCursor,
|
||||
"RemoveAllMultiCursors": (*BufPane).RemoveAllMultiCursors,
|
||||
@@ -573,6 +641,7 @@ var MultiActions = map[string]bool{
|
||||
"DeleteWordLeft": true,
|
||||
"SelectLine": true,
|
||||
"SelectToStartOfLine": true,
|
||||
"SelectToStartOfText": true,
|
||||
"SelectToEndOfLine": true,
|
||||
"ParagraphPrevious": true,
|
||||
"ParagraphNext": true,
|
||||
@@ -596,6 +665,7 @@ var MultiActions = map[string]bool{
|
||||
"SelectPageUp": true,
|
||||
"SelectPageDown": true,
|
||||
"StartOfLine": true,
|
||||
"StartOfText": true,
|
||||
"EndOfLine": true,
|
||||
"JumpToMatchingBrace": true,
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
|
||||
luar "layeh.com/gopher-luar"
|
||||
|
||||
shellquote "github.com/kballard/go-shellquote"
|
||||
lua "github.com/yuin/gopher-lua"
|
||||
"github.com/zyedidia/micro/internal/buffer"
|
||||
"github.com/zyedidia/micro/internal/config"
|
||||
@@ -21,7 +22,6 @@ import (
|
||||
"github.com/zyedidia/micro/internal/screen"
|
||||
"github.com/zyedidia/micro/internal/shell"
|
||||
"github.com/zyedidia/micro/internal/util"
|
||||
"github.com/zyedidia/micro/pkg/shellwords"
|
||||
)
|
||||
|
||||
// A Command contains information about how to execute a command
|
||||
@@ -35,37 +35,37 @@ var commands map[string]Command
|
||||
|
||||
func InitCommands() {
|
||||
commands = map[string]Command{
|
||||
"set": Command{(*BufPane).SetCmd, OptionValueComplete},
|
||||
"reset": Command{(*BufPane).ResetCmd, OptionValueComplete},
|
||||
"setlocal": Command{(*BufPane).SetLocalCmd, OptionValueComplete},
|
||||
"show": Command{(*BufPane).ShowCmd, OptionComplete},
|
||||
"showkey": Command{(*BufPane).ShowKeyCmd, nil},
|
||||
"run": Command{(*BufPane).RunCmd, nil},
|
||||
"bind": Command{(*BufPane).BindCmd, nil},
|
||||
"unbind": Command{(*BufPane).UnbindCmd, nil},
|
||||
"quit": Command{(*BufPane).QuitCmd, nil},
|
||||
"goto": Command{(*BufPane).GotoCmd, nil},
|
||||
"save": Command{(*BufPane).SaveCmd, nil},
|
||||
"replace": Command{(*BufPane).ReplaceCmd, nil},
|
||||
"replaceall": Command{(*BufPane).ReplaceAllCmd, nil},
|
||||
"vsplit": Command{(*BufPane).VSplitCmd, buffer.FileComplete},
|
||||
"hsplit": Command{(*BufPane).HSplitCmd, buffer.FileComplete},
|
||||
"tab": Command{(*BufPane).NewTabCmd, buffer.FileComplete},
|
||||
"help": Command{(*BufPane).HelpCmd, HelpComplete},
|
||||
"eval": Command{(*BufPane).EvalCmd, nil},
|
||||
"log": Command{(*BufPane).ToggleLogCmd, nil},
|
||||
"plugin": Command{(*BufPane).PluginCmd, PluginComplete},
|
||||
"reload": Command{(*BufPane).ReloadCmd, nil},
|
||||
"reopen": Command{(*BufPane).ReopenCmd, nil},
|
||||
"cd": Command{(*BufPane).CdCmd, buffer.FileComplete},
|
||||
"pwd": Command{(*BufPane).PwdCmd, nil},
|
||||
"open": Command{(*BufPane).OpenCmd, buffer.FileComplete},
|
||||
"tabswitch": Command{(*BufPane).TabSwitchCmd, nil},
|
||||
"term": Command{(*BufPane).TermCmd, nil},
|
||||
"memusage": Command{(*BufPane).MemUsageCmd, nil},
|
||||
"retab": Command{(*BufPane).RetabCmd, nil},
|
||||
"raw": Command{(*BufPane).RawCmd, nil},
|
||||
"textfilter": Command{(*BufPane).TextFilterCmd, nil},
|
||||
"set": {(*BufPane).SetCmd, OptionValueComplete},
|
||||
"reset": {(*BufPane).ResetCmd, OptionValueComplete},
|
||||
"setlocal": {(*BufPane).SetLocalCmd, OptionValueComplete},
|
||||
"show": {(*BufPane).ShowCmd, OptionComplete},
|
||||
"showkey": {(*BufPane).ShowKeyCmd, nil},
|
||||
"run": {(*BufPane).RunCmd, nil},
|
||||
"bind": {(*BufPane).BindCmd, nil},
|
||||
"unbind": {(*BufPane).UnbindCmd, nil},
|
||||
"quit": {(*BufPane).QuitCmd, nil},
|
||||
"goto": {(*BufPane).GotoCmd, nil},
|
||||
"save": {(*BufPane).SaveCmd, nil},
|
||||
"replace": {(*BufPane).ReplaceCmd, nil},
|
||||
"replaceall": {(*BufPane).ReplaceAllCmd, nil},
|
||||
"vsplit": {(*BufPane).VSplitCmd, buffer.FileComplete},
|
||||
"hsplit": {(*BufPane).HSplitCmd, buffer.FileComplete},
|
||||
"tab": {(*BufPane).NewTabCmd, buffer.FileComplete},
|
||||
"help": {(*BufPane).HelpCmd, HelpComplete},
|
||||
"eval": {(*BufPane).EvalCmd, nil},
|
||||
"log": {(*BufPane).ToggleLogCmd, nil},
|
||||
"plugin": {(*BufPane).PluginCmd, PluginComplete},
|
||||
"reload": {(*BufPane).ReloadCmd, nil},
|
||||
"reopen": {(*BufPane).ReopenCmd, nil},
|
||||
"cd": {(*BufPane).CdCmd, buffer.FileComplete},
|
||||
"pwd": {(*BufPane).PwdCmd, nil},
|
||||
"open": {(*BufPane).OpenCmd, buffer.FileComplete},
|
||||
"tabswitch": {(*BufPane).TabSwitchCmd, nil},
|
||||
"term": {(*BufPane).TermCmd, nil},
|
||||
"memusage": {(*BufPane).MemUsageCmd, nil},
|
||||
"retab": {(*BufPane).RetabCmd, nil},
|
||||
"raw": {(*BufPane).RawCmd, nil},
|
||||
"textfilter": {(*BufPane).TextFilterCmd, nil},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,106 +120,20 @@ func CommandAction(cmd string) BufKeyAction {
|
||||
}
|
||||
}
|
||||
|
||||
var PluginCmds = []string{"list", "info", "version"}
|
||||
var PluginCmds = []string{"install", "remove", "update", "available", "list", "search"}
|
||||
|
||||
// PluginCmd installs, removes, updates, lists, or searches for given plugins
|
||||
func (h *BufPane) PluginCmd(args []string) {
|
||||
if len(args) <= 0 {
|
||||
InfoBar.Error("Not enough arguments, see 'help commands'")
|
||||
if len(args) < 1 {
|
||||
InfoBar.Error("Not enough arguments")
|
||||
return
|
||||
}
|
||||
|
||||
valid := true
|
||||
switch args[0] {
|
||||
case "list":
|
||||
for _, pl := range config.Plugins {
|
||||
var en string
|
||||
if pl.IsEnabled() {
|
||||
en = "enabled"
|
||||
} else {
|
||||
en = "disabled"
|
||||
}
|
||||
WriteLog(fmt.Sprintf("%s: %s", pl.Name, en))
|
||||
if pl.Default {
|
||||
WriteLog(" (default)\n")
|
||||
} else {
|
||||
WriteLog("\n")
|
||||
}
|
||||
}
|
||||
WriteLog("Default plugins come pre-installed with micro.")
|
||||
case "version":
|
||||
if len(args) <= 1 {
|
||||
InfoBar.Error("No plugin provided to give info for")
|
||||
return
|
||||
}
|
||||
found := false
|
||||
for _, pl := range config.Plugins {
|
||||
if pl.Name == args[1] {
|
||||
found = true
|
||||
if pl.Info == nil {
|
||||
InfoBar.Message("Sorry no version for", pl.Name)
|
||||
return
|
||||
}
|
||||
|
||||
WriteLog("Version: " + pl.Info.Vstr + "\n")
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
InfoBar.Message(args[1], "is not installed")
|
||||
}
|
||||
case "info":
|
||||
if len(args) <= 1 {
|
||||
InfoBar.Error("No plugin provided to give info for")
|
||||
return
|
||||
}
|
||||
found := false
|
||||
for _, pl := range config.Plugins {
|
||||
if pl.Name == args[1] {
|
||||
found = true
|
||||
if pl.Info == nil {
|
||||
InfoBar.Message("Sorry no info for ", pl.Name)
|
||||
return
|
||||
}
|
||||
|
||||
var buffer bytes.Buffer
|
||||
buffer.WriteString("Name: ")
|
||||
buffer.WriteString(pl.Info.Name)
|
||||
buffer.WriteString("\n")
|
||||
buffer.WriteString("Description: ")
|
||||
buffer.WriteString(pl.Info.Desc)
|
||||
buffer.WriteString("\n")
|
||||
buffer.WriteString("Website: ")
|
||||
buffer.WriteString(pl.Info.Site)
|
||||
buffer.WriteString("\n")
|
||||
buffer.WriteString("Installation link: ")
|
||||
buffer.WriteString(pl.Info.Install)
|
||||
buffer.WriteString("\n")
|
||||
buffer.WriteString("Version: ")
|
||||
buffer.WriteString(pl.Info.Vstr)
|
||||
buffer.WriteString("\n")
|
||||
buffer.WriteString("Requirements:")
|
||||
buffer.WriteString("\n")
|
||||
for _, r := range pl.Info.Require {
|
||||
buffer.WriteString(" - ")
|
||||
buffer.WriteString(r)
|
||||
buffer.WriteString("\n")
|
||||
}
|
||||
|
||||
WriteLog(buffer.String())
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
InfoBar.Message(args[1], "is not installed")
|
||||
return
|
||||
}
|
||||
default:
|
||||
InfoBar.Error("Not a valid plugin command")
|
||||
return
|
||||
}
|
||||
|
||||
if valid && h.Buf.Type != buffer.BTLog {
|
||||
if h.Buf.Type != buffer.BTLog {
|
||||
OpenLogBuf(h)
|
||||
}
|
||||
|
||||
config.PluginCommand(buffer.LogBuf, args[0], args[1:])
|
||||
}
|
||||
|
||||
// RetabCmd changes all spaces to tabs or all tabs to spaces
|
||||
@@ -233,14 +147,14 @@ func (h *BufPane) RetabCmd(args []string) {
|
||||
func (h *BufPane) RawCmd(args []string) {
|
||||
width, height := screen.Screen.Size()
|
||||
iOffset := config.GetInfoBarOffset()
|
||||
tp := NewTabFromPane(0, 0, width, height-iOffset, NewRawPane())
|
||||
tp := NewTabFromPane(0, 0, width, height-iOffset, NewRawPane(nil))
|
||||
Tabs.AddTab(tp)
|
||||
Tabs.SetActive(len(Tabs.List) - 1)
|
||||
}
|
||||
|
||||
// TextFilterCmd filters the selection through the command.
|
||||
// Selection goes to the command input.
|
||||
// On successfull run command output replaces the current selection.
|
||||
// On successful run command output replaces the current selection.
|
||||
func (h *BufPane) TextFilterCmd(args []string) {
|
||||
if len(args) == 0 {
|
||||
InfoBar.Error("usage: textfilter arguments")
|
||||
@@ -344,11 +258,14 @@ func (h *BufPane) OpenCmd(args []string) {
|
||||
if len(args) > 0 {
|
||||
filename := args[0]
|
||||
// the filename might or might not be quoted, so unquote first then join the strings.
|
||||
args, err := shellwords.Split(filename)
|
||||
args, err := shellquote.Split(filename)
|
||||
if err != nil {
|
||||
InfoBar.Error("Error parsing args ", err)
|
||||
return
|
||||
}
|
||||
if len(args) == 0 {
|
||||
return
|
||||
}
|
||||
filename = strings.Join(args, " ")
|
||||
|
||||
open := func() {
|
||||
@@ -497,6 +414,7 @@ func (h *BufPane) HSplitCmd(args []string) {
|
||||
|
||||
// EvalCmd evaluates a lua expression
|
||||
func (h *BufPane) EvalCmd(args []string) {
|
||||
InfoBar.Error("Eval unsupported")
|
||||
}
|
||||
|
||||
// NewTabCmd opens the given file in a new tab
|
||||
@@ -523,37 +441,56 @@ func (h *BufPane) NewTabCmd(args []string) {
|
||||
}
|
||||
|
||||
func SetGlobalOptionNative(option string, nativeValue interface{}) error {
|
||||
config.GlobalSettings[option] = nativeValue
|
||||
local := false
|
||||
for _, s := range config.LocalSettings {
|
||||
if s == option {
|
||||
local = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if option == "colorscheme" {
|
||||
// LoadSyntaxFiles()
|
||||
config.InitColorscheme()
|
||||
for _, b := range buffer.OpenBuffers {
|
||||
b.UpdateRules()
|
||||
}
|
||||
} else if option == "infobar" || option == "keymenu" {
|
||||
Tabs.Resize()
|
||||
} else if option == "mouse" {
|
||||
if !nativeValue.(bool) {
|
||||
screen.Screen.DisableMouse()
|
||||
if !local {
|
||||
config.GlobalSettings[option] = nativeValue
|
||||
|
||||
if option == "colorscheme" {
|
||||
// LoadSyntaxFiles()
|
||||
config.InitColorscheme()
|
||||
for _, b := range buffer.OpenBuffers {
|
||||
b.UpdateRules()
|
||||
}
|
||||
} else if option == "infobar" || option == "keymenu" {
|
||||
Tabs.Resize()
|
||||
} else if option == "mouse" {
|
||||
if !nativeValue.(bool) {
|
||||
screen.Screen.DisableMouse()
|
||||
} else {
|
||||
screen.Screen.EnableMouse()
|
||||
}
|
||||
// autosave option has been removed
|
||||
// } else if option == "autosave" {
|
||||
// if nativeValue.(float64) > 0 {
|
||||
// config.SetAutoTime(int(nativeValue.(float64)))
|
||||
// config.StartAutoSave()
|
||||
// } else {
|
||||
// config.SetAutoTime(0)
|
||||
// }
|
||||
} else if option == "paste" {
|
||||
screen.Screen.SetPaste(nativeValue.(bool))
|
||||
} else {
|
||||
screen.Screen.EnableMouse()
|
||||
}
|
||||
} else if option == "autosave" {
|
||||
if nativeValue.(float64) > 0 {
|
||||
config.SetAutoTime(int(nativeValue.(float64)))
|
||||
config.StartAutoSave()
|
||||
} else {
|
||||
config.SetAutoTime(0)
|
||||
}
|
||||
} else {
|
||||
for _, pl := range config.Plugins {
|
||||
if option == pl.Name {
|
||||
if nativeValue.(bool) && !pl.Loaded {
|
||||
pl.Load()
|
||||
pl.Call("init")
|
||||
} else if !nativeValue.(bool) && pl.Loaded {
|
||||
pl.Call("deinit")
|
||||
for _, pl := range config.Plugins {
|
||||
if option == pl.Name {
|
||||
if nativeValue.(bool) && !pl.Loaded {
|
||||
pl.Load()
|
||||
_, err := pl.Call("init")
|
||||
if err != nil && err != config.ErrNoSuchFunction {
|
||||
screen.TermMessage(err)
|
||||
}
|
||||
} else if !nativeValue.(bool) && pl.Loaded {
|
||||
_, err := pl.Call("deinit")
|
||||
if err != nil && err != config.ErrNoSuchFunction {
|
||||
screen.TermMessage(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -691,7 +628,7 @@ func (h *BufPane) BindCmd(args []string) {
|
||||
// UnbindCmd binds a key to its default action
|
||||
func (h *BufPane) UnbindCmd(args []string) {
|
||||
if len(args) < 1 {
|
||||
InfoBar.Error("Not enough arguements")
|
||||
InfoBar.Error("Not enough arguments")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -703,7 +640,7 @@ func (h *BufPane) UnbindCmd(args []string) {
|
||||
|
||||
// RunCmd runs a shell command in the background
|
||||
func (h *BufPane) RunCmd(args []string) {
|
||||
runf, err := shell.RunBackgroundShell(shellwords.Join(args...))
|
||||
runf, err := shell.RunBackgroundShell(shellquote.Join(args...))
|
||||
if err != nil {
|
||||
InfoBar.Error(err)
|
||||
} else {
|
||||
@@ -820,23 +757,23 @@ func (h *BufPane) ReplaceCmd(args []string) {
|
||||
|
||||
nreplaced := 0
|
||||
start := h.Buf.Start()
|
||||
end := h.Buf.End()
|
||||
if h.Cursor.HasSelection() {
|
||||
start = h.Cursor.CurSelection[0]
|
||||
end = h.Cursor.CurSelection[1]
|
||||
}
|
||||
// end := h.Buf.End()
|
||||
// if h.Cursor.HasSelection() {
|
||||
// start = h.Cursor.CurSelection[0]
|
||||
// end = h.Cursor.CurSelection[1]
|
||||
// }
|
||||
if all {
|
||||
nreplaced = h.Buf.ReplaceRegex(start, end, regex, replace)
|
||||
nreplaced = h.Buf.ReplaceRegex(start, h.Buf.End(), regex, replace)
|
||||
} else {
|
||||
inRange := func(l buffer.Loc) bool {
|
||||
return l.GreaterEqual(start) && l.LessThan(end)
|
||||
return l.GreaterEqual(start) && l.LessEqual(h.Buf.End())
|
||||
}
|
||||
|
||||
searchLoc := start
|
||||
searching := true
|
||||
var doReplacement func()
|
||||
doReplacement = func() {
|
||||
locs, found, err := h.Buf.FindNext(search, start, end, searchLoc, true, !noRegex)
|
||||
locs, found, err := h.Buf.FindNext(search, start, h.Buf.End(), searchLoc, true, !noRegex)
|
||||
if err != nil {
|
||||
InfoBar.Error(err)
|
||||
return
|
||||
@@ -853,6 +790,7 @@ func (h *BufPane) ReplaceCmd(args []string) {
|
||||
InfoBar.YNPrompt("Perform replacement (y,n,esc)", func(yes, canceled bool) {
|
||||
if !canceled && yes {
|
||||
h.Buf.Replace(locs[0], locs[1], replaceStr)
|
||||
|
||||
searchLoc = locs[0]
|
||||
searchLoc.X += utf8.RuneCount(replace)
|
||||
h.Cursor.Loc = searchLoc
|
||||
@@ -892,7 +830,7 @@ func (h *BufPane) ReplaceAllCmd(args []string) {
|
||||
|
||||
// TermCmd opens a terminal in the current view
|
||||
func (h *BufPane) TermCmd(args []string) {
|
||||
ps := MainTab().Panes
|
||||
ps := h.tab.Panes
|
||||
|
||||
if len(args) == 0 {
|
||||
sh := os.Getenv("SHELL")
|
||||
@@ -917,7 +855,7 @@ func (h *BufPane) TermCmd(args []string) {
|
||||
}
|
||||
|
||||
v := h.GetView()
|
||||
MainTab().Panes[i] = NewTermPane(v.X, v.Y, v.Width, v.Height, t, id)
|
||||
MainTab().Panes[i] = NewTermPane(v.X, v.Y, v.Width, v.Height, t, id, MainTab())
|
||||
MainTab().SetActive(i)
|
||||
}
|
||||
|
||||
@@ -949,12 +887,16 @@ func (h *BufPane) TermCmd(args []string) {
|
||||
|
||||
// HandleCommand handles input from the user
|
||||
func (h *BufPane) HandleCommand(input string) {
|
||||
args, err := shellwords.Split(input)
|
||||
args, err := shellquote.Split(input)
|
||||
if err != nil {
|
||||
InfoBar.Error("Error parsing args ", err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(args) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
inputCmd := args[0]
|
||||
|
||||
if _, ok := commands[inputCmd]; !ok {
|
||||
|
||||
107
internal/action/defaults_darwin.go
Normal file
107
internal/action/defaults_darwin.go
Normal file
@@ -0,0 +1,107 @@
|
||||
package action
|
||||
|
||||
// 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": "StartOfText",
|
||||
"CtrlRight": "EndOfLine",
|
||||
"CtrlShiftLeft": "SelectToStartOfText",
|
||||
"ShiftHome": "SelectToStartOfText",
|
||||
"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": "Copy",
|
||||
"CtrlX": "Cut",
|
||||
"CtrlK": "CutLine",
|
||||
"CtrlD": "DuplicateLine",
|
||||
"CtrlV": "Paste",
|
||||
"CtrlA": "SelectAll",
|
||||
"CtrlT": "AddTab",
|
||||
"Alt,": "PreviousTab",
|
||||
"Alt.": "NextTab",
|
||||
"Home": "StartOfText",
|
||||
"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",
|
||||
|
||||
// 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",
|
||||
|
||||
"Alt-n": "SpawnMultiCursor",
|
||||
"AltShiftUp": "SpawnMultiCursorUp",
|
||||
"AltShiftDown": "SpawnMultiCursorDown",
|
||||
"Alt-m": "SpawnMultiCursorSelect",
|
||||
"Alt-p": "RemoveMultiCursor",
|
||||
"Alt-c": "RemoveAllMultiCursors",
|
||||
"Alt-x": "SkipMultiCursor",
|
||||
}
|
||||
}
|
||||
109
internal/action/defaults_other.go
Normal file
109
internal/action/defaults_other.go
Normal file
@@ -0,0 +1,109 @@
|
||||
// +build !darwin
|
||||
|
||||
package action
|
||||
|
||||
// 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": "StartOfText",
|
||||
"AltRight": "EndOfLine",
|
||||
"AltShiftLeft": "SelectToStartOfText",
|
||||
"ShiftHome": "SelectToStartOfText",
|
||||
"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": "Copy",
|
||||
"CtrlX": "Cut",
|
||||
"CtrlK": "CutLine",
|
||||
"CtrlD": "DuplicateLine",
|
||||
"CtrlV": "Paste",
|
||||
"CtrlA": "SelectAll",
|
||||
"CtrlT": "AddTab",
|
||||
"Alt,": "PreviousTab",
|
||||
"Alt.": "NextTab",
|
||||
"Home": "StartOfText",
|
||||
"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",
|
||||
|
||||
// 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",
|
||||
|
||||
"Alt-n": "SpawnMultiCursor",
|
||||
"Alt-m": "SpawnMultiCursorSelect",
|
||||
"AltShiftUp": "SpawnMultiCursorUp",
|
||||
"AltShiftDown": "SpawnMultiCursorDown",
|
||||
"Alt-p": "RemoveMultiCursor",
|
||||
"Alt-c": "RemoveAllMultiCursors",
|
||||
"Alt-x": "SkipMultiCursor",
|
||||
}
|
||||
}
|
||||
@@ -17,10 +17,10 @@ type InfoPane struct {
|
||||
*info.InfoBuf
|
||||
}
|
||||
|
||||
func NewInfoPane(ib *info.InfoBuf, w display.BWindow) *InfoPane {
|
||||
func NewInfoPane(ib *info.InfoBuf, w display.BWindow, tab *Tab) *InfoPane {
|
||||
ip := new(InfoPane)
|
||||
ip.InfoBuf = ib
|
||||
ip.BufPane = NewBufPane(ib.Buffer, w)
|
||||
ip.BufPane = NewBufPane(ib.Buffer, w, tab)
|
||||
|
||||
return ip
|
||||
}
|
||||
@@ -28,7 +28,7 @@ func NewInfoPane(ib *info.InfoBuf, w display.BWindow) *InfoPane {
|
||||
func NewInfoBar() *InfoPane {
|
||||
ib := info.NewBuffer()
|
||||
w := display.NewInfoWindow(ib)
|
||||
return NewInfoPane(ib, w)
|
||||
return NewInfoPane(ib, w, nil)
|
||||
}
|
||||
|
||||
func (h *InfoPane) Close() {
|
||||
@@ -73,6 +73,7 @@ func (h *InfoPane) HandleEvent(event tcell.Event) {
|
||||
}
|
||||
}
|
||||
|
||||
// DoKeyEvent executes a key event for the command bar, doing any overriden actions
|
||||
func (h *InfoPane) DoKeyEvent(e KeyEvent) bool {
|
||||
done := false
|
||||
if action, ok := BufKeyBindings[e]; ok {
|
||||
@@ -85,7 +86,7 @@ func (h *InfoPane) DoKeyEvent(e KeyEvent) bool {
|
||||
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.Contains(estr, s) {
|
||||
if strings.HasPrefix(estr, s) {
|
||||
done = true
|
||||
a(h)
|
||||
break
|
||||
@@ -147,14 +148,13 @@ var InfoNones = []string{
|
||||
"SkipMultiCursor",
|
||||
}
|
||||
|
||||
// InfoOverrides is the list of actions which have been overriden
|
||||
// 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,
|
||||
"OutdentLine": (*InfoPane).CycleBack,
|
||||
"Escape": (*InfoPane).Escape,
|
||||
"Quit": (*InfoPane).Quit,
|
||||
"QuitAll": (*InfoPane).QuitAll,
|
||||
@@ -196,13 +196,6 @@ func (h *InfoPane) Autocomplete() {
|
||||
}
|
||||
}
|
||||
|
||||
// CycleBack cycles back in the autocomplete suggestion list
|
||||
func (h *InfoPane) CycleBack() {
|
||||
if h.Buf.HasSuggestions {
|
||||
h.Buf.CycleAutocomplete(false)
|
||||
}
|
||||
}
|
||||
|
||||
// InsertNewline completes the prompt
|
||||
func (h *InfoPane) InsertNewline() {
|
||||
if !h.HasYN {
|
||||
|
||||
@@ -11,4 +11,6 @@ type Pane interface {
|
||||
SetID(i uint64)
|
||||
Name() string
|
||||
Close()
|
||||
SetTab(t *Tab)
|
||||
Tab() *Tab
|
||||
}
|
||||
|
||||
@@ -13,17 +13,17 @@ type RawPane struct {
|
||||
*BufPane
|
||||
}
|
||||
|
||||
func NewRawPaneFromWin(b *buffer.Buffer, win display.BWindow) *RawPane {
|
||||
func NewRawPaneFromWin(b *buffer.Buffer, win display.BWindow, tab *Tab) *RawPane {
|
||||
rh := new(RawPane)
|
||||
rh.BufPane = NewBufPane(b, win)
|
||||
rh.BufPane = NewBufPane(b, win, tab)
|
||||
|
||||
return rh
|
||||
}
|
||||
|
||||
func NewRawPane() *RawPane {
|
||||
func NewRawPane(tab *Tab) *RawPane {
|
||||
b := buffer.NewBufferFromString("", "", buffer.BTRaw)
|
||||
w := display.NewBufWindow(0, 0, 0, 0, b)
|
||||
return NewRawPaneFromWin(b, w)
|
||||
return NewRawPaneFromWin(b, w, tab)
|
||||
}
|
||||
|
||||
func (h *RawPane) HandleEvent(event tcell.Event) {
|
||||
|
||||
@@ -91,6 +91,7 @@ func (t *TabList) Resize() {
|
||||
t.List[0].Node.Resize(w, h-iOffset)
|
||||
t.List[0].Resize()
|
||||
}
|
||||
t.TabWindow.Resize(w, h)
|
||||
}
|
||||
|
||||
// HandleEvent checks for a resize event or a mouse event on the tab bar
|
||||
@@ -103,9 +104,15 @@ func (t *TabList) HandleEvent(event tcell.Event) {
|
||||
mx, my := e.Position()
|
||||
switch e.Buttons() {
|
||||
case tcell.Button1:
|
||||
ind := t.LocFromVisual(buffer.Loc{mx, my})
|
||||
if ind != -1 {
|
||||
t.SetActive(ind)
|
||||
if len(t.List) > 1 {
|
||||
ind := t.LocFromVisual(buffer.Loc{mx, my})
|
||||
if ind != -1 {
|
||||
t.SetActive(ind)
|
||||
return
|
||||
}
|
||||
if my == 0 {
|
||||
return
|
||||
}
|
||||
}
|
||||
case tcell.WheelUp:
|
||||
if my == t.Y {
|
||||
@@ -160,7 +167,7 @@ func NewTabFromBuffer(x, y, width, height int, b *buffer.Buffer) *Tab {
|
||||
t.Node = views.NewRoot(x, y, width, height)
|
||||
t.UIWindow = display.NewUIWindow(t.Node)
|
||||
|
||||
e := NewBufPaneFromBuf(b)
|
||||
e := NewBufPaneFromBuf(b, t)
|
||||
e.SetID(t.ID())
|
||||
|
||||
t.Panes = append(t.Panes, e)
|
||||
@@ -171,7 +178,7 @@ 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)
|
||||
|
||||
pane.SetTab(t)
|
||||
pane.SetID(t.ID())
|
||||
|
||||
t.Panes = append(t.Panes, pane)
|
||||
@@ -189,7 +196,6 @@ func (t *Tab) HandleEvent(event tcell.Event) {
|
||||
mx, my := e.Position()
|
||||
switch e.Buttons() {
|
||||
case tcell.Button1:
|
||||
resizeID := t.GetMouseSplitID(buffer.Loc{mx, my})
|
||||
if t.resizing != nil {
|
||||
var size int
|
||||
if t.resizing.Kind == views.STVert {
|
||||
@@ -202,6 +208,7 @@ func (t *Tab) HandleEvent(event tcell.Event) {
|
||||
return
|
||||
}
|
||||
|
||||
resizeID := t.GetMouseSplitID(buffer.Loc{mx, my})
|
||||
if resizeID != 0 {
|
||||
t.resizing = t.GetNode(uint64(resizeID))
|
||||
return
|
||||
@@ -277,6 +284,10 @@ func (t *Tab) Resize() {
|
||||
}
|
||||
|
||||
// CurPane returns the currently active pane
|
||||
func (t *Tab) CurPane() Pane {
|
||||
return t.Panes[t.active]
|
||||
func (t *Tab) CurPane() *BufPane {
|
||||
p, ok := t.Panes[t.active].(*BufPane)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
@@ -3,27 +3,29 @@
|
||||
package action
|
||||
|
||||
import (
|
||||
shellquote "github.com/kballard/go-shellquote"
|
||||
"github.com/zyedidia/micro/internal/shell"
|
||||
"github.com/zyedidia/micro/pkg/shellwords"
|
||||
)
|
||||
|
||||
const TermEmuSupported = true
|
||||
|
||||
func RunTermEmulator(h *BufPane, input string, wait bool, getOutput bool, callback string, userargs []interface{}) error {
|
||||
args, err := shellwords.Split(input)
|
||||
args, err := shellquote.Split(input)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(args) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
t := new(shell.Terminal)
|
||||
t.Start(args, getOutput, wait, callback, userargs)
|
||||
|
||||
id := h.ID()
|
||||
h.AddTab()
|
||||
id = MainTab().Panes[0].ID()
|
||||
id := MainTab().Panes[0].ID()
|
||||
|
||||
v := h.GetView()
|
||||
MainTab().Panes[0] = NewTermPane(v.X, v.Y, v.Width, v.Height, t, id)
|
||||
MainTab().Panes[0] = NewTermPane(v.X, v.Y, v.Width, v.Height, t, id, MainTab())
|
||||
MainTab().SetActive(0)
|
||||
|
||||
return nil
|
||||
|
||||
@@ -17,14 +17,16 @@ type TermPane struct {
|
||||
|
||||
mouseReleased bool
|
||||
id uint64
|
||||
tab *Tab
|
||||
}
|
||||
|
||||
func NewTermPane(x, y, w, h int, t *shell.Terminal, id uint64) *TermPane {
|
||||
func NewTermPane(x, y, w, h int, t *shell.Terminal, id uint64, tab *Tab) *TermPane {
|
||||
th := new(TermPane)
|
||||
th.Terminal = t
|
||||
th.id = id
|
||||
th.mouseReleased = true
|
||||
th.Window = display.NewTermWindow(x, y, w, h, t)
|
||||
th.tab = tab
|
||||
return th
|
||||
}
|
||||
|
||||
@@ -36,6 +38,14 @@ func (t *TermPane) SetID(i uint64) {
|
||||
t.id = i
|
||||
}
|
||||
|
||||
func (t *TermPane) SetTab(tab *Tab) {
|
||||
t.tab = tab
|
||||
}
|
||||
|
||||
func (t *TermPane) Tab() *Tab {
|
||||
return t.tab
|
||||
}
|
||||
|
||||
func (t *TermPane) Close() {}
|
||||
|
||||
func (t *TermPane) Quit() {
|
||||
@@ -80,8 +90,12 @@ func (t *TermPane) HandleEvent(event tcell.Event) {
|
||||
} else if t.Status != shell.TTDone {
|
||||
t.WriteString(event.EscSeq())
|
||||
}
|
||||
} else if _, ok := event.(*tcell.EventPaste); ok {
|
||||
if t.Status != shell.TTDone {
|
||||
t.WriteString(event.EscSeq())
|
||||
}
|
||||
} else if e, ok := event.(*tcell.EventMouse); e != nil && (!ok || t.State.Mode(terminal.ModeMouseMask)) {
|
||||
t.WriteString(event.EscSeq())
|
||||
// t.WriteString(event.EscSeq())
|
||||
} else if e != nil {
|
||||
x, y := e.Position()
|
||||
v := t.GetView()
|
||||
|
||||
@@ -3,7 +3,6 @@ package buffer
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
@@ -17,7 +16,9 @@ const backupMsg = `A backup was detected for this file. This likely means that m
|
||||
crashed while editing this file, or another instance of micro is currently
|
||||
editing this file.
|
||||
|
||||
The backup was created on %s.
|
||||
The backup was created on %s, and the file is
|
||||
|
||||
%s
|
||||
|
||||
* 'recover' will apply the backup as unsaved changes to the current buffer.
|
||||
When the buffer is closed, the backup will be removed.
|
||||
@@ -28,14 +29,13 @@ Options: [r]ecover, [i]gnore: `
|
||||
|
||||
// Backup saves the current buffer to ConfigDir/backups
|
||||
func (b *Buffer) Backup(checkTime bool) error {
|
||||
if !b.Settings["backup"].(bool) || b.Path == "" {
|
||||
if !b.Settings["backup"].(bool) || b.Path == "" || b.Type != BTDefault {
|
||||
return nil
|
||||
}
|
||||
|
||||
if checkTime {
|
||||
sub := time.Now().Sub(b.lastbackup)
|
||||
if sub < time.Duration(backup_time)*time.Millisecond {
|
||||
log.Println("Backup event but not enough time has passed", sub)
|
||||
if sub < time.Duration(backupTime)*time.Millisecond {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -45,13 +45,10 @@ func (b *Buffer) Backup(checkTime bool) error {
|
||||
backupdir := config.ConfigDir + "/backups/"
|
||||
if _, err := os.Stat(backupdir); os.IsNotExist(err) {
|
||||
os.Mkdir(backupdir, os.ModePerm)
|
||||
log.Println("Creating backup dir")
|
||||
}
|
||||
|
||||
name := backupdir + util.EscapePath(b.AbsPath)
|
||||
|
||||
log.Println("Backing up to", name)
|
||||
|
||||
err := overwriteFile(name, encoding.Nop, func(file io.Writer) (e error) {
|
||||
if len(b.lines) == 0 {
|
||||
return
|
||||
@@ -74,14 +71,14 @@ func (b *Buffer) Backup(checkTime bool) error {
|
||||
}
|
||||
}
|
||||
return
|
||||
})
|
||||
}, false)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// RemoveBackup removes any backup file associated with this buffer
|
||||
func (b *Buffer) RemoveBackup() {
|
||||
if !b.Settings["backup"].(bool) || b.Path == "" {
|
||||
if !b.Settings["backup"].(bool) || b.Path == "" || b.Type != BTDefault {
|
||||
return
|
||||
}
|
||||
f := config.ConfigDir + "/backups/" + util.EscapePath(b.AbsPath)
|
||||
@@ -91,14 +88,14 @@ 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) && len(b.Path) > 0 {
|
||||
if b.Settings["backup"].(bool) && len(b.Path) > 0 && b.Type == BTDefault {
|
||||
backupfile := config.ConfigDir + "/backups/" + util.EscapePath(b.AbsPath)
|
||||
if info, err := os.Stat(backupfile); err == nil {
|
||||
backup, err := os.Open(backupfile)
|
||||
if err == nil {
|
||||
defer backup.Close()
|
||||
t := info.ModTime()
|
||||
msg := fmt.Sprintf(backupMsg, t.Format("Mon Jan _2 at 15:04, 2006"))
|
||||
msg := fmt.Sprintf(backupMsg, t.Format("Mon Jan _2 at 15:04, 2006"), util.EscapePath(b.AbsPath))
|
||||
choice := screen.TermPrompt(msg, []string{"r", "i", "recover", "ignore"}, true)
|
||||
|
||||
if choice%2 == 0 {
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
@@ -18,18 +19,21 @@ import (
|
||||
"github.com/zyedidia/micro/internal/config"
|
||||
ulua "github.com/zyedidia/micro/internal/lua"
|
||||
"github.com/zyedidia/micro/internal/screen"
|
||||
. "github.com/zyedidia/micro/internal/util"
|
||||
"github.com/zyedidia/micro/internal/util"
|
||||
"github.com/zyedidia/micro/pkg/highlight"
|
||||
"golang.org/x/text/encoding/htmlindex"
|
||||
"golang.org/x/text/encoding/unicode"
|
||||
"golang.org/x/text/transform"
|
||||
)
|
||||
|
||||
const backup_time = 8000
|
||||
const backupTime = 8000
|
||||
|
||||
var (
|
||||
// OpenBuffers is a list of the currently open buffers
|
||||
OpenBuffers []*Buffer
|
||||
LogBuf *Buffer
|
||||
// LogBuf is a reference to the log buffer which can be opened with the
|
||||
// `> log` command
|
||||
LogBuf *Buffer
|
||||
)
|
||||
|
||||
// The BufType defines what kind of buffer this is
|
||||
@@ -41,16 +45,26 @@ type BufType struct {
|
||||
}
|
||||
|
||||
var (
|
||||
// BTDefault is a default buffer
|
||||
BTDefault = BufType{0, false, false, true}
|
||||
BTHelp = BufType{1, true, true, true}
|
||||
BTLog = BufType{2, true, true, false}
|
||||
// BTHelp is a help buffer
|
||||
BTHelp = BufType{1, true, true, true}
|
||||
// BTLog is a log buffer
|
||||
BTLog = BufType{2, true, true, false}
|
||||
// BTScratch is a buffer that cannot be saved (for scratch work)
|
||||
BTScratch = BufType{3, false, true, false}
|
||||
BTRaw = BufType{4, false, true, false}
|
||||
BTInfo = BufType{5, false, true, false}
|
||||
// BTRaw is is a buffer that shows raw terminal events
|
||||
BTRaw = BufType{4, false, true, false}
|
||||
// BTInfo is a buffer for inputting information
|
||||
BTInfo = BufType{5, false, true, false}
|
||||
|
||||
// ErrFileTooLarge is returned when the file is too large to hash
|
||||
// (fastdirty is automatically enabled)
|
||||
ErrFileTooLarge = errors.New("File is too large to hash")
|
||||
)
|
||||
|
||||
// SharedBuffer is a struct containing info that is shared among buffers
|
||||
// that have the same file open
|
||||
type SharedBuffer struct {
|
||||
*LineArray
|
||||
// Stores the last modification time of the file the buffer is pointing to
|
||||
@@ -62,16 +76,24 @@ type SharedBuffer struct {
|
||||
// Whether or not suggestions can be autocompleted must be shared because
|
||||
// it changes based on how the buffer has changed
|
||||
HasSuggestions bool
|
||||
|
||||
// Modifications is the list of modified regions for syntax highlighting
|
||||
Modifications []Loc
|
||||
}
|
||||
|
||||
func (b *SharedBuffer) insert(pos Loc, value []byte) {
|
||||
b.isModified = true
|
||||
b.HasSuggestions = false
|
||||
b.LineArray.insert(pos, value)
|
||||
|
||||
// b.Modifications is cleared every screen redraw so it's
|
||||
// ok to append duplicates
|
||||
b.Modifications = append(b.Modifications, Loc{pos.Y, pos.Y + bytes.Count(value, []byte{'\n'})})
|
||||
}
|
||||
func (b *SharedBuffer) remove(start, end Loc) []byte {
|
||||
b.isModified = true
|
||||
b.HasSuggestions = false
|
||||
b.Modifications = append(b.Modifications, Loc{start.Y, start.Y})
|
||||
return b.LineArray.remove(start, end)
|
||||
}
|
||||
|
||||
@@ -97,8 +119,12 @@ type Buffer struct {
|
||||
// Name of the buffer on the status line
|
||||
name string
|
||||
|
||||
SyntaxDef *highlight.Def
|
||||
Highlighter *highlight.Highlighter
|
||||
// SyntaxDef represents the syntax highlighting definition being used
|
||||
// This stores the highlighting rules and filetype detection info
|
||||
SyntaxDef *highlight.Def
|
||||
// The Highlighter struct actually performs the highlighting
|
||||
Highlighter *highlight.Highlighter
|
||||
HighlightLock sync.Mutex
|
||||
|
||||
// Hash of the original buffer -- empty if fastdirty is on
|
||||
origHash [md5.Size]byte
|
||||
@@ -113,7 +139,7 @@ type Buffer struct {
|
||||
Messages []*Message
|
||||
|
||||
// counts the number of edits
|
||||
// resets every backup_time edits
|
||||
// resets every backupTime edits
|
||||
lastbackup time.Time
|
||||
}
|
||||
|
||||
@@ -123,8 +149,8 @@ type Buffer struct {
|
||||
// and an error if the file is a directory
|
||||
func NewBufferFromFile(path string, btype BufType) (*Buffer, error) {
|
||||
var err error
|
||||
filename, cursorPos := GetPathAndCursorPosition(path)
|
||||
filename, err = ReplaceHome(filename)
|
||||
filename, cursorPos := util.GetPathAndCursorPosition(path)
|
||||
filename, err = util.ReplaceHome(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -133,7 +159,7 @@ func NewBufferFromFile(path string, btype BufType) (*Buffer, error) {
|
||||
fileInfo, _ := os.Stat(filename)
|
||||
|
||||
if err == nil && fileInfo.IsDir() {
|
||||
return nil, errors.New(filename + " is a directory")
|
||||
return nil, errors.New("Error: " + filename + " is a directory and cannot be opened")
|
||||
}
|
||||
|
||||
defer file.Close()
|
||||
@@ -148,7 +174,7 @@ func NewBufferFromFile(path string, btype BufType) (*Buffer, error) {
|
||||
// File does not exist -- create an empty buffer with that name
|
||||
buf = NewBufferFromString("", filename, btype)
|
||||
} else {
|
||||
buf = NewBuffer(file, FSize(file), filename, cursorLoc, btype)
|
||||
buf = NewBuffer(file, util.FSize(file), filename, cursorLoc, btype)
|
||||
}
|
||||
|
||||
return buf, nil
|
||||
@@ -211,12 +237,12 @@ func NewBuffer(r io.Reader, size int64, path string, startcursor Loc, btype BufT
|
||||
b.EventHandler = NewEventHandler(b.SharedBuffer, b.cursors)
|
||||
}
|
||||
|
||||
if b.Settings["readonly"].(bool) {
|
||||
if b.Settings["readonly"].(bool) && b.Type == BTDefault {
|
||||
b.Type.Readonly = true
|
||||
}
|
||||
|
||||
// The last time this file was modified
|
||||
b.ModTime, _ = GetModTime(b.Path)
|
||||
b.UpdateModTime()
|
||||
|
||||
switch b.Endings {
|
||||
case FFUnix:
|
||||
@@ -260,6 +286,8 @@ func NewBuffer(r io.Reader, size int64, path string, startcursor Loc, btype BufT
|
||||
screen.TermMessage(err)
|
||||
}
|
||||
|
||||
b.Modifications = make([]Loc, 0, 10)
|
||||
|
||||
OpenBuffers = append(OpenBuffers, b)
|
||||
|
||||
return b
|
||||
@@ -304,6 +332,7 @@ func (b *Buffer) SetName(s string) {
|
||||
b.name = s
|
||||
}
|
||||
|
||||
// Insert inserts the given string of text at the start location
|
||||
func (b *Buffer) Insert(start Loc, text string) {
|
||||
if !b.Type.Readonly {
|
||||
b.EventHandler.cursors = b.cursors
|
||||
@@ -314,6 +343,7 @@ func (b *Buffer) Insert(start Loc, text string) {
|
||||
}
|
||||
}
|
||||
|
||||
// Remove removes the characters between the start and end locations
|
||||
func (b *Buffer) Remove(start, end Loc) {
|
||||
if !b.Type.Readonly {
|
||||
b.EventHandler.cursors = b.cursors
|
||||
@@ -324,11 +354,37 @@ func (b *Buffer) Remove(start, end Loc) {
|
||||
}
|
||||
}
|
||||
|
||||
// ClearModifications clears the list of modified lines in this buffer
|
||||
// The list of modified lines is used for syntax highlighting so that
|
||||
// we can selectively highlight only the necessary lines
|
||||
// This function should be called every time this buffer is drawn to
|
||||
// the screen
|
||||
func (b *Buffer) ClearModifications() {
|
||||
// clear slice without resetting the cap
|
||||
b.Modifications = b.Modifications[:0]
|
||||
}
|
||||
|
||||
// FileType returns the buffer's filetype
|
||||
func (b *Buffer) FileType() string {
|
||||
return b.Settings["filetype"].(string)
|
||||
}
|
||||
|
||||
// ExternallyModified returns whether the file being edited has
|
||||
// been modified by some external process
|
||||
func (b *Buffer) ExternallyModified() bool {
|
||||
modTime, err := util.GetModTime(b.Path)
|
||||
if err == nil {
|
||||
return modTime != b.ModTime
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// UpdateModTime updates the modtime of this file
|
||||
func (b *Buffer) UpdateModTime() (err error) {
|
||||
b.ModTime, err = util.GetModTime(b.Path)
|
||||
return
|
||||
}
|
||||
|
||||
// ReOpen reloads the current buffer from disk
|
||||
func (b *Buffer) ReOpen() error {
|
||||
file, err := os.Open(b.Path)
|
||||
@@ -350,12 +406,13 @@ func (b *Buffer) ReOpen() error {
|
||||
}
|
||||
b.EventHandler.ApplyDiff(txt)
|
||||
|
||||
b.ModTime, err = GetModTime(b.Path)
|
||||
err = b.UpdateModTime()
|
||||
b.isModified = false
|
||||
b.RelocateCursors()
|
||||
return err
|
||||
}
|
||||
|
||||
// RelocateCursors relocates all cursors (makes sure they are in the buffer)
|
||||
func (b *Buffer) RelocateCursors() {
|
||||
for _, c := range b.cursors {
|
||||
c.Relocate()
|
||||
@@ -437,65 +494,139 @@ func (b *Buffer) UpdateRules() {
|
||||
if !b.Type.Syntax {
|
||||
return
|
||||
}
|
||||
rehighlight := false
|
||||
var files []*highlight.File
|
||||
for _, f := range config.ListRuntimeFiles(config.RTSyntax) {
|
||||
ft := b.Settings["filetype"].(string)
|
||||
if ft == "off" {
|
||||
return
|
||||
}
|
||||
syntaxFile := ""
|
||||
var header *highlight.Header
|
||||
for _, f := range config.ListRuntimeFiles(config.RTSyntaxHeader) {
|
||||
data, err := f.Data()
|
||||
if err != nil {
|
||||
screen.TermMessage("Error loading syntax file " + f.Name() + ": " + err.Error())
|
||||
} else {
|
||||
file, err := highlight.ParseFile(data)
|
||||
if err != nil {
|
||||
screen.TermMessage("Error loading syntax file " + f.Name() + ": " + err.Error())
|
||||
continue
|
||||
}
|
||||
ftdetect, err := highlight.ParseFtDetect(file)
|
||||
if err != nil {
|
||||
screen.TermMessage("Error loading syntax file " + f.Name() + ": " + err.Error())
|
||||
continue
|
||||
}
|
||||
screen.TermMessage("Error loading syntax header file " + f.Name() + ": " + err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
ft := b.Settings["filetype"].(string)
|
||||
if (ft == "unknown" || ft == "") && !rehighlight {
|
||||
if highlight.MatchFiletype(ftdetect, b.Path, b.lines[0].data) {
|
||||
header := new(highlight.Header)
|
||||
header.FileType = file.FileType
|
||||
header.FtDetect = ftdetect
|
||||
b.SyntaxDef, err = highlight.ParseDef(file, header)
|
||||
if err != nil {
|
||||
screen.TermMessage("Error loading syntax file " + f.Name() + ": " + err.Error())
|
||||
continue
|
||||
}
|
||||
rehighlight = true
|
||||
}
|
||||
} else {
|
||||
if file.FileType == ft && !rehighlight {
|
||||
header := new(highlight.Header)
|
||||
header.FileType = file.FileType
|
||||
header.FtDetect = ftdetect
|
||||
b.SyntaxDef, err = highlight.ParseDef(file, header)
|
||||
if err != nil {
|
||||
screen.TermMessage("Error loading syntax file " + f.Name() + ": " + err.Error())
|
||||
continue
|
||||
}
|
||||
rehighlight = true
|
||||
}
|
||||
header, err = highlight.MakeHeader(data)
|
||||
if err != nil {
|
||||
screen.TermMessage("Error reading syntax header file", f.Name(), err)
|
||||
continue
|
||||
}
|
||||
|
||||
if ft == "unknown" || ft == "" {
|
||||
if highlight.MatchFiletype(header.FtDetect, b.Path, b.lines[0].data) {
|
||||
syntaxFile = f.Name()
|
||||
break
|
||||
}
|
||||
files = append(files, file)
|
||||
} else if header.FileType == ft {
|
||||
syntaxFile = f.Name()
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if b.SyntaxDef != nil {
|
||||
if syntaxFile == "" {
|
||||
// search for the syntax file in the user's custom syntax files
|
||||
for _, f := range config.ListRealRuntimeFiles(config.RTSyntax) {
|
||||
data, err := f.Data()
|
||||
if err != nil {
|
||||
screen.TermMessage("Error loading syntax file " + f.Name() + ": " + err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
header, err = highlight.MakeHeaderYaml(data)
|
||||
file, err := highlight.ParseFile(data)
|
||||
if err != nil {
|
||||
screen.TermMessage("Error parsing syntax file " + f.Name() + ": " + err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
if (ft == "unknown" || ft == "" && highlight.MatchFiletype(header.FtDetect, b.Path, b.lines[0].data)) || header.FileType == ft {
|
||||
syndef, err := highlight.ParseDef(file, header)
|
||||
if err != nil {
|
||||
screen.TermMessage("Error parsing syntax file " + f.Name() + ": " + err.Error())
|
||||
continue
|
||||
}
|
||||
b.SyntaxDef = syndef
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for _, f := range config.ListRuntimeFiles(config.RTSyntax) {
|
||||
if f.Name() == syntaxFile {
|
||||
data, err := f.Data()
|
||||
if err != nil {
|
||||
screen.TermMessage("Error loading syntax file " + f.Name() + ": " + err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
file, err := highlight.ParseFile(data)
|
||||
if err != nil {
|
||||
screen.TermMessage("Error parsing syntax file " + f.Name() + ": " + err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
syndef, err := highlight.ParseDef(file, header)
|
||||
if err != nil {
|
||||
screen.TermMessage("Error parsing syntax file " + f.Name() + ": " + err.Error())
|
||||
continue
|
||||
}
|
||||
b.SyntaxDef = syndef
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if b.SyntaxDef != nil && highlight.HasIncludes(b.SyntaxDef) {
|
||||
includes := highlight.GetIncludes(b.SyntaxDef)
|
||||
|
||||
var files []*highlight.File
|
||||
for _, f := range config.ListRuntimeFiles(config.RTSyntax) {
|
||||
data, err := f.Data()
|
||||
if err != nil {
|
||||
screen.TermMessage("Error parsing syntax file " + f.Name() + ": " + err.Error())
|
||||
continue
|
||||
}
|
||||
header, err := highlight.MakeHeaderYaml(data)
|
||||
if err != nil {
|
||||
screen.TermMessage("Error parsing syntax file " + f.Name() + ": " + err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
for _, i := range includes {
|
||||
if header.FileType == i {
|
||||
file, err := highlight.ParseFile(data)
|
||||
if err != nil {
|
||||
screen.TermMessage("Error parsing syntax file " + f.Name() + ": " + err.Error())
|
||||
continue
|
||||
}
|
||||
files = append(files, file)
|
||||
break
|
||||
}
|
||||
}
|
||||
if len(files) >= len(includes) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
highlight.ResolveIncludes(b.SyntaxDef, files)
|
||||
}
|
||||
|
||||
if b.Highlighter == nil || rehighlight {
|
||||
if b.Highlighter == nil || syntaxFile != "" {
|
||||
if b.SyntaxDef != nil {
|
||||
b.Settings["filetype"] = b.SyntaxDef.FileType
|
||||
b.Highlighter = highlight.NewHighlighter(b.SyntaxDef)
|
||||
if b.Settings["syntax"].(bool) {
|
||||
}
|
||||
} else {
|
||||
b.SyntaxDef = &highlight.EmptyDef
|
||||
}
|
||||
|
||||
if b.SyntaxDef != nil {
|
||||
b.Highlighter = highlight.NewHighlighter(b.SyntaxDef)
|
||||
if b.Settings["syntax"].(bool) {
|
||||
go func() {
|
||||
b.Highlighter.HighlightStates(b)
|
||||
}
|
||||
b.Highlighter.HighlightMatches(b, 0, b.End().Y)
|
||||
screen.DrawChan <- true
|
||||
}()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -512,7 +643,7 @@ func (b *Buffer) ClearMatches() {
|
||||
// depending on the settings)
|
||||
func (b *Buffer) IndentString(tabsize int) string {
|
||||
if b.Settings["tabstospaces"].(bool) {
|
||||
return Spaces(tabsize)
|
||||
return util.Spaces(tabsize)
|
||||
}
|
||||
return "\t"
|
||||
}
|
||||
@@ -600,7 +731,7 @@ func (b *Buffer) RemoveCursor(i int) {
|
||||
copy(b.cursors[i:], b.cursors[i+1:])
|
||||
b.cursors[len(b.cursors)-1] = nil
|
||||
b.cursors = b.cursors[:len(b.cursors)-1]
|
||||
b.curCursor = Clamp(b.curCursor, 0, len(b.cursors)-1)
|
||||
b.curCursor = util.Clamp(b.curCursor, 0, len(b.cursors)-1)
|
||||
b.UpdateCursors()
|
||||
}
|
||||
|
||||
@@ -741,13 +872,13 @@ func (b *Buffer) FindMatchingBrace(braceType [2]rune, start Loc) (Loc, bool) {
|
||||
// Retab changes all tabs to spaces or vice versa
|
||||
func (b *Buffer) Retab() {
|
||||
toSpaces := b.Settings["tabstospaces"].(bool)
|
||||
tabsize := IntOpt(b.Settings["tabsize"])
|
||||
tabsize := util.IntOpt(b.Settings["tabsize"])
|
||||
dirty := false
|
||||
|
||||
for i := 0; i < b.LinesNum(); i++ {
|
||||
l := b.LineBytes(i)
|
||||
|
||||
ws := GetLeadingWhitespace(l)
|
||||
ws := util.GetLeadingWhitespace(l)
|
||||
if len(ws) != 0 {
|
||||
if toSpaces {
|
||||
ws = bytes.Replace(ws, []byte{'\t'}, bytes.Repeat([]byte{' '}, tabsize), -1)
|
||||
@@ -776,12 +907,12 @@ func ParseCursorLocation(cursorPositions []string) (Loc, error) {
|
||||
}
|
||||
|
||||
startpos.Y, err = strconv.Atoi(cursorPositions[0])
|
||||
startpos.Y -= 1
|
||||
startpos.Y--
|
||||
if err == nil {
|
||||
if len(cursorPositions) > 1 {
|
||||
startpos.X, err = strconv.Atoi(cursorPositions[1])
|
||||
if startpos.X > 0 {
|
||||
startpos.X -= 1
|
||||
startpos.X--
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -789,10 +920,22 @@ func ParseCursorLocation(cursorPositions []string) (Loc, error) {
|
||||
return startpos, err
|
||||
}
|
||||
|
||||
// Line returns the string representation of the given line number
|
||||
func (b *Buffer) Line(i int) string {
|
||||
return string(b.LineBytes(i))
|
||||
}
|
||||
|
||||
func (b *Buffer) Write(bytes []byte) (n int, err error) {
|
||||
b.EventHandler.InsertBytes(b.End(), bytes)
|
||||
return len(bytes), nil
|
||||
}
|
||||
|
||||
// WriteLog writes a string to the log buffer
|
||||
func WriteLog(s string) {
|
||||
LogBuf.EventHandler.Insert(LogBuf.End(), s)
|
||||
}
|
||||
|
||||
// GetLogBuf returns the log buffer
|
||||
func GetLogBuf() *Buffer {
|
||||
return LogBuf
|
||||
}
|
||||
|
||||
@@ -5,6 +5,10 @@ import (
|
||||
"unicode/utf8"
|
||||
|
||||
dmp "github.com/sergi/go-diff/diffmatchpatch"
|
||||
"github.com/zyedidia/micro/internal/config"
|
||||
ulua "github.com/zyedidia/micro/internal/lua"
|
||||
"github.com/zyedidia/micro/internal/screen"
|
||||
luar "layeh.com/gopher-luar"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -17,7 +21,7 @@ const (
|
||||
// TextEventReplace represents a replace event
|
||||
TextEventReplace = 0
|
||||
|
||||
undoThreshold = 500 // If two events are less than n milliseconds apart, undo both of them
|
||||
undoThreshold = 1000 // If two events are less than n milliseconds apart, undo both of them
|
||||
)
|
||||
|
||||
// TextEvent holds data for a manipulation on some text that can be undone
|
||||
@@ -107,6 +111,11 @@ func (eh *EventHandler) ApplyDiff(new string) {
|
||||
// Insert creates an insert text event and executes it
|
||||
func (eh *EventHandler) Insert(start Loc, textStr string) {
|
||||
text := []byte(textStr)
|
||||
eh.InsertBytes(start, text)
|
||||
}
|
||||
|
||||
// InsertBytes creates an insert text event and executes it
|
||||
func (eh *EventHandler) InsertBytes(start Loc, text []byte) {
|
||||
e := &TextEvent{
|
||||
C: *eh.cursors[eh.active],
|
||||
EventType: TextEventInsert,
|
||||
@@ -187,16 +196,14 @@ func (eh *EventHandler) Execute(t *TextEvent) {
|
||||
}
|
||||
eh.UndoStack.Push(t)
|
||||
|
||||
// TODO: Call plugins on text events
|
||||
// for pl := range loadedPlugins {
|
||||
// ret, err := Call(pl+".onBeforeTextEvent", t)
|
||||
// if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") {
|
||||
// screen.TermMessage(err)
|
||||
// }
|
||||
// if val, ok := ret.(lua.LBool); ok && val == lua.LFalse {
|
||||
// return
|
||||
// }
|
||||
// }
|
||||
b, err := config.RunPluginFnBool("onBeforeTextEvent", luar.New(ulua.L, eh.buf), luar.New(ulua.L, t))
|
||||
if err != nil {
|
||||
screen.TermMessage(err)
|
||||
}
|
||||
|
||||
if !b {
|
||||
return
|
||||
}
|
||||
|
||||
ExecuteTextEvent(t, eh.buf)
|
||||
}
|
||||
@@ -209,8 +216,7 @@ func (eh *EventHandler) Undo() {
|
||||
}
|
||||
|
||||
startTime := t.Time.UnixNano() / int64(time.Millisecond)
|
||||
|
||||
eh.UndoOneEvent()
|
||||
endTime := startTime - (startTime % undoThreshold)
|
||||
|
||||
for {
|
||||
t = eh.UndoStack.Peek()
|
||||
@@ -218,10 +224,9 @@ func (eh *EventHandler) Undo() {
|
||||
return
|
||||
}
|
||||
|
||||
if startTime-(t.Time.UnixNano()/int64(time.Millisecond)) > undoThreshold {
|
||||
if t.Time.UnixNano()/int64(time.Millisecond) < endTime {
|
||||
return
|
||||
}
|
||||
startTime = t.Time.UnixNano() / int64(time.Millisecond)
|
||||
|
||||
eh.UndoOneEvent()
|
||||
}
|
||||
@@ -261,8 +266,7 @@ func (eh *EventHandler) Redo() {
|
||||
}
|
||||
|
||||
startTime := t.Time.UnixNano() / int64(time.Millisecond)
|
||||
|
||||
eh.RedoOneEvent()
|
||||
endTime := startTime - (startTime % undoThreshold) + undoThreshold
|
||||
|
||||
for {
|
||||
t = eh.RedoStack.Peek()
|
||||
@@ -270,7 +274,7 @@ func (eh *EventHandler) Redo() {
|
||||
return
|
||||
}
|
||||
|
||||
if (t.Time.UnixNano()/int64(time.Millisecond))-startTime > undoThreshold {
|
||||
if t.Time.UnixNano()/int64(time.Millisecond) > endTime {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ package buffer
|
||||
import (
|
||||
"bufio"
|
||||
"io"
|
||||
"sync"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/zyedidia/micro/pkg/highlight"
|
||||
@@ -38,6 +39,7 @@ type Line struct {
|
||||
state highlight.State
|
||||
match highlight.LineMatch
|
||||
rehighlight bool
|
||||
lock sync.Mutex
|
||||
}
|
||||
|
||||
const (
|
||||
@@ -124,12 +126,22 @@ func NewLineArray(size uint64, endings FileFormat, reader io.Reader) *LineArray
|
||||
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
la.lines = Append(la.lines, Line{data[:], nil, nil, false})
|
||||
la.lines = Append(la.lines, Line{
|
||||
data: data[:],
|
||||
state: nil,
|
||||
match: nil,
|
||||
rehighlight: false,
|
||||
})
|
||||
}
|
||||
// Last line was read
|
||||
break
|
||||
} else {
|
||||
la.lines = Append(la.lines, Line{data[:dlen-1], nil, nil, false})
|
||||
la.lines = Append(la.lines, Line{
|
||||
data: data[:dlen-1],
|
||||
state: nil,
|
||||
match: nil,
|
||||
rehighlight: false,
|
||||
})
|
||||
}
|
||||
n++
|
||||
}
|
||||
@@ -155,9 +167,19 @@ func (la *LineArray) Bytes() []byte {
|
||||
|
||||
// newlineBelow adds a newline below the given line number
|
||||
func (la *LineArray) newlineBelow(y int) {
|
||||
la.lines = append(la.lines, Line{[]byte{' '}, nil, nil, false})
|
||||
la.lines = append(la.lines, Line{
|
||||
data: []byte{' '},
|
||||
state: nil,
|
||||
match: nil,
|
||||
rehighlight: false,
|
||||
})
|
||||
copy(la.lines[y+2:], la.lines[y+1:])
|
||||
la.lines[y+1] = Line{[]byte{}, la.lines[y].state, nil, false}
|
||||
la.lines[y+1] = Line{
|
||||
data: []byte{},
|
||||
state: la.lines[y].state,
|
||||
match: nil,
|
||||
rehighlight: false,
|
||||
}
|
||||
}
|
||||
|
||||
// Inserts a byte array at a given location
|
||||
@@ -285,28 +307,40 @@ func (la *LineArray) LineBytes(n int) []byte {
|
||||
|
||||
// State gets the highlight state for the given line number
|
||||
func (la *LineArray) State(lineN int) highlight.State {
|
||||
la.lines[lineN].lock.Lock()
|
||||
defer la.lines[lineN].lock.Unlock()
|
||||
return la.lines[lineN].state
|
||||
}
|
||||
|
||||
// SetState sets the highlight state at the given line number
|
||||
func (la *LineArray) SetState(lineN int, s highlight.State) {
|
||||
la.lines[lineN].lock.Lock()
|
||||
defer la.lines[lineN].lock.Unlock()
|
||||
la.lines[lineN].state = s
|
||||
}
|
||||
|
||||
// SetMatch sets the match at the given line number
|
||||
func (la *LineArray) SetMatch(lineN int, m highlight.LineMatch) {
|
||||
la.lines[lineN].lock.Lock()
|
||||
defer la.lines[lineN].lock.Unlock()
|
||||
la.lines[lineN].match = m
|
||||
}
|
||||
|
||||
// Match retrieves the match for the given line number
|
||||
func (la *LineArray) Match(lineN int) highlight.LineMatch {
|
||||
la.lines[lineN].lock.Lock()
|
||||
defer la.lines[lineN].lock.Unlock()
|
||||
return la.lines[lineN].match
|
||||
}
|
||||
|
||||
func (la *LineArray) Rehighlight(lineN int) bool {
|
||||
la.lines[lineN].lock.Lock()
|
||||
defer la.lines[lineN].lock.Unlock()
|
||||
return la.lines[lineN].rehighlight
|
||||
}
|
||||
|
||||
func (la *LineArray) SetRehighlight(lineN int, on bool) {
|
||||
la.lines[lineN].lock.Lock()
|
||||
defer la.lines[lineN].lock.Unlock()
|
||||
la.lines[lineN].rehighlight = on
|
||||
}
|
||||
|
||||
@@ -8,12 +8,13 @@ import (
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/zyedidia/micro/internal/config"
|
||||
"github.com/zyedidia/micro/internal/screen"
|
||||
. "github.com/zyedidia/micro/internal/util"
|
||||
"github.com/zyedidia/micro/internal/util"
|
||||
"golang.org/x/text/encoding"
|
||||
"golang.org/x/text/encoding/htmlindex"
|
||||
"golang.org/x/text/transform"
|
||||
@@ -26,70 +27,42 @@ const LargeFileThreshold = 50000
|
||||
// overwriteFile opens the given file for writing, truncating if one exists, and then calls
|
||||
// the supplied function with the file as io.Writer object, also making sure the file is
|
||||
// closed afterwards.
|
||||
func overwriteFile(name string, enc encoding.Encoding, fn func(io.Writer) error) (err error) {
|
||||
var file *os.File
|
||||
func overwriteFile(name string, enc encoding.Encoding, fn func(io.Writer) error, withSudo bool) (err error) {
|
||||
var writeCloser io.WriteCloser
|
||||
|
||||
if file, err = os.OpenFile(name, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644); err != nil {
|
||||
return
|
||||
}
|
||||
if withSudo {
|
||||
cmd := exec.Command(config.GlobalSettings["sucmd"].(string), "dd", "bs=4k", "of="+name)
|
||||
|
||||
defer func() {
|
||||
if e := file.Close(); e != nil && err == nil {
|
||||
err = e
|
||||
}
|
||||
}()
|
||||
if writeCloser, err = cmd.StdinPipe(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
w := transform.NewWriter(file, enc.NewEncoder())
|
||||
// w := bufio.NewWriter(file)
|
||||
c := make(chan os.Signal, 1)
|
||||
signal.Notify(c, os.Interrupt)
|
||||
go func() {
|
||||
<-c
|
||||
cmd.Process.Kill()
|
||||
}()
|
||||
|
||||
if err = fn(w); err != nil {
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
screenb := screen.TempFini()
|
||||
if e := cmd.Run(); e != nil && err == nil {
|
||||
err = e
|
||||
}
|
||||
screen.TempStart(screenb)
|
||||
}()
|
||||
} else if writeCloser, err = os.OpenFile(name, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// err = w.Flush()
|
||||
return
|
||||
}
|
||||
w := transform.NewWriter(writeCloser, enc.NewEncoder())
|
||||
err = fn(w)
|
||||
|
||||
// overwriteFileAsRoot executes dd as root and then calls the supplied function
|
||||
// with dd's standard input as an io.Writer object. Dd opens the given file for writing,
|
||||
// truncating it if it exists, and writes what it receives on its standard input to the file.
|
||||
func overwriteFileAsRoot(name string, enc encoding.Encoding, fn func(io.Writer) error) (err error) {
|
||||
cmd := exec.Command(config.GlobalSettings["sucmd"].(string), "dd", "status=none", "bs=4K", "of="+name)
|
||||
var stdin io.WriteCloser
|
||||
if e := writeCloser.Close(); e != nil && err == nil {
|
||||
err = e
|
||||
}
|
||||
|
||||
screenb := screen.TempFini()
|
||||
|
||||
// This is a trap for Ctrl-C so that it doesn't kill micro
|
||||
// Instead we trap Ctrl-C to kill the program we're running
|
||||
c := make(chan os.Signal, 1)
|
||||
signal.Notify(c, os.Interrupt)
|
||||
go func() {
|
||||
for range c {
|
||||
cmd.Process.Kill()
|
||||
}
|
||||
}()
|
||||
|
||||
if stdin, err = cmd.StdinPipe(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = cmd.Start(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
e := fn(stdin)
|
||||
|
||||
if err = stdin.Close(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = cmd.Wait(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
screen.TempStart(screenb)
|
||||
|
||||
return e
|
||||
return
|
||||
}
|
||||
|
||||
// Save saves the buffer to its default path
|
||||
@@ -118,6 +91,9 @@ func (b *Buffer) saveToFile(filename string, withSudo bool) error {
|
||||
if b.Type.Scratch {
|
||||
return errors.New("Cannot save scratch buffer")
|
||||
}
|
||||
if withSudo && runtime.GOOS == "windows" {
|
||||
return errors.New("Save with sudo not supported on Windows")
|
||||
}
|
||||
|
||||
b.UpdateRules()
|
||||
if b.Settings["rmtrailingws"].(bool) {
|
||||
@@ -140,12 +116,12 @@ func (b *Buffer) saveToFile(filename string, withSudo bool) error {
|
||||
|
||||
// Update the last time this file was updated after saving
|
||||
defer func() {
|
||||
b.ModTime, _ = GetModTime(filename)
|
||||
b.ModTime, _ = util.GetModTime(filename)
|
||||
err = b.Serialize()
|
||||
}()
|
||||
|
||||
// Removes any tilde and replaces with the absolute path to home
|
||||
absFilename, _ := ReplaceHome(filename)
|
||||
absFilename, _ := util.ReplaceHome(filename)
|
||||
|
||||
// Get the leading path to the file | "." is returned if there's no leading path provided
|
||||
if dirname := filepath.Dir(absFilename); dirname != "." {
|
||||
@@ -201,14 +177,8 @@ func (b *Buffer) saveToFile(filename string, withSudo bool) error {
|
||||
return
|
||||
}
|
||||
|
||||
if withSudo {
|
||||
err = overwriteFileAsRoot(absFilename, enc, fwriter)
|
||||
} else {
|
||||
err = overwriteFile(absFilename, enc, fwriter)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
if err = overwriteFile(absFilename, enc, fwriter, withSudo); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !b.Settings["fastdirty"].(bool) {
|
||||
|
||||
@@ -115,7 +115,7 @@ func (b *Buffer) FindNext(s string, start, end, from Loc, down bool, useRegex bo
|
||||
return [2]Loc{}, false, err
|
||||
}
|
||||
|
||||
found := false
|
||||
var found bool
|
||||
var l [2]Loc
|
||||
if down {
|
||||
l, found = b.findDown(r, from, end)
|
||||
|
||||
@@ -5,12 +5,13 @@ import (
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"golang.org/x/text/encoding"
|
||||
|
||||
"github.com/zyedidia/micro/internal/config"
|
||||
. "github.com/zyedidia/micro/internal/util"
|
||||
"github.com/zyedidia/micro/internal/util"
|
||||
)
|
||||
|
||||
// The SerializedBuffer holds the types that get serialized when a buffer is saved
|
||||
@@ -30,7 +31,7 @@ func (b *Buffer) Serialize() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
name := config.ConfigDir + "/buffers/" + EscapePath(b.AbsPath)
|
||||
name := config.ConfigDir + "/buffers/" + util.EscapePath(b.AbsPath)
|
||||
|
||||
return overwriteFile(name, encoding.Nop, func(file io.Writer) error {
|
||||
err := gob.NewEncoder(file).Encode(SerializedBuffer{
|
||||
@@ -39,23 +40,24 @@ func (b *Buffer) Serialize() error {
|
||||
b.ModTime,
|
||||
})
|
||||
return err
|
||||
})
|
||||
}, false)
|
||||
}
|
||||
|
||||
// Unserialize loads the buffer info from config.ConfigDir/buffers
|
||||
func (b *Buffer) Unserialize() error {
|
||||
// If either savecursor or saveundo is turned on, we need to load the serialized information
|
||||
// from ~/.config/micro/buffers
|
||||
if b.Path == "" {
|
||||
return nil
|
||||
}
|
||||
file, err := os.Open(config.ConfigDir + "/buffers/" + EscapePath(b.AbsPath))
|
||||
file, err := os.Open(filepath.Join(config.ConfigDir, "buffers", util.EscapePath(b.AbsPath)))
|
||||
defer file.Close()
|
||||
if err == nil {
|
||||
var buffer SerializedBuffer
|
||||
decoder := gob.NewDecoder(file)
|
||||
err = decoder.Decode(&buffer)
|
||||
if err != nil {
|
||||
return errors.New(err.Error() + "\nYou may want to remove the files in ~/.config/micro/buffers (these files store the information for the 'saveundo' and 'savecursor' options) if this problem persists.")
|
||||
return errors.New(err.Error() + "\nYou may want to remove the files in ~/.config/micro/buffers (these files\nstore the information for the 'saveundo' and 'savecursor' options) if\nthis problem persists.\nThis may be caused by upgrading to version 2.0, and removing the 'buffers'\ndirectory will reset the cursor and undo history and solve the problem.")
|
||||
}
|
||||
if b.Settings["savecursor"].(bool) {
|
||||
b.StartCursor = buffer.Cursor
|
||||
|
||||
@@ -35,7 +35,7 @@ func (b *Buffer) SetOptionNative(option string, nativeValue interface{}) error {
|
||||
}
|
||||
} else if option == "encoding" {
|
||||
b.isModified = true
|
||||
} else if option == "readonly" {
|
||||
} else if option == "readonly" && b.Type == BTDefault {
|
||||
b.Type.Readonly = nativeValue.(bool)
|
||||
}
|
||||
|
||||
|
||||
@@ -14,16 +14,22 @@ var ConfigDir string
|
||||
func InitConfigDir(flagConfigDir string) error {
|
||||
var e error
|
||||
|
||||
xdgHome := os.Getenv("XDG_CONFIG_HOME")
|
||||
if xdgHome == "" {
|
||||
// The user has not set $XDG_CONFIG_HOME so we should act like it was set to ~/.config
|
||||
home, err := homedir.Dir()
|
||||
if err != nil {
|
||||
return errors.New("Error finding your home directory\nCan't load config files")
|
||||
microHome := os.Getenv("MICRO_CONFIG_HOME")
|
||||
if microHome == "" {
|
||||
// The user has not set $MICRO_CONFIG_HOME so we'll try $XDG_CONFIG_HOME
|
||||
xdgHome := os.Getenv("XDG_CONFIG_HOME")
|
||||
if xdgHome == "" {
|
||||
// The user has not set $XDG_CONFIG_HOME so we should act like it was set to ~/.config
|
||||
home, err := homedir.Dir()
|
||||
if err != nil {
|
||||
return errors.New("Error finding your home directory\nCan't load config files: " + err.Error())
|
||||
}
|
||||
xdgHome = home + "/.config"
|
||||
}
|
||||
xdgHome = home + "/.config"
|
||||
|
||||
microHome = xdgHome + "/micro"
|
||||
}
|
||||
ConfigDir = xdgHome + "/micro"
|
||||
ConfigDir = microHome
|
||||
|
||||
if len(flagConfigDir) > 0 {
|
||||
if _, err := os.Stat(flagConfigDir); os.IsNotExist(err) {
|
||||
@@ -34,20 +40,11 @@ func InitConfigDir(flagConfigDir string) error {
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := os.Stat(xdgHome); os.IsNotExist(err) {
|
||||
// If the xdgHome doesn't exist we should create it
|
||||
err = os.Mkdir(xdgHome, os.ModePerm)
|
||||
if err != nil {
|
||||
return errors.New("Error creating XDG_CONFIG_HOME directory: " + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := os.Stat(ConfigDir); os.IsNotExist(err) {
|
||||
// If the micro specific config directory doesn't exist we should create that too
|
||||
err = os.Mkdir(ConfigDir, os.ModePerm)
|
||||
if err != nil {
|
||||
return errors.New("Error creating configuration directory: " + err.Error())
|
||||
}
|
||||
// Create micro config home directory if it does not exist
|
||||
// This creates parent directories and does nothing if it already exists
|
||||
err := os.MkdirAll(ConfigDir, os.ModePerm)
|
||||
if err != nil {
|
||||
return errors.New("Error creating configuration directory: " + err.Error())
|
||||
}
|
||||
|
||||
return e
|
||||
|
||||
@@ -2,11 +2,13 @@ package config
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"log"
|
||||
|
||||
lua "github.com/yuin/gopher-lua"
|
||||
ulua "github.com/zyedidia/micro/internal/lua"
|
||||
)
|
||||
|
||||
// ErrNoSuchFunction is returned when Call is executed on a function that does not exist
|
||||
var ErrNoSuchFunction = errors.New("No such function exists")
|
||||
|
||||
// LoadAllPlugins loads all detected plugins (in runtime/plugins and ConfigDir/plugins)
|
||||
@@ -55,16 +57,16 @@ func RunPluginFnBool(fn string, args ...lua.LValue) (bool, error) {
|
||||
reterr = errors.New("Plugin " + p.Name + ": " + err.Error())
|
||||
continue
|
||||
}
|
||||
if v, ok := val.(lua.LBool); !ok {
|
||||
reterr = errors.New(p.Name + "." + fn + " should return a boolean")
|
||||
} else {
|
||||
if v, ok := val.(lua.LBool); ok {
|
||||
retbool = retbool && bool(v)
|
||||
}
|
||||
}
|
||||
return retbool, reterr
|
||||
}
|
||||
|
||||
// Plugin stores information about the source files/info for a plugin
|
||||
type Plugin struct {
|
||||
DirName string // name of plugin folder
|
||||
Name string // name of plugin
|
||||
Info *PluginInfo // json file containing info
|
||||
Srcs []RuntimeFile // lua files
|
||||
@@ -72,6 +74,7 @@ type Plugin struct {
|
||||
Default bool // pre-installed plugin
|
||||
}
|
||||
|
||||
// IsEnabled returns if a plugin is enabled
|
||||
func (p *Plugin) IsEnabled() bool {
|
||||
if v, ok := GlobalSettings[p.Name]; ok {
|
||||
return v.(bool) && p.Loaded
|
||||
@@ -79,13 +82,15 @@ func (p *Plugin) IsEnabled() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// Plugins is a list of all detected plugins (enabled or disabled)
|
||||
var Plugins []*Plugin
|
||||
|
||||
// Load creates an option for the plugin and runs all source files
|
||||
func (p *Plugin) Load() error {
|
||||
if v, ok := GlobalSettings[p.Name]; ok && !v.(bool) {
|
||||
return nil
|
||||
}
|
||||
for _, f := range p.Srcs {
|
||||
if v, ok := GlobalSettings[p.Name]; ok && !v.(bool) {
|
||||
return nil
|
||||
}
|
||||
dat, err := f.Data()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -94,14 +99,19 @@ func (p *Plugin) Load() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.Loaded = true
|
||||
RegisterGlobalOption(p.Name, true)
|
||||
}
|
||||
p.Loaded = true
|
||||
RegisterGlobalOption(p.Name, true)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Call calls a given function in this plugin
|
||||
func (p *Plugin) Call(fn string, args ...lua.LValue) (lua.LValue, error) {
|
||||
plug := ulua.L.GetGlobal(p.Name)
|
||||
if plug == lua.LNil {
|
||||
log.Println("Plugin does not exist:", p.Name, "at", p.DirName, ":", p)
|
||||
return nil, nil
|
||||
}
|
||||
luafn := ulua.L.GetField(plug, fn)
|
||||
if luafn == lua.LNil {
|
||||
return nil, ErrNoSuchFunction
|
||||
@@ -119,7 +129,23 @@ func (p *Plugin) Call(fn string, args ...lua.LValue) (lua.LValue, error) {
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// FindPlugin returns the plugin with the given name
|
||||
func FindPlugin(name string) *Plugin {
|
||||
var pl *Plugin
|
||||
for _, p := range Plugins {
|
||||
if !p.IsEnabled() {
|
||||
continue
|
||||
}
|
||||
if p.Name == name {
|
||||
pl = p
|
||||
break
|
||||
}
|
||||
}
|
||||
return pl
|
||||
}
|
||||
|
||||
// FindAnyPlugin does not require the plugin to be enabled
|
||||
func FindAnyPlugin(name string) *Plugin {
|
||||
var pl *Plugin
|
||||
for _, p := range Plugins {
|
||||
if p.Name == name {
|
||||
|
||||
712
internal/config/plugin_installer.go
Normal file
712
internal/config/plugin_installer.go
Normal file
@@ -0,0 +1,712 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/blang/semver"
|
||||
lua "github.com/yuin/gopher-lua"
|
||||
"github.com/zyedidia/json5"
|
||||
ulua "github.com/zyedidia/micro/internal/lua"
|
||||
"github.com/zyedidia/micro/internal/util"
|
||||
)
|
||||
|
||||
var (
|
||||
allPluginPackages PluginPackages
|
||||
)
|
||||
|
||||
// CorePluginName is a plugin dependency name for the micro core.
|
||||
const CorePluginName = "micro"
|
||||
|
||||
// PluginChannel contains an url to a json list of PluginRepository
|
||||
type PluginChannel string
|
||||
|
||||
// PluginChannels is a slice of PluginChannel
|
||||
type PluginChannels []PluginChannel
|
||||
|
||||
// PluginRepository contains an url to json file containing PluginPackages
|
||||
type PluginRepository string
|
||||
|
||||
// PluginPackage contains the meta-data of a plugin and all available versions
|
||||
type PluginPackage struct {
|
||||
Name string
|
||||
Description string
|
||||
Author string
|
||||
Tags []string
|
||||
Versions PluginVersions
|
||||
}
|
||||
|
||||
// PluginPackages is a list of PluginPackage instances.
|
||||
type PluginPackages []*PluginPackage
|
||||
|
||||
// PluginVersion descripes a version of a PluginPackage. Containing a version, download url and also dependencies.
|
||||
type PluginVersion struct {
|
||||
pack *PluginPackage
|
||||
Version semver.Version
|
||||
Url string
|
||||
Require PluginDependencies
|
||||
}
|
||||
|
||||
func (pv *PluginVersion) Pack() *PluginPackage {
|
||||
return pv.pack
|
||||
}
|
||||
|
||||
// PluginVersions is a slice of PluginVersion
|
||||
type PluginVersions []*PluginVersion
|
||||
|
||||
// PluginDependency descripes a dependency to another plugin or micro itself.
|
||||
type PluginDependency struct {
|
||||
Name string
|
||||
Range semver.Range
|
||||
}
|
||||
|
||||
// PluginDependencies is a slice of PluginDependency
|
||||
type PluginDependencies []*PluginDependency
|
||||
|
||||
func (pp *PluginPackage) String() string {
|
||||
buf := new(bytes.Buffer)
|
||||
buf.WriteString("Plugin: ")
|
||||
buf.WriteString(pp.Name)
|
||||
buf.WriteRune('\n')
|
||||
if pp.Author != "" {
|
||||
buf.WriteString("Author: ")
|
||||
buf.WriteString(pp.Author)
|
||||
buf.WriteRune('\n')
|
||||
}
|
||||
if pp.Description != "" {
|
||||
buf.WriteRune('\n')
|
||||
buf.WriteString(pp.Description)
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func fetchAllSources(count int, fetcher func(i int) PluginPackages) PluginPackages {
|
||||
wgQuery := new(sync.WaitGroup)
|
||||
wgQuery.Add(count)
|
||||
|
||||
results := make(chan PluginPackages)
|
||||
|
||||
wgDone := new(sync.WaitGroup)
|
||||
wgDone.Add(1)
|
||||
var packages PluginPackages
|
||||
for i := 0; i < count; i++ {
|
||||
go func(i int) {
|
||||
results <- fetcher(i)
|
||||
wgQuery.Done()
|
||||
}(i)
|
||||
}
|
||||
go func() {
|
||||
packages = make(PluginPackages, 0)
|
||||
for res := range results {
|
||||
packages = append(packages, res...)
|
||||
}
|
||||
wgDone.Done()
|
||||
}()
|
||||
wgQuery.Wait()
|
||||
close(results)
|
||||
wgDone.Wait()
|
||||
return packages
|
||||
}
|
||||
|
||||
// Fetch retrieves all available PluginPackages from the given channels
|
||||
func (pc PluginChannels) Fetch(out io.Writer) PluginPackages {
|
||||
return fetchAllSources(len(pc), func(i int) PluginPackages {
|
||||
return pc[i].Fetch(out)
|
||||
})
|
||||
}
|
||||
|
||||
// Fetch retrieves all available PluginPackages from the given channel
|
||||
func (pc PluginChannel) Fetch(out io.Writer) PluginPackages {
|
||||
resp, err := http.Get(string(pc))
|
||||
if err != nil {
|
||||
fmt.Fprintln(out, "Failed to query plugin channel:\n", err)
|
||||
return PluginPackages{}
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
decoder := json5.NewDecoder(resp.Body)
|
||||
|
||||
var repositories []PluginRepository
|
||||
if err := decoder.Decode(&repositories); err != nil {
|
||||
fmt.Fprintln(out, "Failed to decode channel data:\n", err)
|
||||
return PluginPackages{}
|
||||
}
|
||||
return fetchAllSources(len(repositories), func(i int) PluginPackages {
|
||||
return repositories[i].Fetch(out)
|
||||
})
|
||||
}
|
||||
|
||||
// Fetch retrieves all available PluginPackages from the given repository
|
||||
func (pr PluginRepository) Fetch(out io.Writer) PluginPackages {
|
||||
resp, err := http.Get(string(pr))
|
||||
if err != nil {
|
||||
fmt.Fprintln(out, "Failed to query plugin repository:\n", err)
|
||||
return PluginPackages{}
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
decoder := json5.NewDecoder(resp.Body)
|
||||
|
||||
var plugins PluginPackages
|
||||
if err := decoder.Decode(&plugins); err != nil {
|
||||
fmt.Fprintln(out, "Failed to decode repository data:\n", err)
|
||||
return PluginPackages{}
|
||||
}
|
||||
if len(plugins) > 0 {
|
||||
return PluginPackages{plugins[0]}
|
||||
}
|
||||
return nil
|
||||
// return plugins
|
||||
}
|
||||
|
||||
// UnmarshalJSON unmarshals raw json to a PluginVersion
|
||||
func (pv *PluginVersion) UnmarshalJSON(data []byte) error {
|
||||
var values struct {
|
||||
Version semver.Version
|
||||
Url string
|
||||
Require map[string]string
|
||||
}
|
||||
|
||||
if err := json5.Unmarshal(data, &values); err != nil {
|
||||
return err
|
||||
}
|
||||
pv.Version = values.Version
|
||||
pv.Url = values.Url
|
||||
pv.Require = make(PluginDependencies, 0)
|
||||
|
||||
for k, v := range values.Require {
|
||||
// don't add the dependency if it's the core and
|
||||
// we have a unknown version number.
|
||||
// in that case just accept that dependency (which equals to not adding it.)
|
||||
if k != CorePluginName || !isUnknownCoreVersion() {
|
||||
if vRange, err := semver.ParseRange(v); err == nil {
|
||||
pv.Require = append(pv.Require, &PluginDependency{k, vRange})
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON unmarshals raw json to a PluginPackage
|
||||
func (pp *PluginPackage) UnmarshalJSON(data []byte) error {
|
||||
var values struct {
|
||||
Name string
|
||||
Description string
|
||||
Author string
|
||||
Tags []string
|
||||
Versions PluginVersions
|
||||
}
|
||||
if err := json5.Unmarshal(data, &values); err != nil {
|
||||
return err
|
||||
}
|
||||
pp.Name = values.Name
|
||||
pp.Description = values.Description
|
||||
pp.Author = values.Author
|
||||
pp.Tags = values.Tags
|
||||
pp.Versions = values.Versions
|
||||
for _, v := range pp.Versions {
|
||||
v.pack = pp
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetAllPluginPackages gets all PluginPackages which may be available.
|
||||
func GetAllPluginPackages(out io.Writer) PluginPackages {
|
||||
if allPluginPackages == nil {
|
||||
getOption := func(name string) []string {
|
||||
data := GetGlobalOption(name)
|
||||
if strs, ok := data.([]string); ok {
|
||||
return strs
|
||||
}
|
||||
if ifs, ok := data.([]interface{}); ok {
|
||||
result := make([]string, len(ifs))
|
||||
for i, urlIf := range ifs {
|
||||
if url, ok := urlIf.(string); ok {
|
||||
result[i] = url
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
channels := PluginChannels{}
|
||||
for _, url := range getOption("pluginchannels") {
|
||||
channels = append(channels, PluginChannel(url))
|
||||
}
|
||||
repos := []PluginRepository{}
|
||||
for _, url := range getOption("pluginrepos") {
|
||||
repos = append(repos, PluginRepository(url))
|
||||
}
|
||||
allPluginPackages = fetchAllSources(len(repos)+1, func(i int) PluginPackages {
|
||||
if i == 0 {
|
||||
return channels.Fetch(out)
|
||||
}
|
||||
return repos[i-1].Fetch(out)
|
||||
})
|
||||
}
|
||||
return allPluginPackages
|
||||
}
|
||||
|
||||
func (pv PluginVersions) find(ppName string) *PluginVersion {
|
||||
for _, v := range pv {
|
||||
if v.pack.Name == ppName {
|
||||
return v
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Len returns the number of pluginversions in this slice
|
||||
func (pv PluginVersions) Len() int {
|
||||
return len(pv)
|
||||
}
|
||||
|
||||
// Swap two entries of the slice
|
||||
func (pv PluginVersions) Swap(i, j int) {
|
||||
pv[i], pv[j] = pv[j], pv[i]
|
||||
}
|
||||
|
||||
// Less returns true if the version at position i is greater then the version at position j (used for sorting)
|
||||
func (pv PluginVersions) Less(i, j int) bool {
|
||||
return pv[i].Version.GT(pv[j].Version)
|
||||
}
|
||||
|
||||
// Match returns true if the package matches a given search text
|
||||
func (pp PluginPackage) Match(text string) bool {
|
||||
text = strings.ToLower(text)
|
||||
for _, t := range pp.Tags {
|
||||
if strings.ToLower(t) == text {
|
||||
return true
|
||||
}
|
||||
}
|
||||
if strings.Contains(strings.ToLower(pp.Name), text) {
|
||||
return true
|
||||
}
|
||||
|
||||
if strings.Contains(strings.ToLower(pp.Description), text) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// IsInstallable returns true if the package can be installed.
|
||||
func (pp PluginPackage) IsInstallable(out io.Writer) error {
|
||||
_, err := GetAllPluginPackages(out).Resolve(GetInstalledVersions(true), PluginDependencies{
|
||||
&PluginDependency{
|
||||
Name: pp.Name,
|
||||
Range: semver.Range(func(v semver.Version) bool { return true }),
|
||||
}})
|
||||
return err
|
||||
}
|
||||
|
||||
// SearchPlugin retrieves a list of all PluginPackages which match the given search text and
|
||||
// could be or are already installed
|
||||
func SearchPlugin(out io.Writer, texts []string) (plugins PluginPackages) {
|
||||
plugins = make(PluginPackages, 0)
|
||||
|
||||
pluginLoop:
|
||||
for _, pp := range GetAllPluginPackages(out) {
|
||||
for _, text := range texts {
|
||||
if !pp.Match(text) {
|
||||
continue pluginLoop
|
||||
}
|
||||
}
|
||||
|
||||
if err := pp.IsInstallable(out); err == nil {
|
||||
plugins = append(plugins, pp)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func isUnknownCoreVersion() bool {
|
||||
_, err := semver.ParseTolerant(util.Version)
|
||||
return err != nil
|
||||
}
|
||||
|
||||
func newStaticPluginVersion(name, version string) *PluginVersion {
|
||||
vers, err := semver.ParseTolerant(version)
|
||||
|
||||
if err != nil {
|
||||
if vers, err = semver.ParseTolerant("0.0.0-" + version); err != nil {
|
||||
vers = semver.MustParse("0.0.0-unknown")
|
||||
}
|
||||
}
|
||||
pl := &PluginPackage{
|
||||
Name: name,
|
||||
}
|
||||
pv := &PluginVersion{
|
||||
pack: pl,
|
||||
Version: vers,
|
||||
}
|
||||
pl.Versions = PluginVersions{pv}
|
||||
return pv
|
||||
}
|
||||
|
||||
// GetInstalledVersions returns a list of all currently installed plugins including an entry for
|
||||
// micro itself. This can be used to resolve dependencies.
|
||||
func GetInstalledVersions(withCore bool) PluginVersions {
|
||||
result := PluginVersions{}
|
||||
if withCore {
|
||||
result = append(result, newStaticPluginVersion(CorePluginName, util.Version))
|
||||
}
|
||||
|
||||
for _, p := range Plugins {
|
||||
if !p.IsEnabled() {
|
||||
continue
|
||||
}
|
||||
version := GetInstalledPluginVersion(p.Name)
|
||||
if pv := newStaticPluginVersion(p.Name, version); pv != nil {
|
||||
result = append(result, pv)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// GetInstalledPluginVersion returns the string of the exported VERSION variable of a loaded plugin
|
||||
func GetInstalledPluginVersion(name string) string {
|
||||
plugin := ulua.L.GetGlobal(name)
|
||||
if plugin != lua.LNil {
|
||||
version := ulua.L.GetField(plugin, "VERSION")
|
||||
if str, ok := version.(lua.LString); ok {
|
||||
return string(str)
|
||||
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// DownloadAndInstall downloads and installs the given plugin and version
|
||||
func (pv *PluginVersion) DownloadAndInstall(out io.Writer) error {
|
||||
fmt.Fprintf(out, "Downloading %q (%s) from %q\n", pv.pack.Name, pv.Version, pv.Url)
|
||||
resp, err := http.Get(pv.Url)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
data, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
zipbuf := bytes.NewReader(data)
|
||||
z, err := zip.NewReader(zipbuf, zipbuf.Size())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
targetDir := filepath.Join(ConfigDir, "plug", pv.pack.Name)
|
||||
dirPerm := os.FileMode(0755)
|
||||
if err = os.MkdirAll(targetDir, dirPerm); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Check if all files in zip are in the same directory.
|
||||
// this might be the case if the plugin zip contains the whole plugin dir
|
||||
// instead of its content.
|
||||
var prefix string
|
||||
allPrefixed := false
|
||||
for i, f := range z.File {
|
||||
parts := strings.Split(f.Name, "/")
|
||||
if i == 0 {
|
||||
prefix = parts[0]
|
||||
} else if parts[0] != prefix {
|
||||
allPrefixed = false
|
||||
break
|
||||
} else {
|
||||
// switch to true since we have at least a second file
|
||||
allPrefixed = true
|
||||
}
|
||||
}
|
||||
|
||||
// Install files and directory's
|
||||
for _, f := range z.File {
|
||||
parts := strings.Split(f.Name, "/")
|
||||
if allPrefixed {
|
||||
parts = parts[1:]
|
||||
}
|
||||
|
||||
targetName := filepath.Join(targetDir, filepath.Join(parts...))
|
||||
if f.FileInfo().IsDir() {
|
||||
if err := os.MkdirAll(targetName, dirPerm); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
basepath := filepath.Dir(targetName)
|
||||
|
||||
if err := os.MkdirAll(basepath, dirPerm); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
content, err := f.Open()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer content.Close()
|
||||
target, err := os.Create(targetName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer target.Close()
|
||||
if _, err = io.Copy(target, content); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pl PluginPackages) Get(name string) *PluginPackage {
|
||||
for _, p := range pl {
|
||||
if p.Name == name {
|
||||
return p
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pl PluginPackages) GetAllVersions(name string) PluginVersions {
|
||||
result := make(PluginVersions, 0)
|
||||
p := pl.Get(name)
|
||||
if p != nil {
|
||||
for _, v := range p.Versions {
|
||||
result = append(result, v)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (req PluginDependencies) Join(other PluginDependencies) PluginDependencies {
|
||||
m := make(map[string]*PluginDependency)
|
||||
for _, r := range req {
|
||||
m[r.Name] = r
|
||||
}
|
||||
for _, o := range other {
|
||||
cur, ok := m[o.Name]
|
||||
if ok {
|
||||
m[o.Name] = &PluginDependency{
|
||||
o.Name,
|
||||
o.Range.AND(cur.Range),
|
||||
}
|
||||
} else {
|
||||
m[o.Name] = o
|
||||
}
|
||||
}
|
||||
result := make(PluginDependencies, 0, len(m))
|
||||
for _, v := range m {
|
||||
result = append(result, v)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// Resolve resolves dependencies between different plugins
|
||||
func (all PluginPackages) Resolve(selectedVersions PluginVersions, open PluginDependencies) (PluginVersions, error) {
|
||||
if len(open) == 0 {
|
||||
return selectedVersions, nil
|
||||
}
|
||||
currentRequirement, stillOpen := open[0], open[1:]
|
||||
if currentRequirement != nil {
|
||||
if selVersion := selectedVersions.find(currentRequirement.Name); selVersion != nil {
|
||||
if currentRequirement.Range(selVersion.Version) {
|
||||
return all.Resolve(selectedVersions, stillOpen)
|
||||
}
|
||||
return nil, fmt.Errorf("unable to find a matching version for \"%s\"", currentRequirement.Name)
|
||||
}
|
||||
availableVersions := all.GetAllVersions(currentRequirement.Name)
|
||||
sort.Sort(availableVersions)
|
||||
|
||||
for _, version := range availableVersions {
|
||||
if currentRequirement.Range(version.Version) {
|
||||
resolved, err := all.Resolve(append(selectedVersions, version), stillOpen.Join(version.Require))
|
||||
|
||||
if err == nil {
|
||||
return resolved, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("unable to find a matching version for \"%s\"", currentRequirement.Name)
|
||||
}
|
||||
return selectedVersions, nil
|
||||
}
|
||||
|
||||
func (pv PluginVersions) install(out io.Writer) {
|
||||
anyInstalled := false
|
||||
currentlyInstalled := GetInstalledVersions(true)
|
||||
|
||||
for _, sel := range pv {
|
||||
if sel.pack.Name != CorePluginName {
|
||||
shouldInstall := true
|
||||
if pv := currentlyInstalled.find(sel.pack.Name); pv != nil {
|
||||
if pv.Version.NE(sel.Version) {
|
||||
fmt.Fprintln(out, "Uninstalling", sel.pack.Name)
|
||||
UninstallPlugin(out, sel.pack.Name)
|
||||
} else {
|
||||
shouldInstall = false
|
||||
}
|
||||
}
|
||||
|
||||
if shouldInstall {
|
||||
if err := sel.DownloadAndInstall(out); err != nil {
|
||||
fmt.Fprintln(out, err)
|
||||
return
|
||||
}
|
||||
anyInstalled = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if anyInstalled {
|
||||
fmt.Fprintln(out, "One or more plugins installed.")
|
||||
} else {
|
||||
fmt.Fprintln(out, "Nothing to install / update")
|
||||
}
|
||||
}
|
||||
|
||||
// UninstallPlugin deletes the plugin folder of the given plugin
|
||||
func UninstallPlugin(out io.Writer, name string) {
|
||||
for _, p := range Plugins {
|
||||
if !p.IsEnabled() {
|
||||
continue
|
||||
}
|
||||
if p.Name == name {
|
||||
p.Loaded = false
|
||||
if err := os.RemoveAll(filepath.Join(ConfigDir, "plug", p.DirName)); err != nil {
|
||||
fmt.Fprintln(out, err)
|
||||
return
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Install installs the plugin
|
||||
func (pl PluginPackage) Install(out io.Writer) {
|
||||
selected, err := GetAllPluginPackages(out).Resolve(GetInstalledVersions(true), PluginDependencies{
|
||||
&PluginDependency{
|
||||
Name: pl.Name,
|
||||
Range: semver.Range(func(v semver.Version) bool { return true }),
|
||||
}})
|
||||
if err != nil {
|
||||
fmt.Fprintln(out, err)
|
||||
return
|
||||
}
|
||||
selected.install(out)
|
||||
}
|
||||
|
||||
// UpdatePlugins updates the given plugins
|
||||
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() {
|
||||
continue
|
||||
}
|
||||
plugins = append(plugins, p.Name)
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Fprintln(out, "Checking for plugin updates")
|
||||
microVersion := PluginVersions{
|
||||
newStaticPluginVersion(CorePluginName, util.Version),
|
||||
}
|
||||
|
||||
var updates = make(PluginDependencies, 0)
|
||||
for _, name := range plugins {
|
||||
pv := GetInstalledPluginVersion(name)
|
||||
r, err := semver.ParseRange(">=" + pv) // Try to get newer versions.
|
||||
if err == nil {
|
||||
updates = append(updates, &PluginDependency{
|
||||
Name: name,
|
||||
Range: r,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
selected, err := GetAllPluginPackages(out).Resolve(microVersion, updates)
|
||||
if err != nil {
|
||||
fmt.Fprintln(out, err)
|
||||
return
|
||||
}
|
||||
selected.install(out)
|
||||
}
|
||||
|
||||
func PluginCommand(out io.Writer, cmd string, args []string) {
|
||||
switch cmd {
|
||||
case "install":
|
||||
installedVersions := GetInstalledVersions(false)
|
||||
for _, plugin := range args {
|
||||
pp := GetAllPluginPackages(out).Get(plugin)
|
||||
if pp == nil {
|
||||
fmt.Fprintln(out, "Unknown plugin \""+plugin+"\"")
|
||||
} else if err := pp.IsInstallable(out); err != nil {
|
||||
fmt.Fprintln(out, "Error installing ", plugin, ": ", err)
|
||||
} else {
|
||||
for _, installed := range installedVersions {
|
||||
if pp.Name == installed.Pack().Name {
|
||||
if pp.Versions[0].Version.Compare(installed.Version) == 1 {
|
||||
fmt.Fprintln(out, pp.Name, " is already installed but out-of-date: use 'plugin update ", pp.Name, "' to update")
|
||||
} else {
|
||||
fmt.Fprintln(out, pp.Name, " is already installed")
|
||||
}
|
||||
}
|
||||
}
|
||||
pp.Install(out)
|
||||
}
|
||||
}
|
||||
|
||||
case "remove":
|
||||
removed := ""
|
||||
for _, plugin := range args {
|
||||
// check if the plugin exists.
|
||||
for _, p := range Plugins {
|
||||
if p.Name == plugin && p.Default {
|
||||
fmt.Fprintln(out, "Default plugins cannot be removed, but can be disabled via settings.")
|
||||
continue
|
||||
}
|
||||
if p.Name == plugin {
|
||||
UninstallPlugin(out, plugin)
|
||||
removed += plugin + " "
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
if removed != "" {
|
||||
fmt.Fprintln(out, "Removed ", removed)
|
||||
} else {
|
||||
fmt.Fprintln(out, "No plugins removed")
|
||||
}
|
||||
case "update":
|
||||
UpdatePlugins(out, args)
|
||||
case "list":
|
||||
plugins := GetInstalledVersions(false)
|
||||
fmt.Fprintln(out, "The following plugins are currently installed:")
|
||||
for _, p := range plugins {
|
||||
fmt.Fprintf(out, "%s (%s)\n", p.Pack().Name, p.Version)
|
||||
}
|
||||
case "search":
|
||||
plugins := SearchPlugin(out, args)
|
||||
fmt.Fprintln(out, len(plugins), " plugins found")
|
||||
for _, p := range plugins {
|
||||
fmt.Fprintln(out, "----------------")
|
||||
fmt.Fprintln(out, p.String())
|
||||
}
|
||||
fmt.Fprintln(out, "----------------")
|
||||
case "available":
|
||||
packages := GetAllPluginPackages(out)
|
||||
fmt.Fprintln(out, "Available Plugins:")
|
||||
for _, pkg := range packages {
|
||||
fmt.Fprintln(out, pkg.Name)
|
||||
}
|
||||
default:
|
||||
fmt.Fprintln(out, "Invalid plugin command")
|
||||
}
|
||||
}
|
||||
56
internal/config/plugin_installer_test.go
Normal file
56
internal/config/plugin_installer_test.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/blang/semver"
|
||||
|
||||
"github.com/zyedidia/json5"
|
||||
)
|
||||
|
||||
func TestDependencyResolving(t *testing.T) {
|
||||
js := `
|
||||
[{
|
||||
"Name": "Foo",
|
||||
"Versions": [{ "Version": "1.0.0" }, { "Version": "1.5.0" },{ "Version": "2.0.0" }]
|
||||
}, {
|
||||
"Name": "Bar",
|
||||
"Versions": [{ "Version": "1.0.0", "Require": {"Foo": ">1.0.0 <2.0.0"} }]
|
||||
}, {
|
||||
"Name": "Unresolvable",
|
||||
"Versions": [{ "Version": "1.0.0", "Require": {"Foo": "<=1.0.0", "Bar": ">0.0.0"} }]
|
||||
}]
|
||||
`
|
||||
var all PluginPackages
|
||||
err := json5.Unmarshal([]byte(js), &all)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
selected, err := all.Resolve(PluginVersions{}, PluginDependencies{
|
||||
&PluginDependency{"Bar", semver.MustParseRange(">=1.0.0")},
|
||||
})
|
||||
|
||||
check := func(name, version string) {
|
||||
v := selected.find(name)
|
||||
expected := semver.MustParse(version)
|
||||
if v == nil {
|
||||
t.Errorf("Failed to resolve %s", name)
|
||||
} else if expected.NE(v.Version) {
|
||||
t.Errorf("%s resolved in wrong version %v", name, v)
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
} else {
|
||||
check("Foo", "1.5.0")
|
||||
check("Bar", "1.0.0")
|
||||
}
|
||||
|
||||
selected, err = all.Resolve(PluginVersions{}, PluginDependencies{
|
||||
&PluginDependency{"Unresolvable", semver.MustParseRange(">0.0.0")},
|
||||
})
|
||||
if err == nil {
|
||||
t.Error("Unresolvable package resolved:", selected)
|
||||
}
|
||||
}
|
||||
@@ -7,12 +7,9 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
ErrMissingName = errors.New("Missing or empty name field")
|
||||
ErrMissingDesc = errors.New("Missing or empty description field")
|
||||
ErrMissingSite = errors.New("Missing or empty website field")
|
||||
ErrMissingInstall = errors.New("Missing or empty install field")
|
||||
ErrMissingVstr = errors.New("Missing or empty versions field")
|
||||
ErrMissingRequire = errors.New("Missing or empty require field")
|
||||
ErrMissingName = errors.New("Missing or empty name field")
|
||||
ErrMissingDesc = errors.New("Missing or empty description field")
|
||||
ErrMissingSite = errors.New("Missing or empty website field")
|
||||
)
|
||||
|
||||
// PluginInfo contains all the needed info about a plugin
|
||||
@@ -27,19 +24,16 @@ var (
|
||||
// Vstr: version
|
||||
// Require: list of dependencies and requirements
|
||||
type PluginInfo struct {
|
||||
Name string `json:"name"`
|
||||
Desc string `json:"description"`
|
||||
Site string `json:"website"`
|
||||
Install string `json:"install"`
|
||||
Vstr string `json:"version"`
|
||||
Require []string `json:"require"`
|
||||
Name string `json:"Name"`
|
||||
Desc string `json:"Description"`
|
||||
Site string `json:"Website"`
|
||||
}
|
||||
|
||||
// NewPluginInfo parses a JSON input into a valid PluginInfo struct
|
||||
// Returns an error if there are any missing fields or any invalid fields
|
||||
// There are no optional fields in a plugin info json file
|
||||
func NewPluginInfo(data []byte) (*PluginInfo, error) {
|
||||
var info PluginInfo
|
||||
var info []PluginInfo
|
||||
|
||||
dec := json.NewDecoder(bytes.NewReader(data))
|
||||
// dec.DisallowUnknownFields() // Force errors
|
||||
@@ -48,19 +42,5 @@ func NewPluginInfo(data []byte) (*PluginInfo, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// if len(info.Name) == 0 {
|
||||
// return nil, ErrMissingName
|
||||
// } else if len(info.Desc) == 0 {
|
||||
// return nil, ErrMissingDesc
|
||||
// } else if len(info.Site) == 0 {
|
||||
// return nil, ErrMissingSite
|
||||
// } else if len(info.Install) == 0 {
|
||||
// return nil, ErrMissingInstall
|
||||
// } else if len(info.Vstr) == 0 {
|
||||
// return nil, ErrMissingVstr
|
||||
// } else if len(info.Require) == 0 {
|
||||
// return nil, ErrMissingRequire
|
||||
// }
|
||||
|
||||
return &info, nil
|
||||
return &info[0], nil
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
@@ -11,14 +12,18 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
RTColorscheme = 0
|
||||
RTSyntax = 1
|
||||
RTHelp = 2
|
||||
RTPlugin = 3
|
||||
NumTypes = 4 // How many filetypes are there
|
||||
RTColorscheme = 0
|
||||
RTSyntax = 1
|
||||
RTHelp = 2
|
||||
RTPlugin = 3
|
||||
RTSyntaxHeader = 4
|
||||
)
|
||||
|
||||
type RTFiletype byte
|
||||
var (
|
||||
NumTypes = 5 // How many filetypes are there
|
||||
)
|
||||
|
||||
type RTFiletype int
|
||||
|
||||
// RuntimeFile allows the program to read runtime data like colorschemes or syntax files
|
||||
type RuntimeFile interface {
|
||||
@@ -29,7 +34,20 @@ type RuntimeFile interface {
|
||||
}
|
||||
|
||||
// allFiles contains all available files, mapped by filetype
|
||||
var allFiles [NumTypes][]RuntimeFile
|
||||
var allFiles [][]RuntimeFile
|
||||
var realFiles [][]RuntimeFile
|
||||
|
||||
func init() {
|
||||
allFiles = make([][]RuntimeFile, NumTypes)
|
||||
realFiles = make([][]RuntimeFile, NumTypes)
|
||||
}
|
||||
|
||||
func NewRTFiletype() int {
|
||||
NumTypes++
|
||||
allFiles = append(allFiles, []RuntimeFile{})
|
||||
realFiles = append(realFiles, []RuntimeFile{})
|
||||
return NumTypes - 1
|
||||
}
|
||||
|
||||
// some file on filesystem
|
||||
type realFile string
|
||||
@@ -83,6 +101,12 @@ func AddRuntimeFile(fileType RTFiletype, file RuntimeFile) {
|
||||
allFiles[fileType] = append(allFiles[fileType], file)
|
||||
}
|
||||
|
||||
// AddRealRuntimeFile registers a file for the given filetype
|
||||
func AddRealRuntimeFile(fileType RTFiletype, file RuntimeFile) {
|
||||
allFiles[fileType] = append(allFiles[fileType], file)
|
||||
realFiles[fileType] = append(realFiles[fileType], file)
|
||||
}
|
||||
|
||||
// AddRuntimeFilesFromDirectory registers each file from the given directory for
|
||||
// the filetype which matches the file-pattern
|
||||
func AddRuntimeFilesFromDirectory(fileType RTFiletype, directory, pattern string) {
|
||||
@@ -90,7 +114,7 @@ func AddRuntimeFilesFromDirectory(fileType RTFiletype, directory, pattern string
|
||||
for _, f := range files {
|
||||
if ok, _ := filepath.Match(pattern, f.Name()); !f.IsDir() && ok {
|
||||
fullPath := filepath.Join(directory, f.Name())
|
||||
AddRuntimeFile(fileType, realFile(fullPath))
|
||||
AddRealRuntimeFile(fileType, realFile(fullPath))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -125,6 +149,12 @@ func ListRuntimeFiles(fileType RTFiletype) []RuntimeFile {
|
||||
return allFiles[fileType]
|
||||
}
|
||||
|
||||
// ListRealRuntimeFiles lists all real runtime files (on disk) for a filetype
|
||||
// these runtime files will be ones defined by the user and loaded from the config directory
|
||||
func ListRealRuntimeFiles(fileType RTFiletype) []RuntimeFile {
|
||||
return realFiles[fileType]
|
||||
}
|
||||
|
||||
// InitRuntimeFiles initializes all assets file and the config directory
|
||||
func InitRuntimeFiles() {
|
||||
add := func(fileType RTFiletype, dir, pattern string) {
|
||||
@@ -134,12 +164,14 @@ func InitRuntimeFiles() {
|
||||
|
||||
add(RTColorscheme, "colorschemes", "*.micro")
|
||||
add(RTSyntax, "syntax", "*.yaml")
|
||||
add(RTSyntaxHeader, "syntax", "*.hdr")
|
||||
add(RTHelp, "help", "*.md")
|
||||
|
||||
initlua := filepath.Join(ConfigDir, "init.lua")
|
||||
if _, err := os.Stat(initlua); !os.IsNotExist(err) {
|
||||
p := new(Plugin)
|
||||
p.Name = "initlua"
|
||||
p.DirName = "initlua"
|
||||
p.Srcs = append(p.Srcs, realFile(initlua))
|
||||
Plugins = append(Plugins, p)
|
||||
}
|
||||
@@ -155,21 +187,26 @@ func InitRuntimeFiles() {
|
||||
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 f.Name() == "info.json" {
|
||||
data, err := ioutil.ReadFile(filepath.Join(plugdir, d.Name(), "info.json"))
|
||||
} else if strings.HasSuffix(f.Name(), ".json") {
|
||||
data, err := ioutil.ReadFile(filepath.Join(plugdir, d.Name(), f.Name()))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
p.Info, _ = NewPluginInfo(data)
|
||||
p.Info, err = NewPluginInfo(data)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
continue
|
||||
}
|
||||
p.Name = p.Info.Name
|
||||
}
|
||||
}
|
||||
|
||||
if !isID(p.Name) {
|
||||
log.Println("Invalid plugin name", p.Name)
|
||||
if !isID(p.Name) || len(p.Srcs) <= 0 {
|
||||
log.Println(p.Name, "is not a plugin")
|
||||
continue
|
||||
}
|
||||
Plugins = append(Plugins, p)
|
||||
@@ -182,21 +219,26 @@ func InitRuntimeFiles() {
|
||||
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 f == "info.json" {
|
||||
data, err := Asset(filepath.Join(plugdir, d, "info.json"))
|
||||
} else if strings.HasSuffix(f, ".json") {
|
||||
data, err := Asset(filepath.Join(plugdir, d, f))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
p.Info, _ = NewPluginInfo(data)
|
||||
p.Info, err = NewPluginInfo(data)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
continue
|
||||
}
|
||||
p.Name = p.Info.Name
|
||||
}
|
||||
}
|
||||
if !isID(p.Name) {
|
||||
log.Println("Invalid plugin name", p.Name)
|
||||
if !isID(p.Name) || len(p.Srcs) <= 0 {
|
||||
log.Println(p.Name, "is not a plugin")
|
||||
continue
|
||||
}
|
||||
Plugins = append(Plugins, p)
|
||||
@@ -226,28 +268,40 @@ func PluginListRuntimeFiles(fileType RTFiletype) []string {
|
||||
}
|
||||
|
||||
// PluginAddRuntimeFile adds a file to the runtime files for a plugin
|
||||
func PluginAddRuntimeFile(plugin string, filetype RTFiletype, filePath string) {
|
||||
fullpath := filepath.Join(ConfigDir, "plug", plugin, filePath)
|
||||
func PluginAddRuntimeFile(plugin string, filetype RTFiletype, filePath string) error {
|
||||
pl := FindPlugin(plugin)
|
||||
if pl == nil {
|
||||
return errors.New("Plugin " + plugin + " does not exist")
|
||||
}
|
||||
pldir := pl.DirName
|
||||
fullpath := filepath.Join(ConfigDir, "plug", pldir, filePath)
|
||||
if _, err := os.Stat(fullpath); err == nil {
|
||||
AddRuntimeFile(filetype, realFile(fullpath))
|
||||
AddRealRuntimeFile(filetype, realFile(fullpath))
|
||||
} else {
|
||||
fullpath = path.Join("runtime", "plugins", plugin, filePath)
|
||||
fullpath = path.Join("runtime", "plugins", pldir, filePath)
|
||||
AddRuntimeFile(filetype, assetFile(fullpath))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// PluginAddRuntimeFilesFromDirectory adds files from a directory to the runtime files for a plugin
|
||||
func PluginAddRuntimeFilesFromDirectory(plugin string, filetype RTFiletype, directory, pattern string) {
|
||||
fullpath := filepath.Join(ConfigDir, "plug", plugin, directory)
|
||||
func PluginAddRuntimeFilesFromDirectory(plugin string, filetype RTFiletype, directory, pattern string) error {
|
||||
pl := FindPlugin(plugin)
|
||||
if pl == nil {
|
||||
return errors.New("Plugin " + plugin + " does not exist")
|
||||
}
|
||||
pldir := pl.DirName
|
||||
fullpath := filepath.Join(ConfigDir, "plug", pldir, directory)
|
||||
if _, err := os.Stat(fullpath); err == nil {
|
||||
AddRuntimeFilesFromDirectory(filetype, fullpath, pattern)
|
||||
} else {
|
||||
fullpath = path.Join("runtime", "plugins", plugin, directory)
|
||||
fullpath = path.Join("runtime", "plugins", pldir, directory)
|
||||
AddRuntimeFilesFromAssets(filetype, fullpath, pattern)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// PluginAddRuntimeFileFromMemory adds a file to the runtime files for a plugin from a given string
|
||||
func PluginAddRuntimeFileFromMemory(plugin string, filetype RTFiletype, filename, data string) {
|
||||
AddRuntimeFile(filetype, memoryFile{filename, []byte(data)})
|
||||
func PluginAddRuntimeFileFromMemory(filetype RTFiletype, filename, data string) {
|
||||
AddRealRuntimeFile(filetype, memoryFile{filename, []byte(data)})
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -5,12 +5,13 @@ import (
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/flynn/json5"
|
||||
"github.com/zyedidia/glob"
|
||||
"github.com/zyedidia/json5"
|
||||
"github.com/zyedidia/micro/internal/util"
|
||||
"golang.org/x/text/encoding/htmlindex"
|
||||
)
|
||||
@@ -34,7 +35,7 @@ func init() {
|
||||
|
||||
// Options with validators
|
||||
var optionValidators = map[string]optionValidator{
|
||||
"autosave": validateNonNegativeValue,
|
||||
// "autosave": validateNonNegativeValue,
|
||||
"tabsize": validatePositiveValue,
|
||||
"scrollmargin": validateNonNegativeValue,
|
||||
"scrollspeed": validateNonNegativeValue,
|
||||
@@ -45,7 +46,7 @@ var optionValidators = map[string]optionValidator{
|
||||
}
|
||||
|
||||
func ReadSettings() error {
|
||||
filename := ConfigDir + "/settings.json"
|
||||
filename := filepath.Join(ConfigDir, "settings.json")
|
||||
if _, e := os.Stat(filename); e == nil {
|
||||
input, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
@@ -119,12 +120,22 @@ func WriteSettings(filename string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// RegisterCommonOption creates a new option. This is meant to be called by plugins to add options.
|
||||
func RegisterCommonOption(name string, defaultvalue interface{}) error {
|
||||
func OverwriteSettings(filename string) error {
|
||||
var err error
|
||||
if _, e := os.Stat(ConfigDir); e == nil {
|
||||
txt, _ := json.MarshalIndent(GlobalSettings, "", " ")
|
||||
err = ioutil.WriteFile(filename, append(txt, '\n'), 0644)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// RegisterCommonOptionPlug creates a new option (called pl.name). This is meant to be called by plugins to add options.
|
||||
func RegisterCommonOptionPlug(pl string, name string, defaultvalue interface{}) error {
|
||||
name = pl + "." + name
|
||||
if v, ok := GlobalSettings[name]; !ok {
|
||||
defaultCommonSettings[name] = defaultvalue
|
||||
GlobalSettings[name] = defaultvalue
|
||||
err := WriteSettings(ConfigDir + "/settings.json")
|
||||
err := WriteSettings(filepath.Join(ConfigDir, "/settings.json"))
|
||||
if err != nil {
|
||||
return errors.New("Error writing settings.json file: " + err.Error())
|
||||
}
|
||||
@@ -134,11 +145,17 @@ func RegisterCommonOption(name string, defaultvalue interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// RegisterGlobalOptionPlug creates a new global-only option (named pl.name)
|
||||
func RegisterGlobalOptionPlug(pl string, name string, defaultvalue interface{}) error {
|
||||
return RegisterGlobalOption(pl+"."+name, defaultvalue)
|
||||
}
|
||||
|
||||
// RegisterGlobalOption creates a new global-only option
|
||||
func RegisterGlobalOption(name string, defaultvalue interface{}) error {
|
||||
if v, ok := GlobalSettings[name]; !ok {
|
||||
defaultGlobalSettings[name] = defaultvalue
|
||||
GlobalSettings[name] = defaultvalue
|
||||
err := WriteSettings(ConfigDir + "/settings.json")
|
||||
err := WriteSettings(filepath.Join(ConfigDir, "settings.json"))
|
||||
if err != nil {
|
||||
return errors.New("Error writing settings.json file: " + err.Error())
|
||||
}
|
||||
@@ -181,8 +198,8 @@ var defaultCommonSettings = map[string]interface{}{
|
||||
"softwrap": false,
|
||||
"splitbottom": true,
|
||||
"splitright": true,
|
||||
"statusformatl": "$(filename) $(modified)($(line),$(col)) | ft:$(opt:filetype) | $(opt:fileformat) | $(opt:encoding)",
|
||||
"statusformatr": "$(bind:ToggleKeyMenu): show bindings, $(bind:ToggleHelp): toggle help",
|
||||
"statusformatl": "$(filename) $(modified)($(line),$(col)) $(status.paste)| ft:$(opt:filetype) | $(opt:fileformat) | $(opt:encoding)",
|
||||
"statusformatr": "$(bind:ToggleKeyMenu): bindings, $(bind:ToggleHelp): help",
|
||||
"statusline": true,
|
||||
"syntax": true,
|
||||
"tabmovement": false,
|
||||
@@ -212,15 +229,25 @@ func DefaultCommonSettings() map[string]interface{} {
|
||||
return commonsettings
|
||||
}
|
||||
|
||||
// a list of settings that should only be globally modified and their
|
||||
// default values
|
||||
var defaultGlobalSettings = map[string]interface{}{
|
||||
"autosave": float64(0),
|
||||
"colorscheme": "default",
|
||||
"infobar": true,
|
||||
"keymenu": false,
|
||||
"mouse": true,
|
||||
"savehistory": true,
|
||||
"sucmd": "sudo",
|
||||
"termtitle": false,
|
||||
// "autosave": float64(0),
|
||||
"colorscheme": "default",
|
||||
"infobar": true,
|
||||
"keymenu": false,
|
||||
"mouse": true,
|
||||
"paste": false,
|
||||
"savehistory": true,
|
||||
"sucmd": "sudo",
|
||||
"pluginchannels": []string{"https://raw.githubusercontent.com/micro-editor/plugin-channel/master/channel.json"},
|
||||
"pluginrepos": []string{},
|
||||
}
|
||||
|
||||
// a list of settings that should never be globally modified
|
||||
var LocalSettings = []string{
|
||||
"filetype",
|
||||
"readonly",
|
||||
}
|
||||
|
||||
// DefaultGlobalSettings returns the default global settings for micro
|
||||
@@ -249,6 +276,7 @@ func DefaultAllSettings() map[string]interface{} {
|
||||
return allsettings
|
||||
}
|
||||
|
||||
// GetNativeValue parses and validates a value for a given option
|
||||
func GetNativeValue(option string, realValue interface{}, value string) (interface{}, error) {
|
||||
var native interface{}
|
||||
kind := reflect.TypeOf(realValue).Kind()
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package display
|
||||
|
||||
import (
|
||||
"log"
|
||||
"strconv"
|
||||
"unicode/utf8"
|
||||
|
||||
@@ -45,12 +44,12 @@ func (w *BufWindow) SetBuffer(b *buffer.Buffer) {
|
||||
w.Buf = b
|
||||
}
|
||||
|
||||
func (v *View) GetView() *View {
|
||||
return v
|
||||
func (w *BufWindow) GetView() *View {
|
||||
return w.View
|
||||
}
|
||||
|
||||
func (v *View) SetView(view *View) {
|
||||
v = view
|
||||
func (w *BufWindow) SetView(view *View) {
|
||||
w.View = view
|
||||
}
|
||||
|
||||
func (w *BufWindow) Resize(width, height int) {
|
||||
@@ -103,7 +102,7 @@ func (w *BufWindow) getStartInfo(n, lineN int) ([]byte, int, int, *tcell.Style)
|
||||
func (w *BufWindow) Clear() {
|
||||
for y := 0; y < w.Height; y++ {
|
||||
for x := 0; x < w.Width; x++ {
|
||||
screen.Screen.SetContent(w.X+x, w.Y+y, ' ', nil, config.DefStyle)
|
||||
screen.SetContent(w.X+x, w.Y+y, ' ', nil, config.DefStyle)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -121,9 +120,8 @@ func (w *BufWindow) Bottomline() int {
|
||||
return h
|
||||
}
|
||||
|
||||
l := w.LocFromVisual(buffer.Loc{0, w.Height})
|
||||
l := w.LocFromVisual(buffer.Loc{0, w.Y + w.Height})
|
||||
|
||||
log.Println("Bottom line:", l.Y)
|
||||
return l.Y
|
||||
}
|
||||
|
||||
@@ -134,7 +132,6 @@ func (w *BufWindow) Relocate() bool {
|
||||
b := w.Buf
|
||||
// how many buffer lines are in the view
|
||||
height := w.Bottomline() + 1 - w.StartLine
|
||||
log.Printf("Height: %d, w.Height: %d\n", height, w.Height)
|
||||
h := w.Height
|
||||
if w.drawStatus {
|
||||
h--
|
||||
@@ -191,6 +188,11 @@ func (w *BufWindow) LocFromVisual(svloc buffer.Loc) buffer.Loc {
|
||||
bufHeight--
|
||||
}
|
||||
|
||||
bufWidth := w.Width
|
||||
if w.Buf.Settings["scrollbar"].(bool) && w.Buf.LinesNum() > w.Height {
|
||||
bufWidth--
|
||||
}
|
||||
|
||||
// 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()))
|
||||
@@ -262,7 +264,7 @@ func (w *BufWindow) LocFromVisual(svloc buffer.Loc) buffer.Loc {
|
||||
totalwidth += width
|
||||
|
||||
// If we reach the end of the window then we either stop or we wrap for softwrap
|
||||
if vloc.X >= w.Width {
|
||||
if vloc.X >= bufWidth {
|
||||
if !softwrap {
|
||||
break
|
||||
} else {
|
||||
@@ -272,7 +274,9 @@ func (w *BufWindow) LocFromVisual(svloc buffer.Loc) buffer.Loc {
|
||||
}
|
||||
vloc.X = 0
|
||||
// This will draw an empty line number because the current line is wrapped
|
||||
vloc.X += maxLineNumLength + 1
|
||||
if b.Settings["ruler"].(bool) {
|
||||
vloc.X += maxLineNumLength + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -301,9 +305,9 @@ func (w *BufWindow) drawGutter(vloc *buffer.Loc, bloc *buffer.Loc) {
|
||||
break
|
||||
}
|
||||
}
|
||||
screen.Screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, char, nil, s)
|
||||
screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, char, nil, s)
|
||||
vloc.X++
|
||||
screen.Screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, char, nil, s)
|
||||
screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, char, nil, s)
|
||||
vloc.X++
|
||||
}
|
||||
|
||||
@@ -312,21 +316,21 @@ func (w *BufWindow) drawLineNum(lineNumStyle tcell.Style, softwrapped bool, maxL
|
||||
|
||||
// Write the spaces before the line number if necessary
|
||||
for i := 0; i < maxLineNumLength-len(lineNum); i++ {
|
||||
screen.Screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, ' ', nil, lineNumStyle)
|
||||
screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, ' ', nil, lineNumStyle)
|
||||
vloc.X++
|
||||
}
|
||||
// Write the actual line number
|
||||
for _, ch := range lineNum {
|
||||
if softwrapped {
|
||||
screen.Screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, ' ', nil, lineNumStyle)
|
||||
screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, ' ', nil, lineNumStyle)
|
||||
} else {
|
||||
screen.Screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, ch, nil, lineNumStyle)
|
||||
screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, ch, nil, lineNumStyle)
|
||||
}
|
||||
vloc.X++
|
||||
}
|
||||
|
||||
// Write the extra space
|
||||
screen.Screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, ' ', nil, lineNumStyle)
|
||||
screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, ' ', nil, lineNumStyle)
|
||||
vloc.X++
|
||||
}
|
||||
|
||||
@@ -343,10 +347,9 @@ func (w *BufWindow) getStyle(style tcell.Style, bloc buffer.Loc, r rune) (tcell.
|
||||
func (w *BufWindow) showCursor(x, y int, main bool) {
|
||||
if w.active {
|
||||
if main {
|
||||
screen.Screen.ShowCursor(x, y)
|
||||
screen.ShowCursor(x, y)
|
||||
} else {
|
||||
r, _, _, _ := screen.Screen.GetContent(x, y)
|
||||
screen.Screen.SetContent(x, y, r, nil, config.DefStyle.Reverse(true))
|
||||
screen.ShowFakeCursorMulti(x, y)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -355,6 +358,10 @@ func (w *BufWindow) showCursor(x, y int, main bool) {
|
||||
func (w *BufWindow) displayBuffer() {
|
||||
b := w.Buf
|
||||
|
||||
if w.Height <= 0 || w.Width <= 0 {
|
||||
return
|
||||
}
|
||||
|
||||
hasMessage := len(b.Messages) > 0
|
||||
bufHeight := w.Height
|
||||
if w.drawStatus {
|
||||
@@ -367,17 +374,14 @@ func (w *BufWindow) displayBuffer() {
|
||||
}
|
||||
|
||||
if b.Settings["syntax"].(bool) && b.SyntaxDef != nil {
|
||||
for _, c := range b.GetCursors() {
|
||||
// rehighlight starting from where the cursor is
|
||||
start := c.Y
|
||||
if start > 0 && b.Rehighlight(start-1) {
|
||||
b.Highlighter.ReHighlightLine(b, start-1)
|
||||
b.SetRehighlight(start-1, false)
|
||||
for _, r := range b.Modifications {
|
||||
final := -1
|
||||
for i := r.X; i <= r.Y; i++ {
|
||||
final = util.Max(b.Highlighter.ReHighlightStates(b, i), final)
|
||||
}
|
||||
|
||||
b.Highlighter.ReHighlightStates(b, start)
|
||||
b.Highlighter.HighlightMatches(b, w.StartLine, w.StartLine+bufHeight)
|
||||
b.Highlighter.HighlightMatches(b, r.X, final+1)
|
||||
}
|
||||
b.ClearModifications()
|
||||
}
|
||||
|
||||
var matchingBraces []buffer.Loc
|
||||
@@ -412,7 +416,11 @@ func (w *BufWindow) displayBuffer() {
|
||||
}
|
||||
curNumStyle := config.DefStyle
|
||||
if style, ok := config.Colorscheme["current-line-number"]; ok {
|
||||
curNumStyle = style
|
||||
if !b.Settings["cursorline"].(bool) {
|
||||
curNumStyle = lineNumStyle
|
||||
} else {
|
||||
curNumStyle = style
|
||||
}
|
||||
}
|
||||
|
||||
// We need to know the string length of the largest line number
|
||||
@@ -517,7 +525,7 @@ func (w *BufWindow) displayBuffer() {
|
||||
}
|
||||
}
|
||||
|
||||
screen.Screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, r, nil, style)
|
||||
screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, r, nil, style)
|
||||
|
||||
if showcursor {
|
||||
for _, c := range cursors {
|
||||
@@ -572,7 +580,9 @@ func (w *BufWindow) displayBuffer() {
|
||||
}
|
||||
vloc.X = 0
|
||||
// This will draw an empty line number because the current line is wrapped
|
||||
w.drawLineNum(lineNumStyle, true, maxLineNumLength, &vloc, &bloc)
|
||||
if b.Settings["ruler"].(bool) {
|
||||
w.drawLineNum(lineNumStyle, true, maxLineNumLength, &vloc, &bloc)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -595,17 +605,13 @@ func (w *BufWindow) displayBuffer() {
|
||||
curStyle = style.Background(fg)
|
||||
}
|
||||
}
|
||||
screen.Screen.SetContent(i+w.X, vloc.Y+w.Y, ' ', nil, curStyle)
|
||||
screen.SetContent(i+w.X, vloc.Y+w.Y, ' ', nil, curStyle)
|
||||
}
|
||||
|
||||
for _, c := range cursors {
|
||||
if c.X == bloc.X && c.Y == bloc.Y && !c.HasSelection() {
|
||||
w.showCursor(w.X+vloc.X, w.Y+vloc.Y, c.Num == 0)
|
||||
}
|
||||
if vloc.X != bufWidth {
|
||||
draw(' ', curStyle, true)
|
||||
}
|
||||
|
||||
draw(' ', curStyle, false)
|
||||
|
||||
bloc.X = w.StartCol
|
||||
bloc.Y++
|
||||
if bloc.Y >= b.LinesNum() {
|
||||
@@ -627,7 +633,7 @@ func (w *BufWindow) displayStatusLine() {
|
||||
} else if w.Y+w.Height != infoY {
|
||||
w.drawStatus = true
|
||||
for x := w.X; x < w.X+w.Width; x++ {
|
||||
screen.Screen.SetContent(x, w.Y+w.Height-1, '-', nil, config.DefStyle.Reverse(true))
|
||||
screen.SetContent(x, w.Y+w.Height-1, '-', nil, config.DefStyle.Reverse(true))
|
||||
}
|
||||
} else {
|
||||
w.drawStatus = false
|
||||
@@ -647,7 +653,7 @@ func (w *BufWindow) displayScrollBar() {
|
||||
}
|
||||
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.Screen.SetContent(scrollX, y, '|', nil, config.DefStyle.Reverse(true))
|
||||
screen.SetContent(scrollX, y, '|', nil, config.DefStyle.Reverse(true))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,8 @@ import (
|
||||
type InfoWindow struct {
|
||||
*info.InfoBuf
|
||||
*View
|
||||
|
||||
hscroll int
|
||||
}
|
||||
|
||||
func (i *InfoWindow) errStyle() tcell.Style {
|
||||
@@ -74,7 +76,7 @@ func (i *InfoWindow) LocFromVisual(vloc buffer.Loc) buffer.Loc {
|
||||
|
||||
func (i *InfoWindow) Clear() {
|
||||
for x := 0; x < i.Width; x++ {
|
||||
screen.Screen.SetContent(x, i.Y, ' ', nil, i.defStyle())
|
||||
screen.SetContent(x, i.Y, ' ', nil, i.defStyle())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,7 +113,7 @@ func (i *InfoWindow) displayBuffer() {
|
||||
if j > 0 {
|
||||
c = ' '
|
||||
}
|
||||
screen.Screen.SetContent(vlocX, i.Y, c, nil, style)
|
||||
screen.SetContent(vlocX, i.Y, c, nil, style)
|
||||
}
|
||||
vlocX++
|
||||
}
|
||||
@@ -121,7 +123,7 @@ func (i *InfoWindow) displayBuffer() {
|
||||
totalwidth := blocX - nColsBeforeStart
|
||||
for len(line) > 0 {
|
||||
if activeC.X == blocX {
|
||||
screen.Screen.ShowCursor(vlocX, i.Y)
|
||||
screen.ShowCursor(vlocX, i.Y)
|
||||
}
|
||||
|
||||
r, size := utf8.DecodeRune(line)
|
||||
@@ -155,7 +157,7 @@ func (i *InfoWindow) displayBuffer() {
|
||||
}
|
||||
}
|
||||
if activeC.X == blocX {
|
||||
screen.Screen.ShowCursor(vlocX, i.Y)
|
||||
screen.ShowCursor(vlocX, i.Y)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,14 +169,44 @@ func (i *InfoWindow) displayKeyMenu() {
|
||||
for y := 0; y < len(keydisplay); y++ {
|
||||
for x := 0; x < i.Width; x++ {
|
||||
if x < len(keydisplay[y]) {
|
||||
screen.Screen.SetContent(x, i.Y-len(keydisplay)+y, rune(keydisplay[y][x]), nil, i.defStyle())
|
||||
screen.SetContent(x, i.Y-len(keydisplay)+y, rune(keydisplay[y][x]), nil, i.defStyle())
|
||||
} else {
|
||||
screen.Screen.SetContent(x, i.Y-len(keydisplay)+y, ' ', nil, i.defStyle())
|
||||
screen.SetContent(x, i.Y-len(keydisplay)+y, ' ', nil, i.defStyle())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (i *InfoWindow) totalSize() int {
|
||||
sum := 0
|
||||
for _, n := range i.Suggestions {
|
||||
sum += runewidth.StringWidth(n) + 1
|
||||
}
|
||||
return sum
|
||||
}
|
||||
|
||||
func (i *InfoWindow) scrollToSuggestion() {
|
||||
x := 0
|
||||
s := i.totalSize()
|
||||
|
||||
for j, n := range i.Suggestions {
|
||||
c := utf8.RuneCountInString(n)
|
||||
if j == i.CurSuggestion {
|
||||
if x+c >= i.hscroll+i.Width {
|
||||
i.hscroll = util.Clamp(x+c+1-i.Width, 0, s-i.Width)
|
||||
} else if x < i.hscroll {
|
||||
i.hscroll = util.Clamp(x-1, 0, s-i.Width)
|
||||
}
|
||||
break
|
||||
}
|
||||
x += c + 1
|
||||
}
|
||||
|
||||
if s-i.Width <= 0 {
|
||||
i.hscroll = 0
|
||||
}
|
||||
}
|
||||
|
||||
func (i *InfoWindow) Display() {
|
||||
x := 0
|
||||
if config.GetGlobalOption("keymenu").(bool) {
|
||||
@@ -194,7 +226,7 @@ func (i *InfoWindow) Display() {
|
||||
|
||||
display := i.Msg
|
||||
for _, c := range display {
|
||||
screen.Screen.SetContent(x, i.Y, c, nil, style)
|
||||
screen.SetContent(x, i.Y, c, nil, style)
|
||||
x += runewidth.RuneWidth(c)
|
||||
}
|
||||
|
||||
@@ -204,6 +236,11 @@ func (i *InfoWindow) Display() {
|
||||
}
|
||||
|
||||
if i.HasSuggestions && len(i.Suggestions) > 1 {
|
||||
i.scrollToSuggestion()
|
||||
|
||||
x := -i.hscroll
|
||||
done := false
|
||||
|
||||
statusLineStyle := config.DefStyle.Reverse(true)
|
||||
if style, ok := config.Colorscheme["statusline"]; ok {
|
||||
statusLineStyle = style
|
||||
@@ -212,29 +249,43 @@ func (i *InfoWindow) Display() {
|
||||
if config.GetGlobalOption("keymenu").(bool) {
|
||||
keymenuOffset = len(keydisplay)
|
||||
}
|
||||
x := 0
|
||||
|
||||
draw := func(r rune, s tcell.Style) {
|
||||
y := i.Y - keymenuOffset - 1
|
||||
rw := runewidth.RuneWidth(r)
|
||||
for j := 0; j < rw; j++ {
|
||||
c := r
|
||||
if j > 0 {
|
||||
c = ' '
|
||||
}
|
||||
|
||||
if x == i.Width-1 && !done {
|
||||
screen.SetContent(i.Width-1, y, '>', nil, s)
|
||||
x++
|
||||
break
|
||||
} else if x == 0 && i.hscroll > 0 {
|
||||
screen.SetContent(0, y, '<', nil, s)
|
||||
} else if x >= 0 && x < i.Width {
|
||||
screen.SetContent(x, y, c, nil, s)
|
||||
}
|
||||
x++
|
||||
}
|
||||
}
|
||||
|
||||
for j, s := range i.Suggestions {
|
||||
style := statusLineStyle
|
||||
if i.CurSuggestion == j {
|
||||
style = style.Reverse(true)
|
||||
}
|
||||
for _, r := range s {
|
||||
screen.Screen.SetContent(x, i.Y-keymenuOffset-1, r, nil, style)
|
||||
x++
|
||||
if x >= i.Width {
|
||||
return
|
||||
}
|
||||
}
|
||||
screen.Screen.SetContent(x, i.Y-keymenuOffset-1, ' ', nil, statusLineStyle)
|
||||
x++
|
||||
if x >= i.Width {
|
||||
return
|
||||
draw(r, style)
|
||||
// screen.SetContent(x, i.Y-keymenuOffset-1, r, nil, style)
|
||||
}
|
||||
draw(' ', statusLineStyle)
|
||||
}
|
||||
|
||||
for x < i.Width {
|
||||
screen.Screen.SetContent(x, i.Y-keymenuOffset-1, ' ', nil, statusLineStyle)
|
||||
x++
|
||||
draw(' ', statusLineStyle)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ var statusInfo = map[string]func(*buffer.Buffer) string{
|
||||
},
|
||||
}
|
||||
|
||||
func SetStatusInfoFnLua(s string, fn string) {
|
||||
func SetStatusInfoFnLua(fn string) {
|
||||
luaFn := strings.Split(fn, ".")
|
||||
if len(luaFn) <= 1 {
|
||||
return
|
||||
@@ -61,7 +61,7 @@ func SetStatusInfoFnLua(s string, fn string) {
|
||||
if pl == nil {
|
||||
return
|
||||
}
|
||||
statusInfo[s] = func(b *buffer.Buffer) string {
|
||||
statusInfo[fn] = func(b *buffer.Buffer) string {
|
||||
if pl == nil || !pl.IsEnabled() {
|
||||
return ""
|
||||
}
|
||||
@@ -118,13 +118,13 @@ func (s *StatusLine) Display() {
|
||||
style = style.Reverse(true)
|
||||
}
|
||||
for _, r := range sug {
|
||||
screen.Screen.SetContent(x, y-keymenuOffset, r, nil, style)
|
||||
screen.SetContent(x, y-keymenuOffset, r, nil, style)
|
||||
x++
|
||||
if x >= s.win.Width {
|
||||
return
|
||||
}
|
||||
}
|
||||
screen.Screen.SetContent(x, y-keymenuOffset, ' ', nil, statusLineStyle)
|
||||
screen.SetContent(x, y-keymenuOffset, ' ', nil, statusLineStyle)
|
||||
x++
|
||||
if x >= s.win.Width {
|
||||
return
|
||||
@@ -132,7 +132,7 @@ func (s *StatusLine) Display() {
|
||||
}
|
||||
|
||||
for x < s.win.Width {
|
||||
screen.Screen.SetContent(x, y-keymenuOffset, ' ', nil, statusLineStyle)
|
||||
screen.SetContent(x, y-keymenuOffset, ' ', nil, statusLineStyle)
|
||||
x++
|
||||
}
|
||||
return
|
||||
@@ -184,7 +184,7 @@ func (s *StatusLine) Display() {
|
||||
c = ' '
|
||||
x++
|
||||
}
|
||||
screen.Screen.SetContent(winX+x, y, c, nil, statusLineStyle)
|
||||
screen.SetContent(winX+x, y, c, nil, statusLineStyle)
|
||||
}
|
||||
} else if x >= s.win.Width-rightLen && x < rightLen+s.win.Width-rightLen {
|
||||
r, size := utf8.DecodeRune(rightText)
|
||||
@@ -196,10 +196,10 @@ func (s *StatusLine) Display() {
|
||||
c = ' '
|
||||
x++
|
||||
}
|
||||
screen.Screen.SetContent(winX+x, y, c, nil, statusLineStyle)
|
||||
screen.SetContent(winX+x, y, c, nil, statusLineStyle)
|
||||
}
|
||||
} else {
|
||||
screen.Screen.SetContent(winX+x, y, ' ', nil, statusLineStyle)
|
||||
screen.SetContent(winX+x, y, ' ', nil, statusLineStyle)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,10 @@ func NewTabWindow(w int, y int) *TabWindow {
|
||||
return tw
|
||||
}
|
||||
|
||||
func (w *TabWindow) Resize(width, height int) {
|
||||
w.width = width
|
||||
}
|
||||
|
||||
func (w *TabWindow) LocFromVisual(vloc buffer.Loc) int {
|
||||
x := -w.hscroll
|
||||
|
||||
@@ -45,13 +49,18 @@ func (w *TabWindow) LocFromVisual(vloc buffer.Loc) int {
|
||||
|
||||
func (w *TabWindow) Scroll(amt int) {
|
||||
w.hscroll += amt
|
||||
w.hscroll = util.Clamp(w.hscroll, 0, w.TotalSize()-w.width)
|
||||
s := w.TotalSize()
|
||||
w.hscroll = util.Clamp(w.hscroll, 0, s-w.width)
|
||||
|
||||
if s-w.width <= 0 {
|
||||
w.hscroll = 0
|
||||
}
|
||||
}
|
||||
|
||||
func (w *TabWindow) TotalSize() int {
|
||||
sum := 2
|
||||
for _, n := range w.Names {
|
||||
sum += utf8.RuneCountInString(n) + 4
|
||||
sum += runewidth.StringWidth(n) + 4
|
||||
}
|
||||
return sum - 4
|
||||
}
|
||||
@@ -64,6 +73,7 @@ func (w *TabWindow) SetActive(a int) {
|
||||
w.active = a
|
||||
x := 2
|
||||
s := w.TotalSize()
|
||||
|
||||
for i, n := range w.Names {
|
||||
c := utf8.RuneCountInString(n)
|
||||
if i == a {
|
||||
@@ -76,6 +86,10 @@ func (w *TabWindow) SetActive(a int) {
|
||||
}
|
||||
x += c + 4
|
||||
}
|
||||
|
||||
if s-w.width <= 0 {
|
||||
w.hscroll = 0
|
||||
}
|
||||
}
|
||||
|
||||
func (w *TabWindow) Display() {
|
||||
@@ -91,13 +105,13 @@ func (w *TabWindow) Display() {
|
||||
c = ' '
|
||||
}
|
||||
if x == w.width-1 && !done {
|
||||
screen.Screen.SetContent(w.width-1, w.Y, '>', nil, config.DefStyle.Reverse(true))
|
||||
screen.SetContent(w.width-1, w.Y, '>', nil, config.DefStyle.Reverse(true))
|
||||
x++
|
||||
break
|
||||
} else if x == 0 && w.hscroll > 0 {
|
||||
screen.Screen.SetContent(0, w.Y, '<', nil, config.DefStyle.Reverse(true))
|
||||
screen.SetContent(0, w.Y, '<', nil, config.DefStyle.Reverse(true))
|
||||
} else if x >= 0 && x < w.width {
|
||||
screen.Screen.SetContent(x, w.Y, c, nil, config.DefStyle.Reverse(true))
|
||||
screen.SetContent(x, w.Y, c, nil, config.DefStyle.Reverse(true))
|
||||
}
|
||||
x++
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ func (w *TermWindow) LocFromVisual(vloc buffer.Loc) buffer.Loc {
|
||||
func (w *TermWindow) Clear() {
|
||||
for y := 0; y < w.Height; y++ {
|
||||
for x := 0; x < w.Width; x++ {
|
||||
screen.Screen.SetContent(w.X+x, w.Y+y, ' ', nil, config.DefStyle)
|
||||
screen.SetContent(w.X+x, w.Y+y, ' ', nil, config.DefStyle)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -88,7 +88,7 @@ func (w *TermWindow) Display() {
|
||||
st = st.Reverse(true)
|
||||
}
|
||||
|
||||
screen.Screen.SetContent(w.X+x, w.Y+y, c, nil, st)
|
||||
screen.SetContent(w.X+x, w.Y+y, c, nil, st)
|
||||
}
|
||||
}
|
||||
if config.GetGlobalOption("statusline").(bool) {
|
||||
@@ -103,14 +103,14 @@ func (w *TermWindow) Display() {
|
||||
if x < textLen {
|
||||
r, size := utf8.DecodeRune(text)
|
||||
text = text[size:]
|
||||
screen.Screen.SetContent(w.X+x, w.Y+w.Height, r, nil, statusLineStyle)
|
||||
screen.SetContent(w.X+x, w.Y+w.Height, r, nil, statusLineStyle)
|
||||
} else {
|
||||
screen.Screen.SetContent(w.X+x, w.Y+w.Height, ' ', nil, statusLineStyle)
|
||||
screen.SetContent(w.X+x, w.Y+w.Height, ' ', nil, statusLineStyle)
|
||||
}
|
||||
}
|
||||
}
|
||||
if w.State.CursorVisible() && w.active {
|
||||
curx, cury := w.State.Cursor()
|
||||
screen.Screen.ShowCursor(curx+w.X, cury+w.Y)
|
||||
screen.ShowCursor(curx+w.X, cury+w.Y)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ func (w *UIWindow) drawNode(n *views.Node) {
|
||||
if c.IsLeaf() && c.Kind == views.STVert {
|
||||
if i != len(cs)-1 {
|
||||
for h := 0; h < c.H; h++ {
|
||||
screen.Screen.SetContent(c.X+c.W, c.Y+h, '|', nil, dividerStyle.Reverse(true))
|
||||
screen.SetContent(c.X+c.W, c.Y+h, '|', nil, dividerStyle.Reverse(true))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"sync"
|
||||
|
||||
"github.com/zyedidia/micro/internal/config"
|
||||
"github.com/zyedidia/micro/pkg/terminfo"
|
||||
"github.com/zyedidia/micro/internal/util"
|
||||
"github.com/zyedidia/tcell"
|
||||
)
|
||||
|
||||
@@ -17,21 +17,85 @@ import (
|
||||
// screen. TODO: maybe we should worry about polling and drawing at the
|
||||
// same time too.
|
||||
var Screen tcell.Screen
|
||||
|
||||
// The lock is necessary since the screen is polled on a separate thread
|
||||
var lock sync.Mutex
|
||||
|
||||
// DrawChan is a channel that will cause the screen to redraw when
|
||||
// written to even if no event user event has occurred
|
||||
var DrawChan chan bool
|
||||
|
||||
// Lock locks the screen lock
|
||||
func Lock() {
|
||||
lock.Lock()
|
||||
}
|
||||
|
||||
// Unlock unlocks the screen lock
|
||||
func Unlock() {
|
||||
lock.Unlock()
|
||||
}
|
||||
|
||||
// Redraw schedules a redraw with the draw channel
|
||||
func Redraw() {
|
||||
DrawChan <- true
|
||||
}
|
||||
|
||||
type screenCell struct {
|
||||
x, y int
|
||||
r rune
|
||||
combc []rune
|
||||
style tcell.Style
|
||||
}
|
||||
|
||||
var lastCursor screenCell
|
||||
|
||||
// ShowFakeCursor displays a cursor at the given position by modifying the
|
||||
// style of the given column instead of actually using the terminal cursor
|
||||
// This can be useful in certain terminals such as the windows console where
|
||||
// modifying the cursor location is slow and frequent modifications cause flashing
|
||||
// This keeps track of the most recent fake cursor location and resets it when
|
||||
// a new fake cursor location is specified
|
||||
func ShowFakeCursor(x, y int) {
|
||||
r, combc, style, _ := Screen.GetContent(x, y)
|
||||
Screen.SetContent(lastCursor.x, lastCursor.y, lastCursor.r, lastCursor.combc, lastCursor.style)
|
||||
Screen.SetContent(x, y, r, combc, config.DefStyle.Reverse(true))
|
||||
|
||||
lastCursor.x, lastCursor.y = x, y
|
||||
lastCursor.r = r
|
||||
lastCursor.combc = combc
|
||||
lastCursor.style = style
|
||||
}
|
||||
|
||||
// ShowFakeCursorMulti is the same as ShowFakeCursor except it does not
|
||||
// reset previous locations of the cursor
|
||||
// Fake cursors are also necessary to display multiple cursors
|
||||
func ShowFakeCursorMulti(x, y int) {
|
||||
r, _, _, _ := Screen.GetContent(x, y)
|
||||
Screen.SetContent(x, y, r, nil, config.DefStyle.Reverse(true))
|
||||
}
|
||||
|
||||
// ShowCursor puts the cursor at the given location using a fake cursor
|
||||
// if enabled or using the terminal cursor otherwise
|
||||
// By default only the windows console will use a fake cursor
|
||||
func ShowCursor(x, y int) {
|
||||
if util.FakeCursor {
|
||||
ShowFakeCursor(x, y)
|
||||
} else {
|
||||
Screen.ShowCursor(x, y)
|
||||
}
|
||||
}
|
||||
|
||||
// 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) {
|
||||
Screen.SetContent(x, y, mainc, combc, style)
|
||||
if util.FakeCursor && lastCursor.x == x && lastCursor.y == y {
|
||||
lastCursor.r = mainc
|
||||
lastCursor.style = style
|
||||
lastCursor.combc = combc
|
||||
}
|
||||
}
|
||||
|
||||
// TempFini shuts the screen down temporarily
|
||||
func TempFini() bool {
|
||||
screenWasNil := Screen == nil
|
||||
@@ -59,52 +123,24 @@ func Init() {
|
||||
// Should we enable true color?
|
||||
truecolor := os.Getenv("MICRO_TRUECOLOR") == "1"
|
||||
|
||||
tcelldb := os.Getenv("TCELLDB")
|
||||
os.Setenv("TCELLDB", config.ConfigDir+"/.tcelldb")
|
||||
|
||||
// In order to enable true color, we have to set the TERM to `xterm-truecolor` when
|
||||
// initializing tcell, but after that, we can set the TERM back to whatever it was
|
||||
oldTerm := os.Getenv("TERM")
|
||||
if truecolor {
|
||||
os.Setenv("TERM", "xterm-truecolor")
|
||||
if !truecolor {
|
||||
os.Setenv("TCELL_TRUECOLOR", "disable")
|
||||
}
|
||||
|
||||
// Initilize tcell
|
||||
var err error
|
||||
Screen, err = tcell.NewScreen()
|
||||
if err != nil {
|
||||
if err == tcell.ErrTermNotFound {
|
||||
err = terminfo.WriteDB(config.ConfigDir + "/.tcelldb")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
fmt.Println("Fatal: Micro could not create terminal database file", config.ConfigDir+"/.tcelldb")
|
||||
os.Exit(1)
|
||||
}
|
||||
Screen, err = tcell.NewScreen()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
fmt.Println("Fatal: Micro could not initialize a Screen.")
|
||||
os.Exit(1)
|
||||
}
|
||||
} else {
|
||||
fmt.Println(err)
|
||||
fmt.Println("Fatal: Micro could not initialize a Screen.")
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Println(err)
|
||||
fmt.Println("Fatal: Micro could not initialize a Screen.")
|
||||
os.Exit(1)
|
||||
}
|
||||
if err = Screen.Init(); err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Now we can put the TERM back to what it was before
|
||||
if truecolor {
|
||||
os.Setenv("TERM", oldTerm)
|
||||
}
|
||||
|
||||
if config.GetGlobalOption("mouse").(bool) {
|
||||
Screen.EnableMouse()
|
||||
}
|
||||
|
||||
os.Setenv("TCELLDB", tcelldb)
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package shell
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
@@ -9,8 +10,8 @@ import (
|
||||
"os/signal"
|
||||
"strings"
|
||||
|
||||
shellquote "github.com/kballard/go-shellquote"
|
||||
"github.com/zyedidia/micro/internal/screen"
|
||||
"github.com/zyedidia/micro/pkg/shellwords"
|
||||
)
|
||||
|
||||
// ExecCommand executes a command using exec
|
||||
@@ -32,10 +33,13 @@ func ExecCommand(name string, arg ...string) (string, error) {
|
||||
|
||||
// RunCommand executes a shell command and returns the output/error
|
||||
func RunCommand(input string) (string, error) {
|
||||
args, err := shellwords.Split(input)
|
||||
args, err := shellquote.Split(input)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(args) == 0 {
|
||||
return "", errors.New("No arguments")
|
||||
}
|
||||
inputCmd := args[0]
|
||||
|
||||
return ExecCommand(inputCmd, args[1:]...)
|
||||
@@ -45,10 +49,13 @@ func RunCommand(input string) (string, error) {
|
||||
// It returns a function which will run the command and returns a string
|
||||
// message result
|
||||
func RunBackgroundShell(input string) (func() string, error) {
|
||||
args, err := shellwords.Split(input)
|
||||
args, err := shellquote.Split(input)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(args) == 0 {
|
||||
return nil, errors.New("No arguments")
|
||||
}
|
||||
inputCmd := args[0]
|
||||
return func() string {
|
||||
output, err := RunCommand(input)
|
||||
@@ -68,10 +75,13 @@ func RunBackgroundShell(input string) (func() string, error) {
|
||||
|
||||
// RunInteractiveShell runs a shellcommand interactively
|
||||
func RunInteractiveShell(input string, wait bool, getOutput bool) (string, error) {
|
||||
args, err := shellwords.Split(input)
|
||||
args, err := shellquote.Split(input)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(args) == 0 {
|
||||
return "", errors.New("No arguments")
|
||||
}
|
||||
inputCmd := args[0]
|
||||
|
||||
// Shut down the screen because we're going to interact directly with the shell
|
||||
|
||||
@@ -2,8 +2,6 @@ package shell
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -115,7 +113,8 @@ func (t *Terminal) Start(execCmd []string, getOutput bool, wait bool, callback s
|
||||
for {
|
||||
err := Term.Parse()
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "[Press enter to close]")
|
||||
Term.Write([]byte("Press enter to close"))
|
||||
screen.Redraw()
|
||||
break
|
||||
}
|
||||
screen.Redraw()
|
||||
|
||||
@@ -16,14 +16,12 @@ func GetMemStats() string {
|
||||
return fmt.Sprintf("Alloc: %s, Sys: %s, GC: %d, PauseTotalNs: %dns", humanize.Bytes(memstats.Alloc), humanize.Bytes(memstats.Sys), memstats.NumGC, memstats.PauseTotalNs)
|
||||
}
|
||||
|
||||
var start time.Time
|
||||
|
||||
func Tic(s string) {
|
||||
func Tic(s string) time.Time {
|
||||
log.Println("START:", s)
|
||||
start = time.Now()
|
||||
return time.Now()
|
||||
}
|
||||
|
||||
func Toc() {
|
||||
func Toc(start time.Time) {
|
||||
end := time.Now()
|
||||
log.Println("END: ElapsedTime in seconds:", end.Sub(start))
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -30,6 +31,9 @@ var (
|
||||
CompileDate = "Unknown"
|
||||
// Debug logging
|
||||
Debug = "ON"
|
||||
// FakeCursor is used to disable the terminal cursor and have micro
|
||||
// draw its own (enabled for windows consoles where the cursor is slow)
|
||||
FakeCursor = false
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -38,6 +42,10 @@ func init() {
|
||||
if err != nil {
|
||||
fmt.Println("Invalid version: ", Version, err)
|
||||
}
|
||||
|
||||
if runtime.GOOS == "windows" {
|
||||
FakeCursor = true
|
||||
}
|
||||
}
|
||||
|
||||
// SliceEnd returns a byte slice where the index is a rune index
|
||||
@@ -267,7 +275,6 @@ func MakeRelative(path, base string) (string, error) {
|
||||
return path, nil
|
||||
}
|
||||
|
||||
// TODO: consider changing because of snap segfault
|
||||
// ReplaceHome takes a path as input and replaces ~ at the start of the path with the user's
|
||||
// home directory. Does nothing if the path does not start with '~'.
|
||||
func ReplaceHome(path string) (string, error) {
|
||||
@@ -410,3 +417,7 @@ func IsNonAlphaNumeric(c rune) bool {
|
||||
func ParseSpecial(s string) string {
|
||||
return strings.Replace(s, "\\t", "\t", -1)
|
||||
}
|
||||
|
||||
func String(s []byte) string {
|
||||
return string(s)
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import "regexp"
|
||||
// to determine the filetype of the file
|
||||
// It will return the corresponding syntax definition for the filetype
|
||||
func MatchFiletype(ftdetect [2]*regexp.Regexp, filename string, firstLine []byte) bool {
|
||||
if ftdetect[0].MatchString(filename) {
|
||||
if ftdetect[0] != nil && ftdetect[0].MatchString(filename) {
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
@@ -68,6 +68,8 @@ func combineLineMatch(src, dst LineMatch) LineMatch {
|
||||
// A State represents the region at the end of a line
|
||||
type State *region
|
||||
|
||||
var EmptyDef = Def{nil, &rules{}}
|
||||
|
||||
// LineStates is an interface for a buffer-like object which can also store the states and matches for every line
|
||||
type LineStates interface {
|
||||
LineBytes(n int) []byte
|
||||
@@ -176,7 +178,7 @@ func (h *Highlighter) highlightRegion(highlights LineMatch, start int, canMatchE
|
||||
return highlights
|
||||
}
|
||||
|
||||
if lineLen == 0 || statesOnly {
|
||||
if lineLen == 0 {
|
||||
if canMatchEnd {
|
||||
h.lastRegion = curRegion
|
||||
}
|
||||
@@ -197,28 +199,32 @@ func (h *Highlighter) highlightRegion(highlights LineMatch, start int, canMatchE
|
||||
}
|
||||
}
|
||||
if firstLoc[0] != lineLen {
|
||||
highlights[start+firstLoc[0]] = firstRegion.limitGroup
|
||||
if !statesOnly {
|
||||
highlights[start+firstLoc[0]] = firstRegion.limitGroup
|
||||
}
|
||||
h.highlightRegion(highlights, start, false, lineNum, sliceEnd(line, firstLoc[0]), curRegion, statesOnly)
|
||||
h.highlightRegion(highlights, start+firstLoc[1], canMatchEnd, lineNum, sliceStart(line, firstLoc[1]), firstRegion, statesOnly)
|
||||
return highlights
|
||||
}
|
||||
|
||||
fullHighlights := make([]Group, lineLen)
|
||||
for i := 0; i < len(fullHighlights); i++ {
|
||||
fullHighlights[i] = curRegion.group
|
||||
}
|
||||
if !statesOnly {
|
||||
fullHighlights := make([]Group, lineLen)
|
||||
for i := 0; i < len(fullHighlights); i++ {
|
||||
fullHighlights[i] = curRegion.group
|
||||
}
|
||||
|
||||
for _, p := range curRegion.rules.patterns {
|
||||
matches := findAllIndex(p.regex, line, start == 0, canMatchEnd)
|
||||
for _, m := range matches {
|
||||
for i := m[0]; i < m[1]; i++ {
|
||||
fullHighlights[i] = p.group
|
||||
for _, p := range curRegion.rules.patterns {
|
||||
matches := findAllIndex(p.regex, line, start == 0, canMatchEnd)
|
||||
for _, m := range matches {
|
||||
for i := m[0]; i < m[1]; i++ {
|
||||
fullHighlights[i] = p.group
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for i, h := range fullHighlights {
|
||||
if i == 0 || h != fullHighlights[i-1] {
|
||||
highlights[start+i] = h
|
||||
for i, h := range fullHighlights {
|
||||
if i == 0 || h != fullHighlights[i-1] {
|
||||
highlights[start+i] = h
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -354,8 +360,9 @@ func (h *Highlighter) HighlightMatches(input LineStates, startline, endline int)
|
||||
}
|
||||
|
||||
// ReHighlightStates will scan down from `startline` and set the appropriate end of line state
|
||||
// for each line until it comes across the same state in two consecutive lines
|
||||
func (h *Highlighter) ReHighlightStates(input LineStates, startline int) {
|
||||
// for each line until it comes across a line whose state does not change
|
||||
// returns the number of the final line
|
||||
func (h *Highlighter) ReHighlightStates(input LineStates, startline int) int {
|
||||
// lines := input.LineData()
|
||||
|
||||
h.lastRegion = nil
|
||||
@@ -378,9 +385,11 @@ func (h *Highlighter) ReHighlightStates(input LineStates, startline int) {
|
||||
input.SetState(i, curState)
|
||||
|
||||
if curState == lastState {
|
||||
break
|
||||
return i
|
||||
}
|
||||
}
|
||||
|
||||
return input.LinesNum() - 1
|
||||
}
|
||||
|
||||
// ReHighlightLine will rehighlight the state and match for a single line
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package highlight
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
@@ -41,6 +42,14 @@ type Header struct {
|
||||
FtDetect [2]*regexp.Regexp
|
||||
}
|
||||
|
||||
type HeaderYaml struct {
|
||||
FileType string `yaml:"filetype"`
|
||||
Detect struct {
|
||||
FNameRgx string `yaml:"filename"`
|
||||
HeaderRgx string `yaml:"header"`
|
||||
} `yaml:"detect"`
|
||||
}
|
||||
|
||||
type File struct {
|
||||
FileType string
|
||||
|
||||
@@ -82,52 +91,59 @@ func init() {
|
||||
Groups = make(map[string]Group)
|
||||
}
|
||||
|
||||
func ParseFtDetect(file *File) (r [2]*regexp.Regexp, err error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
var ok bool
|
||||
err, ok = r.(error)
|
||||
if !ok {
|
||||
err = fmt.Errorf("pkg: %v", r)
|
||||
}
|
||||
}
|
||||
}()
|
||||
// MakeHeader takes a header (.hdr file) file and parses the header
|
||||
// Header files make parsing more efficient when you only want to compute
|
||||
// on the headers of syntax files
|
||||
// A yaml file might take ~400us to parse while a header file only takes ~20us
|
||||
func MakeHeader(data []byte) (*Header, error) {
|
||||
lines := bytes.Split(data, []byte{'\n'})
|
||||
if len(lines) < 3 {
|
||||
return nil, errors.New("Header file has incorrect format")
|
||||
}
|
||||
header := new(Header)
|
||||
var err error
|
||||
header.FileType = string(lines[0])
|
||||
fnameRgx := string(lines[1])
|
||||
headerRgx := string(lines[2])
|
||||
|
||||
rules := file.yamlSrc
|
||||
|
||||
loaded := 0
|
||||
for k, v := range rules {
|
||||
if k == "detect" {
|
||||
ftdetect := v.(map[interface{}]interface{})
|
||||
if len(ftdetect) >= 1 {
|
||||
syntax, err := regexp.Compile(ftdetect["filename"].(string))
|
||||
if err != nil {
|
||||
return r, err
|
||||
}
|
||||
|
||||
r[0] = syntax
|
||||
}
|
||||
if len(ftdetect) >= 2 {
|
||||
header, err := regexp.Compile(ftdetect["header"].(string))
|
||||
if err != nil {
|
||||
return r, err
|
||||
}
|
||||
|
||||
r[1] = header
|
||||
}
|
||||
loaded++
|
||||
}
|
||||
|
||||
if loaded >= 2 {
|
||||
break
|
||||
}
|
||||
if fnameRgx != "" {
|
||||
header.FtDetect[0], err = regexp.Compile(fnameRgx)
|
||||
}
|
||||
if headerRgx != "" {
|
||||
header.FtDetect[1], err = regexp.Compile(headerRgx)
|
||||
}
|
||||
|
||||
if loaded == 0 {
|
||||
return r, errors.New("No detect regexes found")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return r, err
|
||||
return header, nil
|
||||
}
|
||||
|
||||
// MakeHeaderYaml takes a yaml spec for a syntax file and parses the
|
||||
// header
|
||||
func MakeHeaderYaml(data []byte) (*Header, error) {
|
||||
var hdrYaml HeaderYaml
|
||||
err := yaml.Unmarshal(data, &hdrYaml)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
header := new(Header)
|
||||
header.FileType = hdrYaml.FileType
|
||||
|
||||
if hdrYaml.Detect.FNameRgx != "" {
|
||||
header.FtDetect[0], err = regexp.Compile(hdrYaml.Detect.FNameRgx)
|
||||
}
|
||||
if hdrYaml.Detect.HeaderRgx != "" {
|
||||
header.FtDetect[1], err = regexp.Compile(hdrYaml.Detect.HeaderRgx)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return header, nil
|
||||
}
|
||||
|
||||
func ParseFile(input []byte) (f *File, err error) {
|
||||
@@ -196,6 +212,40 @@ func ParseDef(f *File, header *Header) (s *Def, err error) {
|
||||
return s, err
|
||||
}
|
||||
|
||||
// HasIncludes returns whether this syntax def has any include statements
|
||||
func HasIncludes(d *Def) bool {
|
||||
hasIncludes := len(d.rules.includes) > 0
|
||||
for _, r := range d.rules.regions {
|
||||
hasIncludes = hasIncludes || hasIncludesInRegion(r)
|
||||
}
|
||||
return hasIncludes
|
||||
}
|
||||
|
||||
func hasIncludesInRegion(region *region) bool {
|
||||
hasIncludes := len(region.rules.includes) > 0
|
||||
for _, r := range region.rules.regions {
|
||||
hasIncludes = hasIncludes || hasIncludesInRegion(r)
|
||||
}
|
||||
return hasIncludes
|
||||
}
|
||||
|
||||
// GetIncludes returns a list of filetypes that are included by this syntax def
|
||||
func GetIncludes(d *Def) []string {
|
||||
includes := d.rules.includes
|
||||
for _, r := range d.rules.regions {
|
||||
includes = append(includes, getIncludesInRegion(r)...)
|
||||
}
|
||||
return includes
|
||||
}
|
||||
|
||||
func getIncludesInRegion(region *region) []string {
|
||||
includes := region.rules.includes
|
||||
for _, r := range region.rules.regions {
|
||||
includes = append(includes, getIncludesInRegion(r)...)
|
||||
}
|
||||
return includes
|
||||
}
|
||||
|
||||
// ResolveIncludes will sort out the rules for including other filetypes
|
||||
// You should call this after parsing all the Defs
|
||||
func ResolveIncludes(def *Def, files []*File) {
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2017 Yasuhiro Matsumoto
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -1,49 +0,0 @@
|
||||
This is a modified version of `go-shellwords` for the micro editor.
|
||||
|
||||
# go-shellwords
|
||||
|
||||
[](https://coveralls.io/r/mattn/go-shellwords?branch=master)
|
||||
[](https://travis-ci.org/mattn/go-shellwords)
|
||||
|
||||
Parse line as shell words.
|
||||
|
||||
## Usage
|
||||
|
||||
```go
|
||||
args, err := shellwords.Parse("./foo --bar=baz")
|
||||
// args should be ["./foo", "--bar=baz"]
|
||||
```
|
||||
|
||||
```go
|
||||
os.Setenv("FOO", "bar")
|
||||
p := shellwords.NewParser()
|
||||
p.ParseEnv = true
|
||||
args, err := p.Parse("./foo $FOO")
|
||||
// args should be ["./foo", "bar"]
|
||||
```
|
||||
|
||||
```go
|
||||
p := shellwords.NewParser()
|
||||
p.ParseBacktick = true
|
||||
args, err := p.Parse("./foo `echo $SHELL`")
|
||||
// args should be ["./foo", "/bin/bash"]
|
||||
```
|
||||
|
||||
```go
|
||||
shellwords.ParseBacktick = true
|
||||
p := shellwords.NewParser()
|
||||
args, err := p.Parse("./foo `echo $SHELL`")
|
||||
// args should be ["./foo", "/bin/bash"]
|
||||
```
|
||||
|
||||
# Thanks
|
||||
|
||||
This is based on cpan module [Parse::CommandLine](https://metacpan.org/pod/Parse::CommandLine).
|
||||
|
||||
# License
|
||||
|
||||
under the MIT License: http://mattn.mit-license.org/2017
|
||||
|
||||
# Author
|
||||
|
||||
Yasuhiro Matsumoto (a.k.a mattn)
|
||||
@@ -1,180 +0,0 @@
|
||||
package shellwords
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"os"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
var envRe = regexp.MustCompile(`\$({[a-zA-Z0-9_]+}|[a-zA-Z0-9_]+)`)
|
||||
|
||||
func isSpace(r rune) bool {
|
||||
switch r {
|
||||
case ' ', '\t', '\r', '\n':
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func replaceEnv(s string) string {
|
||||
return envRe.ReplaceAllStringFunc(s, func(s string) string {
|
||||
s = s[1:]
|
||||
if s[0] == '{' {
|
||||
s = s[1 : len(s)-1]
|
||||
}
|
||||
return os.Getenv(s)
|
||||
})
|
||||
}
|
||||
|
||||
type Parser struct {
|
||||
Position int
|
||||
}
|
||||
|
||||
func NewParser() *Parser {
|
||||
return &Parser{0}
|
||||
}
|
||||
|
||||
func (p *Parser) Parse(line string) ([]string, error) {
|
||||
args := []string{}
|
||||
buf := ""
|
||||
var escaped, doubleQuoted, singleQuoted, backQuote, dollarQuote bool
|
||||
backtick := ""
|
||||
|
||||
pos := -1
|
||||
got := false
|
||||
|
||||
loop:
|
||||
for i, r := range line {
|
||||
if escaped {
|
||||
buf += string(r)
|
||||
escaped = false
|
||||
continue
|
||||
}
|
||||
|
||||
if r == '\\' {
|
||||
if singleQuoted {
|
||||
buf += string(r)
|
||||
} else {
|
||||
escaped = true
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if isSpace(r) {
|
||||
if singleQuoted || doubleQuoted || backQuote || dollarQuote {
|
||||
buf += string(r)
|
||||
backtick += string(r)
|
||||
} else if got {
|
||||
buf = replaceEnv(buf)
|
||||
args = append(args, buf)
|
||||
buf = ""
|
||||
got = false
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
switch r {
|
||||
case '`':
|
||||
if !singleQuoted && !doubleQuoted && !dollarQuote {
|
||||
if backQuote {
|
||||
out, err := shellRun(backtick)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
buf = out
|
||||
}
|
||||
backtick = ""
|
||||
backQuote = !backQuote
|
||||
continue
|
||||
backtick = ""
|
||||
backQuote = !backQuote
|
||||
}
|
||||
case ')':
|
||||
if !singleQuoted && !doubleQuoted && !backQuote {
|
||||
if dollarQuote {
|
||||
out, err := shellRun(backtick)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
buf = out
|
||||
}
|
||||
backtick = ""
|
||||
dollarQuote = !dollarQuote
|
||||
continue
|
||||
backtick = ""
|
||||
dollarQuote = !dollarQuote
|
||||
}
|
||||
case '(':
|
||||
if !singleQuoted && !doubleQuoted && !backQuote {
|
||||
if !dollarQuote && len(buf) > 0 && buf == "$" {
|
||||
dollarQuote = true
|
||||
buf += "("
|
||||
continue
|
||||
} else {
|
||||
return nil, errors.New("invalid command line string")
|
||||
}
|
||||
}
|
||||
case '"':
|
||||
if !singleQuoted && !dollarQuote {
|
||||
doubleQuoted = !doubleQuoted
|
||||
continue
|
||||
}
|
||||
case '\'':
|
||||
if !doubleQuoted && !dollarQuote {
|
||||
singleQuoted = !singleQuoted
|
||||
continue
|
||||
}
|
||||
case ';', '&', '|', '<', '>':
|
||||
if !(escaped || singleQuoted || doubleQuoted || backQuote) {
|
||||
pos = i
|
||||
break loop
|
||||
}
|
||||
}
|
||||
|
||||
got = true
|
||||
buf += string(r)
|
||||
if backQuote || dollarQuote {
|
||||
backtick += string(r)
|
||||
}
|
||||
}
|
||||
|
||||
buf = replaceEnv(buf)
|
||||
args = append(args, buf)
|
||||
|
||||
if escaped || singleQuoted || doubleQuoted || backQuote || dollarQuote {
|
||||
return nil, errors.New("invalid command line string")
|
||||
}
|
||||
|
||||
p.Position = pos
|
||||
|
||||
return args, nil
|
||||
}
|
||||
|
||||
func Split(line string) ([]string, error) {
|
||||
return NewParser().Parse(line)
|
||||
}
|
||||
|
||||
func Join(args ...string) string {
|
||||
var buf bytes.Buffer
|
||||
for i, w := range args {
|
||||
if i != 0 {
|
||||
buf.WriteByte(' ')
|
||||
}
|
||||
if w == "" {
|
||||
buf.WriteString("''")
|
||||
continue
|
||||
}
|
||||
|
||||
for _, b := range w {
|
||||
switch b {
|
||||
case ' ', '\t', '\r', '\n':
|
||||
buf.WriteByte('\\')
|
||||
buf.WriteString(string(b))
|
||||
default:
|
||||
buf.WriteString(string(b))
|
||||
}
|
||||
}
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
// +build !windows
|
||||
|
||||
package shellwords
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func shellRun(line string) (string, error) {
|
||||
shell := os.Getenv("SHELL")
|
||||
b, err := exec.Command(shell, "-c", line).Output()
|
||||
if err != nil {
|
||||
if eerr, ok := err.(*exec.ExitError); ok {
|
||||
b = eerr.Stderr
|
||||
}
|
||||
return "", errors.New(err.Error() + ":" + string(b))
|
||||
}
|
||||
return strings.TrimSpace(string(b)), nil
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
package shellwords
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func shellRun(line string) (string, error) {
|
||||
shell := os.Getenv("COMSPEC")
|
||||
b, err := exec.Command(shell, "/c", line).Output()
|
||||
if err != nil {
|
||||
if eerr, ok := err.(*exec.ExitError); ok {
|
||||
b = eerr.Stderr
|
||||
}
|
||||
return "", errors.New(err.Error() + ":" + string(b))
|
||||
}
|
||||
return strings.TrimSpace(string(b)), nil
|
||||
}
|
||||
@@ -1,203 +0,0 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
# Terminfo parser
|
||||
|
||||
This terminfo parser was written by the authors of [tcell](https://github.com/gdamore/tcell). We are using it here
|
||||
to compile the terminal database if the terminal entry is not found in set of precompiled terminals.
|
||||
|
||||
The source for `mkinfo.go` is adapted from tcell's `mkinfo` tool to be more of a library.
|
||||
@@ -1,518 +0,0 @@
|
||||
// Copyright 2017 The TCell Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use file except in compliance with the License.
|
||||
// You may obtain a copy of the license at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// This command is used to generate suitable configuration files in either
|
||||
// go syntax or in JSON. It defaults to JSON output on stdout. If no
|
||||
// term values are specified on the command line, then $TERM is used.
|
||||
//
|
||||
// Usage is like this:
|
||||
//
|
||||
// mkinfo [-init] [-go file.go] [-json file.json] [-quiet] [-nofatal] [<term>...]
|
||||
//
|
||||
// -gzip specifies output should be compressed (json only)
|
||||
// -go specifies Go output into the named file. Use - for stdout.
|
||||
// -json specifies JSON output in the named file. Use - for stdout
|
||||
// -nofatal indicates that errors loading definitions should not be fatal
|
||||
//
|
||||
|
||||
package terminfo
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type termcap struct {
|
||||
name string
|
||||
desc string
|
||||
aliases []string
|
||||
bools map[string]bool
|
||||
nums map[string]int
|
||||
strs map[string]string
|
||||
}
|
||||
|
||||
func (tc *termcap) getnum(s string) int {
|
||||
return (tc.nums[s])
|
||||
}
|
||||
|
||||
func (tc *termcap) getflag(s string) bool {
|
||||
return (tc.bools[s])
|
||||
}
|
||||
|
||||
func (tc *termcap) getstr(s string) string {
|
||||
return (tc.strs[s])
|
||||
}
|
||||
|
||||
const (
|
||||
NONE = iota
|
||||
CTRL
|
||||
ESC
|
||||
)
|
||||
|
||||
func unescape(s string) string {
|
||||
// Various escapes are in \x format. Control codes are
|
||||
// encoded as ^M (carat followed by ASCII equivalent).
|
||||
// Escapes are: \e, \E - escape
|
||||
// \0 NULL, \n \l \r \t \b \f \s for equivalent C escape.
|
||||
buf := &bytes.Buffer{}
|
||||
esc := NONE
|
||||
|
||||
for i := 0; i < len(s); i++ {
|
||||
c := s[i]
|
||||
switch esc {
|
||||
case NONE:
|
||||
switch c {
|
||||
case '\\':
|
||||
esc = ESC
|
||||
case '^':
|
||||
esc = CTRL
|
||||
default:
|
||||
buf.WriteByte(c)
|
||||
}
|
||||
case CTRL:
|
||||
buf.WriteByte(c - 0x40)
|
||||
esc = NONE
|
||||
case ESC:
|
||||
switch c {
|
||||
case 'E', 'e':
|
||||
buf.WriteByte(0x1b)
|
||||
case '0', '1', '2', '3', '4', '5', '6', '7':
|
||||
if i+2 < len(s) && s[i+1] >= '0' && s[i+1] <= '7' && s[i+2] >= '0' && s[i+2] <= '7' {
|
||||
buf.WriteByte(((c - '0') * 64) + ((s[i+1] - '0') * 8) + (s[i+2] - '0'))
|
||||
i = i + 2
|
||||
} else if c == '0' {
|
||||
buf.WriteByte(0)
|
||||
}
|
||||
case 'n':
|
||||
buf.WriteByte('\n')
|
||||
case 'r':
|
||||
buf.WriteByte('\r')
|
||||
case 't':
|
||||
buf.WriteByte('\t')
|
||||
case 'b':
|
||||
buf.WriteByte('\b')
|
||||
case 'f':
|
||||
buf.WriteByte('\f')
|
||||
case 's':
|
||||
buf.WriteByte(' ')
|
||||
case 'l':
|
||||
panic("WTF: weird format: " + s)
|
||||
default:
|
||||
buf.WriteByte(c)
|
||||
}
|
||||
esc = NONE
|
||||
}
|
||||
}
|
||||
return (buf.String())
|
||||
}
|
||||
|
||||
func (tc *termcap) setupterm(name string) error {
|
||||
cmd := exec.Command("infocmp", "-1", name)
|
||||
output := &bytes.Buffer{}
|
||||
cmd.Stdout = output
|
||||
|
||||
tc.strs = make(map[string]string)
|
||||
tc.bools = make(map[string]bool)
|
||||
tc.nums = make(map[string]int)
|
||||
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Now parse the output.
|
||||
// We get comment lines (starting with "#"), followed by
|
||||
// a header line that looks like "<name>|<alias>|...|<desc>"
|
||||
// then capabilities, one per line, starting with a tab and ending
|
||||
// with a comma and newline.
|
||||
lines := strings.Split(output.String(), "\n")
|
||||
for len(lines) > 0 && strings.HasPrefix(lines[0], "#") {
|
||||
lines = lines[1:]
|
||||
}
|
||||
|
||||
// Ditch trailing empty last line
|
||||
if lines[len(lines)-1] == "" {
|
||||
lines = lines[:len(lines)-1]
|
||||
}
|
||||
header := lines[0]
|
||||
if strings.HasSuffix(header, ",") {
|
||||
header = header[:len(header)-1]
|
||||
}
|
||||
names := strings.Split(header, "|")
|
||||
tc.name = names[0]
|
||||
names = names[1:]
|
||||
if len(names) > 0 {
|
||||
tc.desc = names[len(names)-1]
|
||||
names = names[:len(names)-1]
|
||||
}
|
||||
tc.aliases = names
|
||||
for _, val := range lines[1:] {
|
||||
if (!strings.HasPrefix(val, "\t")) ||
|
||||
(!strings.HasSuffix(val, ",")) {
|
||||
return (errors.New("malformed infocmp: " + val))
|
||||
}
|
||||
|
||||
val = val[1:]
|
||||
val = val[:len(val)-1]
|
||||
|
||||
if k := strings.SplitN(val, "=", 2); len(k) == 2 {
|
||||
tc.strs[k[0]] = unescape(k[1])
|
||||
} else if k := strings.SplitN(val, "#", 2); len(k) == 2 {
|
||||
if strings.HasPrefix(k[1], "0x") {
|
||||
if u, err := strconv.ParseUint(k[1][2:], 16, 0); err != nil {
|
||||
return (err)
|
||||
} else {
|
||||
tc.nums[k[0]] = int(u)
|
||||
}
|
||||
} else if u, err := strconv.ParseUint(k[1], 10, 0); err != nil {
|
||||
return (err)
|
||||
} else {
|
||||
tc.nums[k[0]] = int(u)
|
||||
}
|
||||
} else {
|
||||
tc.bools[val] = true
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// This program is used to collect data from the system's terminfo library,
|
||||
// and write it into Go source code. That is, we maintain our terminfo
|
||||
// capabilities encoded in the program. It should never need to be run by
|
||||
// an end user, but developers can use this to add codes for additional
|
||||
// terminal types.
|
||||
//
|
||||
// If a terminal name ending with -truecolor is given, and we cannot find
|
||||
// one, we will try to fabricate one from either the -256color (if present)
|
||||
// or the unadorned base name, adding the XTerm specific 24-bit color
|
||||
// escapes. We believe that all 24-bit capable terminals use the same
|
||||
// escape sequences, and terminfo has yet to evolve to support this.
|
||||
func getinfo(name string) (*Terminfo, string, error) {
|
||||
var tc termcap
|
||||
addTrueColor := false
|
||||
if err := tc.setupterm(name); err != nil {
|
||||
if strings.HasSuffix(name, "-truecolor") {
|
||||
base := name[:len(name)-len("-truecolor")]
|
||||
// Probably -256color is closest to what we want
|
||||
if err = tc.setupterm(base + "-256color"); err != nil {
|
||||
err = tc.setupterm(base)
|
||||
}
|
||||
if err == nil {
|
||||
addTrueColor = true
|
||||
}
|
||||
tc.name = name
|
||||
}
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
}
|
||||
t := &Terminfo{}
|
||||
// If this is an alias record, then just emit the alias
|
||||
t.Name = tc.name
|
||||
if t.Name != name {
|
||||
return t, "", nil
|
||||
}
|
||||
t.Aliases = tc.aliases
|
||||
t.Colors = tc.getnum("colors")
|
||||
t.Columns = tc.getnum("cols")
|
||||
t.Lines = tc.getnum("lines")
|
||||
t.Bell = tc.getstr("bel")
|
||||
t.Clear = tc.getstr("clear")
|
||||
t.EnterCA = tc.getstr("smcup")
|
||||
t.ExitCA = tc.getstr("rmcup")
|
||||
t.ShowCursor = tc.getstr("cnorm")
|
||||
t.HideCursor = tc.getstr("civis")
|
||||
t.AttrOff = tc.getstr("sgr0")
|
||||
t.Underline = tc.getstr("smul")
|
||||
t.Bold = tc.getstr("bold")
|
||||
t.Blink = tc.getstr("blink")
|
||||
t.Dim = tc.getstr("dim")
|
||||
t.Reverse = tc.getstr("rev")
|
||||
t.EnterKeypad = tc.getstr("smkx")
|
||||
t.ExitKeypad = tc.getstr("rmkx")
|
||||
t.SetFg = tc.getstr("setaf")
|
||||
t.SetBg = tc.getstr("setab")
|
||||
t.SetCursor = tc.getstr("cup")
|
||||
t.CursorBack1 = tc.getstr("cub1")
|
||||
t.CursorUp1 = tc.getstr("cuu1")
|
||||
t.KeyF1 = tc.getstr("kf1")
|
||||
t.KeyF2 = tc.getstr("kf2")
|
||||
t.KeyF3 = tc.getstr("kf3")
|
||||
t.KeyF4 = tc.getstr("kf4")
|
||||
t.KeyF5 = tc.getstr("kf5")
|
||||
t.KeyF6 = tc.getstr("kf6")
|
||||
t.KeyF7 = tc.getstr("kf7")
|
||||
t.KeyF8 = tc.getstr("kf8")
|
||||
t.KeyF9 = tc.getstr("kf9")
|
||||
t.KeyF10 = tc.getstr("kf10")
|
||||
t.KeyF11 = tc.getstr("kf11")
|
||||
t.KeyF12 = tc.getstr("kf12")
|
||||
t.KeyF13 = tc.getstr("kf13")
|
||||
t.KeyF14 = tc.getstr("kf14")
|
||||
t.KeyF15 = tc.getstr("kf15")
|
||||
t.KeyF16 = tc.getstr("kf16")
|
||||
t.KeyF17 = tc.getstr("kf17")
|
||||
t.KeyF18 = tc.getstr("kf18")
|
||||
t.KeyF19 = tc.getstr("kf19")
|
||||
t.KeyF20 = tc.getstr("kf20")
|
||||
t.KeyF21 = tc.getstr("kf21")
|
||||
t.KeyF22 = tc.getstr("kf22")
|
||||
t.KeyF23 = tc.getstr("kf23")
|
||||
t.KeyF24 = tc.getstr("kf24")
|
||||
t.KeyF25 = tc.getstr("kf25")
|
||||
t.KeyF26 = tc.getstr("kf26")
|
||||
t.KeyF27 = tc.getstr("kf27")
|
||||
t.KeyF28 = tc.getstr("kf28")
|
||||
t.KeyF29 = tc.getstr("kf29")
|
||||
t.KeyF30 = tc.getstr("kf30")
|
||||
t.KeyF31 = tc.getstr("kf31")
|
||||
t.KeyF32 = tc.getstr("kf32")
|
||||
t.KeyF33 = tc.getstr("kf33")
|
||||
t.KeyF34 = tc.getstr("kf34")
|
||||
t.KeyF35 = tc.getstr("kf35")
|
||||
t.KeyF36 = tc.getstr("kf36")
|
||||
t.KeyF37 = tc.getstr("kf37")
|
||||
t.KeyF38 = tc.getstr("kf38")
|
||||
t.KeyF39 = tc.getstr("kf39")
|
||||
t.KeyF40 = tc.getstr("kf40")
|
||||
t.KeyF41 = tc.getstr("kf41")
|
||||
t.KeyF42 = tc.getstr("kf42")
|
||||
t.KeyF43 = tc.getstr("kf43")
|
||||
t.KeyF44 = tc.getstr("kf44")
|
||||
t.KeyF45 = tc.getstr("kf45")
|
||||
t.KeyF46 = tc.getstr("kf46")
|
||||
t.KeyF47 = tc.getstr("kf47")
|
||||
t.KeyF48 = tc.getstr("kf48")
|
||||
t.KeyF49 = tc.getstr("kf49")
|
||||
t.KeyF50 = tc.getstr("kf50")
|
||||
t.KeyF51 = tc.getstr("kf51")
|
||||
t.KeyF52 = tc.getstr("kf52")
|
||||
t.KeyF53 = tc.getstr("kf53")
|
||||
t.KeyF54 = tc.getstr("kf54")
|
||||
t.KeyF55 = tc.getstr("kf55")
|
||||
t.KeyF56 = tc.getstr("kf56")
|
||||
t.KeyF57 = tc.getstr("kf57")
|
||||
t.KeyF58 = tc.getstr("kf58")
|
||||
t.KeyF59 = tc.getstr("kf59")
|
||||
t.KeyF60 = tc.getstr("kf60")
|
||||
t.KeyF61 = tc.getstr("kf61")
|
||||
t.KeyF62 = tc.getstr("kf62")
|
||||
t.KeyF63 = tc.getstr("kf63")
|
||||
t.KeyF64 = tc.getstr("kf64")
|
||||
t.KeyInsert = tc.getstr("kich1")
|
||||
t.KeyDelete = tc.getstr("kdch1")
|
||||
t.KeyBackspace = tc.getstr("kbs")
|
||||
t.KeyHome = tc.getstr("khome")
|
||||
t.KeyEnd = tc.getstr("kend")
|
||||
t.KeyUp = tc.getstr("kcuu1")
|
||||
t.KeyDown = tc.getstr("kcud1")
|
||||
t.KeyRight = tc.getstr("kcuf1")
|
||||
t.KeyLeft = tc.getstr("kcub1")
|
||||
t.KeyPgDn = tc.getstr("knp")
|
||||
t.KeyPgUp = tc.getstr("kpp")
|
||||
t.KeyBacktab = tc.getstr("kcbt")
|
||||
t.KeyExit = tc.getstr("kext")
|
||||
t.KeyCancel = tc.getstr("kcan")
|
||||
t.KeyPrint = tc.getstr("kprt")
|
||||
t.KeyHelp = tc.getstr("khlp")
|
||||
t.KeyClear = tc.getstr("kclr")
|
||||
t.AltChars = tc.getstr("acsc")
|
||||
t.EnterAcs = tc.getstr("smacs")
|
||||
t.ExitAcs = tc.getstr("rmacs")
|
||||
t.EnableAcs = tc.getstr("enacs")
|
||||
t.Mouse = tc.getstr("kmous")
|
||||
t.KeyShfRight = tc.getstr("kRIT")
|
||||
t.KeyShfLeft = tc.getstr("kLFT")
|
||||
t.KeyShfHome = tc.getstr("kHOM")
|
||||
t.KeyShfEnd = tc.getstr("kEND")
|
||||
|
||||
// Terminfo lacks descriptions for a bunch of modified keys,
|
||||
// but modern XTerm and emulators often have them. Let's add them,
|
||||
// if the shifted right and left arrows are defined.
|
||||
if t.KeyShfRight == "\x1b[1;2C" && t.KeyShfLeft == "\x1b[1;2D" {
|
||||
t.KeyShfUp = "\x1b[1;2A"
|
||||
t.KeyShfDown = "\x1b[1;2B"
|
||||
t.KeyMetaUp = "\x1b[1;9A"
|
||||
t.KeyMetaDown = "\x1b[1;9B"
|
||||
t.KeyMetaRight = "\x1b[1;9C"
|
||||
t.KeyMetaLeft = "\x1b[1;9D"
|
||||
t.KeyAltUp = "\x1b[1;3A"
|
||||
t.KeyAltDown = "\x1b[1;3B"
|
||||
t.KeyAltRight = "\x1b[1;3C"
|
||||
t.KeyAltLeft = "\x1b[1;3D"
|
||||
t.KeyCtrlUp = "\x1b[1;5A"
|
||||
t.KeyCtrlDown = "\x1b[1;5B"
|
||||
t.KeyCtrlRight = "\x1b[1;5C"
|
||||
t.KeyCtrlLeft = "\x1b[1;5D"
|
||||
t.KeyAltShfUp = "\x1b[1;4A"
|
||||
t.KeyAltShfDown = "\x1b[1;4B"
|
||||
t.KeyAltShfRight = "\x1b[1;4C"
|
||||
t.KeyAltShfLeft = "\x1b[1;4D"
|
||||
|
||||
t.KeyMetaShfUp = "\x1b[1;10A"
|
||||
t.KeyMetaShfDown = "\x1b[1;10B"
|
||||
t.KeyMetaShfRight = "\x1b[1;10C"
|
||||
t.KeyMetaShfLeft = "\x1b[1;10D"
|
||||
|
||||
t.KeyCtrlShfUp = "\x1b[1;6A"
|
||||
t.KeyCtrlShfDown = "\x1b[1;6B"
|
||||
t.KeyCtrlShfRight = "\x1b[1;6C"
|
||||
t.KeyCtrlShfLeft = "\x1b[1;6D"
|
||||
}
|
||||
// And also for Home and End
|
||||
if t.KeyShfHome == "\x1b[1;2H" && t.KeyShfEnd == "\x1b[1;2F" {
|
||||
t.KeyCtrlHome = "\x1b[1;5H"
|
||||
t.KeyCtrlEnd = "\x1b[1;5F"
|
||||
t.KeyAltHome = "\x1b[1;9H"
|
||||
t.KeyAltEnd = "\x1b[1;9F"
|
||||
t.KeyCtrlShfHome = "\x1b[1;6H"
|
||||
t.KeyCtrlShfEnd = "\x1b[1;6F"
|
||||
t.KeyAltShfHome = "\x1b[1;4H"
|
||||
t.KeyAltShfEnd = "\x1b[1;4F"
|
||||
t.KeyMetaShfHome = "\x1b[1;10H"
|
||||
t.KeyMetaShfEnd = "\x1b[1;10F"
|
||||
}
|
||||
|
||||
// And the same thing for rxvt and workalikes (Eterm, aterm, etc.)
|
||||
// It seems that urxvt at least send ESC as ALT prefix for these,
|
||||
// although some places seem to indicate a separate ALT key sesquence.
|
||||
if t.KeyShfRight == "\x1b[c" && t.KeyShfLeft == "\x1b[d" {
|
||||
t.KeyShfUp = "\x1b[a"
|
||||
t.KeyShfDown = "\x1b[b"
|
||||
t.KeyCtrlUp = "\x1b[Oa"
|
||||
t.KeyCtrlDown = "\x1b[Ob"
|
||||
t.KeyCtrlRight = "\x1b[Oc"
|
||||
t.KeyCtrlLeft = "\x1b[Od"
|
||||
}
|
||||
if t.KeyShfHome == "\x1b[7$" && t.KeyShfEnd == "\x1b[8$" {
|
||||
t.KeyCtrlHome = "\x1b[7^"
|
||||
t.KeyCtrlEnd = "\x1b[8^"
|
||||
}
|
||||
|
||||
// If the kmous entry is present, then we need to record the
|
||||
// the codes to enter and exit mouse mode. Sadly, this is not
|
||||
// part of the terminfo databases anywhere that I've found, but
|
||||
// is an extension. The escape codes are documented in the XTerm
|
||||
// manual, and all terminals that have kmous are expected to
|
||||
// use these same codes, unless explicitly configured otherwise
|
||||
// vi XM. Note that in any event, we only known how to parse either
|
||||
// x11 or SGR mouse events -- if your terminal doesn't support one
|
||||
// of these two forms, you maybe out of luck.
|
||||
t.MouseMode = tc.getstr("XM")
|
||||
if t.Mouse != "" && t.MouseMode == "" {
|
||||
// we anticipate that all xterm mouse tracking compatible
|
||||
// terminals understand mouse tracking (1000), but we hope
|
||||
// that those that don't understand any-event tracking (1003)
|
||||
// will at least ignore it. Likewise we hope that terminals
|
||||
// that don't understand SGR reporting (1006) just ignore it.
|
||||
t.MouseMode = "%?%p1%{1}%=%t%'h'%Pa%e%'l'%Pa%;" +
|
||||
"\x1b[?1000%ga%c\x1b[?1002%ga%c\x1b[?1003%ga%c\x1b[?1006%ga%c"
|
||||
}
|
||||
|
||||
// We only support colors in ANSI 8 or 256 color mode.
|
||||
if t.Colors < 8 || t.SetFg == "" {
|
||||
t.Colors = 0
|
||||
}
|
||||
if t.SetCursor == "" {
|
||||
return nil, "", errors.New("terminal not cursor addressable")
|
||||
}
|
||||
|
||||
// For padding, we lookup the pad char. If that isn't present,
|
||||
// and npc is *not* set, then we assume a null byte.
|
||||
t.PadChar = tc.getstr("pad")
|
||||
if t.PadChar == "" {
|
||||
if !tc.getflag("npc") {
|
||||
t.PadChar = "\u0000"
|
||||
}
|
||||
}
|
||||
|
||||
// For some terminals we fabricate a -truecolor entry, that may
|
||||
// not exist in terminfo.
|
||||
if addTrueColor {
|
||||
t.SetFgRGB = "\x1b[38;2;%p1%d;%p2%d;%p3%dm"
|
||||
t.SetBgRGB = "\x1b[48;2;%p1%d;%p2%d;%p3%dm"
|
||||
t.SetFgBgRGB = "\x1b[38;2;%p1%d;%p2%d;%p3%d;" +
|
||||
"48;2;%p4%d;%p5%d;%p6%dm"
|
||||
}
|
||||
|
||||
// For terminals that use "standard" SGR sequences, lets combine the
|
||||
// foreground and background together.
|
||||
if strings.HasPrefix(t.SetFg, "\x1b[") &&
|
||||
strings.HasPrefix(t.SetBg, "\x1b[") &&
|
||||
strings.HasSuffix(t.SetFg, "m") &&
|
||||
strings.HasSuffix(t.SetBg, "m") {
|
||||
fg := t.SetFg[:len(t.SetFg)-1]
|
||||
r := regexp.MustCompile("%p1")
|
||||
bg := r.ReplaceAllString(t.SetBg[2:], "%p2")
|
||||
t.SetFgBg = fg + ";" + bg
|
||||
}
|
||||
|
||||
return t, tc.desc, nil
|
||||
}
|
||||
|
||||
func WriteDB(filename string) error {
|
||||
var e error
|
||||
js := []byte{}
|
||||
args := []string{os.Getenv("TERM")}
|
||||
|
||||
tdata := make(map[string]*Terminfo)
|
||||
descs := make(map[string]string)
|
||||
|
||||
for _, term := range args {
|
||||
if t, desc, e := getinfo(term); e != nil {
|
||||
return e
|
||||
} else {
|
||||
tdata[term] = t
|
||||
descs[term] = desc
|
||||
}
|
||||
}
|
||||
|
||||
if len(tdata) == 0 {
|
||||
// No data.
|
||||
return errors.New("No data")
|
||||
}
|
||||
o := os.Stdout
|
||||
if o, e = os.Create(filename); e != nil {
|
||||
return e
|
||||
}
|
||||
var w io.WriteCloser
|
||||
w = o
|
||||
for _, term := range args {
|
||||
if t := tdata[term]; t != nil {
|
||||
js, e = json.Marshal(t)
|
||||
fmt.Fprintln(w, string(js))
|
||||
}
|
||||
// arguably if there is more than one term, this
|
||||
// should be a javascript array, but that's not how
|
||||
// we load it. We marshal objects one at a time from
|
||||
// the file.
|
||||
}
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
w.Close()
|
||||
if w != o {
|
||||
o.Close()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,838 +0,0 @@
|
||||
// Copyright 2017 The TCell Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use file except in compliance with the License.
|
||||
// You may obtain a copy of the license at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package terminfo
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrTermNotFound indicates that a suitable terminal entry could
|
||||
// not be found. This can result from either not having TERM set,
|
||||
// or from the TERM failing to support certain minimal functionality,
|
||||
// in particular absolute cursor addressability (the cup capability)
|
||||
// is required. For example, legacy "adm3" lacks this capability,
|
||||
// whereas the slightly newer "adm3a" supports it. This failure
|
||||
// occurs most often with "dumb".
|
||||
ErrTermNotFound = errors.New("terminal entry not found")
|
||||
)
|
||||
|
||||
// Terminfo represents a terminfo entry. Note that we use friendly names
|
||||
// in Go, but when we write out JSON, we use the same names as terminfo.
|
||||
// The name, aliases and smous, rmous fields do not come from terminfo directly.
|
||||
type Terminfo struct {
|
||||
Name string `json:"name"`
|
||||
Aliases []string `json:"aliases,omitempty"`
|
||||
Columns int `json:"cols,omitempty"` // cols
|
||||
Lines int `json:"lines,omitempty"` // lines
|
||||
Colors int `json:"colors,omitempty"` // colors
|
||||
Bell string `json:"bell,omitempty"` // bell
|
||||
Clear string `json:"clear,omitempty"` // clear
|
||||
EnterCA string `json:"smcup,omitempty"` // smcup
|
||||
ExitCA string `json:"rmcup,omitempty"` // rmcup
|
||||
ShowCursor string `json:"cnorm,omitempty"` // cnorm
|
||||
HideCursor string `json:"civis,omitempty"` // civis
|
||||
AttrOff string `json:"sgr0,omitempty"` // sgr0
|
||||
Underline string `json:"smul,omitempty"` // smul
|
||||
Bold string `json:"bold,omitempty"` // bold
|
||||
Blink string `json:"blink,omitempty"` // blink
|
||||
Reverse string `json:"rev,omitempty"` // rev
|
||||
Dim string `json:"dim,omitempty"` // dim
|
||||
EnterKeypad string `json:"smkx,omitempty"` // smkx
|
||||
ExitKeypad string `json:"rmkx,omitempty"` // rmkx
|
||||
SetFg string `json:"setaf,omitempty"` // setaf
|
||||
SetBg string `json:"setbg,omitempty"` // setab
|
||||
SetCursor string `json:"cup,omitempty"` // cup
|
||||
CursorBack1 string `json:"cub1,omitempty"` // cub1
|
||||
CursorUp1 string `json:"cuu1,omitempty"` // cuu1
|
||||
PadChar string `json:"pad,omitempty"` // pad
|
||||
KeyBackspace string `json:"kbs,omitempty"` // kbs
|
||||
KeyF1 string `json:"kf1,omitempty"` // kf1
|
||||
KeyF2 string `json:"kf2,omitempty"` // kf2
|
||||
KeyF3 string `json:"kf3,omitempty"` // kf3
|
||||
KeyF4 string `json:"kf4,omitempty"` // kf4
|
||||
KeyF5 string `json:"kf5,omitempty"` // kf5
|
||||
KeyF6 string `json:"kf6,omitempty"` // kf6
|
||||
KeyF7 string `json:"kf7,omitempty"` // kf7
|
||||
KeyF8 string `json:"kf8,omitempty"` // kf8
|
||||
KeyF9 string `json:"kf9,omitempty"` // kf9
|
||||
KeyF10 string `json:"kf10,omitempty"` // kf10
|
||||
KeyF11 string `json:"kf11,omitempty"` // kf11
|
||||
KeyF12 string `json:"kf12,omitempty"` // kf12
|
||||
KeyF13 string `json:"kf13,omitempty"` // kf13
|
||||
KeyF14 string `json:"kf14,omitempty"` // kf14
|
||||
KeyF15 string `json:"kf15,omitempty"` // kf15
|
||||
KeyF16 string `json:"kf16,omitempty"` // kf16
|
||||
KeyF17 string `json:"kf17,omitempty"` // kf17
|
||||
KeyF18 string `json:"kf18,omitempty"` // kf18
|
||||
KeyF19 string `json:"kf19,omitempty"` // kf19
|
||||
KeyF20 string `json:"kf20,omitempty"` // kf20
|
||||
KeyF21 string `json:"kf21,omitempty"` // kf21
|
||||
KeyF22 string `json:"kf22,omitempty"` // kf22
|
||||
KeyF23 string `json:"kf23,omitempty"` // kf23
|
||||
KeyF24 string `json:"kf24,omitempty"` // kf24
|
||||
KeyF25 string `json:"kf25,omitempty"` // kf25
|
||||
KeyF26 string `json:"kf26,omitempty"` // kf26
|
||||
KeyF27 string `json:"kf27,omitempty"` // kf27
|
||||
KeyF28 string `json:"kf28,omitempty"` // kf28
|
||||
KeyF29 string `json:"kf29,omitempty"` // kf29
|
||||
KeyF30 string `json:"kf30,omitempty"` // kf30
|
||||
KeyF31 string `json:"kf31,omitempty"` // kf31
|
||||
KeyF32 string `json:"kf32,omitempty"` // kf32
|
||||
KeyF33 string `json:"kf33,omitempty"` // kf33
|
||||
KeyF34 string `json:"kf34,omitempty"` // kf34
|
||||
KeyF35 string `json:"kf35,omitempty"` // kf35
|
||||
KeyF36 string `json:"kf36,omitempty"` // kf36
|
||||
KeyF37 string `json:"kf37,omitempty"` // kf37
|
||||
KeyF38 string `json:"kf38,omitempty"` // kf38
|
||||
KeyF39 string `json:"kf39,omitempty"` // kf39
|
||||
KeyF40 string `json:"kf40,omitempty"` // kf40
|
||||
KeyF41 string `json:"kf41,omitempty"` // kf41
|
||||
KeyF42 string `json:"kf42,omitempty"` // kf42
|
||||
KeyF43 string `json:"kf43,omitempty"` // kf43
|
||||
KeyF44 string `json:"kf44,omitempty"` // kf44
|
||||
KeyF45 string `json:"kf45,omitempty"` // kf45
|
||||
KeyF46 string `json:"kf46,omitempty"` // kf46
|
||||
KeyF47 string `json:"kf47,omitempty"` // kf47
|
||||
KeyF48 string `json:"kf48,omitempty"` // kf48
|
||||
KeyF49 string `json:"kf49,omitempty"` // kf49
|
||||
KeyF50 string `json:"kf50,omitempty"` // kf50
|
||||
KeyF51 string `json:"kf51,omitempty"` // kf51
|
||||
KeyF52 string `json:"kf52,omitempty"` // kf52
|
||||
KeyF53 string `json:"kf53,omitempty"` // kf53
|
||||
KeyF54 string `json:"kf54,omitempty"` // kf54
|
||||
KeyF55 string `json:"kf55,omitempty"` // kf55
|
||||
KeyF56 string `json:"kf56,omitempty"` // kf56
|
||||
KeyF57 string `json:"kf57,omitempty"` // kf57
|
||||
KeyF58 string `json:"kf58,omitempty"` // kf58
|
||||
KeyF59 string `json:"kf59,omitempty"` // kf59
|
||||
KeyF60 string `json:"kf60,omitempty"` // kf60
|
||||
KeyF61 string `json:"kf61,omitempty"` // kf61
|
||||
KeyF62 string `json:"kf62,omitempty"` // kf62
|
||||
KeyF63 string `json:"kf63,omitempty"` // kf63
|
||||
KeyF64 string `json:"kf64,omitempty"` // kf64
|
||||
KeyInsert string `json:"kich,omitempty"` // kich1
|
||||
KeyDelete string `json:"kdch,omitempty"` // kdch1
|
||||
KeyHome string `json:"khome,omitempty"` // khome
|
||||
KeyEnd string `json:"kend,omitempty"` // kend
|
||||
KeyHelp string `json:"khlp,omitempty"` // khlp
|
||||
KeyPgUp string `json:"kpp,omitempty"` // kpp
|
||||
KeyPgDn string `json:"knp,omitempty"` // knp
|
||||
KeyUp string `json:"kcuu1,omitempty"` // kcuu1
|
||||
KeyDown string `json:"kcud1,omitempty"` // kcud1
|
||||
KeyLeft string `json:"kcub1,omitempty"` // kcub1
|
||||
KeyRight string `json:"kcuf1,omitempty"` // kcuf1
|
||||
KeyBacktab string `json:"kcbt,omitempty"` // kcbt
|
||||
KeyExit string `json:"kext,omitempty"` // kext
|
||||
KeyClear string `json:"kclr,omitempty"` // kclr
|
||||
KeyPrint string `json:"kprt,omitempty"` // kprt
|
||||
KeyCancel string `json:"kcan,omitempty"` // kcan
|
||||
Mouse string `json:"kmous,omitempty"` // kmous
|
||||
MouseMode string `json:"XM,omitempty"` // XM
|
||||
AltChars string `json:"acsc,omitempty"` // acsc
|
||||
EnterAcs string `json:"smacs,omitempty"` // smacs
|
||||
ExitAcs string `json:"rmacs,omitempty"` // rmacs
|
||||
EnableAcs string `json:"enacs,omitempty"` // enacs
|
||||
KeyShfRight string `json:"kRIT,omitempty"` // kRIT
|
||||
KeyShfLeft string `json:"kLFT,omitempty"` // kLFT
|
||||
KeyShfHome string `json:"kHOM,omitempty"` // kHOM
|
||||
KeyShfEnd string `json:"kEND,omitempty"` // kEND
|
||||
|
||||
// These are non-standard extensions to terminfo. This includes
|
||||
// true color support, and some additional keys. Its kind of bizarre
|
||||
// that shifted variants of left and right exist, but not up and down.
|
||||
// Terminal support for these are going to vary amongst XTerm
|
||||
// emulations, so don't depend too much on them in your application.
|
||||
|
||||
SetFgBg string `json:"_setfgbg,omitempty"` // setfgbg
|
||||
SetFgBgRGB string `json:"_setfgbgrgb,omitempty"` // setfgbgrgb
|
||||
SetFgRGB string `json:"_setfrgb,omitempty"` // setfrgb
|
||||
SetBgRGB string `json:"_setbrgb,omitempty"` // setbrgb
|
||||
KeyShfUp string `json:"_kscu1,omitempty"` // shift-up
|
||||
KeyShfDown string `json:"_kscud1,omitempty"` // shift-down
|
||||
KeyCtrlUp string `json:"_kccu1,omitempty"` // ctrl-up
|
||||
KeyCtrlDown string `json:"_kccud1,omitempty"` // ctrl-left
|
||||
KeyCtrlRight string `json:"_kccuf1,omitempty"` // ctrl-right
|
||||
KeyCtrlLeft string `json:"_kccub1,omitempty"` // ctrl-left
|
||||
KeyMetaUp string `json:"_kmcu1,omitempty"` // meta-up
|
||||
KeyMetaDown string `json:"_kmcud1,omitempty"` // meta-left
|
||||
KeyMetaRight string `json:"_kmcuf1,omitempty"` // meta-right
|
||||
KeyMetaLeft string `json:"_kmcub1,omitempty"` // meta-left
|
||||
KeyAltUp string `json:"_kacu1,omitempty"` // alt-up
|
||||
KeyAltDown string `json:"_kacud1,omitempty"` // alt-left
|
||||
KeyAltRight string `json:"_kacuf1,omitempty"` // alt-right
|
||||
KeyAltLeft string `json:"_kacub1,omitempty"` // alt-left
|
||||
KeyCtrlHome string `json:"_kchome,omitempty"`
|
||||
KeyCtrlEnd string `json:"_kcend,omitempty"`
|
||||
KeyMetaHome string `json:"_kmhome,omitempty"`
|
||||
KeyMetaEnd string `json:"_kmend,omitempty"`
|
||||
KeyAltHome string `json:"_kahome,omitempty"`
|
||||
KeyAltEnd string `json:"_kaend,omitempty"`
|
||||
KeyAltShfUp string `json:"_kascu1,omitempty"`
|
||||
KeyAltShfDown string `json:"_kascud1,omitempty"`
|
||||
KeyAltShfLeft string `json:"_kascub1,omitempty"`
|
||||
KeyAltShfRight string `json:"_kascuf1,omitempty"`
|
||||
KeyMetaShfUp string `json:"_kmscu1,omitempty"`
|
||||
KeyMetaShfDown string `json:"_kmscud1,omitempty"`
|
||||
KeyMetaShfLeft string `json:"_kmscub1,omitempty"`
|
||||
KeyMetaShfRight string `json:"_kmscuf1,omitempty"`
|
||||
KeyCtrlShfUp string `json:"_kcscu1,omitempty"`
|
||||
KeyCtrlShfDown string `json:"_kcscud1,omitempty"`
|
||||
KeyCtrlShfLeft string `json:"_kcscub1,omitempty"`
|
||||
KeyCtrlShfRight string `json:"_kcscuf1,omitempty"`
|
||||
KeyCtrlShfHome string `json:"_kcHOME,omitempty"`
|
||||
KeyCtrlShfEnd string `json:"_kcEND,omitempty"`
|
||||
KeyAltShfHome string `json:"_kaHOME,omitempty"`
|
||||
KeyAltShfEnd string `json:"_kaEND,omitempty"`
|
||||
KeyMetaShfHome string `json:"_kmHOME,omitempty"`
|
||||
KeyMetaShfEnd string `json:"_kmEND,omitempty"`
|
||||
}
|
||||
|
||||
type stackElem struct {
|
||||
s string
|
||||
i int
|
||||
isStr bool
|
||||
isInt bool
|
||||
}
|
||||
|
||||
type stack []stackElem
|
||||
|
||||
func (st stack) Push(v string) stack {
|
||||
e := stackElem{
|
||||
s: v,
|
||||
isStr: true,
|
||||
}
|
||||
return append(st, e)
|
||||
}
|
||||
|
||||
func (st stack) Pop() (string, stack) {
|
||||
v := ""
|
||||
if len(st) > 0 {
|
||||
e := st[len(st)-1]
|
||||
st = st[:len(st)-1]
|
||||
if e.isStr {
|
||||
v = e.s
|
||||
} else {
|
||||
v = strconv.Itoa(e.i)
|
||||
}
|
||||
}
|
||||
return v, st
|
||||
}
|
||||
|
||||
func (st stack) PopInt() (int, stack) {
|
||||
if len(st) > 0 {
|
||||
e := st[len(st)-1]
|
||||
st = st[:len(st)-1]
|
||||
if e.isInt {
|
||||
return e.i, st
|
||||
} else if e.isStr {
|
||||
i, _ := strconv.Atoi(e.s)
|
||||
return i, st
|
||||
}
|
||||
}
|
||||
return 0, st
|
||||
}
|
||||
|
||||
func (st stack) PopBool() (bool, stack) {
|
||||
if len(st) > 0 {
|
||||
e := st[len(st)-1]
|
||||
st = st[:len(st)-1]
|
||||
if e.isStr {
|
||||
if e.s == "1" {
|
||||
return true, st
|
||||
}
|
||||
return false, st
|
||||
} else if e.i == 1 {
|
||||
return true, st
|
||||
} else {
|
||||
return false, st
|
||||
}
|
||||
}
|
||||
return false, st
|
||||
}
|
||||
|
||||
func (st stack) PushInt(i int) stack {
|
||||
e := stackElem{
|
||||
i: i,
|
||||
isInt: true,
|
||||
}
|
||||
return append(st, e)
|
||||
}
|
||||
|
||||
func (st stack) PushBool(i bool) stack {
|
||||
if i {
|
||||
return st.PushInt(1)
|
||||
}
|
||||
return st.PushInt(0)
|
||||
}
|
||||
|
||||
func nextch(s string, index int) (byte, int) {
|
||||
if index < len(s) {
|
||||
return s[index], index + 1
|
||||
}
|
||||
return 0, index
|
||||
}
|
||||
|
||||
// static vars
|
||||
var svars [26]string
|
||||
|
||||
// paramsBuffer handles some persistent state for TParam. Technically we
|
||||
// could probably dispense with this, but caching buffer arrays gives us
|
||||
// a nice little performance boost. Furthermore, we know that TParam is
|
||||
// rarely (never?) called re-entrantly, so we can just reuse the same
|
||||
// buffers, making it thread-safe by stashing a lock.
|
||||
type paramsBuffer struct {
|
||||
out bytes.Buffer
|
||||
buf bytes.Buffer
|
||||
lk sync.Mutex
|
||||
}
|
||||
|
||||
// Start initializes the params buffer with the initial string data.
|
||||
// It also locks the paramsBuffer. The caller must call End() when
|
||||
// finished.
|
||||
func (pb *paramsBuffer) Start(s string) {
|
||||
pb.lk.Lock()
|
||||
pb.out.Reset()
|
||||
pb.buf.Reset()
|
||||
pb.buf.WriteString(s)
|
||||
}
|
||||
|
||||
// End returns the final output from TParam, but it also releases the lock.
|
||||
func (pb *paramsBuffer) End() string {
|
||||
s := pb.out.String()
|
||||
pb.lk.Unlock()
|
||||
return s
|
||||
}
|
||||
|
||||
// NextCh returns the next input character to the expander.
|
||||
func (pb *paramsBuffer) NextCh() (byte, error) {
|
||||
return pb.buf.ReadByte()
|
||||
}
|
||||
|
||||
// PutCh "emits" (rather schedules for output) a single byte character.
|
||||
func (pb *paramsBuffer) PutCh(ch byte) {
|
||||
pb.out.WriteByte(ch)
|
||||
}
|
||||
|
||||
// PutString schedules a string for output.
|
||||
func (pb *paramsBuffer) PutString(s string) {
|
||||
pb.out.WriteString(s)
|
||||
}
|
||||
|
||||
var pb = ¶msBuffer{}
|
||||
|
||||
// TParm takes a terminfo parameterized string, such as setaf or cup, and
|
||||
// evaluates the string, and returns the result with the parameter
|
||||
// applied.
|
||||
func (t *Terminfo) TParm(s string, p ...int) string {
|
||||
var stk stack
|
||||
var a, b string
|
||||
var ai, bi int
|
||||
var ab bool
|
||||
var dvars [26]string
|
||||
var params [9]int
|
||||
|
||||
pb.Start(s)
|
||||
|
||||
// make sure we always have 9 parameters -- makes it easier
|
||||
// later to skip checks
|
||||
for i := 0; i < len(params) && i < len(p); i++ {
|
||||
params[i] = p[i]
|
||||
}
|
||||
|
||||
nest := 0
|
||||
|
||||
for {
|
||||
|
||||
ch, err := pb.NextCh()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
if ch != '%' {
|
||||
pb.PutCh(ch)
|
||||
continue
|
||||
}
|
||||
|
||||
ch, err = pb.NextCh()
|
||||
if err != nil {
|
||||
// XXX Error
|
||||
break
|
||||
}
|
||||
|
||||
switch ch {
|
||||
case '%': // quoted %
|
||||
pb.PutCh(ch)
|
||||
|
||||
case 'i': // increment both parameters (ANSI cup support)
|
||||
params[0]++
|
||||
params[1]++
|
||||
|
||||
case 'c', 's':
|
||||
// NB: these, and 'd' below are special cased for
|
||||
// efficiency. They could be handled by the richer
|
||||
// format support below, less efficiently.
|
||||
a, stk = stk.Pop()
|
||||
pb.PutString(a)
|
||||
|
||||
case 'd':
|
||||
ai, stk = stk.PopInt()
|
||||
pb.PutString(strconv.Itoa(ai))
|
||||
|
||||
case '0', '1', '2', '3', '4', 'x', 'X', 'o', ':':
|
||||
// This is pretty suboptimal, but this is rarely used.
|
||||
// None of the mainstream terminals use any of this,
|
||||
// and it would surprise me if this code is ever
|
||||
// executed outside of test cases.
|
||||
f := "%"
|
||||
if ch == ':' {
|
||||
ch, _ = pb.NextCh()
|
||||
}
|
||||
f += string(ch)
|
||||
for ch == '+' || ch == '-' || ch == '#' || ch == ' ' {
|
||||
ch, _ = pb.NextCh()
|
||||
f += string(ch)
|
||||
}
|
||||
for (ch >= '0' && ch <= '9') || ch == '.' {
|
||||
ch, _ = pb.NextCh()
|
||||
f += string(ch)
|
||||
}
|
||||
switch ch {
|
||||
case 'd', 'x', 'X', 'o':
|
||||
ai, stk = stk.PopInt()
|
||||
pb.PutString(fmt.Sprintf(f, ai))
|
||||
case 'c', 's':
|
||||
a, stk = stk.Pop()
|
||||
pb.PutString(fmt.Sprintf(f, a))
|
||||
}
|
||||
|
||||
case 'p': // push parameter
|
||||
ch, _ = pb.NextCh()
|
||||
ai = int(ch - '1')
|
||||
if ai >= 0 && ai < len(params) {
|
||||
stk = stk.PushInt(params[ai])
|
||||
} else {
|
||||
stk = stk.PushInt(0)
|
||||
}
|
||||
|
||||
case 'P': // pop & store variable
|
||||
ch, _ = pb.NextCh()
|
||||
if ch >= 'A' && ch <= 'Z' {
|
||||
svars[int(ch-'A')], stk = stk.Pop()
|
||||
} else if ch >= 'a' && ch <= 'z' {
|
||||
dvars[int(ch-'a')], stk = stk.Pop()
|
||||
}
|
||||
|
||||
case 'g': // recall & push variable
|
||||
ch, _ = pb.NextCh()
|
||||
if ch >= 'A' && ch <= 'Z' {
|
||||
stk = stk.Push(svars[int(ch-'A')])
|
||||
} else if ch >= 'a' && ch <= 'z' {
|
||||
stk = stk.Push(dvars[int(ch-'a')])
|
||||
}
|
||||
|
||||
case '\'': // push(char)
|
||||
ch, _ = pb.NextCh()
|
||||
pb.NextCh() // must be ' but we don't check
|
||||
stk = stk.Push(string(ch))
|
||||
|
||||
case '{': // push(int)
|
||||
ai = 0
|
||||
ch, _ = pb.NextCh()
|
||||
for ch >= '0' && ch <= '9' {
|
||||
ai *= 10
|
||||
ai += int(ch - '0')
|
||||
ch, _ = pb.NextCh()
|
||||
}
|
||||
// ch must be '}' but no verification
|
||||
stk = stk.PushInt(ai)
|
||||
|
||||
case 'l': // push(strlen(pop))
|
||||
a, stk = stk.Pop()
|
||||
stk = stk.PushInt(len(a))
|
||||
|
||||
case '+':
|
||||
bi, stk = stk.PopInt()
|
||||
ai, stk = stk.PopInt()
|
||||
stk = stk.PushInt(ai + bi)
|
||||
|
||||
case '-':
|
||||
bi, stk = stk.PopInt()
|
||||
ai, stk = stk.PopInt()
|
||||
stk = stk.PushInt(ai - bi)
|
||||
|
||||
case '*':
|
||||
bi, stk = stk.PopInt()
|
||||
ai, stk = stk.PopInt()
|
||||
stk = stk.PushInt(ai * bi)
|
||||
|
||||
case '/':
|
||||
bi, stk = stk.PopInt()
|
||||
ai, stk = stk.PopInt()
|
||||
if bi != 0 {
|
||||
stk = stk.PushInt(ai / bi)
|
||||
} else {
|
||||
stk = stk.PushInt(0)
|
||||
}
|
||||
|
||||
case 'm': // push(pop mod pop)
|
||||
bi, stk = stk.PopInt()
|
||||
ai, stk = stk.PopInt()
|
||||
if bi != 0 {
|
||||
stk = stk.PushInt(ai % bi)
|
||||
} else {
|
||||
stk = stk.PushInt(0)
|
||||
}
|
||||
|
||||
case '&': // AND
|
||||
bi, stk = stk.PopInt()
|
||||
ai, stk = stk.PopInt()
|
||||
stk = stk.PushInt(ai & bi)
|
||||
|
||||
case '|': // OR
|
||||
bi, stk = stk.PopInt()
|
||||
ai, stk = stk.PopInt()
|
||||
stk = stk.PushInt(ai | bi)
|
||||
|
||||
case '^': // XOR
|
||||
bi, stk = stk.PopInt()
|
||||
ai, stk = stk.PopInt()
|
||||
stk = stk.PushInt(ai ^ bi)
|
||||
|
||||
case '~': // bit complement
|
||||
ai, stk = stk.PopInt()
|
||||
stk = stk.PushInt(ai ^ -1)
|
||||
|
||||
case '!': // logical NOT
|
||||
ai, stk = stk.PopInt()
|
||||
stk = stk.PushBool(ai != 0)
|
||||
|
||||
case '=': // numeric compare or string compare
|
||||
b, stk = stk.Pop()
|
||||
a, stk = stk.Pop()
|
||||
stk = stk.PushBool(a == b)
|
||||
|
||||
case '>': // greater than, numeric
|
||||
bi, stk = stk.PopInt()
|
||||
ai, stk = stk.PopInt()
|
||||
stk = stk.PushBool(ai > bi)
|
||||
|
||||
case '<': // less than, numeric
|
||||
bi, stk = stk.PopInt()
|
||||
ai, stk = stk.PopInt()
|
||||
stk = stk.PushBool(ai < bi)
|
||||
|
||||
case '?': // start conditional
|
||||
|
||||
case 't':
|
||||
ab, stk = stk.PopBool()
|
||||
if ab {
|
||||
// just keep going
|
||||
break
|
||||
}
|
||||
nest = 0
|
||||
ifloop:
|
||||
// this loop consumes everything until we hit our else,
|
||||
// or the end of the conditional
|
||||
for {
|
||||
ch, err = pb.NextCh()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
if ch != '%' {
|
||||
continue
|
||||
}
|
||||
ch, _ = pb.NextCh()
|
||||
switch ch {
|
||||
case ';':
|
||||
if nest == 0 {
|
||||
break ifloop
|
||||
}
|
||||
nest--
|
||||
case '?':
|
||||
nest++
|
||||
case 'e':
|
||||
if nest == 0 {
|
||||
break ifloop
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case 'e':
|
||||
// if we got here, it means we didn't use the else
|
||||
// in the 't' case above, and we should skip until
|
||||
// the end of the conditional
|
||||
nest = 0
|
||||
elloop:
|
||||
for {
|
||||
ch, err = pb.NextCh()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
if ch != '%' {
|
||||
continue
|
||||
}
|
||||
ch, _ = pb.NextCh()
|
||||
switch ch {
|
||||
case ';':
|
||||
if nest == 0 {
|
||||
break elloop
|
||||
}
|
||||
nest--
|
||||
case '?':
|
||||
nest++
|
||||
}
|
||||
}
|
||||
|
||||
case ';': // endif
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return pb.End()
|
||||
}
|
||||
|
||||
// TPuts emits the string to the writer, but expands inline padding
|
||||
// indications (of the form $<[delay]> where [delay] is msec) to
|
||||
// a suitable number of padding characters (usually null bytes) based
|
||||
// upon the supplied baud. At high baud rates, more padding characters
|
||||
// will be inserted. All Terminfo based strings should be emitted using
|
||||
// this function.
|
||||
func (t *Terminfo) TPuts(w io.Writer, s string, baud int) {
|
||||
for {
|
||||
beg := strings.Index(s, "$<")
|
||||
if beg < 0 {
|
||||
// Most strings don't need padding, which is good news!
|
||||
io.WriteString(w, s)
|
||||
return
|
||||
}
|
||||
io.WriteString(w, s[:beg])
|
||||
s = s[beg+2:]
|
||||
end := strings.Index(s, ">")
|
||||
if end < 0 {
|
||||
// unterminated.. just emit bytes unadulterated
|
||||
io.WriteString(w, "$<"+s)
|
||||
return
|
||||
}
|
||||
val := s[:end]
|
||||
s = s[end+1:]
|
||||
padus := 0
|
||||
unit := 1000
|
||||
dot := false
|
||||
loop:
|
||||
for i := range val {
|
||||
switch val[i] {
|
||||
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
|
||||
padus *= 10
|
||||
padus += int(val[i] - '0')
|
||||
if dot {
|
||||
unit *= 10
|
||||
}
|
||||
case '.':
|
||||
if !dot {
|
||||
dot = true
|
||||
} else {
|
||||
break loop
|
||||
}
|
||||
default:
|
||||
break loop
|
||||
}
|
||||
}
|
||||
cnt := int(((baud / 8) * padus) / unit)
|
||||
for cnt > 0 {
|
||||
io.WriteString(w, t.PadChar)
|
||||
cnt--
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TGoto returns a string suitable for addressing the cursor at the given
|
||||
// row and column. The origin 0, 0 is in the upper left corner of the screen.
|
||||
func (t *Terminfo) TGoto(col, row int) string {
|
||||
return t.TParm(t.SetCursor, row, col)
|
||||
}
|
||||
|
||||
// TColor returns a string corresponding to the given foreground and background
|
||||
// colors. Either fg or bg can be set to -1 to elide.
|
||||
func (t *Terminfo) TColor(fi, bi int) string {
|
||||
rv := ""
|
||||
// As a special case, we map bright colors to lower versions if the
|
||||
// color table only holds 8. For the remaining 240 colors, the user
|
||||
// is out of luck. Someday we could create a mapping table, but its
|
||||
// not worth it.
|
||||
if t.Colors == 8 {
|
||||
if fi > 7 && fi < 16 {
|
||||
fi -= 8
|
||||
}
|
||||
if bi > 7 && bi < 16 {
|
||||
bi -= 8
|
||||
}
|
||||
}
|
||||
if t.Colors > fi && fi >= 0 {
|
||||
rv += t.TParm(t.SetFg, fi)
|
||||
}
|
||||
if t.Colors > bi && bi >= 0 {
|
||||
rv += t.TParm(t.SetBg, bi)
|
||||
}
|
||||
return rv
|
||||
}
|
||||
|
||||
var (
|
||||
dblock sync.Mutex
|
||||
terminfos = make(map[string]*Terminfo)
|
||||
aliases = make(map[string]string)
|
||||
)
|
||||
|
||||
// AddTerminfo can be called to register a new Terminfo entry.
|
||||
func AddTerminfo(t *Terminfo) {
|
||||
dblock.Lock()
|
||||
terminfos[t.Name] = t
|
||||
for _, x := range t.Aliases {
|
||||
terminfos[x] = t
|
||||
}
|
||||
dblock.Unlock()
|
||||
}
|
||||
|
||||
func loadFromFile(fname string, term string) (*Terminfo, error) {
|
||||
var e error
|
||||
var f io.ReadCloser
|
||||
if f, e = os.Open(fname); e != nil {
|
||||
return nil, e
|
||||
}
|
||||
defer f.Close()
|
||||
if strings.HasSuffix(fname, ".gz") {
|
||||
if f, e = gzip.NewReader(f); e != nil {
|
||||
return nil, e
|
||||
}
|
||||
}
|
||||
d := json.NewDecoder(f)
|
||||
for {
|
||||
t := &Terminfo{}
|
||||
if e := d.Decode(t); e != nil {
|
||||
if e == io.EOF {
|
||||
return nil, ErrTermNotFound
|
||||
}
|
||||
return nil, e
|
||||
}
|
||||
if t.SetCursor == "" {
|
||||
// This must be an alias record, return it.
|
||||
return t, nil
|
||||
}
|
||||
if t.Name == term {
|
||||
return t, nil
|
||||
}
|
||||
for _, a := range t.Aliases {
|
||||
if a == term {
|
||||
return t, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// LookupTerminfo attempts to find a definition for the named $TERM.
|
||||
// It first looks in the builtin database, which should cover just about
|
||||
// everyone. If it can't find one there, then it will attempt to read
|
||||
// one from the JSON file located in either $TCELLDB, $HOME/.tcelldb
|
||||
// or in this package's source directory as database.json).
|
||||
func LookupTerminfo(name string) (*Terminfo, error) {
|
||||
if name == "" {
|
||||
// else on windows: index out of bounds
|
||||
// on the name[0] reference below
|
||||
return nil, ErrTermNotFound
|
||||
}
|
||||
|
||||
dblock.Lock()
|
||||
t := terminfos[name]
|
||||
dblock.Unlock()
|
||||
|
||||
if t == nil {
|
||||
|
||||
var files []string
|
||||
letter := fmt.Sprintf("%02x", name[0])
|
||||
gzfile := path.Join(letter, name+".gz")
|
||||
jsfile := path.Join(letter, name)
|
||||
|
||||
// Build up the search path. Old versions of tcell used a
|
||||
// single database file, whereas the new ones locate them
|
||||
// in JSON (optionally compressed) files.
|
||||
//
|
||||
// The search path looks like:
|
||||
//
|
||||
// $TCELLDB/x/xterm.gz
|
||||
// $TCELLDB/x/xterm
|
||||
// $TCELLDB
|
||||
// $HOME/.tcelldb/x/xterm.gz
|
||||
// $HOME/.tcelldb/x/xterm
|
||||
// $HOME/.tcelldb
|
||||
// $GOPATH/terminfo/database/x/xterm.gz
|
||||
// $GOPATH/terminfo/database/x/xterm
|
||||
//
|
||||
if pth := os.Getenv("TCELLDB"); pth != "" {
|
||||
files = append(files, path.Join(pth, gzfile))
|
||||
files = append(files, path.Join(pth, jsfile))
|
||||
files = append(files, pth)
|
||||
}
|
||||
if pth := os.Getenv("HOME"); pth != "" {
|
||||
pth = path.Join(pth, ".tcelldb")
|
||||
files = append(files, path.Join(pth, gzfile))
|
||||
files = append(files, path.Join(pth, jsfile))
|
||||
files = append(files, pth)
|
||||
}
|
||||
|
||||
for _, pth := range strings.Split(os.Getenv("GOPATH"), string(os.PathListSeparator)) {
|
||||
pth = path.Join(pth, "src", "github.com", "gdamore", "tcell", "terminfo", "database")
|
||||
files = append(files, path.Join(pth, gzfile))
|
||||
files = append(files, path.Join(pth, jsfile))
|
||||
}
|
||||
|
||||
for _, fname := range files {
|
||||
t, _ = loadFromFile(fname, name)
|
||||
if t != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
if t != nil {
|
||||
if t.Name != name {
|
||||
// Check for a database loop (no infinite
|
||||
// recursion).
|
||||
dblock.Lock()
|
||||
if aliases[name] != "" {
|
||||
dblock.Unlock()
|
||||
return nil, ErrTermNotFound
|
||||
}
|
||||
aliases[name] = t.Name
|
||||
dblock.Unlock()
|
||||
return LookupTerminfo(t.Name)
|
||||
}
|
||||
dblock.Lock()
|
||||
terminfos[name] = t
|
||||
dblock.Unlock()
|
||||
}
|
||||
}
|
||||
if t == nil {
|
||||
return nil, ErrTermNotFound
|
||||
}
|
||||
return t, nil
|
||||
}
|
||||
@@ -20,5 +20,5 @@ color-link line-number "246,254"
|
||||
color-link cursor-line "254"
|
||||
color-link color-column "254"
|
||||
#No extended types (bool in C, &c.) and plain brackets
|
||||
color-link type.extended "default"
|
||||
color-link symbol.brackets "default"
|
||||
color-link type.extended "241,231"
|
||||
color-link symbol.brackets "241,231"
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
#CaptainMcClellan's personal color scheme.
|
||||
#Paper version
|
||||
color-link default "black,white"
|
||||
color-link comment "bold black"
|
||||
color-link constant "cyan"
|
||||
color-link constant.bool "bold cyan"
|
||||
color-link constant.bool.true "bold green"
|
||||
color-link constant.bool.false "bold red"
|
||||
color-link constant.string "bold yellow"
|
||||
color-link constant.string.url "underline blue, white"
|
||||
color-link constant.number "constant"
|
||||
color-link constant.specialChar "bold magenta"
|
||||
color-link identifier "bold red"
|
||||
color-link identifier.macro "bold red"
|
||||
color-link identifier.var "bold blue"
|
||||
color-link identifier.class "bold green"
|
||||
color-link preproc "bold cyan"
|
||||
color-link statement "bold yellow"
|
||||
color-link symbol "red"
|
||||
color-link symbol.brackets "blue"
|
||||
color-link type "green"
|
||||
color-link type.keyword "bold green"
|
||||
color-link special "magenta"
|
||||
color-link ignore "default"
|
||||
color-link error ",brightred"
|
||||
color-link todo "black,brightyellow"
|
||||
color-link indent-char ",brightgreen"
|
||||
color-link line-number "green"
|
||||
color-link line-number.scrollbar "green"
|
||||
color-link statusline "white,blue"
|
||||
color-link tabbar "white,blue"
|
||||
color-link current-line-number "red"
|
||||
color-link current-line-number.scroller "red"
|
||||
color-link gutter-error ",red"
|
||||
color-link gutter-warning "red"
|
||||
color-link color-column "cyan"
|
||||
color-link underlined.url "underline blue, white"
|
||||
@@ -15,12 +15,12 @@ color-link todo "bold #D33682,#242424"
|
||||
color-link statusline "#242424,#CCCCCC"
|
||||
color-link tabbar "#242424,#CCCCCC"
|
||||
color-link indent-char "#4F4F4F,#242424"
|
||||
color-link line-number "#666666,#242424"
|
||||
color-link line-number "#666666,#2C2C2C"
|
||||
color-link current-line-number "#666666,#242424"
|
||||
color-link gutter-error "#CB4B16,#242424"
|
||||
color-link gutter-warning "#E6DB74,#242424"
|
||||
color-link cursor-line "default,#2C2C2C"
|
||||
color-link color-column "default,#2C2C2C"
|
||||
color-link cursor-line "#2C2C2C"
|
||||
color-link color-column "#2C2C2C"
|
||||
#No extended types; Plain brackets.
|
||||
color-link type.extended "default"
|
||||
#color-link symbol.brackets "default"
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#Geany
|
||||
color-link comment "red"
|
||||
color-link constant "default"
|
||||
color-link constant.number
|
||||
color-link constant.string "bold yellow"
|
||||
color-link identifier "default"
|
||||
color-link preproc "cyan"
|
||||
@@ -20,4 +19,4 @@ color-link statusline "black,white"
|
||||
color-link tabbar "black,white"
|
||||
color-link color-column "bold geren"
|
||||
color-link gutter-error ",red"
|
||||
color-link gutter-warning "red"
|
||||
color-link gutter-warning "red"
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
#True color theme based on Github's syntax highlighting.
|
||||
#Warning, this is based on how it rendered in my Firefox!
|
||||
#Yours may look different.
|
||||
color-link comment "bold #969896"
|
||||
color-link constant "#0086B9"
|
||||
color-link constant.number "#0086B9"
|
||||
color-link constant.specialChar "bold #1836BD"
|
||||
color-link constant.string "bold #1836BD"
|
||||
color-link constant.bool "#0086B9"
|
||||
color-link identifier "#A71D5D"
|
||||
color-link preproc "bold #A71D5D"
|
||||
color-link special "#A71D5D"
|
||||
color-link statement "#A71D5D"
|
||||
color-link symbol "default"
|
||||
color-link type "#A71D5D"
|
||||
color-link error "bold ,#E34234"
|
||||
color-link todo "white"
|
||||
color-link indent-char "default"
|
||||
color-link line-number "bold #969896"
|
||||
color-link current-line-number "bold #969896"
|
||||
color-link gutter-error "bold ,#E34234"
|
||||
color-link gutter-warning "bold #f26522"
|
||||
color-link statusline "bold #c8c9cb,#24292e"
|
||||
color-link tabbar "bold #c8c9cb,#24292e"
|
||||
22
runtime/colorschemes/gotham.micro
Normal file
22
runtime/colorschemes/gotham.micro
Normal file
@@ -0,0 +1,22 @@
|
||||
color-link default "#99D1CE,#0C1014"
|
||||
color-link comment "#245361,#0C1014"
|
||||
color-link identifier "#599CAB,#0C1014"
|
||||
color-link constant "#D26937,#0C1014"
|
||||
color-link constant.string "#2AA889,#0C1014"
|
||||
color-link constant.string.char "#D3EBE9,#0C1014"
|
||||
color-link statement "#599CAB,#0C1014"
|
||||
color-link preproc "#C23127,#0C1014"
|
||||
color-link type "#D26937,#0C1014"
|
||||
color-link special "#D26937,#0C1014"
|
||||
color-link underlined "#EDB443,#0C1014"
|
||||
color-link error "bold #C23127,#0C1014"
|
||||
color-link todo "bold #888CA6,#0C1014"
|
||||
color-link statusline "#091F2E,#599CAB"
|
||||
color-link indent-char "#505050,#0C1014"
|
||||
color-link line-number "#245361,#11151C"
|
||||
color-link current-line-number "#599CAB,#11151C"
|
||||
color-link gutter-error "#C23127,#11151C"
|
||||
color-link gutter-warning "#EDB443,#11151C"
|
||||
color-link cursor-line "#091F2E"
|
||||
color-link color-column "#11151C"
|
||||
color-link symbol "#99D1CE,#0C1014"
|
||||
@@ -13,8 +13,8 @@ color-link underlined "underline #282828"
|
||||
color-link error "#9d0006,#282828"
|
||||
color-link gutter-error "#fb4934,#282828"
|
||||
color-link gutter-warning "#d79921,#282828"
|
||||
color-link line-number "#665c54,#282828"
|
||||
color-link current-line-number "#665c54,#3c3836"
|
||||
color-link line-number "#665c54,#3c3836"
|
||||
color-link current-line-number "#d79921,#282828"
|
||||
color-link cursor-line "#3c3836"
|
||||
color-link color-column "#79740e"
|
||||
color-link statusline "#ebdbb2,#665c54"
|
||||
|
||||
@@ -12,8 +12,8 @@ color-link underlined "underline 109,235"
|
||||
color-link error "235,124"
|
||||
color-link todo "bold 223,235"
|
||||
color-link line-number "243,237"
|
||||
color-link current-line-number "172,237"
|
||||
color-link current-line-number "172,235"
|
||||
color-link cursor-line "237"
|
||||
color-link color-column "237"
|
||||
color-link statusline "223,237"
|
||||
color-link tabbar "223,237"
|
||||
color-link tabbar "223,237"
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
#A colorscheme based on Code::Blocks IDE
|
||||
#but with a white background.
|
||||
color-link default "black,white"
|
||||
color-link comment "bold black"
|
||||
color-link constant "blue"
|
||||
color-link constant.number "bold magenta"
|
||||
color-link constant.string "bold blue"
|
||||
color-link identifier "black"
|
||||
color-link preproc "green"
|
||||
color-link statement "blue"
|
||||
color-link symbol "red"
|
||||
color-link symbol.brackets "blue"
|
||||
color-link type "blue"
|
||||
color-link special "magenta"
|
||||
color-link ignore "default"
|
||||
color-link error "bold white,brightred"
|
||||
color-link todo "bold black,brightyellow"
|
||||
color-link indent-char "bold black"
|
||||
color-link line-number "black,white"
|
||||
color-link statusline "white,red"
|
||||
color-link tabbar "white,red"
|
||||
color-link current-line-number "red,black"
|
||||
color-link gutter-error ",red"
|
||||
color-link gutter-warning "red"
|
||||
color-link color-column "black"
|
||||
@@ -1,23 +0,0 @@
|
||||
#Theme based on Code::Blocks IDE's default syntax highlighting.
|
||||
color-link comment "bold black"
|
||||
color-link constant "blue"
|
||||
color-link constant.string "bold blue"
|
||||
color-link constant.number "bold magenta"
|
||||
color-link identifier "default"
|
||||
color-link preproc "green"
|
||||
color-link statement "blue"
|
||||
color-link symbol "red"
|
||||
color-link symbol.brackets "blue"
|
||||
color-link type "blue"
|
||||
color-link special "magenta"
|
||||
color-link ignore "default"
|
||||
color-link error ",brightred"
|
||||
color-link todo "bold black,brightyellow"
|
||||
color-link indent-char "bold black"
|
||||
color-link line-number "black,white"
|
||||
color-link statusline "white,red"
|
||||
color-link tabbar "white,red"
|
||||
color-link current-line-number "red"
|
||||
color-link gutter-error ",red"
|
||||
color-link gutter-warning "red"
|
||||
color-link color-column "white"
|
||||
@@ -1,31 +0,0 @@
|
||||
#Funky Cactus theme
|
||||
color-link comment "bold black"
|
||||
color-link constant "cyan"
|
||||
color-link constant.bool "bold cyan"
|
||||
color-link constant.bool.true "bold green"
|
||||
color-link constant.bool.false "bold red"
|
||||
color-link constant.string "yellow"
|
||||
color-link constant.number "constant"
|
||||
color-link constant.specialChar "bold magenta"
|
||||
color-link identifier "bold red"
|
||||
color-link identifier.macro "bold red"
|
||||
color-link identifier.var "bold blue"
|
||||
color-link identifier.class "bold green"
|
||||
color-link preproc "bold cyan"
|
||||
color-link statement "bold yellow"
|
||||
color-link symbol "red"
|
||||
color-link symbol.brackets "blue"
|
||||
color-link type "green"
|
||||
color-link type.keyword "bold green"
|
||||
color-link special "magenta"
|
||||
color-link ignore "default"
|
||||
color-link error "bold ,brightred"
|
||||
color-link todo "underline ,brightyellow"
|
||||
color-link indent-char "bold ,brightgreen"
|
||||
color-link line-number "green"
|
||||
color-link statusline "black,green"
|
||||
color-link tabbar "black,magenta"
|
||||
color-link current-line-number "bold magenta"
|
||||
color-link gutter-error ",red"
|
||||
color-link gutter-warning "red"
|
||||
color-link color-column "bold green"
|
||||
@@ -1,23 +0,0 @@
|
||||
#Gameboy theme
|
||||
color-link default "#3f3f3f,#bfc180"
|
||||
color-link comment "#7d7343"
|
||||
color-link constant "#7d7343"
|
||||
color-link identifier "#ddde7d"
|
||||
color-link preproc "#ddde7d,#7d7343"
|
||||
color-link special "#7d7343"
|
||||
color-link statement "#7d7343"
|
||||
color-link symbol "#7d7343"
|
||||
color-link type "#7d7343"
|
||||
color-link error "#ddde7d,#7d7343"
|
||||
color-link todo "#7d7343,#ddde7d"
|
||||
color-link statusline "#ddde7d,#7d7343"
|
||||
color-link tabbar "#ddde7d,#7d7343"
|
||||
color-link color-column "#7d7343"
|
||||
color-link line-number "#ddde7d,#7d7343"
|
||||
color-link current-line-number "#3f3f3f,#bfc180"
|
||||
color-link gutter-error "#ddde7d,#7d7343"
|
||||
color-link gutter-warning "default"
|
||||
#3f3f3f
|
||||
#7d7343
|
||||
#bfc180
|
||||
#ddde76
|
||||
@@ -1,21 +0,0 @@
|
||||
#Geany Alternate theme
|
||||
color-link default "#000000,#fefefe"
|
||||
color-link comment "#808080"
|
||||
color-link constant "default"
|
||||
color-link constant.bool "#003030"
|
||||
color-link constant.number "#300008"
|
||||
color-link constant.string "#008000"
|
||||
color-link identifier "default"
|
||||
color-link preproc "#bbbb77"
|
||||
color-link special "#003030"
|
||||
color-link statement "#003030"
|
||||
color-link symbol "#300008"
|
||||
color-link symbol.tag "bold #4e9d71"
|
||||
color-link type "#003030"
|
||||
color-link error "#a52a2a"
|
||||
color-link todo "#ffa500"
|
||||
color-link line-number "#000000,#d0d0d0"
|
||||
color-link current-line-number "#000000,#d0d0d0"
|
||||
color-link color-column "#c2ebc2"
|
||||
color-link cursor-line "#f0f0f0"
|
||||
color-link type.extended "default"
|
||||
@@ -1,24 +0,0 @@
|
||||
#Theme based on Github's syntax highlighting.
|
||||
color-link comment "bold black"
|
||||
color-link constant "cyan"
|
||||
color-link constant.number "cyan"
|
||||
color-link constant.specialChar "bold blue"
|
||||
color-link constant.string "bold blue"
|
||||
color-link constant.bool "cyan"
|
||||
color-link identifier "magenta"
|
||||
color-link preproc "bold magenta"
|
||||
color-link special "magenta"
|
||||
color-link statement "magenta"
|
||||
color-link symbol "default"
|
||||
color-link type "magenta"
|
||||
color-link error "bold ,brightred"
|
||||
color-link todo "white"
|
||||
color-link indent-char "default"
|
||||
color-link line-number "bold black"
|
||||
color-link current-line-number "bold black"
|
||||
color-link gutter-error ",red"
|
||||
color-link gutter-warning "bold yellow"
|
||||
color-link statusline "bold white,black"
|
||||
color-link tabbar "bold white,black"
|
||||
#Plain brackets.
|
||||
#color-link symbol.brackets "default"
|
||||
@@ -1,25 +0,0 @@
|
||||
#Midnight Commander inspired theme.
|
||||
color-link default "white,blue"
|
||||
color-link comment "bold black"
|
||||
color-link constant "bold white"
|
||||
color-link constant.string "bold yellow"
|
||||
color-link identifier "bold red"
|
||||
color-link statement "bold cyan"
|
||||
color-link symbol "white"
|
||||
color-link symbol.brackets "white"
|
||||
color-link symbol.tag "bold green"
|
||||
color-link preproc "black,cyan"
|
||||
color-link type "green"
|
||||
color-link special "magenta"
|
||||
color-link ignore "default"
|
||||
color-link error ",brightred"
|
||||
color-link todo ",brightyellow"
|
||||
color-link indent-char ",cyan"
|
||||
color-link line-number "green"
|
||||
color-link statusline "black,cyan"
|
||||
color-link tabbar "black,cyan"
|
||||
color-link current-line-number "black,cyan"
|
||||
color-link cursor-line "black,cyan"
|
||||
color-link gutter-error ",red"
|
||||
color-link gutter-warning "red"
|
||||
color-link color-column "cyan"
|
||||
@@ -1,5 +0,0 @@
|
||||
#Monochrome Paper theme.
|
||||
#Edit your files on a white background without colors.
|
||||
color-link default "black,white"
|
||||
color-link statusline "white,black"
|
||||
color-link tabbar "white,black"
|
||||
@@ -1,3 +0,0 @@
|
||||
#Monochrome
|
||||
#This makes micro use only the terminal's default
|
||||
# foreground and background colours.
|
||||
@@ -1,30 +0,0 @@
|
||||
#Colorscheme styled after default Debian nano.
|
||||
color-link comment "bold blue"
|
||||
color-link comment.bright "cyan"
|
||||
color-link constant "red"
|
||||
color-link constant.bool "yellow"
|
||||
color-link constant.bool.true "bold green"
|
||||
color-link constant.bool.false "bold red"
|
||||
color-link constant.number "default"
|
||||
color-link constant.specialChar "bold magenta"
|
||||
color-link constant.string "bold yellow"
|
||||
color-link identifier "bold blue"
|
||||
color-link identifier.macro "bold red"
|
||||
color-link statement "bold green"
|
||||
color-link symbol "green"
|
||||
#color-link symbol.tag "blue"
|
||||
color-link preproc "brightcyan"
|
||||
color-link type "green"
|
||||
color-link special "magenta"
|
||||
color-link ignore "default"
|
||||
color-link error "white,black"
|
||||
color-link todo "bold cyan"
|
||||
color-link indent-char ",green"
|
||||
color-link line-number "default"
|
||||
color-link current-line-number "default"
|
||||
color-link gutter-error ",white"
|
||||
color-link gutter-warning "white"
|
||||
color-link cursor-line "default"
|
||||
color-link color-column "white"
|
||||
#No extended types ( bool in C ); Plain brackets
|
||||
color-link type.extended "default"
|
||||
@@ -1,22 +0,0 @@
|
||||
#Paper theme, true color edition
|
||||
#Edit on an *actual* white background!
|
||||
color-link default "#000000,#efefef"
|
||||
color-link comment ""
|
||||
color-link constant ""
|
||||
color-link constant.string ""
|
||||
color-link constant.string.url "underline #0000dd"
|
||||
color-link identifier ""
|
||||
color-link identifier.var ""
|
||||
color-link special ""
|
||||
color-link statement ""
|
||||
color-link symbol ""
|
||||
color-link symbol.brackets ""
|
||||
color-link symbol.tag ""
|
||||
color-link type ""
|
||||
color-link statusline ""
|
||||
color-link tabbar ""
|
||||
color-link error ""
|
||||
color-link todo ""
|
||||
color-link color-column ""
|
||||
color-link gutter-error ""
|
||||
color-link gutter-warning ""
|
||||
@@ -1,27 +0,0 @@
|
||||
#Paper theme, Edit on a white background.
|
||||
color-link default "black,white"
|
||||
color-link comment "bold black"
|
||||
color-link constant "cyan"
|
||||
color-link constant.string "bold green"
|
||||
color-link identifier "blue"
|
||||
color-link identifier.macro "bold red"
|
||||
color-link identifier.var "bold blue"
|
||||
color-link identifier.class "bold green"
|
||||
color-link statement "green"
|
||||
color-link symbol "red"
|
||||
color-link symbol.brackets "default"
|
||||
color-link symbol.tag "bold blue"
|
||||
color-link preproc "bold cyan"
|
||||
color-link type "green"
|
||||
color-link special "magenta"
|
||||
color-link ignore "default"
|
||||
color-link error ",brightred"
|
||||
color-link todo ",brightyellow"
|
||||
color-link indent-char ",brightgreen"
|
||||
color-link line-number "black"
|
||||
color-link statusline "white,black"
|
||||
color-link tabbar "white,black"
|
||||
color-link current-line-number "blue"
|
||||
color-link gutter-error ",red"
|
||||
color-link gutter-warning "red"
|
||||
color-link color-column "black"
|
||||
@@ -1,23 +0,0 @@
|
||||
#Symbian
|
||||
color-link default "#000000,#ff8a00"
|
||||
color-link comment "#8c0000"
|
||||
color-link constant "#8c0000"
|
||||
color-link identifier "#ffff8c"
|
||||
color-link preproc "#ffff8c,#8c0000"
|
||||
color-link special "#8c0000"
|
||||
color-link statement "#8c0000"
|
||||
color-link symbol "#8c0000"
|
||||
color-link type "#8c0000"
|
||||
color-link error "#ffff8c,#8c0000"
|
||||
color-link todo "#8c0000,#ffff8c"
|
||||
color-link statusline "#ffff8c,#8c0000"
|
||||
color-link tabbar "#ffff8c,#8c0000"
|
||||
color-link color-column "#8c0000"
|
||||
color-link line-number "#ffff8c,#8c0000"
|
||||
color-link current-line-number "#000000,#ff8a00"
|
||||
color-link gutter-error "#ffff8c,#8c0000"
|
||||
color-link gutter-warning "default"
|
||||
#000000
|
||||
#8c0000
|
||||
#ff8a00
|
||||
#ffff8c
|
||||
@@ -5,7 +5,7 @@ color-link constant.number "#F78C6A,#263238"
|
||||
color-link constant.specialChar "#89DDF3,#263238"
|
||||
color-link constant.string "#C3E88D,#263238"
|
||||
color-link current-line-number "#80DEEA,#263238"
|
||||
color-link cursor-line "#3b4d56"
|
||||
color-link cursor-line "#283942"
|
||||
color-link default "#EEFFFF,#263238"
|
||||
color-link divider "#263238,#80DEEA"
|
||||
color-link error "bold #263238,#F07178"
|
||||
@@ -14,7 +14,7 @@ color-link gutter-warning "#EEFFFF,#FFF176"
|
||||
color-link identifier "#82AAFF,#263238"
|
||||
color-link identifier.macro "#FFCB6B,#263238"
|
||||
color-link indent-char "#505050,#263238"
|
||||
color-link line-number "#656866,#263238"
|
||||
color-link line-number "#656866,#283942"
|
||||
color-link preproc "#C792EA,#263238"
|
||||
color-link special "#C792EA,#263238"
|
||||
color-link statement "#C792EA,#263238"
|
||||
|
||||
21
runtime/colorschemes/monokai-dark.micro
Normal file
21
runtime/colorschemes/monokai-dark.micro
Normal file
@@ -0,0 +1,21 @@
|
||||
color-link default "#D5D8D6,#1D0000"
|
||||
color-link comment "#75715E"
|
||||
color-link identifier "#66D9EF"
|
||||
color-link constant "#AE81FF"
|
||||
color-link constant.string "#E6DB74"
|
||||
color-link constant.string.char "#BDE6AD"
|
||||
color-link statement "#F92672"
|
||||
color-link preproc "#CB4B16"
|
||||
color-link type "#66D9EF"
|
||||
color-link special "#A6E22E"
|
||||
color-link underlined "#D33682"
|
||||
color-link error "bold #CB4B16"
|
||||
color-link todo "bold #D33682"
|
||||
color-link statusline "#282828,#F8F8F2"
|
||||
color-link indent-char "#505050,#282828"
|
||||
color-link line-number "#AAAAAA,#282828"
|
||||
color-link current-line-number "#AAAAAA,#1D0000"
|
||||
color-link gutter-error "#CB4B16"
|
||||
color-link gutter-warning "#E6DB74"
|
||||
color-link cursor-line "#323232"
|
||||
color-link color-column "#323232"
|
||||
32
runtime/colorschemes/one-dark.micro
Normal file
32
runtime/colorschemes/one-dark.micro
Normal file
@@ -0,0 +1,32 @@
|
||||
color-link default "#ABB2BF,#21252C"
|
||||
color-link color-column "#282C34"
|
||||
color-link comment "#5C6370"
|
||||
color-link constant "#C678DD"
|
||||
color-link constant.number "#E5C07B"
|
||||
color-link constant.string "#98C379"
|
||||
color-link constant.string.char "#BDE6AD"
|
||||
color-link constant.specialChar "#DDF2A4"
|
||||
color-link current-line-number "#C6C6C6,#21252C"
|
||||
color-link cursor-line "#282C34"
|
||||
color-link divider "#1E1E1E"
|
||||
color-link error "#D2A8A1"
|
||||
color-link gutter-error "#9B859D"
|
||||
color-link gutter-warning "#9B859D"
|
||||
color-link identifier "#61AFEF"
|
||||
color-link identifier.class "#C678DD"
|
||||
color-link identifier.var "#C678DD"
|
||||
color-link indent-char "#515151"
|
||||
color-link line-number "#636D83,#282C34"
|
||||
color-link preproc "#E0C589"
|
||||
color-link special "#E0C589"
|
||||
color-link statement "#C678DD"
|
||||
color-link statusline "#282828,#ABB2BF"
|
||||
color-link symbol "#AC885B"
|
||||
color-link symbol.brackets "#ABB2BF"
|
||||
color-link symbol.operator "#C678DD"
|
||||
color-link symbol.tag "#AC885B"
|
||||
color-link tabbar "#F2F0EC,#2D2D2D"
|
||||
color-link todo "#8B98AB"
|
||||
color-link type "#66D9EF"
|
||||
color-link type.keyword "#C678DD"
|
||||
color-link underlined "#8996A8"
|
||||
22
runtime/colorschemes/sunny-day.micro
Normal file
22
runtime/colorschemes/sunny-day.micro
Normal file
@@ -0,0 +1,22 @@
|
||||
color-link default "0,230"
|
||||
color-link comment "244"
|
||||
color-link constant.string "17"
|
||||
color-link constant "88"
|
||||
color-link identifier "22"
|
||||
color-link statement "0,230"
|
||||
color-link symbol "89"
|
||||
color-link preproc "22"
|
||||
color-link type "88"
|
||||
color-link special "22"
|
||||
color-link underlined "61,230"
|
||||
color-link error "88"
|
||||
color-link todo "210"
|
||||
color-link statusline "233,229"
|
||||
color-link tabbar "233,229"
|
||||
color-link indent-char "229"
|
||||
color-link line-number "244"
|
||||
color-link gutter-error "88"
|
||||
color-link gutter-warning "88"
|
||||
color-link cursor-line "229"
|
||||
#color-link color-column "196"
|
||||
color-link current-line-number "246"
|
||||
@@ -2,37 +2,54 @@
|
||||
|
||||
This help page aims to cover two aspects of micro's syntax highlighting engine:
|
||||
|
||||
- How to create colorschemes and use them.
|
||||
- How to create syntax files to add to the list of languages micro can highlight.
|
||||
* How to create colorschemes and use them.
|
||||
* How to create syntax files to add to the list of languages micro can highlight.
|
||||
|
||||
## 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:
|
||||
|
||||
```
|
||||
set colorscheme monokai
|
||||
set colorscheme twilight
|
||||
```
|
||||
|
||||
(or whichever colorscheme you choose).
|
||||
|
||||
Micro comes with a number of colorschemes by default. Modern terminals tend to
|
||||
have three different kinds of color support. The most common is 256 color where
|
||||
the terminal provides 256 standardized colors (except the first 16 may be configured
|
||||
by the user). A 256-color theme requires a terminal with 256 color support and
|
||||
is the most portable.
|
||||
Micro comes with a number of colorschemes by default. The colorschemes that you
|
||||
can display will depend on what kind of color support your terminal has.
|
||||
|
||||
A 16-color theme uses the 16 user-configurable colors (or 16 default colors on
|
||||
old terminals). These colorschemes are guranteed to work, but won't look great
|
||||
unless the 16 colors are configured to the user's liking. Using a 16-color theme
|
||||
will also preserve the terminal's theme because the terminal usually uses its 16
|
||||
colors for prompts or other coloring.
|
||||
Modern terminals tend to have a palette of 16 user-configurable colors (these
|
||||
colors can often be configured in the terminal preferences), and additional
|
||||
color support comes in three flavors.
|
||||
|
||||
Some terminals support "true color" with 16 million colors (using standard RGB values).
|
||||
There is no one standard for this color support among terminals so this method
|
||||
is not guaranteed to work. Usually truecolor must also be enabled by the user. The
|
||||
colorschemes using true color will look exactly as intended. If true color is not
|
||||
supported, a true color colorscheme will approximate its colors to 256-color.
|
||||
* 16-color: A colorscheme that uses the 16 default colors will always work but
|
||||
will only look good if the 16 default colors have been configured to the user's
|
||||
liking. Using a colorscheme that only uses the 16 colors from the terminal palette
|
||||
will also preserve the terminal's theme from other applications since the terminal
|
||||
will often use those same colors for other applications. Default colorschemes
|
||||
of this type include `simple` and `solarized`.
|
||||
|
||||
* 256-color: Almost all terminals support displaying an additional 240 colors on
|
||||
top of the 16 user-configurable colors (creating 256 colors total). Colorschemes
|
||||
which use 256-color are portable because they will look the same regardless of
|
||||
the configured 16-color palette. However, the color range is fairly limited
|
||||
due to the small number of colors available. Default 256-color colorschemes
|
||||
include `monokai`, `twilight`, `zenburn`, `darcula` and more.
|
||||
|
||||
* true-color: Some terminals support displaying "true color" with 16 million
|
||||
colors using standard RGB values. This mode will be able to support displaying
|
||||
any colorscheme, but it should be noted that the user-configured 16-color palette
|
||||
is ignored when using true-color mode (this means the colors while using the
|
||||
terminal emulator will be slightly off). Not all terminals support true color
|
||||
but at this point most do. True color support in micro is off by default but
|
||||
can be enabled by setting the environment variable `MICRO_TRUECOLOR` to 1.
|
||||
In addition your terminal must support it (usually indicated by setting `$COLORTERM`
|
||||
to `truecolor`).
|
||||
True-color colorschemes in micro typically end with `-tc`, such as `solarized-tc`,
|
||||
`atom-dark-tc`, `material-tc`, etc... If true color is not enabled but a true
|
||||
color colorscheme is used, micro will do its best to approximate the colors
|
||||
to the available 256 colors.
|
||||
|
||||
Here is the list of colorschemes:
|
||||
|
||||
@@ -61,7 +78,9 @@ These may vary widely based on the 16 colors selected for your terminal.
|
||||
|
||||
### True color
|
||||
|
||||
These require terminals that support true color and require `MICRO_TRUECOLOR=1` (this is an environment variable).
|
||||
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.
|
||||
|
||||
* `solarized-tc`: this is the solarized colorscheme for true color.
|
||||
* `atom-dark-tc`: this colorscheme is based off of Atom's "dark" colorscheme.
|
||||
@@ -75,9 +94,12 @@ These require terminals that support true color and require `MICRO_TRUECOLOR=1`
|
||||
Micro's colorschemes are also extremely simple to create. The default ones can
|
||||
be found [here](https://github.com/zyedidia/micro/tree/master/runtime/colorschemes).
|
||||
|
||||
They are only about 18-30 lines in total.
|
||||
Custom colorschemes should be placed in the `~/.config/micro/colorschemes` directory.
|
||||
|
||||
Basically to create the colorscheme you need to link highlight groups with
|
||||
A number of custom directives are placed in a `.micro` file. Colorschemes are
|
||||
typically only 18-30 lines in total.
|
||||
|
||||
To create the colorscheme you need to link highlight groups with
|
||||
actual colors. This is done using the `color-link` command.
|
||||
|
||||
For example, to highlight all comments in green, you would use the command:
|
||||
@@ -200,7 +222,7 @@ In the future, plugins may also be able to use color groups for styling.
|
||||
|
||||
## Syntax files
|
||||
|
||||
The syntax files is written in yaml-format and specify how to highlight
|
||||
The syntax files are written in yaml-format and specify how to highlight
|
||||
languages.
|
||||
|
||||
Micro's builtin syntax highlighting tries very hard to be sane, sensible and
|
||||
@@ -218,7 +240,7 @@ You must start the syntax file by declaring the filetype:
|
||||
filetype: go
|
||||
```
|
||||
|
||||
#### Detect definition
|
||||
### Detect definition
|
||||
|
||||
Then you must provide information about how to detect the filetype:
|
||||
|
||||
@@ -237,7 +259,7 @@ detect:
|
||||
header: "%YAML"
|
||||
```
|
||||
|
||||
#### Syntax rules
|
||||
### Syntax rules
|
||||
|
||||
Next you must provide the syntax highlighting rules. There are two types of
|
||||
rules: patterns and regions. A pattern is matched on a single line and usually a
|
||||
@@ -316,3 +338,16 @@ example, the following is possible for html:
|
||||
rules:
|
||||
- include: "css"
|
||||
```
|
||||
|
||||
## Syntax file headers
|
||||
|
||||
Syntax file headers are an optimization and it is likely you do not need to
|
||||
worry about them.
|
||||
|
||||
Syntax file headers are files that contain only the filetype and the detection
|
||||
regular expressions for a given syntax file. They have a `.hdr` suffix and are
|
||||
used by default only for the pre-installed syntax files. Header files allow micro
|
||||
to parse the syntax files much faster when checking the filetype of a certain
|
||||
file. Custom syntax files may provide header files in `~/.config/micro/syntax` as
|
||||
well but it is not necessary (only do this if you have many (100+) custom syntax
|
||||
files and want to improve performance).
|
||||
|
||||
@@ -1,14 +1,35 @@
|
||||
# Possible commands
|
||||
# Command bar
|
||||
|
||||
You can execute an editor command by pressing `Ctrl-e` followed by the command.
|
||||
Here are the possible commands that you can use.
|
||||
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).
|
||||
|
||||
* `quit`: Quits micro.
|
||||
When running a command, you can use extra syntax that micro will expand before
|
||||
running the command. To use an argument with a space in it, put it in
|
||||
quotes. The command bar parser uses the same rules for parsing arguments that
|
||||
`/bin/sh` would use (single quotes, double quotes, escaping). The command bar
|
||||
does not look up environment variables.
|
||||
|
||||
* `save filename?`: Saves the current buffer. If the filename is provided it
|
||||
will 'save as' the filename.
|
||||
# Commands
|
||||
|
||||
* `replace "search" "value" flags`: This will replace `search` with `value`.
|
||||
Micro provides the following commands that can be executed at the command-bar 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
|
||||
`keybindings` documentation for more information about binding keys.
|
||||
This command will modify `bindings.json` and overwrite any bindings to
|
||||
`key` that already exist.
|
||||
|
||||
* `help 'topic'?`: opens the corresponding help topic. If no topic is provided
|
||||
opens the default help screen.
|
||||
|
||||
* `save 'filename'?`: saves the current buffer. If the file is provided it
|
||||
will 'save as' the filename.
|
||||
|
||||
* `quit`: quits micro.
|
||||
|
||||
* `replace 'search' 'value' 'flags'?`: This will replace `search` with `value`.
|
||||
The `flags` are optional. Possible flags are:
|
||||
* `-a`: Replace all occurrences at once
|
||||
* `-l`: Do a literal search instead of a regex search
|
||||
@@ -16,79 +37,81 @@ Here are the possible commands that you can use.
|
||||
Note that `search` must be a valid regex (unless `-l` is passed). If one
|
||||
of the arguments does not have any spaces in it, you may omit the quotes.
|
||||
|
||||
* `replaceall "search" "value"`: This will replace `search` with `value` without
|
||||
user confirmation.
|
||||
* `replaceall 'search' 'value'`: this will replace all occurrences of `search`
|
||||
with `value` without user confirmation.
|
||||
|
||||
See `replace` command for more information.
|
||||
|
||||
* `set option value`: sets the option to value. See the `options` help topic for
|
||||
a list of options you can set.
|
||||
* `set 'option' 'value'`: sets the option to value. See the `options` help topic for
|
||||
a list of options you can set. This will modify your `settings.json` with the
|
||||
new value.
|
||||
|
||||
* `setlocal option value`: sets the option to value locally (only in the current
|
||||
buffer).
|
||||
* `setlocal 'option' 'value'`: sets the option to value locally (only in the current
|
||||
buffer). This will *not* modify `settings.json`.
|
||||
|
||||
* `show option`: shows the current value of the given option.
|
||||
* `show 'option'`: shows the current value of the given option.
|
||||
|
||||
* `run sh-command`: runs the given shell command in the background. The
|
||||
* `run 'sh-command'`: runs the given shell command in the background. The
|
||||
command's output will be displayed in one line when it finishes running.
|
||||
|
||||
* `bind key action`: creates a keybinding from key to action. See the sections
|
||||
on keybindings above for more info about what keys and actions are available.
|
||||
|
||||
* `vsplit filename`: opens a vertical split with `filename`. If no filename is
|
||||
* `vsplit 'filename'`: opens a vertical split with `filename`. If no filename is
|
||||
provided, a vertical split is opened with an empty buffer.
|
||||
|
||||
* `hsplit filename`: same as `vsplit` but opens a horizontal split instead of a
|
||||
* `hsplit 'filename'`: same as `vsplit` but opens a horizontal split instead of a
|
||||
vertical split.
|
||||
|
||||
* `tab filename`: opens the given file in a new tab.
|
||||
* `tab 'filename'`: opens the given file in a new tab.
|
||||
|
||||
* `tabswitch tab`: This command will switch to the specified tab. The `tab` can
|
||||
* `tabswitch 'tab'`: This command will switch to the specified tab. The `tab` can
|
||||
either be a tab number, or a name of a tab.
|
||||
|
||||
|
||||
* `textfilter 'sh-command'`: filters the current selection through a shell command
|
||||
as standard input and replaces the selection with the stdout of the shell command.
|
||||
For example, to sort a list of numbers, first select them, and then execute
|
||||
`> textfilter sort -n`.
|
||||
|
||||
* `log`: opens a log of all messages and debug statements.
|
||||
|
||||
* `plugin list`: lists all installed plugins.
|
||||
|
||||
* `plugin version pl`: shows version for specified plugin.
|
||||
* `plugin install 'pl'`: install a plugin.
|
||||
|
||||
* `plugin info pl`: shows additional info for specified plugin.
|
||||
* `plugin remove 'pl'`: remove a plugin.
|
||||
|
||||
* `plugin update 'pl'`: update a plugin (if no arguments are provided
|
||||
updates all plugins).
|
||||
|
||||
* `plugin search 'pl'`: search available plugins for a keyword.
|
||||
|
||||
* `plugin available`: show available plugins that can be installed.
|
||||
|
||||
* `reload`: reloads all runtime files.
|
||||
|
||||
* `cd path`: Change the working directory to the given `path`.
|
||||
* `cd 'path'`: Change the working directory to the given `path`.
|
||||
|
||||
* `pwd`: Print the current working directory.
|
||||
|
||||
* `open filename`: Open a file in the current buffer.
|
||||
* `open 'filename'`: Open a file in the current buffer.
|
||||
|
||||
* `reset option`: resets the given option to its default value
|
||||
* `reset 'option'`: resets the given option to its default value
|
||||
|
||||
* `retab`: Replaces all leading tabs with spaces or leading spaces with tabs
|
||||
depending on the value of `tabstospaces`.
|
||||
|
||||
* `raw`: Micro will open a new tab and show the escape sequence for every event
|
||||
* `raw`: micro will open a new tab and show the escape sequence for every event
|
||||
it receives from the terminal. This shows you what micro actually sees from
|
||||
the terminal and helps you see which bindings aren't possible and why. This
|
||||
is most useful for debugging keybindings.
|
||||
|
||||
* `showkey`: Show the action(s) bound to a given key. For example
|
||||
running `> showkey CtrlC` will display `main.(*View).Copy`. Unfortuately
|
||||
showkey does not work well for keys bound to plugin actions. For those
|
||||
it just shows "LuaFunctionBinding."
|
||||
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 emulator.
|
||||
|
||||
---
|
||||
|
||||
The following commands are provided by the default plugins:
|
||||
|
||||
* `lint`: Lint the current file for errors.
|
||||
|
||||
* `comment`: automatically comment or uncomment current selection or line.
|
||||
|
||||
# Command Parsing
|
||||
|
||||
When running a command, you can use extra syntax that micro will expand before
|
||||
running the command. To use an argument with a space in it, simply put it in
|
||||
quotes. You can also use environment variables in the command bar and they
|
||||
will be expanded to their value. Finally, you can put an expression in backticks
|
||||
and it will be evaluated by the shell beforehand.
|
||||
|
||||
109
runtime/help/copypaste.md
Normal file
109
runtime/help/copypaste.md
Normal file
@@ -0,0 +1,109 @@
|
||||
Copy and paste are essential features in micro but can be
|
||||
confusing to get right especially when running micro over SSH
|
||||
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.
|
||||
|
||||
# Pasting
|
||||
|
||||
## Micro paste events
|
||||
|
||||
Micro is an application that runs within the terminal. This means
|
||||
that the terminal sends micro events, such as key events, mouse
|
||||
events, resize events, and paste events. Micro's default keybinding
|
||||
for paste is Ctrl-v. This means that when micro receives the key
|
||||
event saying Ctrl-v has been pressed from the terminal, it will
|
||||
attempt to access the system clipboard and effect a paste. The
|
||||
system clipboard will be accessed through `pbpaste` on MacOS
|
||||
(installed by default), `xclip` or `xsel` on Linux (these
|
||||
applications must be installed by the user) or a system call on
|
||||
Windows.
|
||||
|
||||
## Terminal paste events
|
||||
|
||||
For certain keypresses, the terminal will not send an event to
|
||||
micro and will instead do something itself. In this document,
|
||||
such keypresses will be called "terminal keybindings." Often
|
||||
there will be a terminal keybinding for pasting and copying. On
|
||||
MacOS these are Command-v and Command-c and on Linux Ctrl-Shift-v
|
||||
and Ctrl-Shift-c. When the terminal keybinding for paste is
|
||||
executed, your terminal will access the system clipboard, and send
|
||||
micro either a paste event or a list of key events (one key for each
|
||||
character in the paste), depending on whether or not your terminal
|
||||
supports sending paste events (called bracketed paste).
|
||||
|
||||
If your terminal supports bracketed paste, then it will send a paste
|
||||
event and everything will work well. However, if your terminal
|
||||
sends a list of key events, this can cause issues because micro
|
||||
will think you manually entered each character and may add closing
|
||||
brackets or automatic indentation, which will mess up the pasted
|
||||
text. To avoid this, you can temporarily enable the `paste` option
|
||||
while you perform the paste. When paste option is on, micro will
|
||||
aggregate lists of multiple key events into larger paste events.
|
||||
It is a good idea to disable the `paste` option during normal use
|
||||
as occasionally if you are typing quickly, the terminal will send
|
||||
the key events as lists of characters that were in fact manually
|
||||
entered.
|
||||
|
||||
## Pasting over SSH
|
||||
|
||||
When working over SSH, micro is running on the remote machine and
|
||||
your terminal is running on your local machine. Therefore if you
|
||||
would like to paste, using Ctrl-v (micro's keybinding) will not
|
||||
work because when micro attempts to access the system clipboard,
|
||||
it will access the remote machine's clipboard rather than the local
|
||||
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.
|
||||
|
||||
## Recommendations
|
||||
|
||||
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.
|
||||
|
||||
# Copying
|
||||
|
||||
Copying follows a similar discussion to the one above about pasting.
|
||||
The primary difference is before performing a copy, the application
|
||||
doing the copy must be told what text needs to be copied.
|
||||
|
||||
Micro has a keybinding (Ctrl-c) for copying and will access the system
|
||||
clipboard to perform the copy. The text that micro will copy into is
|
||||
the text that is currently selected in micro (usually such text is
|
||||
displayed with a white background). When the `mouse` option is enabled,
|
||||
the mouse can be used to select text, as well as other keybindings,
|
||||
such as ShiftLeft, etc...
|
||||
|
||||
The terminal also has a keybinding (Ctrl-Shift-c or Command-c) to perform
|
||||
a copy, and the text that it copies is the text selected by the terminal's
|
||||
selection (*not* micro's selection). To select text with the terminal
|
||||
selection, micro's mouse support must first be disabled by turning the
|
||||
`mouse` option off. The terminal, unlike micro, has no sense of different
|
||||
buffers/splits and what the different characters being displayed are. This
|
||||
means that for copying multiple lines using the terminal selection, you
|
||||
should first disable line numbers (turn off the `ruler` option), 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 (`ruler` option) 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).
|
||||
@@ -92,6 +92,8 @@ can change it!
|
||||
| Key | Description of function |
|
||||
|------------------ |---------------------------------------------------------------------------------------------- |
|
||||
| Alt+N | Create new multiple cursor from selection (will select current word if no current selection) |
|
||||
| AltShiftUp | Spawn a new cursor on the line above the current one |
|
||||
| AltShiftDown | Spawn a new cursor on the line below the current one |
|
||||
| Alt+P | Remove latest multiple cursor |
|
||||
| Alt+C | Remove all multiple cursors (cancel) |
|
||||
| Alt+X | Skip multiple cursor selection |
|
||||
|
||||
@@ -13,7 +13,7 @@ For more information on keybindings see `> help keybindings`.
|
||||
|
||||
## Quick-start
|
||||
|
||||
Press CtrlQ to quit, and CtrlS to save. Press CtrlE to start typing commands and
|
||||
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`.
|
||||
|
||||
@@ -23,14 +23,14 @@ what they do. For more info on rebinding keys, see type `> help keybindings`.
|
||||
|
||||
If the colorscheme doesn't look good, you can change it with
|
||||
`> set colorscheme ...`. You can press tab to see the available colorschemes, or
|
||||
see more information with `> help colors`.
|
||||
see more information about colorschemes and syntax highlighting with `> help colors`.
|
||||
|
||||
Press CtrlW to move between splits, and type `> vsplit filename` or
|
||||
Press Ctrl-w to move between splits, and type `> vsplit filename` or
|
||||
`> hsplit filename` to open a new split.
|
||||
|
||||
## Accessing more help
|
||||
|
||||
Micro has a built-in help system much like Vim's (although less extensive).
|
||||
Micro has a built-in help system which can be accessed with the `help` command.
|
||||
|
||||
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.
|
||||
|
||||
@@ -47,6 +47,21 @@ save and quit you can bind it like so:
|
||||
}
|
||||
```
|
||||
|
||||
Each action will return a success flag. Actions can be chained such that
|
||||
the chain only continues when there are successes, or failures, or either.
|
||||
The `,` separator will always chain to the next action. The `|` separator
|
||||
will abort the chain if the action preceding it succeeds, and the `&` will
|
||||
abort the chain if the action preceding it fails. For example, in the default
|
||||
bindings, tab is bound as
|
||||
|
||||
```
|
||||
"Tab": "Autocomplete|IndentSelection|InsertTab"
|
||||
```
|
||||
|
||||
This means that if the `Autocomplete` action is successful, the chain will abort.
|
||||
Otherwise, it will try `IndentSelection`, and if that fails too, it will
|
||||
execute `InsertTab`.
|
||||
|
||||
## Binding commands
|
||||
|
||||
You can also bind a key to execute a command in command mode (see
|
||||
@@ -215,12 +230,15 @@ Suspend (Unix only)
|
||||
ScrollUp
|
||||
ScrollDown
|
||||
SpawnMultiCursor
|
||||
SpawnMultiCursorUp
|
||||
SpawnMultiCursorDown
|
||||
SpawnMultiCursorSelect
|
||||
RemoveMultiCursor
|
||||
RemoveAllMultiCursors
|
||||
SkipMultiCursor
|
||||
None
|
||||
JumpToMatchingBrace
|
||||
Autocomplete
|
||||
```
|
||||
|
||||
You can also bind some mouse actions (these must be bound to mouse buttons)
|
||||
@@ -407,8 +425,8 @@ MouseWheelRight
|
||||
"Backspace": "Backspace",
|
||||
"Alt-CtrlH": "DeleteWordLeft",
|
||||
"Alt-Backspace": "DeleteWordLeft",
|
||||
"Tab": "IndentSelection,InsertTab",
|
||||
"Backtab": "OutdentSelection,OutdentLine",
|
||||
"Tab": "Autocomplete|IndentSelection|InsertTab",
|
||||
"Backtab": "OutdentSelection|OutdentLine",
|
||||
"CtrlO": "OpenFile",
|
||||
"CtrlS": "Save",
|
||||
"CtrlF": "Find",
|
||||
@@ -431,9 +449,12 @@ MouseWheelRight
|
||||
"CtrlEnd": "CursorEnd",
|
||||
"PageUp": "CursorPageUp",
|
||||
"PageDown": "CursorPageDown",
|
||||
"CtrlPageUp": "PreviousTab",
|
||||
"CtrlPageDown": "NextTab",
|
||||
"CtrlG": "ToggleHelp",
|
||||
"Alt-g": "ToggleKeyMenu",
|
||||
"CtrlR": "ToggleRuler",
|
||||
"CtrlL": "JumpLine",
|
||||
"CtrlL": "command-edit:goto ",
|
||||
"Delete": "Delete",
|
||||
"CtrlB": "ShellMode",
|
||||
"CtrlQ": "Quit",
|
||||
@@ -441,6 +462,7 @@ MouseWheelRight
|
||||
"CtrlW": "NextSplit",
|
||||
"CtrlU": "ToggleMacro",
|
||||
"CtrlJ": "PlayMacro",
|
||||
"Insert": "ToggleOverwriteMode",
|
||||
|
||||
// Emacs-style keybindings
|
||||
"Alt-f": "WordRight",
|
||||
@@ -449,7 +471,6 @@ MouseWheelRight
|
||||
"Alt-e": "EndOfLine",
|
||||
|
||||
// Integration with file managers
|
||||
"F1": "ToggleHelp",
|
||||
"F2": "Save",
|
||||
"F3": "Find",
|
||||
"F4": "Quit",
|
||||
@@ -464,12 +485,13 @@ MouseWheelRight
|
||||
"MouseMiddle": "PastePrimary",
|
||||
"Ctrl-MouseLeft": "MouseMultiCursor",
|
||||
|
||||
// Multiple cursors bindings
|
||||
"Alt-n": "SpawnMultiCursor",
|
||||
"Alt-m": "SpawnMultiCursorSelect",
|
||||
"Alt-p": "RemoveMultiCursor",
|
||||
"Alt-c": "RemoveAllMultiCursors",
|
||||
"Alt-x": "SkipMultiCursor",
|
||||
"Alt-n": "SpawnMultiCursor",
|
||||
"AltShiftUp": "SpawnMultiCursorUp",
|
||||
"AltShiftDown": "SpawnMultiCursorDown",
|
||||
"Alt-m": "SpawnMultiCursorSelect",
|
||||
"Alt-p": "RemoveMultiCursor",
|
||||
"Alt-c": "RemoveAllMultiCursors",
|
||||
"Alt-x": "SkipMultiCursor",
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -2,26 +2,20 @@
|
||||
|
||||
Micro stores all of the user configuration in its configuration directory.
|
||||
|
||||
Micro uses the `$XDG_CONFIG_HOME/micro` as the configuration directory. As per
|
||||
the XDG spec, if `$XDG_CONFIG_HOME` is not set, `~/.config/micro` is used as
|
||||
the config directory.
|
||||
Micro uses `$MICRO_CONFIG_HOME` as the configuration directory. If this environment
|
||||
variable is not set, it uses `$XDG_CONFIG_HOME/micro` instead. If that
|
||||
environment variable is not set, it uses `~/.config/micro` as the configuration
|
||||
directory. In the documentation, we use `~/.config/micro` to refer to the
|
||||
configuration directory (even if it may in fact be somewhere else if you have
|
||||
set either of the above environment variables).
|
||||
|
||||
Here are the options that you can set:
|
||||
Here are the available options:
|
||||
|
||||
* `autoindent`: when creating a new line, use the same indentation as the
|
||||
previous line.
|
||||
|
||||
default value: `true`
|
||||
|
||||
* `autosave`: micro will save the buffer every `n` seconds automatically
|
||||
(where `n` is the value of the option). Micro also will automatically
|
||||
save and quit when you exit without asking. Be careful when using this
|
||||
feature, because you might accidentally save a file, overwriting what
|
||||
was there before. If the value of the option is `0` then micro will
|
||||
not autosave.
|
||||
|
||||
default value: `0`
|
||||
|
||||
* `backup`: micro will automatically keep backups of all open buffers. Backups
|
||||
are stored in `~/.config/micro/backups` and are removed when the buffer is
|
||||
closed cleanly. In the case of a system crash or a micro crash, the contents
|
||||
@@ -94,11 +88,11 @@ Here are the options that you can set:
|
||||
|
||||
default value: `unix`
|
||||
|
||||
* `filetype`: sets the filetype for the current buffer. This setting is
|
||||
`local only`.
|
||||
* `filetype`: sets the filetype for the current buffer. Set this option to `off`
|
||||
to completely disable filetype detection.
|
||||
|
||||
default value: this will be automatically set depending on the file you have
|
||||
open
|
||||
default value: `unknown`. This will be automatically overridden depending
|
||||
on the file you open.
|
||||
|
||||
* `ignorecase`: perform case-insensitive searches.
|
||||
|
||||
@@ -137,7 +131,7 @@ Here are the options that you can set:
|
||||
|
||||
default value: `false`
|
||||
|
||||
* `mouse`: whether to enable mouse support. When mouse support is disabled,
|
||||
* `mouse`: mouse support. When mouse support is disabled,
|
||||
usually the terminal will be able to access mouse events which can be useful
|
||||
if you want to copy from the terminal instead of from micro (if over ssh for
|
||||
example, because the terminal has access to the local clipboard and micro
|
||||
@@ -145,7 +139,22 @@ Here are the options that you can set:
|
||||
|
||||
default value: `true`
|
||||
|
||||
* `rmtrailingws`: micro will automatically trim trailing whitespaces at eol.
|
||||
* `paste`: Treat characters sent from the terminal in a single chunk as a paste
|
||||
event rather than a series of manual key presses. If you are pasting using
|
||||
the terminal keybinding (not Ctrl-v, which is micro's default paste keybinding)
|
||||
then it is a good idea to enable this option during the paste and disable
|
||||
once the paste is over. See `> help copypaste` for details about copying
|
||||
and pasting in a terminal environment.
|
||||
|
||||
default value: `false`
|
||||
|
||||
* `readonly`: when enabled, disallows edits to the buffer. It is recommended
|
||||
to only ever set this option locally using `setlocal`.
|
||||
|
||||
default value: `false`
|
||||
|
||||
* `rmtrailingws`: micro will automatically trim trailing whitespaces at ends of
|
||||
lines.
|
||||
|
||||
default value: `false`
|
||||
|
||||
@@ -154,17 +163,19 @@ Here are the options that you can set:
|
||||
default value: `true`
|
||||
|
||||
* `savecursor`: remember where the cursor was last time the file was opened and
|
||||
put it there when you open the file again.
|
||||
put it there when you open the file again. Information is saved to
|
||||
`~/.config/micro/buffers/`
|
||||
|
||||
default value: `false`
|
||||
|
||||
* `savehistory`: remember command history between closing and re-opening
|
||||
micro.
|
||||
micro. Information is saved to `~/.config/micro/buffers/history`.
|
||||
|
||||
default value: `true`
|
||||
|
||||
* `saveundo`: when this option is on, undo is saved even after you close a file
|
||||
so if you close and reopen a file, you can keep undoing.
|
||||
so if you close and reopen a file, you can keep undoing. Information is
|
||||
saved to `~/.config/micro/buffers/`.
|
||||
|
||||
default value: `false`
|
||||
|
||||
@@ -172,8 +183,8 @@ Here are the options that you can set:
|
||||
|
||||
default value: `false`
|
||||
|
||||
* `scrollmargin`: amount of lines you would like to see above and below the
|
||||
cursor.
|
||||
* `scrollmargin`: margin at which the view starts scrolling when the cursor
|
||||
approaches the edge of the view.
|
||||
|
||||
default value: `3`
|
||||
|
||||
@@ -181,23 +192,23 @@ Here are the options that you can set:
|
||||
|
||||
default value: `2`
|
||||
|
||||
* `smartpaste`: should micro add leading whitespace when pasting multiple lines?
|
||||
* `smartpaste`: add leading whitespace when pasting multiple lines.
|
||||
This will attempt to preserve the current indentation level when pasting an
|
||||
unindented block.
|
||||
|
||||
default value: `true`
|
||||
|
||||
* `softwrap`: should micro wrap lines that are too long to fit on the screen.
|
||||
* `softwrap`: wrap lines that are too long to fit on the screen.
|
||||
|
||||
default value: `false`
|
||||
|
||||
* `splitbottom`: when a horizontal split is created, should it be created below
|
||||
the current split?
|
||||
* `splitbottom`: when a horizontal split is created, create it below the
|
||||
current split.
|
||||
|
||||
default value: `true`
|
||||
|
||||
* `splitright`: when a vertical split is created, should it be created to the
|
||||
right of the current split?
|
||||
* `splitright`: when a vertical split is created, create it to the right of the
|
||||
current split.
|
||||
|
||||
default value: `true`
|
||||
|
||||
@@ -207,14 +218,13 @@ Here are the options that you can set:
|
||||
The `opt` and `bind` directives take either an option or an action afterward
|
||||
and fill in the value of the option or the key bound to the action.
|
||||
|
||||
default value: `$(filename) $(modified)($(line),$(col)) $(opt:filetype)
|
||||
$(opt:fileformat) $(opt:encoding)`
|
||||
default value: `$(filename) $(modified)($(line),$(col)) $(status.paste)|
|
||||
ft:$(opt:filetype) | $(opt:fileformat) | $(opt:encoding)`
|
||||
|
||||
* `statusformatl`: format string definition for the left-justified part of the
|
||||
* `statusformatr`: format string definition for the right-justified part of the
|
||||
statusline.
|
||||
|
||||
default value: `$(bind:ToggleKeyMenu): show bindings, $(bind:ToggleHelp):
|
||||
toggle help`
|
||||
default value: `$(bind:ToggleKeyMenu): bindings, $(bind:ToggleHelp): help`
|
||||
|
||||
* `statusline`: display the status line at the bottom of the screen.
|
||||
|
||||
@@ -226,7 +236,7 @@ Here are the options that you can set:
|
||||
|
||||
default value: `sudo`
|
||||
|
||||
* `syntax`: turns syntax on or off.
|
||||
* `syntax`: enables syntax highlighting.
|
||||
|
||||
default value: `true`
|
||||
|
||||
@@ -236,31 +246,26 @@ Here are the options that you can set:
|
||||
|
||||
default value: `false`
|
||||
|
||||
* `tabsize`: sets the tab size to `option`
|
||||
* `tabsize`: the size in spaces that a tab character should be displayed with.
|
||||
|
||||
default value: `4`
|
||||
|
||||
* `tabstospaces`: use spaces instead of tabs
|
||||
* `tabstospaces`: use spaces instead of tabs.
|
||||
|
||||
default value: `false`
|
||||
|
||||
* `termtitle`: defines whether or not your terminal's title will be set by micro
|
||||
when opened.
|
||||
|
||||
default value: `false`
|
||||
|
||||
* `useprimary` (only useful on *nix): defines whether or not micro will use the
|
||||
* `useprimary` (only useful on unix): defines whether or not micro will use the
|
||||
primary clipboard to copy selections in the background. This does not affect
|
||||
the normal clipboard using Ctrl-C and Ctrl-V.
|
||||
the normal clipboard using Ctrl-c and Ctrl-v.
|
||||
|
||||
default value: `true`
|
||||
|
||||
---
|
||||
|
||||
Plugin options: all plugins come with a special option to enable or disable them. THe option
|
||||
Plugin options: all plugins come with a special option to enable or disable them. The option
|
||||
is a boolean with the same name as the plugin itself.
|
||||
|
||||
Any option you set in the editor will be saved to the file
|
||||
Any option you set in the editor will be saved to the file
|
||||
~/.config/micro/settings.json so, in effect, your configuration file will be
|
||||
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.
|
||||
@@ -270,7 +275,8 @@ machine, simply copy the settings.json to the other machine.
|
||||
You can set these settings either globally or locally. Locally means that the
|
||||
setting won't be saved to `~/.config/micro/settings.json` and that it will only
|
||||
be set in the current buffer. Setting an option globally is the default, and
|
||||
will set the option in all buffers.
|
||||
will set the option in all buffers. Use the `setlocal` command to set an option
|
||||
locally rather than globally.
|
||||
|
||||
The `colorscheme` option is global only, and the `filetype` option is local
|
||||
only. To set an option locally, use `setlocal` instead of `set`.
|
||||
|
||||
@@ -9,12 +9,36 @@ plugin's website, dependencies, etc... Here is an example info file
|
||||
from the go plugin, which has the following file structure:
|
||||
|
||||
```
|
||||
~/.config/micro/plug/go-plugin
|
||||
~/.config/micro/plug/go-plugin/
|
||||
go.lua
|
||||
info.json
|
||||
help/
|
||||
go-plugin.md
|
||||
```
|
||||
|
||||
info.json:
|
||||
The `go.lua` file contains the main code for the plugin, though the
|
||||
code may be distributed across multiple Lua files. The `info.json`
|
||||
file contains information about the plugin such as the website,
|
||||
description, version, and any requirements. Plugins may also
|
||||
have additional files which can be added to micro's runtime files,
|
||||
of which there are 5 types:
|
||||
|
||||
* Colorschemes
|
||||
* Syntax files
|
||||
* Help files
|
||||
* Plugin files
|
||||
* Syntax header files
|
||||
|
||||
In most cases, a plugin will want to add help files, but in certain
|
||||
cases a plugin may also want to add colorschemes or syntax files. It
|
||||
is unlikely for a plugin to need to add plugin files at runtime or
|
||||
syntax header files. No directory structure is enforced but keeping
|
||||
runtime files in their own directories is good practice.
|
||||
|
||||
# Info file
|
||||
|
||||
The `info.json` for the Go plugin is the following:
|
||||
|
||||
```
|
||||
{
|
||||
"name": "go",
|
||||
@@ -35,6 +59,10 @@ the website should point to a valid website. The install field should
|
||||
provide info about installing the plugin, or point to a website that
|
||||
provides information.
|
||||
|
||||
Note that the name of the plugin is defined by the name field in
|
||||
the `info.json` and not by the installation path. Some functions micro
|
||||
exposes to plugins require passing the name of the plugin.
|
||||
|
||||
## Lua callbacks
|
||||
|
||||
Plugins use Lua but also have access to many functions both from micro
|
||||
@@ -98,14 +126,14 @@ local micro = import("micro")
|
||||
micro.Log("Hello")
|
||||
```
|
||||
|
||||
The packages and functions are listed below:
|
||||
The packages and functions are listed below (in Go type signatures):
|
||||
|
||||
* `micro`
|
||||
- `TermMessage(msg interface{}...)`
|
||||
- `TermError()`
|
||||
- `InfoBar()`
|
||||
- `Log(msg interface{}...)`
|
||||
- `SetStatusInfoFn`
|
||||
- `SetStatusInfoFn(fn string)`
|
||||
* `micro/config`
|
||||
- `MakeCommand`
|
||||
- `FileComplete`
|
||||
@@ -150,6 +178,8 @@ The packages and functions are listed below:
|
||||
- `BTInfo`
|
||||
- `NewBufferFromFile`
|
||||
- `ByteOffset`
|
||||
- `Log`
|
||||
- `LogBuf`
|
||||
* `micro/util`
|
||||
- `RuneAt`
|
||||
- `GetLeadingWhitespace`
|
||||
@@ -158,9 +188,15 @@ The packages and functions are listed below:
|
||||
|
||||
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
|
||||
public methods of an object returned by any of the functions above. For example,
|
||||
with a BufPane object called `bp`, you could called the `Save` function in Lua
|
||||
with `bp:Save()`.
|
||||
public methods of an object returned by any of the functions above. Unfortunately
|
||||
it is not possible to list all the available functions on this page. Please
|
||||
go to the internal documentation at https://godoc.org/github.com/zyedidia/micro
|
||||
to see the full list of available methods. Note that only methods of types that
|
||||
are available to plugins via the functions above can be called from a plugin.
|
||||
For an even more detailed reference see the source code on Github.
|
||||
|
||||
For example, with a BufPane object called `bp`, you could call the `Save` function
|
||||
in Lua with `bp:Save()`.
|
||||
|
||||
Note that Lua uses the `:` syntax to call a function rather than Go's `.` syntax.
|
||||
|
||||
@@ -222,68 +258,55 @@ errors
|
||||
time
|
||||
```
|
||||
|
||||
For documentation for each of these functions, you can simply look
|
||||
through the Go standard library documentation.
|
||||
For documentation for each of these functions, see the Go standard
|
||||
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.
|
||||
|
||||
## Adding help files, syntax files, or colorschemes in your plugin
|
||||
|
||||
You can use the `AddRuntimeFile(name, type, path string)` function to add
|
||||
various kinds of files to your plugin. For example, if you'd like to add a help
|
||||
topic to your plugin called `test`, you would create a `test.md` file, and call
|
||||
the function:
|
||||
You can use the `AddRuntimeFile(name string, type config.RTFiletype, path string)`
|
||||
function to add various kinds of files to your plugin. For example, if you'd
|
||||
like to add a help topic to your plugin called `test`, you would create a
|
||||
`test.md` file, and call the function:
|
||||
|
||||
```lua
|
||||
AddRuntimeFile("test", "help", "test.md")
|
||||
config = import("micro/config")
|
||||
config.AddRuntimeFile("test", config.RTHelp, "test.md")
|
||||
```
|
||||
|
||||
Use `AddRuntimeFilesFromDirectory(name, type, dir, pattern)` to add a number of
|
||||
files to the runtime. To read the content of a runtime file use
|
||||
`ReadRuntimeFile(fileType, name string)` or `ListRuntimeFiles(fileType string)`
|
||||
for all runtime files.
|
||||
|
||||
## Autocomplete command arguments
|
||||
|
||||
See this example to learn how to use `MakeCompletion` and `MakeCommand`
|
||||
|
||||
```lua
|
||||
local function StartsWith(String,Start)
|
||||
String = String:upper()
|
||||
Start = Start:upper()
|
||||
return string.sub(String,1,string.len(Start))==Start
|
||||
end
|
||||
|
||||
function complete(input)
|
||||
local allCompletions = {"Hello", "World", "Foo", "Bar"}
|
||||
local result = {}
|
||||
|
||||
for i,v in pairs(allCompletions) do
|
||||
if StartsWith(v, input) then
|
||||
table.insert(result, v)
|
||||
end
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
function foo(arg)
|
||||
messenger:Message(arg)
|
||||
end
|
||||
|
||||
MakeCommand("foo", "example.foo", MakeCompletion("example.complete"))
|
||||
```
|
||||
for all runtime files. In addition, there is `AddRuntimeFileFromMemory` which
|
||||
adds a runtime file based on a string that may have been constructed at
|
||||
runtime.
|
||||
|
||||
## Default plugins
|
||||
|
||||
For examples of plugins, see the default `autoclose` and `linter` plugins
|
||||
(stored in the normal micro core repo under `runtime/plugins`) as well as any
|
||||
plugins that are stored in the official channel
|
||||
[here](https://github.com/micro-editor/plugin-channel).
|
||||
There are 6 default plugins that come pre-installed with micro. These are
|
||||
|
||||
* `autoclose`: automatically closes brackets, quotes, etc...
|
||||
* `comment`: provides automatic commenting for a number of languages
|
||||
* `ftoptions`: alters some default options depending on the filetype
|
||||
* `linter`: provides extensible linting for many languages
|
||||
* `literate`: provides advanced syntax highlighting for the Literate
|
||||
programming tool.
|
||||
* `status`: provides some extensions to the status line (integration with
|
||||
Git and more).
|
||||
|
||||
See `> help linter`, `> help comment`, and `> help status` for additional
|
||||
documentation specific to those plugins.
|
||||
|
||||
These are good examples for many use-cases if you are looking to write
|
||||
your own plugins.
|
||||
|
||||
## Plugin Manager
|
||||
|
||||
Micro also has a built in plugin manager which you can invoke with the
|
||||
`> plugin ...` command.
|
||||
`> 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
|
||||
@@ -300,6 +323,7 @@ This file will contain the metadata for your plugin. Here is an example:
|
||||
[{
|
||||
"Name": "pluginname",
|
||||
"Description": "Here is a nice concise description of my plugin",
|
||||
"Website": "https://github.com/user/plugin"
|
||||
"Tags": ["python", "linting"],
|
||||
"Versions": [
|
||||
{
|
||||
@@ -314,7 +338,8 @@ This file will contain the metadata for your plugin. Here is an example:
|
||||
```
|
||||
|
||||
Then open a pull request at github.com/micro-editor/plugin-channel adding a link
|
||||
to the raw `repo.json` that is in your plugin repository. To make updating the
|
||||
plugin work, the first line of your plugins lua code should contain the version
|
||||
of the plugin. (Like this: `VERSION = "1.0.0"`) Please make sure to use
|
||||
[semver](http://semver.org/) for versioning.
|
||||
to the raw `repo.json` that is in your plugin repository.
|
||||
|
||||
To make updating the plugin work, the first line of your plugins lua code
|
||||
should contain the version of the plugin. (Like this: `VERSION = "1.0.0"`)
|
||||
Please make sure to use [semver](http://semver.org/) for versioning.
|
||||
|
||||
@@ -16,7 +16,7 @@ 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.
|
||||
|
||||
Simply press CtrlE to go to command mode, and type `set option value` (in the
|
||||
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.
|
||||
@@ -70,31 +70,40 @@ what actions are available, see the `keybindings` help topic (`> help keybinding
|
||||
|
||||
If you need more power than the json files provide, you can use the `init.lua`
|
||||
file. Create it in `~/.config/micro`. This file is a lua file that is run when
|
||||
micro starts and is essentially a one-file plugin.
|
||||
micro starts and is essentially a one-file plugin. The plugin name is `initlua`.
|
||||
|
||||
I'll show you how to use the `init.lua` file by giving an example of how to
|
||||
create a binding to `CtrlR` which will execute `go run` on the current file,
|
||||
This example will show you how to use the `init.lua` file by creating
|
||||
a binding 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`:
|
||||
|
||||
```lua
|
||||
function gorun()
|
||||
local buf = CurView().Buf -- The current buffer
|
||||
if buf:FileType() == "go" then
|
||||
HandleShellCommand("go run " .. buf.Path, true, true) -- the first true means don't run it in the background
|
||||
end
|
||||
local config = import("micro/config")
|
||||
local shell = import("micro/shell")
|
||||
|
||||
function init()
|
||||
-- true means overwrite any existing binding to CtrlR
|
||||
-- this will modify the bindings.json file
|
||||
config.TryBindKey("CtrlR", "lua:initlua.gorun", true)
|
||||
end
|
||||
|
||||
BindKey("CtrlR", "init.gorun")
|
||||
function gorun(bp)
|
||||
local buf = bp.Buf
|
||||
if buf:FileType() == "go" then
|
||||
-- the true means run in the foreground
|
||||
-- the false means send output to stdout (instead of returning it)
|
||||
shell.RunInteractiveShell("go run " .. buf.Path, true, false)
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
Alternatively, you could get rid of the `BindKey` line, and put this line in the
|
||||
Alternatively, you could get rid of the `TryBindKey` line, and put this line in the
|
||||
`bindings.json` file:
|
||||
|
||||
```json
|
||||
{
|
||||
"CtrlR": "init.gorun"
|
||||
"CtrlR": "lua:initlua.gorun"
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
VERSION = "1.0.0"
|
||||
|
||||
local uutil = import("micro/util")
|
||||
local utf8 = import("utf8")
|
||||
local autoclosePairs = {"\"\"", "''", "``", "()", "{}", "[]"}
|
||||
@@ -36,7 +38,7 @@ function onRune(bp, r)
|
||||
end
|
||||
end
|
||||
end
|
||||
return false
|
||||
return true
|
||||
end
|
||||
|
||||
function preInsertNewline(bp)
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"name": "autoclose",
|
||||
"description": "Automatically places closing characters for quotes, parentheses, brackets, etc...",
|
||||
"website": "https://github.com/zyedidia/micro",
|
||||
"install": "https://github.com/zyedidia/micro",
|
||||
"version": "1.0.0",
|
||||
"require": [
|
||||
"micro >= 2.0.0"
|
||||
]
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
VERSION = "1.0.0"
|
||||
|
||||
local util = import("micro/util")
|
||||
local config = import("micro/config")
|
||||
local buffer = import("micro/buffer")
|
||||
@@ -102,5 +104,8 @@ function string.starts(String,Start)
|
||||
return string.sub(String,1,string.len(Start))==Start
|
||||
end
|
||||
|
||||
config.MakeCommand("comment", "comment.comment", config.NoComplete)
|
||||
config.TryBindKey("Alt-/", "lua:comment.comment", false)
|
||||
function init()
|
||||
config.MakeCommand("comment", "comment.comment", config.NoComplete)
|
||||
config.TryBindKey("Alt-/", "lua:comment.comment", false)
|
||||
config.AddRuntimeFile("comment", config.RTHelp, "help/comment.md")
|
||||
end
|
||||
|
||||
60
runtime/plugins/comment/help/comment.md
Normal file
60
runtime/plugins/comment/help/comment.md
Normal file
@@ -0,0 +1,60 @@
|
||||
# Comment Plugin
|
||||
|
||||
The comment plugin provides auto commenting/uncommenting.
|
||||
The default binding to comment/uncomment a line is `Alt-/`,
|
||||
but you can easily modify that in your `bindings.json` file:
|
||||
|
||||
```json
|
||||
{
|
||||
"Alt-g": "comment.comment"
|
||||
}
|
||||
```
|
||||
|
||||
You can also execute a command which will do the same thing as
|
||||
the binding:
|
||||
|
||||
```
|
||||
> comment
|
||||
```
|
||||
|
||||
If you have a selection, the plugin will comment all the lines
|
||||
selected.
|
||||
|
||||
The comment type will be auto detected based on the filetype,
|
||||
but it is only available for certain filetypes:
|
||||
|
||||
* c: `// %s`
|
||||
* c++: `// %s`
|
||||
* d: `// %s`
|
||||
* go: `// %s`
|
||||
* html: `<!-- %s -->`
|
||||
* java: `// %s`
|
||||
* javascript: `// %s`
|
||||
* julia: `# %s`
|
||||
* lua: `-- %s`
|
||||
* perl: `# %s`
|
||||
* php: `// %s`
|
||||
* python: `# %s`
|
||||
* python3: `# %s`
|
||||
* ruby: `# %s`
|
||||
* rust: `// %s`
|
||||
* shell: `# %s`
|
||||
* swift: `// %s`
|
||||
|
||||
If your filetype is not available here, you can simply modify
|
||||
the `commenttype` option:
|
||||
|
||||
```
|
||||
set commenttype "/* %s */"
|
||||
```
|
||||
|
||||
Or in your `settings.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"*.c": {
|
||||
"commenttype": "/* %s */"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"name": "comment",
|
||||
"description": "Support for automatically commenting blocks of code. Extensible and multiple languages supported.",
|
||||
"website": "https://github.com/zyedidia/micro",
|
||||
"install": "https://github.com/zyedidia/micro",
|
||||
"version": "1.0.0",
|
||||
"require": [
|
||||
"micro >= 2.0.0"
|
||||
]
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
VERSION = "1.0.0"
|
||||
|
||||
function onBufferOpen(b)
|
||||
local ft = b:FileType()
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user