mirror of
https://github.com/zyedidia/micro.git
synced 2026-03-31 07:07:09 +09:00
Compare commits
139 Commits
terminal-e
...
v1.1.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b09093f78c | ||
|
|
104699e500 | ||
|
|
38bf8c0225 | ||
|
|
6acda994e4 | ||
|
|
e563211790 | ||
|
|
6a5879cc15 | ||
|
|
aa624d86e6 | ||
|
|
c410b7b2ce | ||
|
|
79f1539486 | ||
|
|
d7b7cc954a | ||
|
|
1914a5b5ff | ||
|
|
921b828afb | ||
|
|
d3d35bd9ff | ||
|
|
76a328a062 | ||
|
|
fb90e169cb | ||
|
|
a1a307d858 | ||
|
|
3733e7e223 | ||
|
|
3e8a587aa3 | ||
|
|
8f2f1f8c1d | ||
|
|
a940ce3036 | ||
|
|
d7da72a720 | ||
|
|
b54853140a | ||
|
|
8ad2179423 | ||
|
|
3037d72bcb | ||
|
|
7d16e97b95 | ||
|
|
0293b774f3 | ||
|
|
32cd94b88f | ||
|
|
5e5dd78b7c | ||
|
|
1c5c741e87 | ||
|
|
095e6993a8 | ||
|
|
7c3425a012 | ||
|
|
bc724bf781 | ||
|
|
13144d4b57 | ||
|
|
97bdb15bd6 | ||
|
|
fb69ecdc9b | ||
|
|
1fe1c3eabb | ||
|
|
191fd5e495 | ||
|
|
8aa017bfda | ||
|
|
9ea947c808 | ||
|
|
2a7a55eca4 | ||
|
|
759c00098b | ||
|
|
cce36624dc | ||
|
|
4664850186 | ||
|
|
d9c666f6df | ||
|
|
d7e38a52ea | ||
|
|
f3f4790103 | ||
|
|
83c1136ac5 | ||
|
|
0ae5ae5d9a | ||
|
|
c070e3e8f7 | ||
|
|
0de167b07b | ||
|
|
f904e2fe99 | ||
|
|
b195ebad46 | ||
|
|
23ef69b935 | ||
|
|
55c790f069 | ||
|
|
4bcb13efc0 | ||
|
|
50c7441533 | ||
|
|
c1a3ee1706 | ||
|
|
357fc09e69 | ||
|
|
c1d08a6dc0 | ||
|
|
f689143670 | ||
|
|
56b3b79c50 | ||
|
|
f351c251e4 | ||
|
|
5cc66cef42 | ||
|
|
6791759440 | ||
|
|
ac98f21199 | ||
|
|
22ebbcfd89 | ||
|
|
292df7a9f7 | ||
|
|
64fd96611c | ||
|
|
de4a007bdf | ||
|
|
567faeb07e | ||
|
|
3afb3d0b22 | ||
|
|
8172ebf62b | ||
|
|
1720d4023f | ||
|
|
da6ab78384 | ||
|
|
6fe20fb305 | ||
|
|
d41f0bb324 | ||
|
|
8e555e60f7 | ||
|
|
243f99aeb1 | ||
|
|
ab36db7646 | ||
|
|
2e3c87b67d | ||
|
|
a549d12808 | ||
|
|
149fea8b76 | ||
|
|
f7295a25d8 | ||
|
|
9eeb14956c | ||
|
|
796638d095 | ||
|
|
79621505f1 | ||
|
|
e484445b1e | ||
|
|
5eddba5516 | ||
|
|
bdc857952a | ||
|
|
2bcc59faea | ||
|
|
6cc12b871c | ||
|
|
d201e7c503 | ||
|
|
c695df0adf | ||
|
|
a04e3080fb | ||
|
|
7d395a29a7 | ||
|
|
4046bb977e | ||
|
|
d250b9d7b0 | ||
|
|
a7f159bddc | ||
|
|
d0fa467a3c | ||
|
|
f4e0a3c0f8 | ||
|
|
0bc80adc28 | ||
|
|
cfdaf0e3f6 | ||
|
|
b5160c5d2c | ||
|
|
210a538cdd | ||
|
|
fd786b3020 | ||
|
|
ba9560079c | ||
|
|
d5694c0f35 | ||
|
|
1522b24803 | ||
|
|
922baa930d | ||
|
|
faafda6b21 | ||
|
|
9efc4fb5e9 | ||
|
|
37d83a280f | ||
|
|
8c0544c264 | ||
|
|
5e6a26a6ea | ||
|
|
9a09647330 | ||
|
|
0c00e8da0e | ||
|
|
af47cce86b | ||
|
|
301e86a46e | ||
|
|
0b1afe7f6c | ||
|
|
1739f0631a | ||
|
|
4f4e87a82c | ||
|
|
49ec611c8f | ||
|
|
8f06e51170 | ||
|
|
a853164302 | ||
|
|
dc8207149b | ||
|
|
cc73efc0bd | ||
|
|
4992efd172 | ||
|
|
956b1bdb3a | ||
|
|
cd52aaba13 | ||
|
|
cc6189b7ce | ||
|
|
5e82fc4673 | ||
|
|
107a6f877b | ||
|
|
e643860e3d | ||
|
|
0373589ab8 | ||
|
|
dce56a2b85 | ||
|
|
27d2ebfb45 | ||
|
|
1f457f9d9e | ||
|
|
e7ee18e421 | ||
|
|
725533d991 |
8
Makefile
8
Makefile
@@ -1,22 +1,23 @@
|
||||
.PHONY: runtime
|
||||
|
||||
VERSION = $(shell git describe --tags --abbrev=0)
|
||||
VERSION = $(shell go run tools/build-version.go)
|
||||
HASH = $(shell git rev-parse --short HEAD)
|
||||
DATE = $(shell go run tools/build-date.go)
|
||||
|
||||
# Builds micro after checking dependencies but without updating the runtime
|
||||
build: deps tcell
|
||||
go build -ldflags "-X main.Version=$(VERSION) -X main.CommitHash=$(HASH) -X 'main.CompileDate=$(DATE)'" ./cmd/micro
|
||||
go build -ldflags "-s -w -X main.Version=$(VERSION) -X main.CommitHash=$(HASH) -X 'main.CompileDate=$(DATE)'" ./cmd/micro
|
||||
|
||||
# Builds micro after building the runtime and checking dependencies
|
||||
build-all: runtime build
|
||||
|
||||
# Builds micro without checking for dependencies
|
||||
build-quick:
|
||||
go build -ldflags "-X main.Version=$(VERSION) -X main.CommitHash=$(HASH) -X 'main.CompileDate=$(DATE)'" ./cmd/micro
|
||||
go build -ldflags "-s -w -X main.Version=$(VERSION) -X main.CommitHash=$(HASH) -X 'main.CompileDate=$(DATE)'" ./cmd/micro
|
||||
|
||||
# Same as 'build' but installs to $GOPATH/bin afterward
|
||||
install: build
|
||||
mkdir -p $(GOPATH)/bin
|
||||
mv micro $(GOPATH)/bin
|
||||
|
||||
# Same as 'build-all' but installs to $GOPATH/bin afterward
|
||||
@@ -24,6 +25,7 @@ install-all: runtime install
|
||||
|
||||
# Same as 'build-quick' but installs to $GOPATH/bin afterward
|
||||
install-quick: build-quick
|
||||
mkdir -p $(GOPATH)/bin
|
||||
mv micro $(GOPATH)/bin
|
||||
|
||||
# Updates tcell
|
||||
|
||||
25
README.md
25
README.md
@@ -31,17 +31,20 @@ To see more screenshots of micro, showcasing all of the default colorschemes, se
|
||||
* 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
|
||||
* 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 [75 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`
|
||||
* Copy and paste with the system clipboard
|
||||
* Small and simple
|
||||
* Easily configurable
|
||||
* Macros
|
||||
* Common editor things such as undo/redo, line numbers, unicode support...
|
||||
* Common editor things such as undo/redo, line numbers, Unicode support...
|
||||
|
||||
Although not yet implemented, I hope to add more features such as autocompletion ([#174](https://github.com/zyedidia/micro/issues/174)), and multiple cursors ([#5](https://github.com/zyedidia/micro/issues/5)) in the future.
|
||||
|
||||
@@ -49,7 +52,7 @@ Although not yet implemented, I hope to add more features such as autocompletion
|
||||
|
||||
To install micro, you can download a [prebuilt binary](https://github.com/zyedidia/micro/releases), or you can build it from source.
|
||||
|
||||
If you want more information about ways to install micro, see this [wiki page](https://github.com/zyedidia/micro/wiki/Installing-Micro)
|
||||
If you want more information about ways to install micro, see this [wiki page](https://github.com/zyedidia/micro/wiki/Installing-Micro).
|
||||
|
||||
### Prebuilt binaries
|
||||
|
||||
@@ -62,9 +65,17 @@ and you'll see all the stable releases with the corresponding binaries.
|
||||
|
||||
If you'd like to see more information after installing micro, run `micro -version`.
|
||||
|
||||
### Homebrew
|
||||
|
||||
You can also install micro using Homebrew on Mac:
|
||||
|
||||
```
|
||||
$ brew install micro
|
||||
```
|
||||
|
||||
### Building from source
|
||||
|
||||
If your operating system does not have binary, but does run Go, you can build from source.
|
||||
If your operating system does not have a binary release, but does run Go, you can build from source.
|
||||
|
||||
Make sure that you have Go version 1.5 or greater (Go 1.4 will work if your version supports CGO).
|
||||
|
||||
@@ -74,7 +85,7 @@ go get -u github.com/zyedidia/micro/...
|
||||
|
||||
### Linux clipboard support
|
||||
|
||||
On Linux, clipboard support requires 'xclip' or 'xsel' command to be installed.
|
||||
On Linux, clipboard support requires the 'xclip' or 'xsel' commands to be installed.
|
||||
|
||||
For Ubuntu:
|
||||
|
||||
@@ -90,7 +101,7 @@ If you open micro and it doesn't seem like syntax highlighting is working, this
|
||||
you are using a terminal which does not support 256 color. Try changing the colorscheme to `simple`
|
||||
by running `> set colorscheme simple`.
|
||||
|
||||
If you are using the default ubuntu terminal, to enable 256 make sure your `TERM` variable is set
|
||||
If you are using the default Ubuntu terminal, to enable 256 make sure your `TERM` variable is set
|
||||
to `xterm-256color`.
|
||||
|
||||
Many of the Windows terminals don't support more than 16 colors, which means
|
||||
@@ -122,7 +133,7 @@ click to enable line selection.
|
||||
|
||||
# Documentation and Help
|
||||
|
||||
Micro has a built-in help system which you can access by pressing `CtrlE` and typing `help`. Additionally, you can
|
||||
Micro has a built-in help system which you can access by pressing `Ctrl-E` and typing `help`. Additionally, you can
|
||||
view the help files here:
|
||||
|
||||
* [main help](https://github.com/zyedidia/micro/tree/master/runtime/help/help.md)
|
||||
@@ -139,6 +150,6 @@ a brief introduction to the more powerful configuration features micro offers.
|
||||
|
||||
If you find any bugs, please report them! I am also happy to accept pull requests from anyone.
|
||||
|
||||
You can use the Github issue tracker to report bugs, ask questions, or suggest new features.
|
||||
You can use the [GitHub issue tracker](https://github.com/zyedidia/micro/issues) to report bugs, ask questions, or suggest new features.
|
||||
|
||||
For a more informal setting to discuss the editor, you can join the [Gitter chat](https://gitter.im/zyedidia/micro).
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/mitchellh/go-homedir"
|
||||
"github.com/yuin/gopher-lua"
|
||||
"github.com/zyedidia/clipboard"
|
||||
)
|
||||
@@ -496,7 +494,7 @@ func (v *View) Backspace(usePlugin bool) bool {
|
||||
// and restore the position
|
||||
|
||||
// If the user is using spaces instead of tabs and they are deleting
|
||||
// whitespace at the start of the line, we should delete as if its a
|
||||
// whitespace at the start of the line, we should delete as if it's a
|
||||
// tab (tabSize number of spaces)
|
||||
lineStart := v.Buf.Line(v.Cursor.Y)[:v.Cursor.X]
|
||||
tabSize := int(v.Buf.Settings["tabsize"].(float64))
|
||||
@@ -713,7 +711,7 @@ func (v *View) Save(usePlugin bool) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
if v.Help {
|
||||
if v.Type == vtHelp {
|
||||
// We can't save the help text
|
||||
return false
|
||||
}
|
||||
@@ -756,6 +754,21 @@ func (v *View) Save(usePlugin bool) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// SaveAs saves the buffer to disk with the given name
|
||||
func (v *View) SaveAs(usePlugin bool) bool {
|
||||
filename, canceled := messenger.Prompt("Filename: ", "Save", NoCompletion)
|
||||
if !canceled {
|
||||
// the filename might or might not be quoted, so unquote first then join the strings.
|
||||
filename = strings.Join(SplitCommandArgs(filename), " ")
|
||||
v.Buf.Path = filename
|
||||
v.Buf.Name = filename
|
||||
|
||||
v.Save(true)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// Find opens a prompt and searches forward for the input
|
||||
func (v *View) Find(usePlugin bool) bool {
|
||||
if usePlugin && !PreActionCall("Find", v) {
|
||||
@@ -1018,18 +1031,7 @@ func (v *View) OpenFile(usePlugin bool) bool {
|
||||
// the filename might or might not be quoted, so unquote first then join the strings.
|
||||
filename = strings.Join(SplitCommandArgs(filename), " ")
|
||||
|
||||
home, _ := homedir.Dir()
|
||||
filename = strings.Replace(filename, "~", home, 1)
|
||||
file, err := ioutil.ReadFile(filename)
|
||||
|
||||
var buf *Buffer
|
||||
if err != nil {
|
||||
// File does not exist -- create an empty buffer with that name
|
||||
buf = NewBuffer([]byte{}, filename)
|
||||
} else {
|
||||
buf = NewBuffer(file, filename)
|
||||
}
|
||||
v.OpenBuffer(buf)
|
||||
v.Open(filename)
|
||||
|
||||
if usePlugin {
|
||||
return PostActionCall("OpenFile", v)
|
||||
@@ -1256,7 +1258,7 @@ func (v *View) ToggleHelp(usePlugin bool) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
if !v.Help {
|
||||
if v.Type != vtHelp {
|
||||
// Open the default help
|
||||
v.openHelp("help")
|
||||
} else {
|
||||
@@ -1278,7 +1280,7 @@ func (v *View) ShellMode(usePlugin bool) bool {
|
||||
input, canceled := messenger.Prompt("$ ", "Shell", NoCompletion)
|
||||
if !canceled {
|
||||
// The true here is for openTerm to make the command interactive
|
||||
HandleShellCommand(input, true)
|
||||
HandleShellCommand(input, true, true)
|
||||
if usePlugin {
|
||||
return PostActionCall("ShellMode", v)
|
||||
}
|
||||
@@ -1443,6 +1445,55 @@ func (v *View) NextTab(usePlugin bool) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// VSplitBinding opens an empty vertical split
|
||||
func (v *View) VSplitBinding(usePlugin bool) bool {
|
||||
if usePlugin && !PreActionCall("VSplit", v) {
|
||||
return false
|
||||
}
|
||||
|
||||
v.VSplit(NewBuffer([]byte{}, ""))
|
||||
|
||||
if usePlugin {
|
||||
return PostActionCall("VSplit", v)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// HSplitBinding opens an empty horizontal split
|
||||
func (v *View) HSplitBinding(usePlugin bool) bool {
|
||||
if usePlugin && !PreActionCall("HSplit", v) {
|
||||
return false
|
||||
}
|
||||
|
||||
v.HSplit(NewBuffer([]byte{}, ""))
|
||||
|
||||
if usePlugin {
|
||||
return PostActionCall("HSplit", v)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Unsplit closes all splits in the current tab except the active one
|
||||
func (v *View) Unsplit(usePlugin bool) bool {
|
||||
if usePlugin && !PreActionCall("Unsplit", v) {
|
||||
return false
|
||||
}
|
||||
|
||||
curView := tabs[curTab].curView
|
||||
for i := len(tabs[curTab].views) - 1; i >= 0; i-- {
|
||||
view := tabs[curTab].views[i]
|
||||
if view != nil && view.Num != curView {
|
||||
view.Quit(true)
|
||||
// messenger.Message("Quit ", view.Buf.Path)
|
||||
}
|
||||
}
|
||||
|
||||
if usePlugin {
|
||||
return PostActionCall("Unsplit", v)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// NextSplit changes the view to the next split
|
||||
func (v *View) NextSplit(usePlugin bool) bool {
|
||||
if usePlugin && !PreActionCall("NextSplit", v) {
|
||||
|
||||
@@ -18,12 +18,14 @@ var pluginCompletions []func(string) []string
|
||||
func FileComplete(input string) (string, []string) {
|
||||
var sep string = string(os.PathSeparator)
|
||||
dirs := strings.Split(input, sep)
|
||||
|
||||
var files []os.FileInfo
|
||||
var err error
|
||||
if len(dirs) > 1 {
|
||||
home, _ := homedir.Dir()
|
||||
|
||||
directories := strings.Join(dirs[:len(dirs)-1], sep)
|
||||
directories := strings.Join(dirs[:len(dirs)-1], sep) + sep
|
||||
|
||||
if strings.HasPrefix(directories, "~") {
|
||||
directories = strings.Replace(directories, "~", home, 1)
|
||||
}
|
||||
@@ -31,6 +33,7 @@ func FileComplete(input string) (string, []string) {
|
||||
} else {
|
||||
files, err = ioutil.ReadDir(".")
|
||||
}
|
||||
|
||||
var suggestions []string
|
||||
if err != nil {
|
||||
return "", suggestions
|
||||
@@ -81,9 +84,9 @@ func CommandComplete(input string) (string, []string) {
|
||||
func HelpComplete(input string) (string, []string) {
|
||||
var suggestions []string
|
||||
|
||||
for _, topic := range helpFiles {
|
||||
for _, file := range ListRuntimeFiles(RTHelp) {
|
||||
topic := file.Name()
|
||||
if strings.HasPrefix(topic, input) {
|
||||
|
||||
suggestions = append(suggestions, topic)
|
||||
}
|
||||
}
|
||||
@@ -146,3 +149,29 @@ func PluginComplete(complete Completion, input string) (chosen string, suggestio
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func PluginCmdComplete(input string) (chosen string, suggestions []string) {
|
||||
for _, cmd := range []string{"install", "remove", "search", "update", "list"} {
|
||||
if strings.HasPrefix(cmd, input) {
|
||||
suggestions = append(suggestions, cmd)
|
||||
}
|
||||
}
|
||||
|
||||
if len(suggestions) == 1 {
|
||||
chosen = suggestions[0]
|
||||
}
|
||||
return chosen, suggestions
|
||||
}
|
||||
|
||||
func PluginNameComplete(input string) (chosen string, suggestions []string) {
|
||||
for _, pp := range GetAllPluginPackages() {
|
||||
if strings.HasPrefix(pp.Name, input) {
|
||||
suggestions = append(suggestions, pp.Name)
|
||||
}
|
||||
}
|
||||
|
||||
if len(suggestions) == 1 {
|
||||
chosen = suggestions[0]
|
||||
}
|
||||
return chosen, suggestions
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/yosuke-furukawa/json5/encoding/json5"
|
||||
"github.com/zyedidia/json5/encoding/json5"
|
||||
"github.com/zyedidia/tcell"
|
||||
)
|
||||
|
||||
@@ -79,6 +79,9 @@ var bindingActions = map[string]func(*View, bool) bool{
|
||||
"NextTab": (*View).NextTab,
|
||||
"NextSplit": (*View).NextSplit,
|
||||
"PreviousSplit": (*View).PreviousSplit,
|
||||
"Unsplit": (*View).Unsplit,
|
||||
"VSplit": (*View).VSplitBinding,
|
||||
"HSplit": (*View).HSplitBinding,
|
||||
"ToggleMacro": (*View).ToggleMacro,
|
||||
"PlayMacro": (*View).PlayMacro,
|
||||
|
||||
@@ -205,12 +208,11 @@ var bindingKeys = map[string]tcell.Key{
|
||||
"CtrlRightSq": tcell.KeyCtrlRightSq,
|
||||
"CtrlCarat": tcell.KeyCtrlCarat,
|
||||
"CtrlUnderscore": tcell.KeyCtrlUnderscore,
|
||||
"Backspace": tcell.KeyBackspace,
|
||||
"Tab": tcell.KeyTab,
|
||||
"Esc": tcell.KeyEsc,
|
||||
"Escape": tcell.KeyEscape,
|
||||
"Enter": tcell.KeyEnter,
|
||||
"Backspace2": tcell.KeyBackspace2,
|
||||
"Backspace": tcell.KeyBackspace2,
|
||||
|
||||
// I renamed these keys to PageUp and PageDown but I don't want to break someone's keybindings
|
||||
"PgUp": tcell.KeyPgUp,
|
||||
@@ -267,7 +269,8 @@ modSearch:
|
||||
case strings.HasPrefix(k, "-"):
|
||||
// We optionally support dashes between modifiers
|
||||
k = k[1:]
|
||||
case strings.HasPrefix(k, "Ctrl"):
|
||||
case strings.HasPrefix(k, "Ctrl") && k != "CtrlH":
|
||||
// CtrlH technically does not have a 'Ctrl' modifier because it is really backspace
|
||||
k = k[4:]
|
||||
modifiers |= tcell.ModCtrl
|
||||
case strings.HasPrefix(k, "Alt"):
|
||||
@@ -373,10 +376,10 @@ func DefaultBindings() map[string]string {
|
||||
"CtrlShiftDown": "SelectToEnd",
|
||||
"Enter": "InsertNewline",
|
||||
"Space": "InsertSpace",
|
||||
"CtrlH": "Backspace",
|
||||
"Backspace": "Backspace",
|
||||
"Backspace2": "Backspace",
|
||||
"Alt-CtrlH": "DeleteWordLeft",
|
||||
"Alt-Backspace": "DeleteWordLeft",
|
||||
"Alt-Backspace2": "DeleteWordLeft",
|
||||
"Tab": "IndentSelection,InsertTab",
|
||||
"Backtab": "OutdentSelection",
|
||||
"CtrlO": "OpenFile",
|
||||
@@ -405,7 +408,6 @@ func DefaultBindings() map[string]string {
|
||||
"CtrlR": "ToggleRuler",
|
||||
"CtrlL": "JumpLine",
|
||||
"Delete": "Delete",
|
||||
"Esc": "ClearStatus",
|
||||
"CtrlB": "ShellMode",
|
||||
"CtrlQ": "Quit",
|
||||
"CtrlE": "CommandMode",
|
||||
@@ -420,5 +422,13 @@ func DefaultBindings() map[string]string {
|
||||
"Alt-e": "EndOfLine",
|
||||
"Alt-p": "CursorUp",
|
||||
"Alt-n": "CursorDown",
|
||||
|
||||
// Integration with file managers
|
||||
"F1": "ToggleHelp",
|
||||
"F2": "Save",
|
||||
"F4": "Quit",
|
||||
"F7": "Find",
|
||||
"F10": "Quit",
|
||||
"Esc": "Quit",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,6 +55,16 @@ type SerializedBuffer struct {
|
||||
|
||||
// NewBuffer creates a new buffer from `txt` with path and name `path`
|
||||
func NewBuffer(txt []byte, path string) *Buffer {
|
||||
if path != "" {
|
||||
for _, tab := range tabs {
|
||||
for _, view := range tab.views {
|
||||
if view.Buf.Path == path {
|
||||
return view.Buf
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
b := new(Buffer)
|
||||
b.LineArray = NewLineArray(txt)
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -16,66 +15,53 @@ type Colorscheme map[string]tcell.Style
|
||||
// The current colorscheme
|
||||
var colorscheme Colorscheme
|
||||
|
||||
var preInstalledColors = []string{"default", "simple", "solarized", "solarized-tc", "atom-dark-tc", "monokai", "gruvbox", "zenburn", "bubblegum"}
|
||||
|
||||
// ColorschemeExists checks if a given colorscheme exists
|
||||
func ColorschemeExists(colorschemeName string) bool {
|
||||
files, _ := ioutil.ReadDir(configDir)
|
||||
for _, f := range files {
|
||||
if f.Name() == colorschemeName+".micro" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
for _, name := range preInstalledColors {
|
||||
if name == colorschemeName {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
return FindRuntimeFile(RTColorscheme, colorschemeName) != nil
|
||||
}
|
||||
|
||||
// InitColorscheme picks and initializes the colorscheme when micro starts
|
||||
func InitColorscheme() {
|
||||
colorscheme = make(Colorscheme)
|
||||
if screen != nil {
|
||||
screen.SetStyle(tcell.StyleDefault.
|
||||
Foreground(tcell.ColorDefault).
|
||||
Background(tcell.ColorDefault))
|
||||
}
|
||||
|
||||
LoadDefaultColorscheme()
|
||||
}
|
||||
|
||||
// LoadDefaultColorscheme loads the default colorscheme from $(configDir)/colorschemes
|
||||
func LoadDefaultColorscheme() {
|
||||
LoadColorscheme(globalSettings["colorscheme"].(string), configDir+"/colorschemes")
|
||||
LoadColorscheme(globalSettings["colorscheme"].(string))
|
||||
}
|
||||
|
||||
// LoadColorscheme loads the given colorscheme from a directory
|
||||
func LoadColorscheme(colorschemeName, dir string) {
|
||||
files, _ := ioutil.ReadDir(dir)
|
||||
found := false
|
||||
for _, f := range files {
|
||||
if f.Name() == colorschemeName+".micro" {
|
||||
text, err := ioutil.ReadFile(dir + "/" + f.Name())
|
||||
if err != nil {
|
||||
fmt.Println("Error loading colorscheme:", err)
|
||||
continue
|
||||
}
|
||||
colorscheme = ParseColorscheme(string(text))
|
||||
found = true
|
||||
}
|
||||
}
|
||||
|
||||
for _, name := range preInstalledColors {
|
||||
if name == colorschemeName {
|
||||
data, err := Asset("runtime/colorschemes/" + name + ".micro")
|
||||
if err != nil {
|
||||
TermMessage("Unable to load pre-installed colorscheme " + name)
|
||||
continue
|
||||
}
|
||||
colorscheme = ParseColorscheme(string(data))
|
||||
found = true
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
func LoadColorscheme(colorschemeName string) {
|
||||
file := FindRuntimeFile(RTColorscheme, colorschemeName)
|
||||
if file == nil {
|
||||
TermMessage(colorschemeName, "is not a valid colorscheme")
|
||||
} else {
|
||||
if data, err := file.Data(); err != nil {
|
||||
fmt.Println("Error loading colorscheme:", err)
|
||||
} else {
|
||||
colorscheme = ParseColorscheme(string(data))
|
||||
|
||||
// Default style
|
||||
defStyle = tcell.StyleDefault.
|
||||
Foreground(tcell.ColorDefault).
|
||||
Background(tcell.ColorDefault)
|
||||
|
||||
// There may be another default style defined in the colorscheme
|
||||
// In that case we should use that one
|
||||
if style, ok := colorscheme["default"]; ok {
|
||||
defStyle = style
|
||||
}
|
||||
if screen != nil {
|
||||
screen.SetStyle(defStyle)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,7 +88,12 @@ func ParseColorscheme(text string) Colorscheme {
|
||||
link := string(matches[1])
|
||||
colors := string(matches[2])
|
||||
|
||||
c[link] = StringToStyle(colors)
|
||||
style := StringToStyle(colors)
|
||||
c[link] = style
|
||||
|
||||
if link == "default" {
|
||||
defStyle = style
|
||||
}
|
||||
} else {
|
||||
fmt.Println("Color-link statement is not valid:", line)
|
||||
}
|
||||
@@ -115,8 +106,7 @@ func ParseColorscheme(text string) Colorscheme {
|
||||
// The strings must be in the format "extra foregroundcolor,backgroundcolor"
|
||||
// The 'extra' can be bold, reverse, or underline
|
||||
func StringToStyle(str string) tcell.Style {
|
||||
var fg string
|
||||
bg := "default"
|
||||
var fg, bg string
|
||||
split := strings.Split(str, ",")
|
||||
if len(split) > 1 {
|
||||
fg, bg = split[0], split[1]
|
||||
@@ -126,7 +116,19 @@ func StringToStyle(str string) tcell.Style {
|
||||
fg = strings.TrimSpace(fg)
|
||||
bg = strings.TrimSpace(bg)
|
||||
|
||||
style := defStyle.Foreground(StringToColor(fg)).Background(StringToColor(bg))
|
||||
var fgColor, bgColor tcell.Color
|
||||
if fg == "" {
|
||||
fgColor, _, _ = defStyle.Decompose()
|
||||
} else {
|
||||
fgColor = StringToColor(fg)
|
||||
}
|
||||
if bg == "" {
|
||||
_, bgColor, _ = defStyle.Decompose()
|
||||
} else {
|
||||
bgColor = StringToColor(bg)
|
||||
}
|
||||
|
||||
style := defStyle.Foreground(fgColor).Background(bgColor)
|
||||
if strings.Contains(str, "bold") {
|
||||
style = style.Bold(true)
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
@@ -25,18 +27,21 @@ type StrCommand struct {
|
||||
var commands map[string]Command
|
||||
|
||||
var commandActions = map[string]func([]string){
|
||||
"Set": Set,
|
||||
"SetLocal": SetLocal,
|
||||
"Show": Show,
|
||||
"Run": Run,
|
||||
"Bind": Bind,
|
||||
"Quit": Quit,
|
||||
"Save": Save,
|
||||
"Replace": Replace,
|
||||
"VSplit": VSplit,
|
||||
"HSplit": HSplit,
|
||||
"Tab": NewTab,
|
||||
"Help": Help,
|
||||
"Set": Set,
|
||||
"SetLocal": SetLocal,
|
||||
"Show": Show,
|
||||
"Run": Run,
|
||||
"Bind": Bind,
|
||||
"Quit": Quit,
|
||||
"Save": Save,
|
||||
"Replace": Replace,
|
||||
"VSplit": VSplit,
|
||||
"HSplit": HSplit,
|
||||
"Tab": NewTab,
|
||||
"Help": Help,
|
||||
"Eval": Eval,
|
||||
"ToggleLog": ToggleLog,
|
||||
"Plugin": PluginCmd,
|
||||
}
|
||||
|
||||
// InitCommands initializes the default commands
|
||||
@@ -81,6 +86,95 @@ func DefaultCommands() map[string]StrCommand {
|
||||
"hsplit": {"HSplit", []Completion{FileCompletion, NoCompletion}},
|
||||
"tab": {"Tab", []Completion{FileCompletion, NoCompletion}},
|
||||
"help": {"Help", []Completion{HelpCompletion, NoCompletion}},
|
||||
"eval": {"Eval", []Completion{NoCompletion}},
|
||||
"log": {"ToggleLog", []Completion{NoCompletion}},
|
||||
"plugin": {"Plugin", []Completion{PluginCmdCompletion, PluginNameCompletion}},
|
||||
}
|
||||
}
|
||||
|
||||
// PluginCmd installs, removes, updates, lists, or searches for given plugins
|
||||
func PluginCmd(args []string) {
|
||||
if len(args) >= 1 {
|
||||
switch args[0] {
|
||||
case "install":
|
||||
installedVersions := GetInstalledVersions(false)
|
||||
for _, plugin := range args[1:] {
|
||||
pp := GetAllPluginPackages().Get(plugin)
|
||||
if pp == nil {
|
||||
messenger.Error("Unknown plugin \"" + plugin + "\"")
|
||||
} else if err := pp.IsInstallable(); err != nil {
|
||||
messenger.Error("Error installing ", plugin, ": ", err)
|
||||
} else {
|
||||
for _, installed := range installedVersions {
|
||||
if pp.Name == installed.pack.Name {
|
||||
if pp.Versions[0].Version.Compare(installed.Version) == 1 {
|
||||
messenger.Error(pp.Name, " is already installed but out-of-date: use 'plugin update ", pp.Name, "' to update")
|
||||
} else {
|
||||
messenger.Error(pp.Name, " is already installed")
|
||||
}
|
||||
}
|
||||
}
|
||||
pp.Install()
|
||||
}
|
||||
}
|
||||
case "remove":
|
||||
removed := ""
|
||||
for _, plugin := range args[1:] {
|
||||
// check if the plugin exists.
|
||||
for _, lp := range loadedPlugins {
|
||||
if lp == plugin {
|
||||
UninstallPlugin(plugin)
|
||||
removed += plugin + " "
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
if !IsSpaces(removed) {
|
||||
messenger.Message("Removed ", removed)
|
||||
} else {
|
||||
messenger.Error("The requested plugins do not exist")
|
||||
}
|
||||
case "update":
|
||||
UpdatePlugins(args[1:])
|
||||
case "list":
|
||||
plugins := GetInstalledVersions(false)
|
||||
messenger.AddLog("----------------")
|
||||
messenger.AddLog("The following plugins are currently installed:\n")
|
||||
for _, p := range plugins {
|
||||
messenger.AddLog(fmt.Sprintf("%s (%s)", p.pack.Name, p.Version))
|
||||
}
|
||||
messenger.AddLog("----------------")
|
||||
if len(plugins) > 0 {
|
||||
if CurView().Type != vtLog {
|
||||
ToggleLog([]string{})
|
||||
}
|
||||
}
|
||||
case "search":
|
||||
plugins := SearchPlugin(args[1:])
|
||||
messenger.Message(len(plugins), " plugins found")
|
||||
for _, p := range plugins {
|
||||
messenger.AddLog("----------------")
|
||||
messenger.AddLog(p.String())
|
||||
}
|
||||
messenger.AddLog("----------------")
|
||||
if len(plugins) > 0 {
|
||||
if CurView().Type != vtLog {
|
||||
ToggleLog([]string{})
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
messenger.Error("Not enough arguments")
|
||||
}
|
||||
}
|
||||
|
||||
func ToggleLog(args []string) {
|
||||
buffer := messenger.getBuffer()
|
||||
if CurView().Type != vtLog {
|
||||
CurView().HSplit(buffer)
|
||||
CurView().Type = vtLog
|
||||
} else {
|
||||
CurView().Quit(true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,7 +185,7 @@ func Help(args []string) {
|
||||
CurView().openHelp("help")
|
||||
} else {
|
||||
helpPage := args[0]
|
||||
if _, ok := helpPages[helpPage]; ok {
|
||||
if FindRuntimeFile(RTHelp, helpPage) != nil {
|
||||
CurView().openHelp(helpPage)
|
||||
} else {
|
||||
messenger.Error("Sorry, no help for ", helpPage)
|
||||
@@ -143,6 +237,18 @@ func HSplit(args []string) {
|
||||
}
|
||||
}
|
||||
|
||||
// Eval evaluates a lua expression
|
||||
func Eval(args []string) {
|
||||
if len(args) >= 1 {
|
||||
err := L.DoString(args[0])
|
||||
if err != nil {
|
||||
messenger.Error(err)
|
||||
}
|
||||
} else {
|
||||
messenger.Error("Not enough arguments")
|
||||
}
|
||||
}
|
||||
|
||||
// NewTab opens the given file in a new tab
|
||||
func NewTab(args []string) {
|
||||
if len(args) == 0 {
|
||||
@@ -225,7 +331,7 @@ func Bind(args []string) {
|
||||
// Run runs a shell command in the background
|
||||
func Run(args []string) {
|
||||
// Run a shell command in the background (openTerm is false)
|
||||
HandleShellCommand(JoinCommandArgs(args...), false)
|
||||
HandleShellCommand(JoinCommandArgs(args...), false, true)
|
||||
}
|
||||
|
||||
// Quit closes the main view
|
||||
@@ -236,8 +342,12 @@ func Quit(args []string) {
|
||||
|
||||
// Save saves the buffer in the main view
|
||||
func Save(args []string) {
|
||||
// Save the main view
|
||||
CurView().Save(true)
|
||||
if len(args) == 0 {
|
||||
// Save the main view
|
||||
CurView().Save(true)
|
||||
} else {
|
||||
CurView().Buf.SaveAs(args[0])
|
||||
}
|
||||
}
|
||||
|
||||
// Replace runs search and replace
|
||||
@@ -304,13 +414,25 @@ func Replace(args []string) {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for {
|
||||
match := regex.FindStringIndex(view.Buf.String())
|
||||
if match == nil {
|
||||
break
|
||||
matches := regex.FindAllStringIndex(view.Buf.String(), -1)
|
||||
if matches != nil && len(matches) > 0 {
|
||||
adjust := 0
|
||||
prevMatch := matches[0]
|
||||
from := FromCharPos(prevMatch[0], view.Buf)
|
||||
to := from.Move(Count(search), view.Buf)
|
||||
adjust += Count(replace) - Count(search)
|
||||
view.Buf.Replace(from, to, replace)
|
||||
if len(matches) > 1 {
|
||||
for _, match := range matches[1:] {
|
||||
found++
|
||||
from = from.Move(match[0]-prevMatch[0]+adjust, view.Buf)
|
||||
to := from.Move(Count(search), view.Buf)
|
||||
// TermMessage(match[0], " ", prevMatch[0], " ", adjust, "\n", from, " ", to)
|
||||
view.Buf.Replace(from, to, replace)
|
||||
prevMatch = match
|
||||
// adjust += Count(replace) - Count(search)
|
||||
}
|
||||
}
|
||||
found++
|
||||
view.Buf.Replace(FromCharPos(match[0], view.Buf), FromCharPos(match[1], view.Buf), replace)
|
||||
}
|
||||
}
|
||||
view.Cursor.Relocate()
|
||||
@@ -342,7 +464,7 @@ func RunShellCommand(input string) (string, error) {
|
||||
// HandleShellCommand runs the shell command
|
||||
// The openTerm argument specifies whether a terminal should be opened (for viewing output
|
||||
// or interacting with stdin)
|
||||
func HandleShellCommand(input string, openTerm bool) {
|
||||
func HandleShellCommand(input string, openTerm bool, waitToFinish bool) string {
|
||||
inputCmd := SplitCommandArgs(input)[0]
|
||||
if !openTerm {
|
||||
// Simply run the command in the background and notify the user when it's done
|
||||
@@ -371,9 +493,10 @@ func HandleShellCommand(input string, openTerm bool) {
|
||||
args := SplitCommandArgs(input)[1:]
|
||||
|
||||
// Set up everything for the command
|
||||
var outputBuf bytes.Buffer
|
||||
cmd := exec.Command(inputCmd, args...)
|
||||
cmd.Stdin = os.Stdin
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stdout = io.MultiWriter(os.Stdout, &outputBuf)
|
||||
cmd.Stderr = os.Stderr
|
||||
|
||||
// This is a trap for Ctrl-C so that it doesn't kill micro
|
||||
@@ -386,16 +509,25 @@ func HandleShellCommand(input string, openTerm bool) {
|
||||
}
|
||||
}()
|
||||
|
||||
// Start the command
|
||||
cmd.Start()
|
||||
cmd.Wait()
|
||||
err := cmd.Wait()
|
||||
|
||||
// This is just so we don't return right away and let the user press enter to return
|
||||
TermMessage("")
|
||||
output := outputBuf.String()
|
||||
if err != nil {
|
||||
output = err.Error()
|
||||
}
|
||||
|
||||
if waitToFinish {
|
||||
// This is just so we don't return right away and let the user press enter to return
|
||||
TermMessage("")
|
||||
}
|
||||
|
||||
// Start the screen back up
|
||||
InitScreen()
|
||||
|
||||
return output
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// HandleCommand handles input from the user
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
dmp "github.com/sergi/go-diff/diffmatchpatch"
|
||||
"github.com/yuin/gopher-lua"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -114,6 +116,17 @@ func (eh *EventHandler) Execute(t *TextEvent) {
|
||||
eh.RedoStack = new(Stack)
|
||||
}
|
||||
eh.UndoStack.Push(t)
|
||||
|
||||
for _, pl := range loadedPlugins {
|
||||
ret, err := Call(pl+".onBeforeTextEvent", t)
|
||||
if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") {
|
||||
TermMessage(err)
|
||||
}
|
||||
if val, ok := ret.(lua.LBool); ok && val == lua.LFalse {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
ExecuteTextEvent(t, eh.buf)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
package main
|
||||
|
||||
var helpPages map[string]string
|
||||
|
||||
var helpFiles = []string{
|
||||
"help",
|
||||
"keybindings",
|
||||
"plugins",
|
||||
"colors",
|
||||
"options",
|
||||
"commands",
|
||||
"tutorial",
|
||||
}
|
||||
|
||||
// LoadHelp loads the help text from inside the binary
|
||||
func LoadHelp() {
|
||||
helpPages = make(map[string]string)
|
||||
for _, file := range helpFiles {
|
||||
data, err := Asset("runtime/help/" + file + ".md")
|
||||
if err != nil {
|
||||
TermMessage("Unable to load help text", file)
|
||||
}
|
||||
helpPages[file] = string(data)
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,10 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/zyedidia/tcell"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/zyedidia/tcell"
|
||||
)
|
||||
|
||||
// FileTypeRules represents a complete set of syntax rules for a filetype
|
||||
@@ -29,151 +28,16 @@ type SyntaxRule struct {
|
||||
|
||||
var syntaxFiles map[[2]*regexp.Regexp]FileTypeRules
|
||||
|
||||
// These syntax files are pre installed and embedded in the resulting binary by go-bindata
|
||||
var preInstalledSynFiles = []string{
|
||||
"Dockerfile",
|
||||
"apacheconf",
|
||||
"arduino",
|
||||
"asciidoc",
|
||||
"asm",
|
||||
"awk",
|
||||
"c",
|
||||
"caddyfile",
|
||||
"cmake",
|
||||
"coffeescript",
|
||||
"colortest",
|
||||
"conf",
|
||||
"conky",
|
||||
"csharp",
|
||||
"css",
|
||||
"cython",
|
||||
"d",
|
||||
"dart",
|
||||
"dot",
|
||||
"erb",
|
||||
"fish",
|
||||
"fortran",
|
||||
"gdscript",
|
||||
"gentoo-ebuild",
|
||||
"gentoo-etc-portage",
|
||||
"git-commit",
|
||||
"git-config",
|
||||
"git-rebase-todo",
|
||||
"glsl",
|
||||
"go",
|
||||
"golo",
|
||||
"groff",
|
||||
"haml",
|
||||
"haskell",
|
||||
"html",
|
||||
"ini",
|
||||
"inputrc",
|
||||
"java",
|
||||
"javascript",
|
||||
"json",
|
||||
"keymap",
|
||||
"kickstart",
|
||||
"ledger",
|
||||
"lilypond",
|
||||
"lisp",
|
||||
"lua",
|
||||
"makefile",
|
||||
"man",
|
||||
"markdown",
|
||||
"mpdconf",
|
||||
"micro",
|
||||
"nanorc",
|
||||
"nginx",
|
||||
"ocaml",
|
||||
"pascal",
|
||||
"patch",
|
||||
"peg",
|
||||
"perl",
|
||||
"perl6",
|
||||
"php",
|
||||
"pkg-config",
|
||||
"pkgbuild",
|
||||
"po",
|
||||
"pov",
|
||||
"privoxy-action",
|
||||
"privoxy-config",
|
||||
"privoxy-filter",
|
||||
"puppet",
|
||||
"python",
|
||||
"r",
|
||||
"reST",
|
||||
"rpmspec",
|
||||
"ruby",
|
||||
"rust",
|
||||
"scala",
|
||||
"sed",
|
||||
"sh",
|
||||
"sls",
|
||||
"sql",
|
||||
"swift",
|
||||
"systemd",
|
||||
"tcl",
|
||||
"tex",
|
||||
"vala",
|
||||
"vi",
|
||||
"xml",
|
||||
"xresources",
|
||||
"yaml",
|
||||
"yum",
|
||||
"zsh",
|
||||
}
|
||||
|
||||
// LoadSyntaxFiles loads the syntax files from the default directory (configDir)
|
||||
func LoadSyntaxFiles() {
|
||||
// Load the user's custom syntax files, if there are any
|
||||
LoadSyntaxFilesFromDir(configDir + "/syntax")
|
||||
|
||||
// Load the pre-installed syntax files from inside the binary
|
||||
for _, filetype := range preInstalledSynFiles {
|
||||
data, err := Asset("runtime/syntax/" + filetype + ".micro")
|
||||
if err != nil {
|
||||
TermMessage("Unable to load pre-installed syntax file " + filetype)
|
||||
continue
|
||||
}
|
||||
|
||||
LoadSyntaxFile(string(data), filetype+".micro")
|
||||
}
|
||||
}
|
||||
|
||||
// LoadSyntaxFilesFromDir loads the syntax files from a specified directory
|
||||
// To load the syntax files, we must fill the `syntaxFiles` map
|
||||
// This involves finding the regex for syntax and if it exists, the regex
|
||||
// for the header. Then we must get the text for the file and the filetype.
|
||||
func LoadSyntaxFilesFromDir(dir string) {
|
||||
colorscheme = make(Colorscheme)
|
||||
InitColorscheme()
|
||||
|
||||
// Default style
|
||||
defStyle = tcell.StyleDefault.
|
||||
Foreground(tcell.ColorDefault).
|
||||
Background(tcell.ColorDefault)
|
||||
|
||||
// There may be another default style defined in the colorscheme
|
||||
// In that case we should use that one
|
||||
if style, ok := colorscheme["default"]; ok {
|
||||
defStyle = style
|
||||
}
|
||||
if screen != nil {
|
||||
screen.SetStyle(defStyle)
|
||||
}
|
||||
|
||||
syntaxFiles = make(map[[2]*regexp.Regexp]FileTypeRules)
|
||||
files, _ := ioutil.ReadDir(dir)
|
||||
for _, f := range files {
|
||||
if filepath.Ext(f.Name()) == ".micro" {
|
||||
filename := dir + "/" + f.Name()
|
||||
text, err := ioutil.ReadFile(filename)
|
||||
|
||||
if err != nil {
|
||||
TermMessage("Error loading syntax file " + filename + ": " + err.Error())
|
||||
return
|
||||
}
|
||||
LoadSyntaxFile(string(text), filename)
|
||||
for _, f := range ListRuntimeFiles(RTSyntax) {
|
||||
data, err := f.Data()
|
||||
if err != nil {
|
||||
TermMessage("Error loading syntax file " + f.Name() + ": " + err.Error())
|
||||
} else {
|
||||
LoadSyntaxFile(string(data), f.Name())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ func TermMessage(msg ...interface{}) {
|
||||
screenWasNil := screen == nil
|
||||
if !screenWasNil {
|
||||
screen.Fini()
|
||||
screen = nil
|
||||
}
|
||||
|
||||
fmt.Println(msg...)
|
||||
@@ -43,6 +44,7 @@ func TermError(filename string, lineNum int, err string) {
|
||||
// Messenger is an object that makes it easy to send messages to the user
|
||||
// and get input from the user
|
||||
type Messenger struct {
|
||||
log *Buffer
|
||||
// Are we currently prompting the user?
|
||||
hasPrompt bool
|
||||
// Is there a message to print
|
||||
@@ -67,16 +69,30 @@ type Messenger struct {
|
||||
gutterMessage bool
|
||||
}
|
||||
|
||||
func (m *Messenger) AddLog(msg string) {
|
||||
buffer := m.getBuffer()
|
||||
buffer.insert(buffer.End(), []byte(msg+"\n"))
|
||||
buffer.Cursor.Loc = buffer.End()
|
||||
buffer.Cursor.Relocate()
|
||||
}
|
||||
|
||||
func (m *Messenger) getBuffer() *Buffer {
|
||||
if m.log == nil {
|
||||
m.log = NewBuffer([]byte{}, "")
|
||||
m.log.Name = "Log"
|
||||
}
|
||||
return m.log
|
||||
}
|
||||
|
||||
// Message sends a message to the user
|
||||
func (m *Messenger) Message(msg ...interface{}) {
|
||||
buf := new(bytes.Buffer)
|
||||
fmt.Fprint(buf, msg...)
|
||||
m.message = buf.String()
|
||||
m.message = fmt.Sprint(msg...)
|
||||
m.style = defStyle
|
||||
|
||||
if _, ok := colorscheme["message"]; ok {
|
||||
m.style = colorscheme["message"]
|
||||
}
|
||||
m.AddLog(m.message)
|
||||
m.hasMessage = true
|
||||
}
|
||||
|
||||
@@ -92,6 +108,7 @@ func (m *Messenger) Error(msg ...interface{}) {
|
||||
if _, ok := colorscheme["error-message"]; ok {
|
||||
m.style = colorscheme["error-message"]
|
||||
}
|
||||
m.AddLog(m.message)
|
||||
m.hasMessage = true
|
||||
}
|
||||
|
||||
@@ -113,13 +130,16 @@ func (m *Messenger) YesNoPrompt(prompt string) (bool, bool) {
|
||||
switch e.Key() {
|
||||
case tcell.KeyRune:
|
||||
if e.Rune() == 'y' {
|
||||
m.AddLog("\t--> y")
|
||||
m.hasPrompt = false
|
||||
return true, false
|
||||
} else if e.Rune() == 'n' {
|
||||
m.AddLog("\t--> n")
|
||||
m.hasPrompt = false
|
||||
return false, false
|
||||
}
|
||||
case tcell.KeyCtrlC, tcell.KeyCtrlQ, tcell.KeyEscape:
|
||||
m.AddLog("\t--> (cancel)")
|
||||
m.hasPrompt = false
|
||||
return false, true
|
||||
}
|
||||
@@ -146,6 +166,7 @@ func (m *Messenger) LetterPrompt(prompt string, responses ...rune) (rune, bool)
|
||||
case tcell.KeyRune:
|
||||
for _, r := range responses {
|
||||
if e.Rune() == r {
|
||||
m.AddLog("\t--> " + string(r))
|
||||
m.Clear()
|
||||
m.Reset()
|
||||
m.hasPrompt = false
|
||||
@@ -153,6 +174,7 @@ func (m *Messenger) LetterPrompt(prompt string, responses ...rune) (rune, bool)
|
||||
}
|
||||
}
|
||||
case tcell.KeyCtrlC, tcell.KeyCtrlQ, tcell.KeyEscape:
|
||||
m.AddLog("\t--> (cancel)")
|
||||
m.Clear()
|
||||
m.Reset()
|
||||
m.hasPrompt = false
|
||||
@@ -170,6 +192,8 @@ const (
|
||||
CommandCompletion
|
||||
HelpCompletion
|
||||
OptionCompletion
|
||||
PluginCmdCompletion
|
||||
PluginNameCompletion
|
||||
)
|
||||
|
||||
// Prompt sends the user a message and waits for a response to be typed in
|
||||
@@ -198,9 +222,11 @@ func (m *Messenger) Prompt(prompt, historyType string, completionTypes ...Comple
|
||||
switch e.Key() {
|
||||
case tcell.KeyCtrlQ, tcell.KeyCtrlC, tcell.KeyEscape:
|
||||
// Cancel
|
||||
m.AddLog("\t--> (cancel)")
|
||||
m.hasPrompt = false
|
||||
case tcell.KeyEnter:
|
||||
// User is done entering their response
|
||||
m.AddLog("\t--> " + m.response)
|
||||
m.hasPrompt = false
|
||||
response, canceled = m.response, false
|
||||
m.history[historyType][len(m.history[historyType])-1] = response
|
||||
@@ -231,6 +257,10 @@ func (m *Messenger) Prompt(prompt, historyType string, completionTypes ...Comple
|
||||
chosen, suggestions = HelpComplete(currentArg)
|
||||
} else if completionType == OptionCompletion {
|
||||
chosen, suggestions = OptionComplete(currentArg)
|
||||
} else if completionType == PluginCmdCompletion {
|
||||
chosen, suggestions = PluginCmdComplete(currentArg)
|
||||
} else if completionType == PluginNameCompletion {
|
||||
chosen, suggestions = PluginNameComplete(currentArg)
|
||||
} else if completionType < NoCompletion {
|
||||
chosen, suggestions = PluginComplete(completionType, currentArg)
|
||||
}
|
||||
@@ -269,36 +299,58 @@ func (m *Messenger) Prompt(prompt, historyType string, completionTypes ...Comple
|
||||
func (m *Messenger) HandleEvent(event tcell.Event, history []string) {
|
||||
switch e := event.(type) {
|
||||
case *tcell.EventKey:
|
||||
if e.Key() != tcell.KeyRune || e.Modifiers() != 0 {
|
||||
for key, actions := range bindings {
|
||||
if e.Key() == key.keyCode {
|
||||
if e.Key() == tcell.KeyRune {
|
||||
if e.Rune() != key.r {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if e.Modifiers() == key.modifiers {
|
||||
for _, action := range actions {
|
||||
funcName := FuncName(action)
|
||||
switch funcName {
|
||||
case "main.(*View).CursorUp":
|
||||
if m.historyNum > 0 {
|
||||
m.historyNum--
|
||||
m.response = history[m.historyNum]
|
||||
m.cursorx = Count(m.response)
|
||||
}
|
||||
case "main.(*View).CursorDown":
|
||||
if m.historyNum < len(history)-1 {
|
||||
m.historyNum++
|
||||
m.response = history[m.historyNum]
|
||||
m.cursorx = Count(m.response)
|
||||
}
|
||||
case "main.(*View).CursorLeft":
|
||||
if m.cursorx > 0 {
|
||||
m.cursorx--
|
||||
}
|
||||
case "main.(*View).CursorRight":
|
||||
if m.cursorx < Count(m.response) {
|
||||
m.cursorx++
|
||||
}
|
||||
case "main.(*View).CursorStart", "main.(*View).StartOfLine":
|
||||
m.cursorx = 0
|
||||
case "main.(*View).CursorEnd", "main.(*View).EndOfLine":
|
||||
m.cursorx = Count(m.response)
|
||||
case "main.(*View).Backspace":
|
||||
if m.cursorx > 0 {
|
||||
m.response = string([]rune(m.response)[:m.cursorx-1]) + string([]rune(m.response)[m.cursorx:])
|
||||
m.cursorx--
|
||||
}
|
||||
case "main.(*View).Paste":
|
||||
clip, _ := clipboard.ReadAll("clipboard")
|
||||
m.response = Insert(m.response, m.cursorx, clip)
|
||||
m.cursorx += Count(clip)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
switch e.Key() {
|
||||
case tcell.KeyUp:
|
||||
if m.historyNum > 0 {
|
||||
m.historyNum--
|
||||
m.response = history[m.historyNum]
|
||||
m.cursorx = Count(m.response)
|
||||
}
|
||||
case tcell.KeyDown:
|
||||
if m.historyNum < len(history)-1 {
|
||||
m.historyNum++
|
||||
m.response = history[m.historyNum]
|
||||
m.cursorx = Count(m.response)
|
||||
}
|
||||
case tcell.KeyLeft:
|
||||
if m.cursorx > 0 {
|
||||
m.cursorx--
|
||||
}
|
||||
case tcell.KeyRight:
|
||||
if m.cursorx < Count(m.response) {
|
||||
m.cursorx++
|
||||
}
|
||||
case tcell.KeyBackspace2, tcell.KeyBackspace:
|
||||
if m.cursorx > 0 {
|
||||
m.response = string([]rune(m.response)[:m.cursorx-1]) + string([]rune(m.response)[m.cursorx:])
|
||||
m.cursorx--
|
||||
}
|
||||
case tcell.KeyCtrlV:
|
||||
clip, _ := clipboard.ReadAll("clipboard")
|
||||
m.response = Insert(m.response, m.cursorx, clip)
|
||||
m.cursorx += Count(clip)
|
||||
case tcell.KeyRune:
|
||||
m.response = Insert(m.response, m.cursorx, string(e.Rune()))
|
||||
m.cursorx++
|
||||
@@ -309,6 +361,23 @@ func (m *Messenger) HandleEvent(event tcell.Event, history []string) {
|
||||
clip := e.Text()
|
||||
m.response = Insert(m.response, m.cursorx, clip)
|
||||
m.cursorx += Count(clip)
|
||||
case *tcell.EventMouse:
|
||||
x, y := e.Position()
|
||||
x -= Count(m.message)
|
||||
button := e.Buttons()
|
||||
_, screenH := screen.Size()
|
||||
|
||||
if y == screenH-1 {
|
||||
switch button {
|
||||
case tcell.Button1:
|
||||
m.cursorx = x
|
||||
if m.cursorx < 0 {
|
||||
m.cursorx = 0
|
||||
} else if m.cursorx > Count(m.response) {
|
||||
m.cursorx = Count(m.response)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,8 +5,10 @@ import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-errors/errors"
|
||||
"github.com/layeh/gopher-luar"
|
||||
@@ -23,6 +25,7 @@ const (
|
||||
synLinesDown = 75 // How many lines down to look to do syntax highlighting
|
||||
doubleClickThreshold = 400 // How many milliseconds to wait before a second click is not a double click
|
||||
undoThreshold = 500 // If two events are less than n milliseconds apart, undo both of them
|
||||
autosaveTime = 8 // Number of seconds to wait before autosaving
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -60,7 +63,8 @@ var (
|
||||
// Channel of jobs running in the background
|
||||
jobs chan JobFunction
|
||||
// Event channel
|
||||
events chan tcell.Event
|
||||
events chan tcell.Event
|
||||
autosave chan bool
|
||||
)
|
||||
|
||||
// LoadInput determines which files should be loaded into buffers
|
||||
@@ -163,29 +167,9 @@ func InitScreen() {
|
||||
os.Setenv("TERM", "xterm-truecolor")
|
||||
}
|
||||
|
||||
os.Setenv("TCELLDB", configDir+"/.tcelldb")
|
||||
|
||||
// Initilize tcell
|
||||
var err error
|
||||
screen, err = tcell.NewScreen()
|
||||
|
||||
if err != nil && err.Error() == "terminal entry not found" {
|
||||
var termDB []byte
|
||||
termDB, err = MkInfo()
|
||||
|
||||
if err != nil {
|
||||
fmt.Println("Terminal entry not found")
|
||||
fmt.Println("Error when trying to read terminfo: ", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if _, e := os.Stat(configDir); e == nil {
|
||||
ioutil.WriteFile(configDir+"/.tcelldb", termDB, 0644)
|
||||
}
|
||||
|
||||
screen, err = tcell.NewScreen()
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
@@ -253,18 +237,15 @@ func main() {
|
||||
// Find the user's configuration directory (probably $XDG_CONFIG_HOME/micro)
|
||||
InitConfigDir()
|
||||
|
||||
// Build a list of available Extensions (Syntax, Colorscheme etc.)
|
||||
InitRuntimeFiles()
|
||||
|
||||
// Load the user's settings
|
||||
InitGlobalSettings()
|
||||
|
||||
InitCommands()
|
||||
InitBindings()
|
||||
|
||||
// Load the syntax files, including the colorscheme
|
||||
LoadSyntaxFiles()
|
||||
|
||||
// Load the help files
|
||||
LoadHelp()
|
||||
|
||||
// Start the screen
|
||||
InitScreen()
|
||||
|
||||
@@ -300,6 +281,8 @@ func main() {
|
||||
v.matches = Match(v)
|
||||
}
|
||||
}
|
||||
|
||||
t.Resize()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -327,19 +310,40 @@ func main() {
|
||||
L.SetGlobal("HandleShellCommand", luar.New(L, HandleShellCommand))
|
||||
L.SetGlobal("GetLeadingWhitespace", luar.New(L, GetLeadingWhitespace))
|
||||
L.SetGlobal("MakeCompletion", luar.New(L, MakeCompletion))
|
||||
L.SetGlobal("NewBuffer", luar.New(L, NewBuffer))
|
||||
L.SetGlobal("RuneStr", luar.New(L, func(r rune) string {
|
||||
return string(r)
|
||||
}))
|
||||
L.SetGlobal("Loc", luar.New(L, func(x, y int) Loc {
|
||||
return Loc{x, y}
|
||||
}))
|
||||
L.SetGlobal("JoinPaths", luar.New(L, filepath.Join))
|
||||
L.SetGlobal("configDir", luar.New(L, configDir))
|
||||
|
||||
// Used for asynchronous jobs
|
||||
L.SetGlobal("JobStart", luar.New(L, JobStart))
|
||||
L.SetGlobal("JobSend", luar.New(L, JobSend))
|
||||
L.SetGlobal("JobStop", luar.New(L, JobStop))
|
||||
|
||||
LoadPlugins()
|
||||
// Extension Files
|
||||
L.SetGlobal("ReadRuntimeFile", luar.New(L, PluginReadRuntimeFile))
|
||||
L.SetGlobal("ListRuntimeFiles", luar.New(L, PluginListRuntimeFiles))
|
||||
L.SetGlobal("AddRuntimeFile", luar.New(L, PluginAddRuntimeFile))
|
||||
L.SetGlobal("AddRuntimeFilesFromDirectory", luar.New(L, PluginAddRuntimeFilesFromDirectory))
|
||||
|
||||
jobs = make(chan JobFunction, 100)
|
||||
events = make(chan tcell.Event)
|
||||
events = make(chan tcell.Event, 100)
|
||||
autosave = make(chan bool)
|
||||
|
||||
LoadPlugins()
|
||||
|
||||
// Load the syntax files, including the colorscheme
|
||||
LoadSyntaxFiles()
|
||||
|
||||
for _, t := range tabs {
|
||||
for _, v := range t.views {
|
||||
v.Buf.FindFileType()
|
||||
v.Buf.UpdateRules()
|
||||
for _, pl := range loadedPlugins {
|
||||
_, err := Call(pl+".onViewOpen", v)
|
||||
if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") {
|
||||
@@ -360,6 +364,15 @@ func main() {
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
for {
|
||||
time.Sleep(autosaveTime * time.Second)
|
||||
if globalSettings["autosave"].(bool) {
|
||||
autosave <- true
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
for {
|
||||
// Display everything
|
||||
RedrawAll()
|
||||
@@ -372,50 +385,61 @@ func main() {
|
||||
// If a new job has finished while running in the background we should execute the callback
|
||||
f.function(f.output, f.args...)
|
||||
continue
|
||||
case <-autosave:
|
||||
CurView().Save(true)
|
||||
case event = <-events:
|
||||
}
|
||||
|
||||
switch e := event.(type) {
|
||||
case *tcell.EventMouse:
|
||||
if e.Buttons() == tcell.Button1 {
|
||||
// If the user left clicked we check a couple things
|
||||
_, h := screen.Size()
|
||||
x, y := e.Position()
|
||||
if y == h-1 && messenger.message != "" && globalSettings["infobar"].(bool) {
|
||||
// If the user clicked in the bottom bar, and there is a message down there
|
||||
// we copy it to the clipboard.
|
||||
// Often error messages are displayed down there so it can be useful to easily
|
||||
// copy the message
|
||||
clipboard.WriteAll(messenger.message, "primary")
|
||||
continue
|
||||
}
|
||||
for event != nil {
|
||||
switch e := event.(type) {
|
||||
case *tcell.EventMouse:
|
||||
if e.Buttons() == tcell.Button1 {
|
||||
// If the user left clicked we check a couple things
|
||||
_, h := screen.Size()
|
||||
x, y := e.Position()
|
||||
if y == h-1 && messenger.message != "" && globalSettings["infobar"].(bool) {
|
||||
// If the user clicked in the bottom bar, and there is a message down there
|
||||
// we copy it to the clipboard.
|
||||
// Often error messages are displayed down there so it can be useful to easily
|
||||
// copy the message
|
||||
clipboard.WriteAll(messenger.message, "primary")
|
||||
break
|
||||
}
|
||||
|
||||
if CurView().mouseReleased {
|
||||
// We loop through each view in the current tab and make sure the current view
|
||||
// is the one being clicked in
|
||||
for _, v := range tabs[curTab].views {
|
||||
if x >= v.x && x < v.x+v.width && y >= v.y && y < v.y+v.height {
|
||||
tabs[curTab].curView = v.Num
|
||||
if CurView().mouseReleased {
|
||||
// We loop through each view in the current tab and make sure the current view
|
||||
// is the one being clicked in
|
||||
for _, v := range tabs[curTab].views {
|
||||
if x >= v.x && x < v.x+v.width && y >= v.y && y < v.y+v.height {
|
||||
tabs[curTab].curView = v.Num
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This function checks the mouse event for the possibility of changing the current tab
|
||||
// If the tab was changed it returns true
|
||||
if TabbarHandleMouseEvent(event) {
|
||||
continue
|
||||
}
|
||||
// This function checks the mouse event for the possibility of changing the current tab
|
||||
// If the tab was changed it returns true
|
||||
if TabbarHandleMouseEvent(event) {
|
||||
break
|
||||
}
|
||||
|
||||
if searching {
|
||||
// Since searching is done in real time, we need to redraw every time
|
||||
// there is a new event in the search bar so we need a special function
|
||||
// to run instead of the standard HandleEvent.
|
||||
HandleSearchEvent(event, CurView())
|
||||
} else {
|
||||
// Send it to the view
|
||||
CurView().HandleEvent(event)
|
||||
}
|
||||
|
||||
select {
|
||||
case event = <-events:
|
||||
default:
|
||||
event = nil
|
||||
}
|
||||
|
||||
if searching {
|
||||
// Since searching is done in real time, we need to redraw every time
|
||||
// there is a new event in the search bar so we need a special function
|
||||
// to run instead of the standard HandleEvent.
|
||||
HandleSearchEvent(event, CurView())
|
||||
} else {
|
||||
// Send it to the view
|
||||
CurView().HandleEvent(event)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,669 +0,0 @@
|
||||
// Copyright 2016 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 [-go file.go] [-json file.json] [-quiet] [-nofatal] [<term>...]
|
||||
//
|
||||
// -go specifiles 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 main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/zyedidia/tcell"
|
||||
)
|
||||
|
||||
// #include <curses.h>
|
||||
// #include <term.h>
|
||||
// #cgo LDFLAGS: -lcurses
|
||||
//
|
||||
// void noenv() {
|
||||
// use_env(FALSE);
|
||||
// }
|
||||
//
|
||||
// char *tigetstr_good(char *name) {
|
||||
// char *r;
|
||||
// r = tigetstr(name);
|
||||
// if (r == (char *)-1) {
|
||||
// r = NULL;
|
||||
// }
|
||||
// return (r);
|
||||
// }
|
||||
import "C"
|
||||
|
||||
func tigetnum(s string) int {
|
||||
n := C.tigetnum(C.CString(s))
|
||||
return int(n)
|
||||
}
|
||||
|
||||
func tigetflag(s string) bool {
|
||||
n := C.tigetflag(C.CString(s))
|
||||
return n != 0
|
||||
}
|
||||
|
||||
func tigetstr(s string) string {
|
||||
// NB: If the string is invalid, we'll get back -1, which causes
|
||||
// no end of grief. So make sure your capability strings are correct!
|
||||
cs := C.tigetstr_good(C.CString(s))
|
||||
if cs == nil {
|
||||
return ""
|
||||
}
|
||||
return C.GoString(cs)
|
||||
}
|
||||
|
||||
// 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 fabricte 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) (*tcell.Terminfo, error) {
|
||||
addTrueColor := false
|
||||
rsn := C.int(0)
|
||||
C.noenv()
|
||||
rv, _ := C.setupterm(C.CString(name), 1, &rsn)
|
||||
if rv == C.ERR {
|
||||
if strings.HasSuffix(name, "-truecolor") {
|
||||
base := name[:len(name)-len("-truecolor")]
|
||||
// Probably -256color is closest to what we want
|
||||
rv, _ = C.setupterm(C.CString(base+"-256color"), 1,
|
||||
&rsn)
|
||||
// Otherwise try the base
|
||||
if rv == C.ERR {
|
||||
rv, _ = C.setupterm(C.CString(base), 1, &rsn)
|
||||
}
|
||||
if rv != C.ERR {
|
||||
addTrueColor = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if rv == C.ERR {
|
||||
switch rsn {
|
||||
case 1:
|
||||
return nil, errors.New("hardcopy terminal")
|
||||
case 0:
|
||||
return nil, errors.New("terminal definition not found")
|
||||
case -1:
|
||||
return nil, errors.New("terminfo database missing")
|
||||
default:
|
||||
return nil, errors.New("setupterm failed (other)")
|
||||
}
|
||||
}
|
||||
t := &tcell.Terminfo{}
|
||||
t.Name = name
|
||||
t.Colors = tigetnum("colors")
|
||||
t.Columns = tigetnum("cols")
|
||||
t.Lines = tigetnum("lines")
|
||||
t.Bell = tigetstr("bel")
|
||||
t.Clear = tigetstr("clear")
|
||||
t.EnterCA = tigetstr("smcup")
|
||||
t.ExitCA = tigetstr("rmcup")
|
||||
t.ShowCursor = tigetstr("cnorm")
|
||||
t.HideCursor = tigetstr("civis")
|
||||
t.AttrOff = tigetstr("sgr0")
|
||||
t.Underline = tigetstr("smul")
|
||||
t.Bold = tigetstr("bold")
|
||||
t.Blink = tigetstr("blink")
|
||||
t.Dim = tigetstr("dim")
|
||||
t.Reverse = tigetstr("rev")
|
||||
t.EnterKeypad = tigetstr("smkx")
|
||||
t.ExitKeypad = tigetstr("rmkx")
|
||||
t.SetFg = tigetstr("setaf")
|
||||
t.SetBg = tigetstr("setab")
|
||||
t.SetCursor = tigetstr("cup")
|
||||
t.CursorBack1 = tigetstr("cub1")
|
||||
t.CursorUp1 = tigetstr("cuu1")
|
||||
t.KeyF1 = tigetstr("kf1")
|
||||
t.KeyF2 = tigetstr("kf2")
|
||||
t.KeyF3 = tigetstr("kf3")
|
||||
t.KeyF4 = tigetstr("kf4")
|
||||
t.KeyF5 = tigetstr("kf5")
|
||||
t.KeyF6 = tigetstr("kf6")
|
||||
t.KeyF7 = tigetstr("kf7")
|
||||
t.KeyF8 = tigetstr("kf8")
|
||||
t.KeyF9 = tigetstr("kf9")
|
||||
t.KeyF10 = tigetstr("kf10")
|
||||
t.KeyF11 = tigetstr("kf11")
|
||||
t.KeyF12 = tigetstr("kf12")
|
||||
t.KeyF13 = tigetstr("kf13")
|
||||
t.KeyF14 = tigetstr("kf14")
|
||||
t.KeyF15 = tigetstr("kf15")
|
||||
t.KeyF16 = tigetstr("kf16")
|
||||
t.KeyF17 = tigetstr("kf17")
|
||||
t.KeyF18 = tigetstr("kf18")
|
||||
t.KeyF19 = tigetstr("kf19")
|
||||
t.KeyF20 = tigetstr("kf20")
|
||||
t.KeyF21 = tigetstr("kf21")
|
||||
t.KeyF22 = tigetstr("kf22")
|
||||
t.KeyF23 = tigetstr("kf23")
|
||||
t.KeyF24 = tigetstr("kf24")
|
||||
t.KeyF25 = tigetstr("kf25")
|
||||
t.KeyF26 = tigetstr("kf26")
|
||||
t.KeyF27 = tigetstr("kf27")
|
||||
t.KeyF28 = tigetstr("kf28")
|
||||
t.KeyF29 = tigetstr("kf29")
|
||||
t.KeyF30 = tigetstr("kf30")
|
||||
t.KeyF31 = tigetstr("kf31")
|
||||
t.KeyF32 = tigetstr("kf32")
|
||||
t.KeyF33 = tigetstr("kf33")
|
||||
t.KeyF34 = tigetstr("kf34")
|
||||
t.KeyF35 = tigetstr("kf35")
|
||||
t.KeyF36 = tigetstr("kf36")
|
||||
t.KeyF37 = tigetstr("kf37")
|
||||
t.KeyF38 = tigetstr("kf38")
|
||||
t.KeyF39 = tigetstr("kf39")
|
||||
t.KeyF40 = tigetstr("kf40")
|
||||
t.KeyF41 = tigetstr("kf41")
|
||||
t.KeyF42 = tigetstr("kf42")
|
||||
t.KeyF43 = tigetstr("kf43")
|
||||
t.KeyF44 = tigetstr("kf44")
|
||||
t.KeyF45 = tigetstr("kf45")
|
||||
t.KeyF46 = tigetstr("kf46")
|
||||
t.KeyF47 = tigetstr("kf47")
|
||||
t.KeyF48 = tigetstr("kf48")
|
||||
t.KeyF49 = tigetstr("kf49")
|
||||
t.KeyF50 = tigetstr("kf50")
|
||||
t.KeyF51 = tigetstr("kf51")
|
||||
t.KeyF52 = tigetstr("kf52")
|
||||
t.KeyF53 = tigetstr("kf53")
|
||||
t.KeyF54 = tigetstr("kf54")
|
||||
t.KeyF55 = tigetstr("kf55")
|
||||
t.KeyF56 = tigetstr("kf56")
|
||||
t.KeyF57 = tigetstr("kf57")
|
||||
t.KeyF58 = tigetstr("kf58")
|
||||
t.KeyF59 = tigetstr("kf59")
|
||||
t.KeyF60 = tigetstr("kf60")
|
||||
t.KeyF61 = tigetstr("kf61")
|
||||
t.KeyF62 = tigetstr("kf62")
|
||||
t.KeyF63 = tigetstr("kf63")
|
||||
t.KeyF64 = tigetstr("kf64")
|
||||
t.KeyInsert = tigetstr("kich1")
|
||||
t.KeyDelete = tigetstr("kdch1")
|
||||
t.KeyBackspace = tigetstr("kbs")
|
||||
t.KeyHome = tigetstr("khome")
|
||||
t.KeyEnd = tigetstr("kend")
|
||||
t.KeyUp = tigetstr("kcuu1")
|
||||
t.KeyDown = tigetstr("kcud1")
|
||||
t.KeyRight = tigetstr("kcuf1")
|
||||
t.KeyLeft = tigetstr("kcub1")
|
||||
t.KeyPgDn = tigetstr("knp")
|
||||
t.KeyPgUp = tigetstr("kpp")
|
||||
t.KeyBacktab = tigetstr("kcbt")
|
||||
t.KeyExit = tigetstr("kext")
|
||||
t.KeyCancel = tigetstr("kcan")
|
||||
t.KeyPrint = tigetstr("kprt")
|
||||
t.KeyHelp = tigetstr("khlp")
|
||||
t.KeyClear = tigetstr("kclr")
|
||||
t.AltChars = tigetstr("acsc")
|
||||
t.EnterAcs = tigetstr("smacs")
|
||||
t.ExitAcs = tigetstr("rmacs")
|
||||
t.EnableAcs = tigetstr("enacs")
|
||||
t.Mouse = tigetstr("kmous")
|
||||
t.KeyShfRight = tigetstr("kRIT")
|
||||
t.KeyShfLeft = tigetstr("kLFT")
|
||||
t.KeyShfHome = tigetstr("kHOM")
|
||||
t.KeyShfEnd = tigetstr("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 = tigetstr("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[?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 = tigetstr("pad")
|
||||
if t.PadChar == "" {
|
||||
if !tigetflag("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, nil
|
||||
}
|
||||
|
||||
func dotGoAddInt(w io.Writer, n string, i int) {
|
||||
if i == 0 {
|
||||
// initialized to 0, ignore
|
||||
return
|
||||
}
|
||||
fmt.Fprintf(w, " %-13s %d,\n", n+":", i)
|
||||
}
|
||||
func dotGoAddStr(w io.Writer, n string, s string) {
|
||||
if s == "" {
|
||||
return
|
||||
}
|
||||
fmt.Fprintf(w, " %-13s %q,\n", n+":", s)
|
||||
}
|
||||
|
||||
func dotGoAddArr(w io.Writer, n string, a []string) {
|
||||
if len(a) == 0 {
|
||||
return
|
||||
}
|
||||
fmt.Fprintf(w, " %-13s []string{", n+":")
|
||||
did := false
|
||||
for _, b := range a {
|
||||
if did {
|
||||
fmt.Fprint(w, ", ")
|
||||
}
|
||||
did = true
|
||||
fmt.Fprintf(w, "%q", b)
|
||||
}
|
||||
fmt.Fprintln(w, "},")
|
||||
}
|
||||
|
||||
func dotGoHeader(w io.Writer) {
|
||||
fmt.Fprintf(w, "// Generated by %s (%s/%s) on %s.\n",
|
||||
os.Args[0],
|
||||
runtime.GOOS, runtime.GOARCH,
|
||||
time.Now().Format(time.UnixDate))
|
||||
fmt.Fprintln(w, "// DO NOT HAND-EDIT")
|
||||
fmt.Fprintln(w, "")
|
||||
fmt.Fprintln(w, "package tcell")
|
||||
fmt.Fprintln(w, "")
|
||||
fmt.Fprintln(w, "func init() {")
|
||||
}
|
||||
|
||||
func dotGoTrailer(w io.Writer) {
|
||||
fmt.Fprintln(w, "}")
|
||||
}
|
||||
|
||||
func dotGoInfo(w io.Writer, t *tcell.Terminfo) {
|
||||
fmt.Fprintln(w, " AddTerminfo(&Terminfo{")
|
||||
dotGoAddStr(w, "Name", t.Name)
|
||||
dotGoAddArr(w, "Aliases", t.Aliases)
|
||||
dotGoAddInt(w, "Columns", t.Columns)
|
||||
dotGoAddInt(w, "Lines", t.Lines)
|
||||
dotGoAddInt(w, "Colors", t.Colors)
|
||||
dotGoAddStr(w, "Bell", t.Bell)
|
||||
dotGoAddStr(w, "Clear", t.Clear)
|
||||
dotGoAddStr(w, "EnterCA", t.EnterCA)
|
||||
dotGoAddStr(w, "ExitCA", t.ExitCA)
|
||||
dotGoAddStr(w, "ShowCursor", t.ShowCursor)
|
||||
dotGoAddStr(w, "HideCursor", t.HideCursor)
|
||||
dotGoAddStr(w, "AttrOff", t.AttrOff)
|
||||
dotGoAddStr(w, "Underline", t.Underline)
|
||||
dotGoAddStr(w, "Bold", t.Bold)
|
||||
dotGoAddStr(w, "Dim", t.Dim)
|
||||
dotGoAddStr(w, "Blink", t.Blink)
|
||||
dotGoAddStr(w, "Reverse", t.Reverse)
|
||||
dotGoAddStr(w, "EnterKeypad", t.EnterKeypad)
|
||||
dotGoAddStr(w, "ExitKeypad", t.ExitKeypad)
|
||||
dotGoAddStr(w, "SetFg", t.SetFg)
|
||||
dotGoAddStr(w, "SetBg", t.SetBg)
|
||||
dotGoAddStr(w, "SetFgBg", t.SetFgBg)
|
||||
dotGoAddStr(w, "PadChar", t.PadChar)
|
||||
dotGoAddStr(w, "AltChars", t.AltChars)
|
||||
dotGoAddStr(w, "EnterAcs", t.EnterAcs)
|
||||
dotGoAddStr(w, "ExitAcs", t.ExitAcs)
|
||||
dotGoAddStr(w, "EnableAcs", t.EnableAcs)
|
||||
dotGoAddStr(w, "SetFgRGB", t.SetFgRGB)
|
||||
dotGoAddStr(w, "SetBgRGB", t.SetBgRGB)
|
||||
dotGoAddStr(w, "SetFgBgRGB", t.SetFgBgRGB)
|
||||
dotGoAddStr(w, "Mouse", t.Mouse)
|
||||
dotGoAddStr(w, "MouseMode", t.MouseMode)
|
||||
dotGoAddStr(w, "SetCursor", t.SetCursor)
|
||||
dotGoAddStr(w, "CursorBack1", t.CursorBack1)
|
||||
dotGoAddStr(w, "CursorUp1", t.CursorUp1)
|
||||
dotGoAddStr(w, "KeyUp", t.KeyUp)
|
||||
dotGoAddStr(w, "KeyDown", t.KeyDown)
|
||||
dotGoAddStr(w, "KeyRight", t.KeyRight)
|
||||
dotGoAddStr(w, "KeyLeft", t.KeyLeft)
|
||||
dotGoAddStr(w, "KeyInsert", t.KeyInsert)
|
||||
dotGoAddStr(w, "KeyDelete", t.KeyDelete)
|
||||
dotGoAddStr(w, "KeyBackspace", t.KeyBackspace)
|
||||
dotGoAddStr(w, "KeyHome", t.KeyHome)
|
||||
dotGoAddStr(w, "KeyEnd", t.KeyEnd)
|
||||
dotGoAddStr(w, "KeyPgUp", t.KeyPgUp)
|
||||
dotGoAddStr(w, "KeyPgDn", t.KeyPgDn)
|
||||
dotGoAddStr(w, "KeyF1", t.KeyF1)
|
||||
dotGoAddStr(w, "KeyF2", t.KeyF2)
|
||||
dotGoAddStr(w, "KeyF3", t.KeyF3)
|
||||
dotGoAddStr(w, "KeyF4", t.KeyF4)
|
||||
dotGoAddStr(w, "KeyF5", t.KeyF5)
|
||||
dotGoAddStr(w, "KeyF6", t.KeyF6)
|
||||
dotGoAddStr(w, "KeyF7", t.KeyF7)
|
||||
dotGoAddStr(w, "KeyF8", t.KeyF8)
|
||||
dotGoAddStr(w, "KeyF9", t.KeyF9)
|
||||
dotGoAddStr(w, "KeyF10", t.KeyF10)
|
||||
dotGoAddStr(w, "KeyF11", t.KeyF11)
|
||||
dotGoAddStr(w, "KeyF12", t.KeyF12)
|
||||
dotGoAddStr(w, "KeyF13", t.KeyF13)
|
||||
dotGoAddStr(w, "KeyF14", t.KeyF14)
|
||||
dotGoAddStr(w, "KeyF15", t.KeyF15)
|
||||
dotGoAddStr(w, "KeyF16", t.KeyF16)
|
||||
dotGoAddStr(w, "KeyF17", t.KeyF17)
|
||||
dotGoAddStr(w, "KeyF18", t.KeyF18)
|
||||
dotGoAddStr(w, "KeyF19", t.KeyF19)
|
||||
dotGoAddStr(w, "KeyF20", t.KeyF20)
|
||||
dotGoAddStr(w, "KeyF21", t.KeyF21)
|
||||
dotGoAddStr(w, "KeyF22", t.KeyF22)
|
||||
dotGoAddStr(w, "KeyF23", t.KeyF23)
|
||||
dotGoAddStr(w, "KeyF24", t.KeyF24)
|
||||
dotGoAddStr(w, "KeyF25", t.KeyF25)
|
||||
dotGoAddStr(w, "KeyF26", t.KeyF26)
|
||||
dotGoAddStr(w, "KeyF27", t.KeyF27)
|
||||
dotGoAddStr(w, "KeyF28", t.KeyF28)
|
||||
dotGoAddStr(w, "KeyF29", t.KeyF29)
|
||||
dotGoAddStr(w, "KeyF30", t.KeyF30)
|
||||
dotGoAddStr(w, "KeyF31", t.KeyF31)
|
||||
dotGoAddStr(w, "KeyF32", t.KeyF32)
|
||||
dotGoAddStr(w, "KeyF33", t.KeyF33)
|
||||
dotGoAddStr(w, "KeyF34", t.KeyF34)
|
||||
dotGoAddStr(w, "KeyF35", t.KeyF35)
|
||||
dotGoAddStr(w, "KeyF36", t.KeyF36)
|
||||
dotGoAddStr(w, "KeyF37", t.KeyF37)
|
||||
dotGoAddStr(w, "KeyF38", t.KeyF38)
|
||||
dotGoAddStr(w, "KeyF39", t.KeyF39)
|
||||
dotGoAddStr(w, "KeyF40", t.KeyF40)
|
||||
dotGoAddStr(w, "KeyF41", t.KeyF41)
|
||||
dotGoAddStr(w, "KeyF42", t.KeyF42)
|
||||
dotGoAddStr(w, "KeyF43", t.KeyF43)
|
||||
dotGoAddStr(w, "KeyF44", t.KeyF44)
|
||||
dotGoAddStr(w, "KeyF45", t.KeyF45)
|
||||
dotGoAddStr(w, "KeyF46", t.KeyF46)
|
||||
dotGoAddStr(w, "KeyF47", t.KeyF47)
|
||||
dotGoAddStr(w, "KeyF48", t.KeyF48)
|
||||
dotGoAddStr(w, "KeyF49", t.KeyF49)
|
||||
dotGoAddStr(w, "KeyF50", t.KeyF50)
|
||||
dotGoAddStr(w, "KeyF51", t.KeyF51)
|
||||
dotGoAddStr(w, "KeyF52", t.KeyF52)
|
||||
dotGoAddStr(w, "KeyF53", t.KeyF53)
|
||||
dotGoAddStr(w, "KeyF54", t.KeyF54)
|
||||
dotGoAddStr(w, "KeyF55", t.KeyF55)
|
||||
dotGoAddStr(w, "KeyF56", t.KeyF56)
|
||||
dotGoAddStr(w, "KeyF57", t.KeyF57)
|
||||
dotGoAddStr(w, "KeyF58", t.KeyF58)
|
||||
dotGoAddStr(w, "KeyF59", t.KeyF59)
|
||||
dotGoAddStr(w, "KeyF60", t.KeyF60)
|
||||
dotGoAddStr(w, "KeyF61", t.KeyF61)
|
||||
dotGoAddStr(w, "KeyF62", t.KeyF62)
|
||||
dotGoAddStr(w, "KeyF63", t.KeyF63)
|
||||
dotGoAddStr(w, "KeyF64", t.KeyF64)
|
||||
dotGoAddStr(w, "KeyCancel", t.KeyCancel)
|
||||
dotGoAddStr(w, "KeyPrint", t.KeyPrint)
|
||||
dotGoAddStr(w, "KeyExit", t.KeyExit)
|
||||
dotGoAddStr(w, "KeyHelp", t.KeyHelp)
|
||||
dotGoAddStr(w, "KeyClear", t.KeyClear)
|
||||
dotGoAddStr(w, "KeyBacktab", t.KeyBacktab)
|
||||
dotGoAddStr(w, "KeyShfLeft", t.KeyShfLeft)
|
||||
dotGoAddStr(w, "KeyShfRight", t.KeyShfRight)
|
||||
dotGoAddStr(w, "KeyShfUp", t.KeyShfUp)
|
||||
dotGoAddStr(w, "KeyShfDown", t.KeyShfDown)
|
||||
dotGoAddStr(w, "KeyCtrlLeft", t.KeyCtrlLeft)
|
||||
dotGoAddStr(w, "KeyCtrlRight", t.KeyCtrlRight)
|
||||
dotGoAddStr(w, "KeyCtrlUp", t.KeyCtrlUp)
|
||||
dotGoAddStr(w, "KeyCtrlDown", t.KeyCtrlDown)
|
||||
dotGoAddStr(w, "KeyMetaLeft", t.KeyMetaLeft)
|
||||
dotGoAddStr(w, "KeyMetaRight", t.KeyMetaRight)
|
||||
dotGoAddStr(w, "KeyMetaUp", t.KeyMetaUp)
|
||||
dotGoAddStr(w, "KeyMetaDown", t.KeyMetaDown)
|
||||
dotGoAddStr(w, "KeyAltLeft", t.KeyAltLeft)
|
||||
dotGoAddStr(w, "KeyAltRight", t.KeyAltRight)
|
||||
dotGoAddStr(w, "KeyAltUp", t.KeyAltUp)
|
||||
dotGoAddStr(w, "KeyAltDown", t.KeyAltDown)
|
||||
dotGoAddStr(w, "KeyAltShfLeft", t.KeyAltShfLeft)
|
||||
dotGoAddStr(w, "KeyAltShfRight", t.KeyAltShfRight)
|
||||
dotGoAddStr(w, "KeyAltShfUp", t.KeyAltShfUp)
|
||||
dotGoAddStr(w, "KeyAltShfDown", t.KeyAltShfDown)
|
||||
dotGoAddStr(w, "KeyMetaShfLeft", t.KeyMetaShfLeft)
|
||||
dotGoAddStr(w, "KeyMetaShfRight", t.KeyMetaShfRight)
|
||||
dotGoAddStr(w, "KeyMetaShfUp", t.KeyMetaShfUp)
|
||||
dotGoAddStr(w, "KeyMetaShfDown", t.KeyMetaShfDown)
|
||||
dotGoAddStr(w, "KeyCtrlShfLeft", t.KeyCtrlShfLeft)
|
||||
dotGoAddStr(w, "KeyCtrlShfRight", t.KeyCtrlShfRight)
|
||||
dotGoAddStr(w, "KeyCtrlShfUp", t.KeyCtrlShfUp)
|
||||
dotGoAddStr(w, "KeyCtrlShfDown", t.KeyCtrlShfDown)
|
||||
dotGoAddStr(w, "KeyShfHome", t.KeyShfHome)
|
||||
dotGoAddStr(w, "KeyShfEnd", t.KeyShfEnd)
|
||||
dotGoAddStr(w, "KeyCtrlHome", t.KeyCtrlHome)
|
||||
dotGoAddStr(w, "KeyCtrlEnd", t.KeyCtrlEnd)
|
||||
dotGoAddStr(w, "KeyMetaHome", t.KeyMetaHome)
|
||||
dotGoAddStr(w, "KeyMetaEnd", t.KeyMetaEnd)
|
||||
dotGoAddStr(w, "KeyAltHome", t.KeyAltHome)
|
||||
dotGoAddStr(w, "KeyAltEnd", t.KeyAltEnd)
|
||||
dotGoAddStr(w, "KeyCtrlShfHome", t.KeyCtrlShfHome)
|
||||
dotGoAddStr(w, "KeyCtrlShfEnd", t.KeyCtrlShfEnd)
|
||||
dotGoAddStr(w, "KeyMetaShfHome", t.KeyMetaShfHome)
|
||||
dotGoAddStr(w, "KeyMetaShfEnd", t.KeyMetaShfEnd)
|
||||
dotGoAddStr(w, "KeyAltShfHome", t.KeyAltShfHome)
|
||||
dotGoAddStr(w, "KeyAltShfEnd", t.KeyAltShfEnd)
|
||||
fmt.Fprintln(w, " })")
|
||||
}
|
||||
|
||||
func MkInfo() ([]byte, error) {
|
||||
jsonfile := ""
|
||||
gofile := ""
|
||||
nofatal := true
|
||||
quiet := true
|
||||
|
||||
var e error
|
||||
js := []byte{}
|
||||
|
||||
args := []string{os.Getenv("TERM")}
|
||||
|
||||
tdata := make(map[string]*tcell.Terminfo)
|
||||
adata := make(map[string]string)
|
||||
for _, term := range args {
|
||||
if arr := strings.SplitN(term, "=", 2); len(arr) == 2 {
|
||||
adata[arr[0]] = arr[1]
|
||||
} else if t, e := getinfo(term); e != nil {
|
||||
if !quiet {
|
||||
fmt.Fprintf(os.Stderr,
|
||||
"Failed loading %s: %v\n", term, e)
|
||||
}
|
||||
if !nofatal {
|
||||
os.Exit(1)
|
||||
}
|
||||
} else {
|
||||
tdata[t.Name] = t
|
||||
}
|
||||
}
|
||||
for alias, canon := range adata {
|
||||
if t, ok := tdata[canon]; ok {
|
||||
t.Aliases = append(t.Aliases, alias)
|
||||
// sort aliases to avoid extra diffs
|
||||
sort.Strings(t.Aliases)
|
||||
} else {
|
||||
if !quiet {
|
||||
fmt.Fprintf(os.Stderr,
|
||||
"Alias %s missing canonical %s\n",
|
||||
alias, canon)
|
||||
}
|
||||
if !nofatal {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if gofile != "" {
|
||||
w := os.Stdout
|
||||
if gofile != "-" {
|
||||
if w, e = os.Create(gofile); e != nil {
|
||||
return []byte{}, fmt.Errorf("Failed: %v", e)
|
||||
}
|
||||
}
|
||||
dotGoHeader(w)
|
||||
for _, term := range args {
|
||||
if t := tdata[term]; t != nil {
|
||||
dotGoInfo(w, t)
|
||||
}
|
||||
}
|
||||
dotGoTrailer(w)
|
||||
if w != os.Stdout {
|
||||
w.Close()
|
||||
}
|
||||
} else if jsonfile != "" {
|
||||
w := os.Stdout
|
||||
if jsonfile != "-" {
|
||||
if w, e = os.Create(jsonfile); e != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed: %v", e)
|
||||
}
|
||||
}
|
||||
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 []byte{}, fmt.Errorf("Failed: %v", e)
|
||||
}
|
||||
} else {
|
||||
for _, term := range args {
|
||||
if t := tdata[term]; t != nil {
|
||||
js, e := json.Marshal(tdata[term])
|
||||
if e != nil {
|
||||
return []byte{}, fmt.Errorf("Failed ", e)
|
||||
}
|
||||
return js, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return []byte{}, nil
|
||||
}
|
||||
@@ -12,12 +12,6 @@ import (
|
||||
|
||||
var loadedPlugins []string
|
||||
|
||||
var preInstalledPlugins = []string{
|
||||
"go",
|
||||
"linter",
|
||||
"autoclose",
|
||||
}
|
||||
|
||||
// Call calls the lua function 'function'
|
||||
// If it does not exist nothing happens, if there is an error,
|
||||
// the error is returned
|
||||
@@ -120,47 +114,28 @@ func LuaFunctionJob(function string) func(string, ...string) {
|
||||
|
||||
// LoadPlugins loads the pre-installed plugins and the plugins located in ~/.config/micro/plugins
|
||||
func LoadPlugins() {
|
||||
files, _ := ioutil.ReadDir(configDir + "/plugins")
|
||||
for _, plugin := range files {
|
||||
if plugin.IsDir() {
|
||||
pluginName := plugin.Name()
|
||||
files, _ := ioutil.ReadDir(configDir + "/plugins/" + pluginName)
|
||||
for _, f := range files {
|
||||
if f.Name() == pluginName+".lua" {
|
||||
data, _ := ioutil.ReadFile(configDir + "/plugins/" + pluginName + "/" + f.Name())
|
||||
pluginDef := "\nlocal P = {}\n" + pluginName + " = P\nsetmetatable(" + pluginName + ", {__index = _G})\nsetfenv(1, P)\n"
|
||||
|
||||
if err := L.DoString(pluginDef + string(data)); err != nil {
|
||||
TermMessage(err)
|
||||
continue
|
||||
}
|
||||
loadedPlugins = append(loadedPlugins, pluginName)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, pluginName := range preInstalledPlugins {
|
||||
for _, plugin := range ListRuntimeFiles(RTPlugin) {
|
||||
alreadyExists := false
|
||||
pluginName := plugin.Name()
|
||||
for _, pl := range loadedPlugins {
|
||||
if pl == pluginName {
|
||||
alreadyExists = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !alreadyExists {
|
||||
plugin := "runtime/plugins/" + pluginName + "/" + pluginName + ".lua"
|
||||
data, err := Asset(plugin)
|
||||
data, err := plugin.Data()
|
||||
if err != nil {
|
||||
TermMessage("Error loading pre-installed plugin: " + pluginName)
|
||||
TermMessage("Error loading plugin: " + pluginName)
|
||||
continue
|
||||
}
|
||||
pluginDef := "\nlocal P = {}\n" + pluginName + " = P\nsetmetatable(" + pluginName + ", {__index = _G})\nsetfenv(1, P)\n"
|
||||
|
||||
if err := L.DoString(pluginDef + string(data)); err != nil {
|
||||
TermMessage(err)
|
||||
continue
|
||||
}
|
||||
|
||||
loadedPlugins = append(loadedPlugins, pluginName)
|
||||
}
|
||||
}
|
||||
|
||||
610
cmd/micro/pluginmanager.go
Normal file
610
cmd/micro/pluginmanager.go
Normal file
@@ -0,0 +1,610 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/blang/semver"
|
||||
"github.com/yuin/gopher-lua"
|
||||
"github.com/zyedidia/json5/encoding/json5"
|
||||
)
|
||||
|
||||
var (
|
||||
allPluginPackages PluginPackages = nil
|
||||
)
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// PluginVersions is a slice of PluginVersion
|
||||
type PluginVersions []*PluginVersion
|
||||
|
||||
// PluginDenendency 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() PluginPackages {
|
||||
return fetchAllSources(len(pc), func(i int) PluginPackages {
|
||||
return pc[i].Fetch()
|
||||
})
|
||||
}
|
||||
|
||||
// Fetch retrieves all available PluginPackages from the given channel
|
||||
func (pc PluginChannel) Fetch() PluginPackages {
|
||||
// messenger.AddLog(fmt.Sprintf("Fetching channel: %q", string(pc)))
|
||||
resp, err := http.Get(string(pc))
|
||||
if err != nil {
|
||||
TermMessage("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 {
|
||||
TermMessage("Failed to decode channel data:\n", err)
|
||||
return PluginPackages{}
|
||||
}
|
||||
return fetchAllSources(len(repositories), func(i int) PluginPackages {
|
||||
return repositories[i].Fetch()
|
||||
})
|
||||
}
|
||||
|
||||
// Fetch retrieves all available PluginPackages from the given repository
|
||||
func (pr PluginRepository) Fetch() PluginPackages {
|
||||
// messenger.AddLog(fmt.Sprintf("Fetching repository: %q", string(pr)))
|
||||
resp, err := http.Get(string(pr))
|
||||
if err != nil {
|
||||
TermMessage("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 {
|
||||
TermMessage("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() PluginPackages {
|
||||
if allPluginPackages == nil {
|
||||
getOption := func(name string) []string {
|
||||
data := GetOption(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()
|
||||
} else {
|
||||
return repos[i-1].Fetch()
|
||||
}
|
||||
})
|
||||
}
|
||||
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 (s PluginVersions) Less(i, j int) bool {
|
||||
return s[i].Version.GT(s[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() error {
|
||||
_, err := GetAllPluginPackages().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(texts []string) (plugins PluginPackages) {
|
||||
plugins = make(PluginPackages, 0)
|
||||
|
||||
pluginLoop:
|
||||
for _, pp := range GetAllPluginPackages() {
|
||||
for _, text := range texts {
|
||||
if !pp.Match(text) {
|
||||
continue pluginLoop
|
||||
}
|
||||
}
|
||||
|
||||
if err := pp.IsInstallable(); err == nil {
|
||||
plugins = append(plugins, pp)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func isUnknownCoreVersion() bool {
|
||||
_, err := semver.ParseTolerant(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, Version))
|
||||
}
|
||||
|
||||
for _, name := range loadedPlugins {
|
||||
version := GetInstalledPluginVersion(name)
|
||||
if pv := newStaticPluginVersion(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 := L.GetGlobal(name)
|
||||
if plugin != lua.LNil {
|
||||
version := L.GetField(plugin, "VERSION")
|
||||
if str, ok := version.(lua.LString); ok {
|
||||
return string(str)
|
||||
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (pv *PluginVersion) DownloadAndInstall() error {
|
||||
messenger.AddLog(fmt.Sprintf("Downloading %q (%s) from %q", 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, "plugins", 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
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
content, err := f.Open()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer content.Close()
|
||||
if target, err := os.Create(targetName); err != nil {
|
||||
return err
|
||||
} else {
|
||||
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
|
||||
}
|
||||
|
||||
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)
|
||||
} else {
|
||||
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)
|
||||
}
|
||||
} else {
|
||||
return selectedVersions, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (versions PluginVersions) install() {
|
||||
anyInstalled := false
|
||||
currentlyInstalled := GetInstalledVersions(true)
|
||||
|
||||
for _, sel := range versions {
|
||||
if sel.pack.Name != CorePluginName {
|
||||
shouldInstall := true
|
||||
if pv := currentlyInstalled.find(sel.pack.Name); pv != nil {
|
||||
if pv.Version.NE(sel.Version) {
|
||||
messenger.AddLog(fmt.Sprint("Uninstalling %q", sel.pack.Name))
|
||||
UninstallPlugin(sel.pack.Name)
|
||||
} else {
|
||||
shouldInstall = false
|
||||
}
|
||||
}
|
||||
|
||||
if shouldInstall {
|
||||
if err := sel.DownloadAndInstall(); err != nil {
|
||||
messenger.Error(err)
|
||||
return
|
||||
}
|
||||
anyInstalled = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if anyInstalled {
|
||||
messenger.Message("One or more plugins installed. Please restart micro.")
|
||||
} else {
|
||||
messenger.AddLog("Nothing to install / update")
|
||||
}
|
||||
}
|
||||
|
||||
// UninstallPlugin deletes the plugin folder of the given plugin
|
||||
func UninstallPlugin(name string) {
|
||||
if err := os.RemoveAll(filepath.Join(configDir, "plugins", name)); err != nil {
|
||||
messenger.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (pl PluginPackage) Install() {
|
||||
selected, err := GetAllPluginPackages().Resolve(GetInstalledVersions(true), PluginDependencies{
|
||||
&PluginDependency{
|
||||
Name: pl.Name,
|
||||
Range: semver.Range(func(v semver.Version) bool { return true }),
|
||||
}})
|
||||
if err != nil {
|
||||
TermMessage(err)
|
||||
return
|
||||
}
|
||||
selected.install()
|
||||
}
|
||||
|
||||
func UpdatePlugins(plugins []string) {
|
||||
// if no plugins are specified, update all installed plugins.
|
||||
if len(plugins) == 0 {
|
||||
plugins = loadedPlugins
|
||||
}
|
||||
|
||||
messenger.AddLog("Checking for plugin updates")
|
||||
microVersion := PluginVersions{
|
||||
newStaticPluginVersion(CorePluginName, 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().Resolve(microVersion, updates)
|
||||
if err != nil {
|
||||
TermMessage(err)
|
||||
return
|
||||
}
|
||||
selected.install()
|
||||
}
|
||||
55
cmd/micro/pluginmanager_test.go
Normal file
55
cmd/micro/pluginmanager_test.go
Normal file
@@ -0,0 +1,55 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/blang/semver"
|
||||
"testing"
|
||||
|
||||
"github.com/zyedidia/json5/encoding/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 got %s", 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)
|
||||
}
|
||||
}
|
||||
187
cmd/micro/rtfiles.go
Normal file
187
cmd/micro/rtfiles.go
Normal file
@@ -0,0 +1,187 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
const (
|
||||
RTColorscheme = "colorscheme"
|
||||
RTSyntax = "syntax"
|
||||
RTHelp = "help"
|
||||
RTPlugin = "plugin"
|
||||
)
|
||||
|
||||
// RuntimeFile allows the program to read runtime data like colorschemes or syntax files
|
||||
type RuntimeFile interface {
|
||||
// Name returns a name of the file without paths or extensions
|
||||
Name() string
|
||||
// Data returns the content of the file.
|
||||
Data() ([]byte, error)
|
||||
}
|
||||
|
||||
// allFiles contains all available files, mapped by filetype
|
||||
var allFiles map[string][]RuntimeFile
|
||||
|
||||
// some file on filesystem
|
||||
type realFile string
|
||||
|
||||
// some asset file
|
||||
type assetFile string
|
||||
|
||||
// some file on filesystem but with a different name
|
||||
type namedFile struct {
|
||||
realFile
|
||||
name string
|
||||
}
|
||||
|
||||
func (rf realFile) Name() string {
|
||||
fn := filepath.Base(string(rf))
|
||||
return fn[:len(fn)-len(filepath.Ext(fn))]
|
||||
}
|
||||
|
||||
func (rf realFile) Data() ([]byte, error) {
|
||||
return ioutil.ReadFile(string(rf))
|
||||
}
|
||||
|
||||
func (af assetFile) Name() string {
|
||||
fn := path.Base(string(af))
|
||||
return fn[:len(fn)-len(path.Ext(fn))]
|
||||
}
|
||||
|
||||
func (af assetFile) Data() ([]byte, error) {
|
||||
return Asset(string(af))
|
||||
}
|
||||
|
||||
func (nf namedFile) Name() string {
|
||||
return nf.name
|
||||
}
|
||||
|
||||
// AddRuntimeFile registers a file for the given filetype
|
||||
func AddRuntimeFile(fileType string, file RuntimeFile) {
|
||||
if allFiles == nil {
|
||||
allFiles = make(map[string][]RuntimeFile)
|
||||
}
|
||||
allFiles[fileType] = append(allFiles[fileType], file)
|
||||
}
|
||||
|
||||
// AddRuntimeFilesFromDirectory registers each file from the given directory for
|
||||
// the filetype which matches the file-pattern
|
||||
func AddRuntimeFilesFromDirectory(fileType, directory, pattern string) {
|
||||
files, _ := ioutil.ReadDir(directory)
|
||||
for _, f := range files {
|
||||
if ok, _ := filepath.Match(pattern, f.Name()); !f.IsDir() && ok {
|
||||
fullPath := filepath.Join(directory, f.Name())
|
||||
AddRuntimeFile(fileType, realFile(fullPath))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// AddRuntimeFilesFromAssets registers each file from the given asset-directory for
|
||||
// the filetype which matches the file-pattern
|
||||
func AddRuntimeFilesFromAssets(fileType, directory, pattern string) {
|
||||
files, err := AssetDir(directory)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for _, f := range files {
|
||||
if ok, _ := path.Match(pattern, f); ok {
|
||||
AddRuntimeFile(fileType, assetFile(path.Join(directory, f)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// FindRuntimeFile finds a runtime file of the given filetype and name
|
||||
// will return nil if no file was found
|
||||
func FindRuntimeFile(fileType, name string) RuntimeFile {
|
||||
for _, f := range ListRuntimeFiles(fileType) {
|
||||
if f.Name() == name {
|
||||
return f
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ListRuntimeFiles lists all known runtime files for the given filetype
|
||||
func ListRuntimeFiles(fileType string) []RuntimeFile {
|
||||
if files, ok := allFiles[fileType]; ok {
|
||||
return files
|
||||
}
|
||||
return []RuntimeFile{}
|
||||
}
|
||||
|
||||
// InitRuntimeFiles initializes all assets file and the config directory
|
||||
func InitRuntimeFiles() {
|
||||
add := func(fileType, dir, pattern string) {
|
||||
AddRuntimeFilesFromDirectory(fileType, filepath.Join(configDir, dir), pattern)
|
||||
AddRuntimeFilesFromAssets(fileType, path.Join("runtime", dir), pattern)
|
||||
}
|
||||
|
||||
add(RTColorscheme, "colorschemes", "*.micro")
|
||||
add(RTSyntax, "syntax", "*.micro")
|
||||
add(RTHelp, "help", "*.md")
|
||||
|
||||
// Search configDir for plugin-scripts
|
||||
files, _ := ioutil.ReadDir(filepath.Join(configDir, "plugins"))
|
||||
for _, f := range files {
|
||||
if f.IsDir() {
|
||||
scriptPath := filepath.Join(configDir, "plugins", f.Name(), f.Name()+".lua")
|
||||
if _, err := os.Stat(scriptPath); err == nil {
|
||||
AddRuntimeFile(RTPlugin, realFile(scriptPath))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if files, err := AssetDir("runtime/plugins"); err == nil {
|
||||
for _, f := range files {
|
||||
scriptPath := path.Join("runtime/plugins", f, f+".lua")
|
||||
if _, err := AssetInfo(scriptPath); err == nil {
|
||||
AddRuntimeFile(RTPlugin, assetFile(scriptPath))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PluginReadRuntimeFile allows plugin scripts to read the content of a runtime file
|
||||
func PluginReadRuntimeFile(fileType, name string) string {
|
||||
if file := FindRuntimeFile(fileType, name); file != nil {
|
||||
if data, err := file.Data(); err == nil {
|
||||
return string(data)
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// PluginListRuntimeFiles allows plugins to lists all runtime files of the given type
|
||||
func PluginListRuntimeFiles(fileType string) []string {
|
||||
files := ListRuntimeFiles(fileType)
|
||||
result := make([]string, len(files))
|
||||
for i, f := range files {
|
||||
result[i] = f.Name()
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// PluginAddRuntimeFile adds a file to the runtime files for a plugin
|
||||
func PluginAddRuntimeFile(plugin, filetype, filePath string) {
|
||||
fullpath := filepath.Join(configDir, "plugins", plugin, filePath)
|
||||
if _, err := os.Stat(fullpath); err == nil {
|
||||
AddRuntimeFile(filetype, realFile(fullpath))
|
||||
} else {
|
||||
fullpath = path.Join("runtime", "plugins", plugin, filePath)
|
||||
AddRuntimeFile(filetype, assetFile(fullpath))
|
||||
}
|
||||
}
|
||||
|
||||
// PluginAddRuntimeFilesFromDirectory adds files from a directory to the runtime files for a plugin
|
||||
func PluginAddRuntimeFilesFromDirectory(plugin, filetype, directory, pattern string) {
|
||||
fullpath := filepath.Join(configDir, "plugins", plugin, directory)
|
||||
if _, err := os.Stat(fullpath); err == nil {
|
||||
AddRuntimeFilesFromDirectory(filetype, fullpath, pattern)
|
||||
} else {
|
||||
fullpath = path.Join("runtime", "plugins", plugin, directory)
|
||||
AddRuntimeFilesFromAssets(filetype, fullpath, pattern)
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -8,13 +8,24 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/yosuke-furukawa/json5/encoding/json5"
|
||||
"github.com/zyedidia/glob"
|
||||
"github.com/zyedidia/json5/encoding/json5"
|
||||
)
|
||||
|
||||
type optionValidator func(string, interface{}) error
|
||||
|
||||
// The options that the user can set
|
||||
var globalSettings map[string]interface{}
|
||||
|
||||
// Options with validators
|
||||
var optionValidators = map[string]optionValidator{
|
||||
"tabsize": validatePositiveValue,
|
||||
"scrollmargin": validateNonNegativeValue,
|
||||
"scrollspeed": validateNonNegativeValue,
|
||||
"colorscheme": validateColorscheme,
|
||||
"colorcolumn": validateNonNegativeValue,
|
||||
}
|
||||
|
||||
// InitGlobalSettings initializes the options map and sets all options to their default values
|
||||
func InitGlobalSettings() {
|
||||
defaults := DefaultGlobalSettings()
|
||||
@@ -165,6 +176,8 @@ func GetOption(name string) interface{} {
|
||||
func DefaultGlobalSettings() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"autoindent": true,
|
||||
"autosave": false,
|
||||
"colorcolumn": float64(0),
|
||||
"colorscheme": "zenburn",
|
||||
"cursorline": true,
|
||||
"ignorecase": false,
|
||||
@@ -179,6 +192,10 @@ func DefaultGlobalSettings() map[string]interface{} {
|
||||
"syntax": true,
|
||||
"tabsize": float64(4),
|
||||
"tabstospaces": false,
|
||||
"pluginchannels": []string{
|
||||
"https://raw.githubusercontent.com/micro-editor/plugin-channel/master/channel.json",
|
||||
},
|
||||
"pluginrepos": []string{},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,6 +204,8 @@ func DefaultGlobalSettings() map[string]interface{} {
|
||||
func DefaultLocalSettings() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"autoindent": true,
|
||||
"autosave": false,
|
||||
"colorcolumn": float64(0),
|
||||
"cursorline": true,
|
||||
"filetype": "Unknown",
|
||||
"ignorecase": false,
|
||||
@@ -208,12 +227,6 @@ func DefaultLocalSettings() map[string]interface{} {
|
||||
// is local only it will set the local version
|
||||
// Use setlocal to force an option to be set locally
|
||||
func SetOption(option, value string) error {
|
||||
if option == "colorscheme" {
|
||||
if !ColorschemeExists(value) {
|
||||
return errors.New(value + " is not a valid colorscheme")
|
||||
}
|
||||
}
|
||||
|
||||
if _, ok := globalSettings[option]; !ok {
|
||||
if _, ok := CurView().Buf.Settings[option]; !ok {
|
||||
return errors.New("Invalid option")
|
||||
@@ -222,23 +235,33 @@ func SetOption(option, value string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
var nativeValue interface{}
|
||||
|
||||
kind := reflect.TypeOf(globalSettings[option]).Kind()
|
||||
if kind == reflect.Bool {
|
||||
b, err := ParseBool(value)
|
||||
if err != nil {
|
||||
return errors.New("Invalid value")
|
||||
}
|
||||
globalSettings[option] = b
|
||||
nativeValue = b
|
||||
} else if kind == reflect.String {
|
||||
globalSettings[option] = value
|
||||
nativeValue = value
|
||||
} else if kind == reflect.Float64 {
|
||||
i, err := strconv.Atoi(value)
|
||||
if err != nil {
|
||||
return errors.New("Invalid value")
|
||||
}
|
||||
globalSettings[option] = float64(i)
|
||||
nativeValue = float64(i)
|
||||
} else {
|
||||
return errors.New("Option has unsupported value type")
|
||||
}
|
||||
|
||||
if err := optionIsValid(option, nativeValue); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
globalSettings[option] = nativeValue
|
||||
|
||||
if option == "colorscheme" {
|
||||
LoadSyntaxFiles()
|
||||
for _, tab := range tabs {
|
||||
@@ -275,23 +298,33 @@ func SetLocalOption(option, value string, view *View) error {
|
||||
return errors.New("Invalid option")
|
||||
}
|
||||
|
||||
var nativeValue interface{}
|
||||
|
||||
kind := reflect.TypeOf(buf.Settings[option]).Kind()
|
||||
if kind == reflect.Bool {
|
||||
b, err := ParseBool(value)
|
||||
if err != nil {
|
||||
return errors.New("Invalid value")
|
||||
}
|
||||
buf.Settings[option] = b
|
||||
nativeValue = b
|
||||
} else if kind == reflect.String {
|
||||
buf.Settings[option] = value
|
||||
nativeValue = value
|
||||
} else if kind == reflect.Float64 {
|
||||
i, err := strconv.Atoi(value)
|
||||
if err != nil {
|
||||
return errors.New("Invalid value")
|
||||
}
|
||||
buf.Settings[option] = float64(i)
|
||||
nativeValue = float64(i)
|
||||
} else {
|
||||
return errors.New("Option has unsupported value type")
|
||||
}
|
||||
|
||||
if err := optionIsValid(option, nativeValue); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
buf.Settings[option] = nativeValue
|
||||
|
||||
if option == "statusline" {
|
||||
view.ToggleStatusLine()
|
||||
if buf.Settings["syntax"].(bool) {
|
||||
@@ -327,3 +360,55 @@ func SetOptionAndSettings(option, value string) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func optionIsValid(option string, value interface{}) error {
|
||||
if validator, ok := optionValidators[option]; ok {
|
||||
return validator(option, value)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Option validators
|
||||
|
||||
func validatePositiveValue(option string, value interface{}) error {
|
||||
tabsize, ok := value.(float64)
|
||||
|
||||
if !ok {
|
||||
return errors.New("Expected numeric type for " + option)
|
||||
}
|
||||
|
||||
if tabsize < 1 {
|
||||
return errors.New(option + " must be greater than 0")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateNonNegativeValue(option string, value interface{}) error {
|
||||
nativeValue, ok := value.(float64)
|
||||
|
||||
if !ok {
|
||||
return errors.New("Expected numeric type for " + option)
|
||||
}
|
||||
|
||||
if nativeValue < 0 {
|
||||
return errors.New(option + " must be non-negative")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateColorscheme(option string, value interface{}) error {
|
||||
colorscheme, ok := value.(string)
|
||||
|
||||
if !ok {
|
||||
return errors.New("Expected string type for colorscheme")
|
||||
}
|
||||
|
||||
if !ColorschemeExists(colorscheme) {
|
||||
return errors.New(colorscheme + " is not a valid colorscheme")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -56,6 +56,7 @@ func (l *LeafNode) VSplit(buf *Buffer) {
|
||||
s := new(SplitTree)
|
||||
s.kind = VerticalSplit
|
||||
s.parent = l.parent
|
||||
s.tabNum = l.parent.tabNum
|
||||
newView := NewView(buf)
|
||||
newView.TabNum = l.parent.tabNum
|
||||
newView.Num = len(tab.views)
|
||||
@@ -81,6 +82,7 @@ func (l *LeafNode) HSplit(buf *Buffer) {
|
||||
} else {
|
||||
s := new(SplitTree)
|
||||
s.kind = HorizontalSplit
|
||||
s.tabNum = l.parent.tabNum
|
||||
s.parent = l.parent
|
||||
newView := NewView(buf)
|
||||
newView.TabNum = l.parent.tabNum
|
||||
|
||||
@@ -37,7 +37,7 @@ func (sline *Statusline) Display() {
|
||||
file += " " + sline.view.Buf.FileType()
|
||||
|
||||
rightText := helpBinding + " for help "
|
||||
if sline.view.Help {
|
||||
if sline.view.Type == vtHelp {
|
||||
rightText = helpBinding + " to close help "
|
||||
}
|
||||
|
||||
|
||||
@@ -37,11 +37,14 @@ func NewTabFromView(v *View) *Tab {
|
||||
t.tree.height--
|
||||
}
|
||||
|
||||
t.Resize()
|
||||
|
||||
return t
|
||||
}
|
||||
|
||||
// SetNum sets all this tab's views to have the correct tab number
|
||||
func (t *Tab) SetNum(num int) {
|
||||
t.tree.tabNum = num
|
||||
for _, v := range t.views {
|
||||
v.TabNum = num
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ func Max(a, b int) int {
|
||||
func IsWordChar(str string) bool {
|
||||
if len(str) > 1 {
|
||||
// Unicode
|
||||
return false
|
||||
return true
|
||||
}
|
||||
c := str[0]
|
||||
return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c == '_')
|
||||
@@ -157,7 +157,19 @@ func GetModTime(path string) (time.Time, bool) {
|
||||
// StringWidth returns the width of a string where tabs count as `tabsize` width
|
||||
func StringWidth(str string, tabsize int) int {
|
||||
sw := runewidth.StringWidth(str)
|
||||
sw += NumOccurrences(str, '\t') * (tabsize - 1)
|
||||
lineIdx := 0
|
||||
for _, ch := range str {
|
||||
switch ch {
|
||||
case '\t':
|
||||
ts := tabsize - (lineIdx % tabsize)
|
||||
sw += ts - 1
|
||||
lineIdx += ts
|
||||
case '\n':
|
||||
lineIdx = 0
|
||||
default:
|
||||
lineIdx++
|
||||
}
|
||||
}
|
||||
return sw
|
||||
}
|
||||
|
||||
@@ -165,16 +177,22 @@ func StringWidth(str string, tabsize int) int {
|
||||
// that have a width larger than 1 (this also counts tabs as `tabsize` width)
|
||||
func WidthOfLargeRunes(str string, tabsize int) int {
|
||||
count := 0
|
||||
lineIdx := 0
|
||||
for _, ch := range str {
|
||||
var w int
|
||||
if ch == '\t' {
|
||||
w = tabsize
|
||||
w = tabsize - (lineIdx % tabsize)
|
||||
} else {
|
||||
w = runewidth.RuneWidth(ch)
|
||||
}
|
||||
if w > 1 {
|
||||
count += (w - 1)
|
||||
}
|
||||
if ch == '\n' {
|
||||
lineIdx = 0
|
||||
} else {
|
||||
lineIdx += w
|
||||
}
|
||||
}
|
||||
return count
|
||||
}
|
||||
@@ -230,42 +248,64 @@ func FuncName(i interface{}) string {
|
||||
// The returned slice contains at least one string
|
||||
func SplitCommandArgs(input string) []string {
|
||||
var result []string
|
||||
var curQuote *bytes.Buffer = nil
|
||||
|
||||
curArg := new(bytes.Buffer)
|
||||
inQuote := false
|
||||
escape := false
|
||||
|
||||
appendResult := func() {
|
||||
str := curArg.String()
|
||||
inQuote = false
|
||||
escape = false
|
||||
if strings.HasPrefix(str, `"`) && strings.HasSuffix(str, `"`) {
|
||||
if unquoted, err := strconv.Unquote(str); err == nil {
|
||||
str = unquoted
|
||||
}
|
||||
finishQuote := func() {
|
||||
if curQuote == nil {
|
||||
return
|
||||
}
|
||||
str := curQuote.String()
|
||||
if unquoted, err := strconv.Unquote(str); err == nil {
|
||||
str = unquoted
|
||||
}
|
||||
curArg.WriteString(str)
|
||||
curQuote = nil
|
||||
}
|
||||
|
||||
appendResult := func() {
|
||||
finishQuote()
|
||||
escape = false
|
||||
|
||||
str := curArg.String()
|
||||
result = append(result, str)
|
||||
curArg.Reset()
|
||||
}
|
||||
|
||||
for _, r := range input {
|
||||
if r == ' ' && !inQuote {
|
||||
if r == ' ' && curQuote == nil {
|
||||
appendResult()
|
||||
} else {
|
||||
curArg.WriteRune(r)
|
||||
runeHandled := false
|
||||
appendRuneToBuff := func() {
|
||||
if curQuote != nil {
|
||||
curQuote.WriteRune(r)
|
||||
} else {
|
||||
curArg.WriteRune(r)
|
||||
}
|
||||
runeHandled = true
|
||||
}
|
||||
|
||||
if r == '"' && !inQuote {
|
||||
inQuote = true
|
||||
if r == '"' && curQuote == nil {
|
||||
curQuote = new(bytes.Buffer)
|
||||
appendRuneToBuff()
|
||||
} else {
|
||||
if inQuote && !escape {
|
||||
if curQuote != nil && !escape {
|
||||
if r == '"' {
|
||||
inQuote = false
|
||||
}
|
||||
if r == '\\' {
|
||||
appendRuneToBuff()
|
||||
finishQuote()
|
||||
} else if r == '\\' {
|
||||
appendRuneToBuff()
|
||||
escape = true
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
if !runeHandled {
|
||||
appendRuneToBuff()
|
||||
}
|
||||
}
|
||||
|
||||
escape = false
|
||||
|
||||
@@ -50,15 +50,15 @@ func TestIsWordChar(t *testing.T) {
|
||||
if IsWordChar("_") == false {
|
||||
t.Errorf("IsWordChar(_) = false")
|
||||
}
|
||||
if IsWordChar("ß") == false {
|
||||
t.Errorf("IsWordChar(ß) = false")
|
||||
}
|
||||
if IsWordChar("~") == true {
|
||||
t.Errorf("IsWordChar(~) = true")
|
||||
}
|
||||
if IsWordChar(" ") == true {
|
||||
t.Errorf("IsWordChar( ) = true")
|
||||
}
|
||||
if IsWordChar("ß") == true {
|
||||
t.Errorf("IsWordChar(ß) = true")
|
||||
}
|
||||
if IsWordChar(")") == true {
|
||||
t.Errorf("IsWordChar()) = true")
|
||||
}
|
||||
@@ -100,9 +100,14 @@ func TestJoinAndSplitCommandArgs(t *testing.T) {
|
||||
Query string
|
||||
Wanted []string
|
||||
}{
|
||||
{`"hallo""Welt"`, []string{`"hallo""Welt"`}},
|
||||
{`"hallo""Welt"`, []string{`halloWelt`}},
|
||||
{`"hallo" "Welt"`, []string{`hallo`, `Welt`}},
|
||||
{`\"`, []string{`\"`}},
|
||||
{`"foo`, []string{`"foo`}},
|
||||
{`"foo"`, []string{`foo`}},
|
||||
{`"\"`, []string{`"\"`}},
|
||||
{`"C:\\"foo.txt`, []string{`C:\foo.txt`}},
|
||||
{`"\n"new"\n"line`, []string{"\nnew\nline"}},
|
||||
}
|
||||
|
||||
for i, test := range splitTests {
|
||||
@@ -111,3 +116,41 @@ func TestJoinAndSplitCommandArgs(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestStringWidth(t *testing.T) {
|
||||
tabsize := 4
|
||||
if w := StringWidth("1\t2", tabsize); w != 5 {
|
||||
t.Error("StringWidth 1 Failed. Got", w)
|
||||
}
|
||||
if w := StringWidth("\t", tabsize); w != 4 {
|
||||
t.Error("StringWidth 2 Failed. Got", w)
|
||||
}
|
||||
if w := StringWidth("1\t", tabsize); w != 4 {
|
||||
t.Error("StringWidth 3 Failed. Got", w)
|
||||
}
|
||||
if w := StringWidth("\t\t", tabsize); w != 8 {
|
||||
t.Error("StringWidth 4 Failed. Got", w)
|
||||
}
|
||||
if w := StringWidth("12\t2\t", tabsize); w != 8 {
|
||||
t.Error("StringWidth 5 Failed. Got", w)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWidthOfLargeRunes(t *testing.T) {
|
||||
tabsize := 4
|
||||
if w := WidthOfLargeRunes("1\t2", tabsize); w != 2 {
|
||||
t.Error("WidthOfLargeRunes 1 Failed. Got", w)
|
||||
}
|
||||
if w := WidthOfLargeRunes("\t", tabsize); w != 3 {
|
||||
t.Error("WidthOfLargeRunes 2 Failed. Got", w)
|
||||
}
|
||||
if w := WidthOfLargeRunes("1\t", tabsize); w != 2 {
|
||||
t.Error("WidthOfLargeRunes 3 Failed. Got", w)
|
||||
}
|
||||
if w := WidthOfLargeRunes("\t\t", tabsize); w != 6 {
|
||||
t.Error("WidthOfLargeRunes 4 Failed. Got", w)
|
||||
}
|
||||
if w := WidthOfLargeRunes("12\t2\t", tabsize); w != 3 {
|
||||
t.Error("WidthOfLargeRunes 5 Failed. Got", w)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,24 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/mattn/go-runewidth"
|
||||
"github.com/mitchellh/go-homedir"
|
||||
"github.com/zyedidia/tcell"
|
||||
)
|
||||
|
||||
type ViewType int
|
||||
|
||||
const (
|
||||
vtDefault ViewType = iota
|
||||
vtHelp
|
||||
vtLog
|
||||
)
|
||||
|
||||
// The View struct stores information about a view into a buffer.
|
||||
// It stores information about the cursor, and the viewport
|
||||
// that the user sees the buffer from.
|
||||
@@ -26,9 +36,9 @@ type View struct {
|
||||
heightPercent int
|
||||
|
||||
// Specifies whether or not this view holds a help buffer
|
||||
Help bool
|
||||
Type ViewType
|
||||
|
||||
// Actual with and height
|
||||
// Actual width and height
|
||||
width int
|
||||
height int
|
||||
|
||||
@@ -182,10 +192,15 @@ func (v *View) ScrollDown(n int) {
|
||||
// CanClose returns whether or not the view can be closed
|
||||
// If there are unsaved changes, the user will be asked if the view can be closed
|
||||
// causing them to lose the unsaved changes
|
||||
// The message is what to print after saying "You have unsaved changes. "
|
||||
func (v *View) CanClose() bool {
|
||||
if v.Buf.IsModified {
|
||||
char, canceled := messenger.LetterPrompt("Save changes to "+v.Buf.Name+" before closing? (y,n,esc) ", 'y', 'n')
|
||||
if v.Type == vtDefault && v.Buf.IsModified {
|
||||
var char rune
|
||||
var canceled bool
|
||||
if v.Buf.Settings["autosave"].(bool) {
|
||||
char = 'y'
|
||||
} else {
|
||||
char, canceled = messenger.LetterPrompt("Save changes to "+v.Buf.Name+" before closing? (y,n,esc) ", 'y', 'n')
|
||||
}
|
||||
if !canceled {
|
||||
if char == 'y' {
|
||||
v.Save(true)
|
||||
@@ -222,6 +237,22 @@ func (v *View) OpenBuffer(buf *Buffer) {
|
||||
v.lastClickTime = time.Time{}
|
||||
}
|
||||
|
||||
func (v *View) Open(filename string) {
|
||||
home, _ := homedir.Dir()
|
||||
filename = strings.Replace(filename, "~", home, 1)
|
||||
file, err := ioutil.ReadFile(filename)
|
||||
|
||||
var buf *Buffer
|
||||
if err != nil {
|
||||
messenger.Message(err.Error())
|
||||
// File does not exist -- create an empty buffer with that name
|
||||
buf = NewBuffer([]byte{}, filename)
|
||||
} else {
|
||||
buf = NewBuffer(file, filename)
|
||||
}
|
||||
v.OpenBuffer(buf)
|
||||
}
|
||||
|
||||
// CloseBuffer performs any closing functions on the buffer
|
||||
func (v *View) CloseBuffer() {
|
||||
if v.Buf != nil {
|
||||
@@ -325,7 +356,34 @@ func (v *View) HandleEvent(event tcell.Event) {
|
||||
// Window resized
|
||||
tabs[v.TabNum].Resize()
|
||||
case *tcell.EventKey:
|
||||
if e.Key() == tcell.KeyRune && (e.Modifiers() == 0 || e.Modifiers() == tcell.ModShift) {
|
||||
// Check first if input is a key binding, if it is we 'eat' the input and don't insert a rune
|
||||
isBinding := false
|
||||
if e.Key() != tcell.KeyRune || e.Modifiers() != 0 {
|
||||
for key, actions := range bindings {
|
||||
if e.Key() == key.keyCode {
|
||||
if e.Key() == tcell.KeyRune {
|
||||
if e.Rune() != key.r {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if e.Modifiers() == key.modifiers {
|
||||
relocate = false
|
||||
isBinding = true
|
||||
for _, action := range actions {
|
||||
relocate = action(v, true) || relocate
|
||||
funcName := FuncName(action)
|
||||
if funcName != "main.(*View).ToggleMacro" && funcName != "main.(*View).PlayMacro" {
|
||||
if recordingMacro {
|
||||
curMacro = append(curMacro, action)
|
||||
}
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if !isBinding && e.Key() == tcell.KeyRune {
|
||||
// Insert a character
|
||||
if v.Cursor.HasSelection() {
|
||||
v.Cursor.DeleteSelection()
|
||||
@@ -344,28 +402,6 @@ func (v *View) HandleEvent(event tcell.Event) {
|
||||
if recordingMacro {
|
||||
curMacro = append(curMacro, e.Rune())
|
||||
}
|
||||
} else {
|
||||
for key, actions := range bindings {
|
||||
if e.Key() == key.keyCode {
|
||||
if e.Key() == tcell.KeyRune {
|
||||
if e.Rune() != key.r {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if e.Modifiers() == key.modifiers {
|
||||
relocate = false
|
||||
for _, action := range actions {
|
||||
relocate = action(v, true) || relocate
|
||||
funcName := FuncName(action)
|
||||
if funcName != "main.(*View).ToggleMacro" && funcName != "main.(*View).PlayMacro" {
|
||||
if recordingMacro {
|
||||
curMacro = append(curMacro, action)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
case *tcell.EventPaste:
|
||||
if !PreActionCall("Paste", v) {
|
||||
@@ -473,9 +509,6 @@ func (v *View) HandleEvent(event tcell.Event) {
|
||||
if relocate {
|
||||
v.Relocate()
|
||||
}
|
||||
if v.Buf.Settings["syntax"].(bool) {
|
||||
v.matches = Match(v)
|
||||
}
|
||||
}
|
||||
|
||||
// GutterMessage creates a message in this view's gutter
|
||||
@@ -511,15 +544,18 @@ func (v *View) ClearAllGutterMessages() {
|
||||
|
||||
// Opens the given help page in a new horizontal split
|
||||
func (v *View) openHelp(helpPage string) {
|
||||
if v.Help {
|
||||
helpBuffer := NewBuffer([]byte(helpPages[helpPage]), helpPage+".md")
|
||||
helpBuffer.Name = "Help"
|
||||
v.OpenBuffer(helpBuffer)
|
||||
if data, err := FindRuntimeFile(RTHelp, helpPage).Data(); err != nil {
|
||||
TermMessage("Unable to load help text", helpPage, "\n", err)
|
||||
} else {
|
||||
helpBuffer := NewBuffer([]byte(helpPages[helpPage]), helpPage+".md")
|
||||
helpBuffer := NewBuffer(data, helpPage+".md")
|
||||
helpBuffer.Name = "Help"
|
||||
v.HSplit(helpBuffer)
|
||||
CurView().Help = true
|
||||
|
||||
if v.Type == vtHelp {
|
||||
v.OpenBuffer(helpBuffer)
|
||||
} else {
|
||||
v.HSplit(helpBuffer)
|
||||
CurView().Type = vtHelp
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -531,6 +567,15 @@ func (v *View) drawCell(x, y int, ch rune, combc []rune, style tcell.Style) {
|
||||
|
||||
// DisplayView renders the view to the screen
|
||||
func (v *View) DisplayView() {
|
||||
if v.Type == vtLog {
|
||||
// Log views should always follow the cursor...
|
||||
v.Relocate()
|
||||
}
|
||||
|
||||
if v.Buf.Settings["syntax"].(bool) {
|
||||
v.matches = Match(v)
|
||||
}
|
||||
|
||||
// The charNum we are currently displaying
|
||||
// starts at the start of the viewport
|
||||
charNum := Loc{0, v.Topline}
|
||||
@@ -670,6 +715,8 @@ func (v *View) DisplayView() {
|
||||
|
||||
// Now we actually draw the line
|
||||
colN := 0
|
||||
strWidth := 0
|
||||
tabSize := int(v.Buf.Settings["tabsize"].(float64))
|
||||
for _, ch := range line {
|
||||
lineStyle := defStyle
|
||||
|
||||
@@ -706,7 +753,7 @@ func (v *View) DisplayView() {
|
||||
// First the user may have configured an `indent-char` to be displayed to show that this
|
||||
// is a tab character
|
||||
lineIndentStyle := defStyle
|
||||
if style, ok := colorscheme["indent-char"]; ok {
|
||||
if style, ok := colorscheme["indent-char"]; ok && v.Buf.Settings["indentchar"].(string) != " " {
|
||||
lineIndentStyle = style
|
||||
}
|
||||
if v.Cursor.HasSelection() &&
|
||||
@@ -731,8 +778,9 @@ func (v *View) DisplayView() {
|
||||
v.drawCell(screenX-v.leftCol, screenY, indentChar[0], nil, lineIndentStyle)
|
||||
}
|
||||
// Now the tab has to be displayed as a bunch of spaces
|
||||
tabSize := int(v.Buf.Settings["tabsize"].(float64))
|
||||
for i := 0; i < tabSize-1; i++ {
|
||||
visLoc := strWidth
|
||||
remainder := tabSize - (visLoc % tabSize)
|
||||
for i := 0; i < remainder-1; i++ {
|
||||
screenX++
|
||||
if screenX-v.x-v.leftCol >= v.lineNumOffset {
|
||||
v.drawCell(screenX-v.leftCol, screenY, ' ', nil, lineStyle)
|
||||
@@ -756,6 +804,7 @@ func (v *View) DisplayView() {
|
||||
charNum = charNum.Move(1, v.Buf)
|
||||
screenX++
|
||||
colN++
|
||||
strWidth += StringWidth(string(ch), tabSize)
|
||||
}
|
||||
// Here we are at a newline
|
||||
|
||||
@@ -785,6 +834,13 @@ func (v *View) DisplayView() {
|
||||
}
|
||||
}
|
||||
if screenX-v.x-v.leftCol+i >= v.lineNumOffset {
|
||||
colorcolumn := int(v.Buf.Settings["colorcolumn"].(float64))
|
||||
if colorcolumn != 0 && screenX-v.lineNumOffset+i == colorcolumn-1 {
|
||||
if style, ok := colorscheme["color-column"]; ok {
|
||||
fg, _, _ := style.Decompose()
|
||||
lineStyle = lineStyle.Background(fg)
|
||||
}
|
||||
}
|
||||
v.drawCell(screenX-v.leftCol+i, screenY, ' ', nil, lineStyle)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,3 +17,4 @@ color-link current-line-number "#656866,#1D1F21"
|
||||
color-link gutter-error "#FF4444,#1D1F21"
|
||||
color-link gutter-warning "#EEEE77,#1D1F21"
|
||||
color-link cursor-line "#2D2F31"
|
||||
color-link color-column "#2D2F31"
|
||||
|
||||
@@ -17,3 +17,4 @@ color-link gutter-error "197,231"
|
||||
color-link gutter-warning "134,231"
|
||||
color-link line-number "246,254"
|
||||
color-link cursor-line "254"
|
||||
color-link color-column "254"
|
||||
|
||||
@@ -13,3 +13,4 @@ color-link todo "bold 223,235"
|
||||
color-link line-number "243,237"
|
||||
color-link current-line-number "172,237"
|
||||
color-link cursor-line "237"
|
||||
color-link color-column "237"
|
||||
|
||||
@@ -17,3 +17,4 @@ color-link current-line-number "#AAAAAA,#282828"
|
||||
color-link gutter-error "#CB4B16,#282828"
|
||||
color-link gutter-warning "#E6DB74,#282828"
|
||||
color-link cursor-line "#323232"
|
||||
color-link color-column "#323232"
|
||||
|
||||
@@ -14,3 +14,4 @@ color-link current-line-number "red"
|
||||
color-link gutter-error ",red"
|
||||
color-link gutter-warning "red"
|
||||
color-link cursor-line "white"
|
||||
color-link color-column "white"
|
||||
|
||||
@@ -16,3 +16,4 @@ color-link current-line-number "#586E75,#002833"
|
||||
color-link gutter-error "#003541,#CB4B16"
|
||||
color-link gutter-warning "#CB4B16,#002833"
|
||||
color-link cursor-line "#003541"
|
||||
color-link color-column "#003541"
|
||||
|
||||
@@ -15,3 +15,4 @@ color-link current-line-number "brightgreen,default"
|
||||
color-link gutter-error "black,brightred"
|
||||
color-link gutter-warning "brightred,default"
|
||||
color-link cursor-line "black"
|
||||
color-link color-column "black"
|
||||
|
||||
@@ -17,4 +17,5 @@ color-link line-number "248,238"
|
||||
color-link gutter-error "237,174"
|
||||
color-link gutter-warning "174,237"
|
||||
color-link cursor-line "238"
|
||||
color-link color-column "238"
|
||||
color-link current-line-number "188,237"
|
||||
|
||||
@@ -19,12 +19,12 @@ Micro comes with a number of colorschemes by default. Here is the list:
|
||||
* solarized: this is the solarized colorscheme.
|
||||
You should have the solarized color palette in your terminal to use it.
|
||||
|
||||
* solarized-tc: this is the solarized colorscheme for true color, just
|
||||
* solarized-tc: this is the solarized colorscheme for true color; just
|
||||
make sure your terminal supports true color before using it and that the
|
||||
MICRO_TRUECOLOR environment variable is set to 1 before starting micro.
|
||||
|
||||
* monokai: this is the monokai colorscheme, you may recognize it as
|
||||
sublime text's default colorscheme. It requires true color to
|
||||
* monokai: this is the monokai colorscheme; you may recognize it as
|
||||
Sublime Text's default colorscheme. It requires true color to
|
||||
look perfect, but the 256 color approximation looks very good as well.
|
||||
|
||||
* atom-dark-tc: this colorscheme is based off of Atom's "dark" colorscheme.
|
||||
@@ -108,6 +108,8 @@ Here is a list of the colorscheme groups that you can use:
|
||||
* gutter-error
|
||||
* gutter-warning
|
||||
* cursor-line
|
||||
* current-line-number
|
||||
* color-column
|
||||
|
||||
Colorschemes can be placed in the `~/.config/micro/colorschemes` directory to be used.
|
||||
|
||||
|
||||
@@ -5,12 +5,13 @@ Here are the possible commands that you can use.
|
||||
|
||||
* `quit`: Quits micro.
|
||||
|
||||
* `save`: Saves the current buffer.
|
||||
* `save filename?`: Saves the current buffer. If the filename is provided it will
|
||||
'save as' the filename.
|
||||
|
||||
* `replace "search" "value" flags`: This will replace `search` with `value`.
|
||||
The `flags` are optional.
|
||||
At this point, there is only one flag: `c`, which enables `check` mode
|
||||
which asks if you'd like to perform the replacement each time
|
||||
which asks if you'd like to perform the replacement each time.
|
||||
|
||||
Note that `search` must be a valid regex. If one of the arguments
|
||||
does not have any spaces in it, you may omit the quotes.
|
||||
@@ -23,6 +24,10 @@ Here are the possible commands that you can use.
|
||||
|
||||
* `show option`: shows the current value of the given option.
|
||||
|
||||
* `eval "expression"`: Evaluates a Lua expression. Note that micro will not
|
||||
print anything so you should use `messenger:Message(...)` to display a
|
||||
value.
|
||||
|
||||
* `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.
|
||||
|
||||
@@ -30,19 +35,32 @@ Here are the possible commands that you can use.
|
||||
keybindings above for more info about what keys and actions are available.
|
||||
|
||||
* `vsplit filename`: opens a vertical split with `filename`. If no filename is
|
||||
provided, a vertical split is opened with an empty buffer
|
||||
provided, a vertical split is opened with an empty buffer.
|
||||
|
||||
* `hsplit filename`: same as `vsplit` but opens a horizontal split instead of
|
||||
a vertical split
|
||||
a vertical split.
|
||||
|
||||
* `tab filename`: opens the given file in a new tab.
|
||||
|
||||
* `log`: opens a log of all messages and debug statements.
|
||||
|
||||
* `plugin install plugin_name`: installs the given plugin.
|
||||
|
||||
* `plugin remove plugin_name`: removes the given plugin.
|
||||
|
||||
* `plugin list`: lists all installed plugins.
|
||||
|
||||
* `plugin update`: updates all installed plugins.
|
||||
|
||||
* `plugin search plugin_name`: searches for the given plugin.
|
||||
Note that you can find a list of all available plugins at
|
||||
github.com/micro-editor/plugin-channel.
|
||||
|
||||
You can also see more information about the plugin manager
|
||||
in the `Plugin Manager` section of the `plugins` help topic.
|
||||
|
||||
---
|
||||
|
||||
The following commands are provided by the default plugins:
|
||||
|
||||
* `lint`: Lint the current file for errors.
|
||||
|
||||
* `gofmt`: Run gofmt on the current file.
|
||||
|
||||
* `goimports`: Run goimports on the current file.
|
||||
|
||||
@@ -3,18 +3,32 @@
|
||||
Micro is a terminal-based text editor that aims to be easy to use and intuitive,
|
||||
while also taking advantage of the full capabilities of modern terminals.
|
||||
|
||||
*Press CtrlQ to quit, and CtrlS to save.*
|
||||
|
||||
If you want to see all the keybindings press CtrlE and type `help keybindings`.
|
||||
|
||||
See the next section for more information about documentation and help.
|
||||
|
||||
### Quick-start
|
||||
|
||||
Press CtrlQ to quit, and CtrlS 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`. When I write `> ...` I mean press
|
||||
CtrlE and then type whatever is there.
|
||||
|
||||
Move the cursor around with the mouse or the arrow keys.
|
||||
|
||||
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`.
|
||||
|
||||
Press CtrlW 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).
|
||||
|
||||
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.
|
||||
To use it, press CtrlE to access command mode and type in `help` followed by a topic.
|
||||
Typing `help` followed by nothing will open this page.
|
||||
|
||||
Here are the possible help topics that you can read:
|
||||
|
||||
@@ -26,7 +40,7 @@ Here are the possible help topics that you can read:
|
||||
* colors: Explains micro's colorscheme and syntax highlighting engine and how to create your
|
||||
own colorschemes or add new languages to the engine
|
||||
|
||||
For example to open the help page on plugins you would press CtrlE and type `help plugins`.
|
||||
For example, to open the help page on plugins you would press CtrlE and type `help plugins`.
|
||||
|
||||
I recommend looking at the `tutorial` help file because it is short for each section and
|
||||
gives concrete examples of how to use the various configuration options in micro. However,
|
||||
|
||||
@@ -5,74 +5,82 @@ you can rebind them to your liking.
|
||||
|
||||
```json
|
||||
{
|
||||
"Up": "CursorUp",
|
||||
"Down": "CursorDown",
|
||||
"Right": "CursorRight",
|
||||
"Left": "CursorLeft",
|
||||
"ShiftUp": "SelectUp",
|
||||
"ShiftDown": "SelectDown",
|
||||
"ShiftLeft": "SelectLeft",
|
||||
"ShiftRight": "SelectRight",
|
||||
"AltLeft": "WordLeft",
|
||||
"AltRight": "WordRight",
|
||||
"AltShiftRight": "SelectWordRight",
|
||||
"AltShiftLeft": "SelectWordLeft",
|
||||
"CtrlLeft": "StartOfLine",
|
||||
"CtrlRight": "EndOfLine",
|
||||
"CtrlShiftLeft": "SelectToStartOfLine",
|
||||
"CtrlShiftRight": "SelectToEndOfLine",
|
||||
"CtrlUp": "CursorStart",
|
||||
"CtrlDown": "CursorEnd",
|
||||
"CtrlShiftUp": "SelectToStart",
|
||||
"CtrlShiftDown": "SelectToEnd",
|
||||
"Enter": "InsertEnter",
|
||||
"Space": "InsertSpace",
|
||||
"Backspace": "Backspace",
|
||||
"Backspace2": "Backspace",
|
||||
"Alt-Backspace": "DeleteWordLeft",
|
||||
"Alt-Backspace2": "DeleteWordLeft",
|
||||
"Tab": "InsertTab,IndentSelection",
|
||||
"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",
|
||||
"CtrlRightSq": "PreviousTab",
|
||||
"CtrlBackslash": "NextTab",
|
||||
"Home": "Start",
|
||||
"End": "End",
|
||||
"CtrlHome": "CursorStart",
|
||||
"CtrlEnd": "CursorEnd",
|
||||
"PageUp": "CursorPageUp",
|
||||
"PageDown": "CursorPageDown",
|
||||
"CtrlG": "ToggleHelp",
|
||||
"CtrlR": "ToggleRuler",
|
||||
"CtrlL": "JumpLine",
|
||||
"Delete": "Delete",
|
||||
"Esc": "ClearStatus",
|
||||
"CtrlB": "ShellMode",
|
||||
"CtrlQ": "Quit",
|
||||
"CtrlE": "CommandMode",
|
||||
"CtrlW": "NextSplit",
|
||||
"CtrlU": "ToggleMacro",
|
||||
"CtrlJ": "PlayMacro",
|
||||
|
||||
// Emacs-style keybindings
|
||||
"Alt-f": "WordRight",
|
||||
"Alt-b": "WordLeft",
|
||||
"Alt-a": "StartOfLine",
|
||||
"Alt-e": "EndOfLine",
|
||||
"Alt-p": "CursorUp",
|
||||
"Alt-n": "CursorDown"
|
||||
"Up": "CursorUp",
|
||||
"Down": "CursorDown",
|
||||
"Right": "CursorRight",
|
||||
"Left": "CursorLeft",
|
||||
"ShiftUp": "SelectUp",
|
||||
"ShiftDown": "SelectDown",
|
||||
"ShiftLeft": "SelectLeft",
|
||||
"ShiftRight": "SelectRight",
|
||||
"AltLeft": "WordLeft",
|
||||
"AltRight": "WordRight",
|
||||
"AltShiftRight": "SelectWordRight",
|
||||
"AltShiftLeft": "SelectWordLeft",
|
||||
"CtrlLeft": "StartOfLine",
|
||||
"CtrlRight": "EndOfLine",
|
||||
"CtrlShiftLeft": "SelectToStartOfLine",
|
||||
"CtrlShiftRight": "SelectToEndOfLine",
|
||||
"CtrlUp": "CursorStart",
|
||||
"CtrlDown": "CursorEnd",
|
||||
"CtrlShiftUp": "SelectToStart",
|
||||
"CtrlShiftDown": "SelectToEnd",
|
||||
"Enter": "InsertNewline",
|
||||
"Space": "InsertSpace",
|
||||
"CtrlH": "Backspace",
|
||||
"Backspace": "Backspace",
|
||||
"Alt-CtrlH": "DeleteWordLeft",
|
||||
"Alt-Backspace": "DeleteWordLeft",
|
||||
"Tab": "IndentSelection,InsertTab",
|
||||
"Backtab": "OutdentSelection",
|
||||
"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",
|
||||
"CtrlRightSq": "PreviousTab",
|
||||
"CtrlBackslash": "NextTab",
|
||||
"Home": "StartOfLine",
|
||||
"End": "EndOfLine",
|
||||
"CtrlHome": "CursorStart",
|
||||
"CtrlEnd": "CursorEnd",
|
||||
"PageUp": "CursorPageUp",
|
||||
"PageDown": "CursorPageDown",
|
||||
"CtrlG": "ToggleHelp",
|
||||
"CtrlR": "ToggleRuler",
|
||||
"CtrlL": "JumpLine",
|
||||
"Delete": "Delete",
|
||||
"CtrlB": "ShellMode",
|
||||
"CtrlQ": "Quit",
|
||||
"CtrlE": "CommandMode",
|
||||
"CtrlW": "NextSplit",
|
||||
"CtrlU": "ToggleMacro",
|
||||
"CtrlJ": "PlayMacro",
|
||||
|
||||
// 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
|
||||
"F1": "ToggleHelp",
|
||||
"F2": "Save",
|
||||
"F4": "Quit",
|
||||
"F7": "Find",
|
||||
"F10": "Quit",
|
||||
"Esc": "Quit",
|
||||
}
|
||||
```
|
||||
|
||||
@@ -143,6 +151,7 @@ Delete
|
||||
Center
|
||||
InsertTab
|
||||
Save
|
||||
SaveAs
|
||||
Find
|
||||
FindNext
|
||||
FindPrevious
|
||||
@@ -178,6 +187,9 @@ AddTab
|
||||
PreviousTab
|
||||
NextTab
|
||||
NextSplit
|
||||
Unsplit
|
||||
VSplit
|
||||
HSplit
|
||||
PreviousSplit
|
||||
ToggleMacro
|
||||
PlayMacro
|
||||
@@ -309,9 +321,11 @@ Tab
|
||||
Esc
|
||||
Escape
|
||||
Enter
|
||||
Backspace2
|
||||
```
|
||||
|
||||
Note: On some old terminal emulators and on Windows machines, `CtrlH` should be used
|
||||
for backspace.
|
||||
|
||||
Additionally, alt keys can be bound by using `Alt-key`. For example `Alt-a`
|
||||
or `Alt-Up`. Micro supports an optional `-` between modifiers like `Alt` and `Ctrl`
|
||||
so `Alt-a` could be rewritten as `Alta` (case matters for alt bindings). This is
|
||||
|
||||
@@ -14,7 +14,7 @@ Here are the options that you can set:
|
||||
|
||||
default value: `default`
|
||||
Note that the default colorschemes (default, solarized, and solarized-tc)
|
||||
are not located in configDir, because they are embedded in the micro binary
|
||||
are not located in configDir, because they are embedded in the micro binary.
|
||||
|
||||
The colorscheme can be selected from all the files in the
|
||||
~/.config/micro/colorschemes/ directory. Micro comes by default with three
|
||||
@@ -23,6 +23,11 @@ Here are the options that you can set:
|
||||
You can read more about micro's colorschemes in the `colors` help topic
|
||||
(`help colors`).
|
||||
|
||||
* `colorcolumn`: if this is not set to 0, it will display a column at the specified
|
||||
column. This is useful if you want column 80 to be highlighted special for example.
|
||||
|
||||
default value: `0`
|
||||
|
||||
* `tabsize`: sets the tab size to `option`
|
||||
|
||||
default value: `4`
|
||||
@@ -88,24 +93,35 @@ Here are the options that you can set:
|
||||
|
||||
default value: `2`
|
||||
|
||||
* `autosave`: micro will save the buffer every 8 seconds automatically.
|
||||
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.
|
||||
|
||||
default value: `off`
|
||||
|
||||
* `pluginchannels`: contains all the channels micro's plugin manager will search
|
||||
for plugins in. A channel is simply a list of 'repository' json files which contain
|
||||
metadata about the given plugin. See the `Plugin Manager` section of the `plugins` help topic
|
||||
for more information.
|
||||
|
||||
default value: `https://github.com/micro-editor/plugin-channel`
|
||||
|
||||
* `pluginrepos`: contains all the 'repositories' micro's plugin manager will search for
|
||||
plugins in. A repository consists of a `repo.json` file which contains metadata for a
|
||||
single plugin.
|
||||
|
||||
default value: ` `
|
||||
|
||||
---
|
||||
|
||||
Default plugin options:
|
||||
|
||||
* `linter`: lint languages on save (supported languages are C, D, Go, Java,
|
||||
Javascript, Lua). Provided by the `linter` plugin.
|
||||
* `autoclose`: Automatically close `{}` `()` `[]` `""` `''`. Provided by the `autoclose` plugin
|
||||
|
||||
default value: `on`
|
||||
|
||||
* `autoclose`: Automatically close `{}` `()` `[]` `""` `''`. Provided by the autoclose plugin
|
||||
|
||||
default value: `on`
|
||||
|
||||
* `goimports`: Run goimports on save. Provided by the `go` plugin.
|
||||
|
||||
default value: `off`
|
||||
|
||||
* `gofmt`: Run gofmt on save. Provided by the `go` plugin.
|
||||
* `linter`: Automatically lint when the file is saved. Provided by the `linter` plugin
|
||||
|
||||
default value: `on`
|
||||
|
||||
|
||||
@@ -43,38 +43,48 @@ for functions are given using Go's type system):
|
||||
* `OS`: variable which gives the OS micro is currently running on (this is the same
|
||||
as Go's GOOS variable, so `darwin`, `windows`, `linux`, `freebsd`...)
|
||||
|
||||
* `configDir`: contains the path to the micro configuration files
|
||||
|
||||
* `tabs`: a list of all the tabs currently in use
|
||||
|
||||
* `curTab`: the index of the current tabs in the tabs list
|
||||
|
||||
* `messenger`: lets you send messages to the user or create prompts
|
||||
|
||||
* `RuneStr(r rune) string`: returns a string containing the given rune
|
||||
|
||||
* `Loc(x, y int) Loc`: returns a new `Loc` struct
|
||||
|
||||
* `JoinPaths(dir... string) string` combines multiple directories to a full path
|
||||
|
||||
* `GetOption(name string)`: returns the value of the requested option
|
||||
|
||||
* `AddOption(name string, value interface{})`: sets the given option with the given
|
||||
value (`interface{}` means any type in Go).
|
||||
value (`interface{}` means any type in Go)
|
||||
|
||||
* `SetOption(option, value string)`: sets the given option to the value. This will
|
||||
set the option globally, unless it is a local only option.
|
||||
|
||||
* `SetLocalOption(option, value string, buffer *Buffer)`: sets the given option to
|
||||
the value locally in the given buffer.
|
||||
the value locally in the given buffer
|
||||
|
||||
* `BindKey(key, action string)`: binds `key` to `action`.
|
||||
* `BindKey(key, action string)`: binds `key` to `action`
|
||||
|
||||
* `MakeCommand(name, function string, completions ...Completion)`:
|
||||
creates a command with `name` which will call `function` when executed.
|
||||
Use 0 for completions to get NoCompletion.
|
||||
|
||||
* `MakeCompletion(function string)`:
|
||||
creates a `Completion` to use with `MakeCommand`.
|
||||
creates a `Completion` to use with `MakeCommand`
|
||||
|
||||
* `CurView()`: returns the current view
|
||||
|
||||
* `HandleCommand(cmd string)`: runs the given command
|
||||
|
||||
* `HandleShellCommand(shellCmd string, interactive bool)`: runs the given shell
|
||||
command
|
||||
* `HandleShellCommand(shellCmd string, interactive bool, waitToClose bool)`: runs the given shell
|
||||
command. The `interactive` bool specifies whether the command should run in the background. The
|
||||
`waitToClose` bool only applies if `interactive` is true and means that it should wait before
|
||||
returning to the editor.
|
||||
|
||||
* `JobStart(cmd string, onStdout, onStderr, onExit string, userargs ...string)`:
|
||||
Starts running the given shell command in the background. `onStdout` `onStderr` and `onExit`
|
||||
@@ -107,6 +117,19 @@ The possible methods which you can call using the `messenger` variable are:
|
||||
|
||||
If you want a standard prompt, just use `messenger.Prompt(prompt, "", 0)`
|
||||
|
||||
# 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 and to your plugin
|
||||
called `test`, you would create the `test.md` file for example, and runt the function:
|
||||
|
||||
```lua
|
||||
AddRuntimeFile("test", "help", "test.md")
|
||||
```
|
||||
|
||||
Use `AddRuntimeFilesFromDirectory(name, type, dir, pattern)` to add a number of files
|
||||
to the runtime.
|
||||
|
||||
# Autocomplete command arguments
|
||||
|
||||
See this example to learn how to use `MakeCompletion` and `MakeCommand`
|
||||
@@ -139,5 +162,44 @@ MakeCommand("foo", "example.foo", MakeCompletion("example.complete"))
|
||||
|
||||
# Default plugins
|
||||
|
||||
For examples of plugins, see the default plugins `linter`, `go`, and `autoclose`.
|
||||
They are stored in Micro's github repository [here](https://github.com/zyedidia/micro/tree/master/runtime/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).
|
||||
|
||||
# Plugin Manager
|
||||
|
||||
Micro also has a built in plugin manager which you can invoke with the `> plugin ...` command.
|
||||
|
||||
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 channel which is located
|
||||
at github.com/micro-editor/plugin-channel but you can add your own third-party channels using
|
||||
the `pluginchannels` option and you can directly link third-party plugins to allow installation
|
||||
through the plugin manager with the `pluginrepos` option.
|
||||
|
||||
If you'd like to publish a plugin you've made as an official plugin, you should upload your
|
||||
plugin online (to Github preferably) and add a `repo.json` file. This file will contain the
|
||||
metadata for your plugin. Here is an example:
|
||||
|
||||
```json
|
||||
[{
|
||||
"Name": "pluginname",
|
||||
"Description": "Here is a nice concise description of my plugin",
|
||||
"Tags": ["python", "linting"],
|
||||
"Versions": [
|
||||
{
|
||||
"Version": "1.0.0",
|
||||
"Url": "https://github.com/user/plugin/archive/v1.0.0.zip",
|
||||
"Require": {
|
||||
"micro": ">=1.0.3"
|
||||
}
|
||||
}
|
||||
]
|
||||
}]
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
@@ -6,6 +6,17 @@ and use `init.lua` to configure micro to your liking.
|
||||
|
||||
Hopefully you'll find this useful.
|
||||
|
||||
### Plugins
|
||||
|
||||
Micro has a plugin manager which can automatically download plugins for you.
|
||||
To see the plugins 'official' plugins, go to github.com/micro-editor/plugin-channel.
|
||||
|
||||
For example, if you'd like to install the snippets plugin (listed in that repo),
|
||||
just press `CtrlE` to execute a command, and type `plugin install snippets`.
|
||||
|
||||
For more information about the plugin manager, see the end of the `plugins` help
|
||||
topic.
|
||||
|
||||
### Settings
|
||||
|
||||
In micro, your settings are stored in `~/.config/micro/settings.json`, a file
|
||||
@@ -80,7 +91,7 @@ You can do that by putting the following in `init.lua`:
|
||||
function gorun()
|
||||
local buf = CurView().Buf -- The current buffer
|
||||
if buf:FileType() == "go" then
|
||||
HandleShellCommand("go run " .. buf.Path, true) -- true means don't run it in the background
|
||||
HandleShellCommand("go run " .. buf.Path, true, true) -- the first true means don't run it in the background
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ if GetOption("autoclose") == nil then
|
||||
AddOption("autoclose", true)
|
||||
end
|
||||
|
||||
local autoclosePairs = {"\"\"", "''", "()", "{}", "[]"}
|
||||
local autoclosePairs = {"\"\"", "''", "``", "()", "{}", "[]"}
|
||||
local autoNewlinePairs = {"()", "{}", "[]"}
|
||||
|
||||
function onRune(r, v)
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
if GetOption("goimports") == nil then
|
||||
AddOption("goimports", false)
|
||||
end
|
||||
if GetOption("gofmt") == nil then
|
||||
AddOption("gofmt", true)
|
||||
end
|
||||
|
||||
MakeCommand("goimports", "go.goimports", 0)
|
||||
MakeCommand("gofmt", "go.gofmt", 0)
|
||||
|
||||
function onViewOpen(view)
|
||||
if view.Buf:FileType() == "go" then
|
||||
SetLocalOption("tabstospaces", "off", view)
|
||||
end
|
||||
end
|
||||
|
||||
function onSave(view)
|
||||
if CurView().Buf:FileType() == "go" then
|
||||
if GetOption("goimports") then
|
||||
goimports()
|
||||
elseif GetOption("gofmt") then
|
||||
gofmt()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function gofmt()
|
||||
CurView():Save(false)
|
||||
local handle = io.popen("gofmt -w " .. CurView().Buf.Path)
|
||||
local result = handle:read("*a")
|
||||
handle:close()
|
||||
|
||||
CurView():ReOpen()
|
||||
end
|
||||
|
||||
function goimports()
|
||||
CurView():Save(false)
|
||||
local handle = io.popen("goimports -w " .. CurView().Buf.Path)
|
||||
local result = split(handle:read("*a"), ":")
|
||||
handle:close()
|
||||
|
||||
CurView():ReOpen()
|
||||
end
|
||||
|
||||
function split(str, sep)
|
||||
local result = {}
|
||||
local regex = ("([^%s]+)"):format(sep)
|
||||
for each in str:gmatch(regex) do
|
||||
table.insert(result, each)
|
||||
end
|
||||
return result
|
||||
end
|
||||
@@ -22,7 +22,7 @@ function runLinter()
|
||||
elseif ft == "lua" then
|
||||
lint("luacheck", "luacheck --no-color " .. file, "%f:%l:%d+: %m")
|
||||
elseif ft == "python" then
|
||||
lint("pyflakes", "pyflakes " .. file, "%f:%l: %m")
|
||||
lint("pyflakes", "pyflakes " .. file, "%f:%l:.-:? %m")
|
||||
elseif ft == "c" then
|
||||
lint("gcc", "gcc -fsyntax-only -Wall -Wextra " .. file, "%f:%l:%d+:.+: %m")
|
||||
elseif ft == "d" then
|
||||
@@ -51,7 +51,7 @@ end
|
||||
function onExit(output, linter, errorformat)
|
||||
local lines = split(output, "\n")
|
||||
|
||||
local regex = errorformat:gsub("%%f", "(.+)"):gsub("%%l", "(%d+)"):gsub("%%m", "(.+)")
|
||||
local regex = errorformat:gsub("%%f", "(..-)"):gsub("%%l", "(%d+)"):gsub("%%m", "(.+)")
|
||||
for _,line in ipairs(lines) do
|
||||
-- Trim whitespace
|
||||
line = line:match("^%s*(.+)%s*$")
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
## Here is a short improved example for HTML.
|
||||
##
|
||||
syntax "html" "\.htm[l]?$"
|
||||
color identifier start="<" end=">"
|
||||
color identifier "<.*?>"
|
||||
color special "&[^;[[:space:]]]*;"
|
||||
color constant ""[^"]*"|qq\|.*\|"
|
||||
color statement "(alt|bgcolor|height|href|label|longdesc|name|onclick|onfocus|onload|onmouseover|size|span|src|style|target|type|value|width)="
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
## Arch PKGBUILD files
|
||||
##
|
||||
syntax "pkgbuild" "^.*PKGBUILD$"
|
||||
color green start="^." end="$"
|
||||
color cyan "^.*(pkgbase|pkgname|pkgver|pkgrel|pkgdesc|arch|url|license).*=.*$"
|
||||
color brightcyan "\<(pkgbase|pkgname|pkgver|pkgrel|pkgdesc|arch|url|license)\>"
|
||||
color brightcyan "(\$|\$\{|\$\()(pkgbase|pkgname|pkgver|pkgrel|pkgdesc|arch|url|license)(\}|\))"
|
||||
color cyan "^.*(depends|makedepends|optdepends|conflicts|provides|replaces).*=.*$"
|
||||
color brightcyan "\<(depends|makedepends|optdepends|conflicts|provides|replaces)\>"
|
||||
color brightcyan "(\$|\$\{|\$\()(depends|makedepends|optdepends|conflicts|provides|replaces)(\}|\))"
|
||||
color cyan "^.*(groups|backup|noextract|options).*=.*$"
|
||||
color brightcyan "\<(groups|backup|noextract|options)\>"
|
||||
color brightcyan "(\$|\$\{|\$\()(groups|backup|noextract|options)(\}|\))"
|
||||
color cyan "^.*(install|source|md5sums|sha1sums|sha256sums|sha384sums|sha512sums).*=.*$"
|
||||
color brightcyan "\<(install|source|md5sums|sha1sums|sha256sums|sha384sums|sha512sums)\>"
|
||||
color brightcyan "(\$|\$\{|\$\()(install|source|md5sums|sha1sums|sha256sums|sha384sums|sha512sums)(\}|\))"
|
||||
color brightcyan "\<(startdir|srcdir|pkgdir)\>"
|
||||
color cyan "\.install"
|
||||
color brightwhite "=" "'" "\(" "\)" "\"" "#.*$" "\," "\{" "\}"
|
||||
color brightred "build\(\)"
|
||||
color brightred "package_.*.*$"
|
||||
color brightred "\<(configure|make|cmake|scons)\>"
|
||||
color red "\<(DESTDIR|PREFIX|prefix|sysconfdir|datadir|libdir|includedir|mandir|infodir)\>"
|
||||
@@ -1,11 +1,7 @@
|
||||
## Here is an example for xml files.
|
||||
##
|
||||
|
||||
syntax "xml" "\.([jrs]?html?|xml|sgml?|rng|plist)$"
|
||||
color white "^.+$"
|
||||
color green start="<" end=">"
|
||||
color cyan "<[^> ]+"
|
||||
color cyan ">"
|
||||
color yellow start="<!DOCTYPE" end="[/]?>"
|
||||
color yellow start="<!--" end="-->"
|
||||
color red "&[^;]*;"
|
||||
syntax "xml" "\.(xml|sgml?|rng|plist)$"
|
||||
color identifier "<.*?>"
|
||||
color comment start="<!DOCTYPE" end="[/]?>"
|
||||
color comment start="<!--" end="-->"
|
||||
color special "&[^;]*;"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
syntax "yaml" "\.ya?ml$"
|
||||
header "^---" "%YAML"
|
||||
header "%YAML"
|
||||
|
||||
color type "(^| )!!(binary|bool|float|int|map|null|omap|seq|set|str) "
|
||||
color constant "\b(YES|yes|Y|y|ON|on|NO|no|N|n|OFF|off)\b"
|
||||
|
||||
19
snapcraft.yaml
Normal file
19
snapcraft.yaml
Normal file
@@ -0,0 +1,19 @@
|
||||
name: micro
|
||||
version: master
|
||||
summary: A modern and intuitive terminal-based text editor
|
||||
description: |
|
||||
Micro is a terminal-based text editor that aims to be easy to use and
|
||||
intuitive, while also taking advantage of the full capabilities of modern
|
||||
terminals.
|
||||
confinement: strict
|
||||
|
||||
apps:
|
||||
micro:
|
||||
command: bin/micro
|
||||
plugs: [home]
|
||||
|
||||
parts:
|
||||
micro:
|
||||
source: .
|
||||
plugin: go
|
||||
go-importpath: github.com/zyedidia/micro
|
||||
65
tools/build-version.go
Normal file
65
tools/build-version.go
Normal file
@@ -0,0 +1,65 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
"github.com/blang/semver"
|
||||
)
|
||||
|
||||
func getTag(match ...string) (string, *semver.PRVersion) {
|
||||
args := append([]string{
|
||||
"describe", "--tags",
|
||||
}, match...)
|
||||
if tag, err := exec.Command("git", args...).Output(); err != nil {
|
||||
return "", nil
|
||||
} else {
|
||||
tagParts := strings.Split(string(tag), "-")
|
||||
if len(tagParts) == 3 {
|
||||
if ahead, err := semver.NewPRVersion(tagParts[1]); err == nil {
|
||||
return tagParts[0], &ahead
|
||||
}
|
||||
}
|
||||
|
||||
return tagParts[0], nil
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
// Find the last vX.X.X Tag and get how many builds we are ahead of it.
|
||||
versionStr, ahead := getTag("--match", "v*")
|
||||
version, err := semver.ParseTolerant(versionStr)
|
||||
if err != nil {
|
||||
// no version tag found so just return what ever we can find.
|
||||
fmt.Println(getTag())
|
||||
return
|
||||
}
|
||||
// Get the tag of the current revision.
|
||||
tag, _ := getTag("--exact-match")
|
||||
if tag == versionStr {
|
||||
// Seems that we are going to build a release.
|
||||
// So the version number should already be correct.
|
||||
fmt.Println(version.String())
|
||||
return
|
||||
}
|
||||
|
||||
// If we don't have any tag assume "dev"
|
||||
if tag == "" {
|
||||
tag = "dev"
|
||||
}
|
||||
// Get the most likely next version:
|
||||
version.Patch = version.Patch + 1
|
||||
|
||||
if pr, err := semver.NewPRVersion(tag); err == nil {
|
||||
// append the tag as pre-release name
|
||||
version.Pre = append(version.Pre, pr)
|
||||
}
|
||||
|
||||
if ahead != nil {
|
||||
// if we know how many commits we are ahead of the last release, append that too.
|
||||
version.Pre = append(version.Pre, *ahead)
|
||||
}
|
||||
|
||||
fmt.Println(version.String())
|
||||
}
|
||||
@@ -10,51 +10,51 @@ HASH="$(git rev-parse --short HEAD)"
|
||||
|
||||
# Mac
|
||||
echo "OSX 64"
|
||||
GOOS=darwin GOARCH=amd64 go build -ldflags "-X main.Version=$1 -X main.CommitHash=$HASH -X 'main.CompileDate=$(date -u '+%B %d, %Y')'" -o micro-$1/micro ./cmd/micro
|
||||
GOOS=darwin GOARCH=amd64 go build -ldflags "-s -w -X main.Version=$1 -X main.CommitHash=$HASH -X 'main.CompileDate=$(date -u '+%B %d, %Y')'" -o micro-$1/micro ./cmd/micro
|
||||
tar -czf micro-$1-osx.tar.gz micro-$1
|
||||
mv micro-$1-osx.tar.gz binaries
|
||||
|
||||
# Linux
|
||||
echo "Linux 64"
|
||||
GOOS=linux GOARCH=amd64 go build -ldflags "-X main.Version=$1 -X main.CommitHash=$HASH -X 'main.CompileDate=$(date -u '+%B %d, %Y')'" -o micro-$1/micro ./cmd/micro
|
||||
GOOS=linux GOARCH=amd64 go build -ldflags "-s -w -X main.Version=$1 -X main.CommitHash=$HASH -X 'main.CompileDate=$(date -u '+%B %d, %Y')'" -o micro-$1/micro ./cmd/micro
|
||||
tar -czf micro-$1-linux64.tar.gz micro-$1
|
||||
mv micro-$1-linux64.tar.gz binaries
|
||||
echo "Linux 32"
|
||||
GOOS=linux GOARCH=386 go build -ldflags "-X main.Version=$1 -X main.CommitHash=$HASH -X 'main.CompileDate=$(date -u '+%B %d, %Y')'" -o micro-$1/micro ./cmd/micro
|
||||
GOOS=linux GOARCH=386 go build -ldflags "-s -w -X main.Version=$1 -X main.CommitHash=$HASH -X 'main.CompileDate=$(date -u '+%B %d, %Y')'" -o micro-$1/micro ./cmd/micro
|
||||
tar -czf micro-$1-linux32.tar.gz micro-$1
|
||||
mv micro-$1-linux32.tar.gz binaries
|
||||
echo "Linux arm"
|
||||
GOOS=linux GOARCH=arm go build -ldflags "-X main.Version=$1 -X main.CommitHash=$HASH -X 'main.CompileDate=$(date -u '+%B %d, %Y')'" -o micro-$1/micro ./cmd/micro
|
||||
GOOS=linux GOARCH=arm go build -ldflags "-s -w -X main.Version=$1 -X main.CommitHash=$HASH -X 'main.CompileDate=$(date -u '+%B %d, %Y')'" -o micro-$1/micro ./cmd/micro
|
||||
tar -czf micro-$1-linux-arm.tar.gz micro-$1
|
||||
mv micro-$1-linux-arm.tar.gz binaries
|
||||
|
||||
# NetBSD
|
||||
echo "NetBSD 64"
|
||||
GOOS=netbsd GOARCH=amd64 go build -ldflags "-X main.Version=$1 -X main.CommitHash=$HASH -X 'main.CompileDate=$(date -u '+%B %d, %Y')'" -o micro-$1/micro ./cmd/micro
|
||||
GOOS=netbsd GOARCH=amd64 go build -ldflags "-s -w -X main.Version=$1 -X main.CommitHash=$HASH -X 'main.CompileDate=$(date -u '+%B %d, %Y')'" -o micro-$1/micro ./cmd/micro
|
||||
tar -czf micro-$1-netbsd64.tar.gz micro-$1
|
||||
mv micro-$1-netbsd64.tar.gz binaries
|
||||
echo "NetBSD 32"
|
||||
GOOS=netbsd GOARCH=386 go build -ldflags "-X main.Version=$1 -X main.CommitHash=$HASH -X 'main.CompileDate=$(date -u '+%B %d, %Y')'" -o micro-$1/micro ./cmd/micro
|
||||
GOOS=netbsd GOARCH=386 go build -ldflags "-s -w -X main.Version=$1 -X main.CommitHash=$HASH -X 'main.CompileDate=$(date -u '+%B %d, %Y')'" -o micro-$1/micro ./cmd/micro
|
||||
tar -czf micro-$1-netbsd32.tar.gz micro-$1
|
||||
mv micro-$1-netbsd32.tar.gz binaries
|
||||
|
||||
# OpenBSD
|
||||
echo "OpenBSD 64"
|
||||
GOOS=openbsd GOARCH=amd64 go build -ldflags "-X main.Version=$1 -X main.CommitHash=$HASH -X 'main.CompileDate=$(date -u '+%B %d, %Y')'" -o micro-$1/micro ./cmd/micro
|
||||
GOOS=openbsd GOARCH=amd64 go build -ldflags "-s -w -X main.Version=$1 -X main.CommitHash=$HASH -X 'main.CompileDate=$(date -u '+%B %d, %Y')'" -o micro-$1/micro ./cmd/micro
|
||||
tar -czf micro-$1-openbsd64.tar.gz micro-$1
|
||||
mv micro-$1-openbsd64.tar.gz binaries
|
||||
echo "OpenBSD 32"
|
||||
GOOS=openbsd GOARCH=386 go build -ldflags "-X main.Version=$1 -X main.CommitHash=$HASH -X 'main.CompileDate=$(date -u '+%B %d, %Y')'" -o micro-$1/micro ./cmd/micro
|
||||
GOOS=openbsd GOARCH=386 go build -ldflags "-s -w -X main.Version=$1 -X main.CommitHash=$HASH -X 'main.CompileDate=$(date -u '+%B %d, %Y')'" -o micro-$1/micro ./cmd/micro
|
||||
tar -czf micro-$1-openbsd32.tar.gz micro-$1
|
||||
mv micro-$1-openbsd32.tar.gz binaries
|
||||
|
||||
# FreeBSD
|
||||
echo "FreeBSD 64"
|
||||
GOOS=freebsd GOARCH=amd64 go build -ldflags "-X main.Version=$1 -X main.CommitHash=$HASH -X 'main.CompileDate=$(date -u '+%B %d, %Y')'" -o micro-$1/micro ./cmd/micro
|
||||
GOOS=freebsd GOARCH=amd64 go build -ldflags "-s -w -X main.Version=$1 -X main.CommitHash=$HASH -X 'main.CompileDate=$(date -u '+%B %d, %Y')'" -o micro-$1/micro ./cmd/micro
|
||||
tar -czf micro-$1-freebsd64.tar.gz micro-$1
|
||||
mv micro-$1-freebsd64.tar.gz binaries
|
||||
echo "FreeBSD 32"
|
||||
GOOS=freebsd GOARCH=386 go build -ldflags "-X main.Version=$1 -X main.CommitHash=$HASH -X 'main.CompileDate=$(date -u '+%B %d, %Y')'" -o micro-$1/micro ./cmd/micro
|
||||
GOOS=freebsd GOARCH=386 go build -ldflags "-s -w -X main.Version=$1 -X main.CommitHash=$HASH -X 'main.CompileDate=$(date -u '+%B %d, %Y')'" -o micro-$1/micro ./cmd/micro
|
||||
tar -czf micro-$1-freebsd32.tar.gz micro-$1
|
||||
mv micro-$1-freebsd32.tar.gz binaries
|
||||
|
||||
@@ -62,11 +62,11 @@ rm micro-$1/micro
|
||||
|
||||
# Windows
|
||||
echo "Windows 64"
|
||||
GOOS=windows GOARCH=amd64 go build -ldflags "-X main.Version=$1 -X main.CommitHash=$HASH -X 'main.CompileDate=$(date -u '+%B %d, %Y')'" -o micro-$1/micro.exe ./cmd/micro
|
||||
GOOS=windows GOARCH=amd64 go build -ldflags "-s -w -X main.Version=$1 -X main.CommitHash=$HASH -X 'main.CompileDate=$(date -u '+%B %d, %Y')'" -o micro-$1/micro.exe ./cmd/micro
|
||||
zip -r -q -T micro-$1-win64.zip micro-$1
|
||||
mv micro-$1-win64.zip binaries
|
||||
echo "Windows 32"
|
||||
GOOS=windows GOARCH=386 go build -ldflags "-X main.Version=$1 -X main.CommitHash=$HASH -X 'main.CompileDate=$(date -u '+%B %d, %Y')'" -o micro-$1/micro.exe ./cmd/micro
|
||||
GOOS=windows GOARCH=386 go build -ldflags "-s -w -X main.Version=$1 -X main.CommitHash=$HASH -X 'main.CompileDate=$(date -u '+%B %d, %Y')'" -o micro-$1/micro.exe ./cmd/micro
|
||||
zip -r -q -T micro-$1-win32.zip micro-$1
|
||||
mv micro-$1-win32.zip binaries
|
||||
|
||||
|
||||
Reference in New Issue
Block a user