mirror of
https://github.com/zyedidia/micro.git
synced 2026-03-31 07:07:09 +09:00
Compare commits
257 Commits
| 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 | ||
|
|
c0282c4a3c | ||
|
|
0a6e1de404 | ||
|
|
131524e670 | ||
|
|
539495d2f7 | ||
|
|
966dac97f8 | ||
|
|
bf6e596808 | ||
|
|
efa24f5a8d | ||
|
|
432146b068 | ||
|
|
02b6eaaff0 | ||
|
|
e7ee18e421 | ||
|
|
dc532e337b | ||
|
|
a952e249b4 | ||
|
|
94465ef1ae | ||
|
|
0a534767f0 | ||
|
|
7937f7038b | ||
|
|
d6a01ad29f | ||
|
|
b1cb583e8c | ||
|
|
ea4d822923 | ||
|
|
583177feff | ||
|
|
3bec1b8c1b | ||
|
|
b992669f5b | ||
|
|
086aa61e5a | ||
|
|
725533d991 | ||
|
|
c310053777 | ||
|
|
2041e12eba | ||
|
|
5d00522d4e | ||
|
|
c71e816e37 | ||
|
|
6721ec8e7d | ||
|
|
93eadfb9dc | ||
|
|
cf3ce29a08 | ||
|
|
cd1117c08c | ||
|
|
f247823936 | ||
|
|
a8feef3c12 | ||
|
|
de9707f088 | ||
|
|
9ff396c69f | ||
|
|
4b350d02e0 | ||
|
|
ae3696e82d | ||
|
|
b3f6731db5 | ||
|
|
2b9ef4d406 | ||
|
|
54a34001e3 | ||
|
|
aad7cc7572 | ||
|
|
def5e29b91 | ||
|
|
187ea0da1c | ||
|
|
5b7fa01825 | ||
|
|
c38044106c | ||
|
|
399e3a5ee1 | ||
|
|
fce5b81c22 | ||
|
|
a4ac9f2b7b | ||
|
|
abedeebc0a | ||
|
|
b905400892 | ||
|
|
c4d6f5e584 | ||
|
|
b5232dd24d | ||
|
|
89886f10c7 | ||
|
|
c679500d06 | ||
|
|
76f80bf694 | ||
|
|
a7b72f0e0e | ||
|
|
00eb6725e6 | ||
|
|
af22e0a567 | ||
|
|
98b6f63b70 | ||
|
|
403a99d2ea | ||
|
|
b2735d7b5b | ||
|
|
699ad316e5 | ||
|
|
8617ae5c1f | ||
|
|
c5ac5be764 | ||
|
|
881f57b047 | ||
|
|
1c2b815d95 | ||
|
|
b45fcf5bd7 | ||
|
|
45ec01d197 | ||
|
|
0df7e59ca4 | ||
|
|
89c34ed8b3 | ||
|
|
d9b8a04841 | ||
|
|
ccfe08bc60 | ||
|
|
2e3ee22aca | ||
|
|
226cf399ba | ||
|
|
0f6260601f | ||
|
|
d2254df062 | ||
|
|
3e83d29fb4 | ||
|
|
a432bc7e7b | ||
|
|
486279e1d1 | ||
|
|
3967981b38 | ||
|
|
597c549b5b | ||
|
|
fc2d9bb461 | ||
|
|
832cc366af | ||
|
|
5458605618 | ||
|
|
104af2ef7a | ||
|
|
64ff933451 | ||
|
|
031ed64305 | ||
|
|
1be332a0f9 | ||
|
|
b9d4dbd5e0 | ||
|
|
2e264b342a | ||
|
|
4972db4bf6 | ||
|
|
b70db77c29 | ||
|
|
f8612d1572 | ||
|
|
f9d0c563e4 | ||
|
|
3105205ab8 | ||
|
|
ec29d592da | ||
|
|
62ac9f79f2 | ||
|
|
a6d695f471 | ||
|
|
59d2fa81dd | ||
|
|
4003586456 | ||
|
|
be44dc3ff5 | ||
|
|
c59b1ea387 | ||
|
|
00089a7c01 | ||
|
|
c661c65c8c | ||
|
|
13da5ced15 | ||
|
|
b970b393f2 | ||
|
|
30323116a5 | ||
|
|
2fc274da7d | ||
|
|
b19b36d113 | ||
|
|
b6f5db3692 | ||
|
|
8bdaacaa5e | ||
|
|
c9ead25b1e | ||
|
|
ddff950fcd | ||
|
|
ed6be89d5c | ||
|
|
6e63472930 | ||
|
|
94397a90bd | ||
|
|
216bfaca7e | ||
|
|
9854fc712f | ||
|
|
99635d9491 | ||
|
|
4499228cdb |
9
.github/ISSUE_TEMPLATE
vendored
Normal file
9
.github/ISSUE_TEMPLATE
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
## Description of the problem or steps to reproduce
|
||||
|
||||
## Specifications
|
||||
|
||||
You can use `micro -version` to get the commit hash.
|
||||
|
||||
Commit hash:
|
||||
OS:
|
||||
Terminal:
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,3 +1,5 @@
|
||||
./micro
|
||||
!cmd/micro
|
||||
binaries/
|
||||
tmp.sh
|
||||
test/
|
||||
|
||||
11
Makefile
11
Makefile
@@ -1,21 +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=$(shell date -u '+%B %d, %Y')'" -o micro ./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 runtiem and checking dependencies
|
||||
# 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=$(shell date -u '+%B %d, %Y')'" -o micro ./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
|
||||
@@ -23,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
|
||||
|
||||
39
README.md
39
README.md
@@ -31,24 +31,28 @@ 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
|
||||
* Common editor things such as undo/redo, line numbers, unicode support...
|
||||
* Macros
|
||||
* Common editor things such as undo/redo, line numbers, Unicode support...
|
||||
|
||||
Although not yet implemented, I hope to add more features such as autocompletion, and multiple cursors in the future.
|
||||
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.
|
||||
|
||||
# Installation
|
||||
|
||||
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
|
||||
|
||||
@@ -61,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).
|
||||
|
||||
@@ -73,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:
|
||||
|
||||
@@ -89,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
|
||||
@@ -121,8 +133,15 @@ 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
|
||||
view the help files online [here](https://github.com/zyedidia/micro/tree/master/runtime/help).
|
||||
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)
|
||||
* [keybindings](https://github.com/zyedidia/micro/tree/master/runtime/help/keybindings.md)
|
||||
* [commands](https://github.com/zyedidia/micro/tree/master/runtime/help/commands.md)
|
||||
* [colors](https://github.com/zyedidia/micro/tree/master/runtime/help/colors.md)
|
||||
* [options](https://github.com/zyedidia/micro/tree/master/runtime/help/options.md)
|
||||
* [plugins](https://github.com/zyedidia/micro/tree/master/runtime/help/plugins.md)
|
||||
|
||||
I also recommend reading the [tutorial](https://github.com/zyedidia/micro/tree/master/runtime/help/tutorial.md) for
|
||||
a brief introduction to the more powerful configuration features micro offers.
|
||||
@@ -131,4 +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"
|
||||
)
|
||||
@@ -44,6 +42,15 @@ func PostActionCall(funcName string, view *View) bool {
|
||||
return relocate
|
||||
}
|
||||
|
||||
func (v *View) deselect(index int) bool {
|
||||
if v.Cursor.HasSelection() {
|
||||
v.Cursor.Loc = v.Cursor.CurSelection[index]
|
||||
v.Cursor.ResetSelection()
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Center centers the view on the cursor
|
||||
func (v *View) Center(usePlugin bool) bool {
|
||||
if usePlugin && !PreActionCall("Center", v) {
|
||||
@@ -70,10 +77,7 @@ func (v *View) CursorUp(usePlugin bool) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
if v.Cursor.HasSelection() {
|
||||
v.Cursor.Loc = v.Cursor.CurSelection[0]
|
||||
v.Cursor.ResetSelection()
|
||||
}
|
||||
v.deselect(0)
|
||||
v.Cursor.Up()
|
||||
|
||||
if usePlugin {
|
||||
@@ -88,10 +92,7 @@ func (v *View) CursorDown(usePlugin bool) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
if v.Cursor.HasSelection() {
|
||||
v.Cursor.Loc = v.Cursor.CurSelection[1]
|
||||
v.Cursor.ResetSelection()
|
||||
}
|
||||
v.deselect(1)
|
||||
v.Cursor.Down()
|
||||
|
||||
if usePlugin {
|
||||
@@ -290,6 +291,8 @@ func (v *View) StartOfLine(usePlugin bool) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
v.deselect(0)
|
||||
|
||||
v.Cursor.Start()
|
||||
|
||||
if usePlugin {
|
||||
@@ -304,6 +307,8 @@ func (v *View) EndOfLine(usePlugin bool) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
v.deselect(0)
|
||||
|
||||
v.Cursor.End()
|
||||
|
||||
if usePlugin {
|
||||
@@ -354,6 +359,8 @@ func (v *View) CursorStart(usePlugin bool) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
v.deselect(0)
|
||||
|
||||
v.Cursor.X = 0
|
||||
v.Cursor.Y = 0
|
||||
|
||||
@@ -369,6 +376,8 @@ func (v *View) CursorEnd(usePlugin bool) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
v.deselect(0)
|
||||
|
||||
v.Cursor.Loc = v.Buf.End()
|
||||
|
||||
if usePlugin {
|
||||
@@ -485,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))
|
||||
@@ -587,21 +596,21 @@ func (v *View) IndentSelection(usePlugin bool) bool {
|
||||
v.Buf.Insert(Loc{0, i}, Spaces(tabsize))
|
||||
if i == start {
|
||||
if v.Cursor.CurSelection[0].X > 0 {
|
||||
v.Cursor.CurSelection[0] = v.Cursor.CurSelection[0].Move(tabsize, v.Buf)
|
||||
v.Cursor.SetSelectionStart(v.Cursor.CurSelection[0].Move(tabsize, v.Buf))
|
||||
}
|
||||
}
|
||||
if i == end {
|
||||
v.Cursor.CurSelection[1] = Loc{endX + tabsize + 1, end}
|
||||
v.Cursor.SetSelectionEnd(Loc{endX + tabsize + 1, end})
|
||||
}
|
||||
} else {
|
||||
v.Buf.Insert(Loc{0, i}, "\t")
|
||||
if i == start {
|
||||
if v.Cursor.CurSelection[0].X > 0 {
|
||||
v.Cursor.CurSelection[0] = v.Cursor.CurSelection[0].Move(1, v.Buf)
|
||||
v.Cursor.SetSelectionStart(v.Cursor.CurSelection[0].Move(1, v.Buf))
|
||||
}
|
||||
}
|
||||
if i == end {
|
||||
v.Cursor.CurSelection[1] = Loc{endX + 2, end}
|
||||
v.Cursor.SetSelectionEnd(Loc{endX + 2, end})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -636,22 +645,22 @@ func (v *View) OutdentSelection(usePlugin bool) bool {
|
||||
v.Buf.Remove(Loc{0, i}, Loc{1, i})
|
||||
if i == start {
|
||||
if v.Cursor.CurSelection[0].X > 0 {
|
||||
v.Cursor.CurSelection[0] = v.Cursor.CurSelection[0].Move(-1, v.Buf)
|
||||
v.Cursor.SetSelectionStart(v.Cursor.CurSelection[0].Move(-1, v.Buf))
|
||||
}
|
||||
}
|
||||
if i == end {
|
||||
v.Cursor.CurSelection[1] = Loc{endX - j, end}
|
||||
v.Cursor.SetSelectionEnd(Loc{endX - j, end})
|
||||
}
|
||||
}
|
||||
} else {
|
||||
v.Buf.Remove(Loc{0, i}, Loc{1, i})
|
||||
if i == start {
|
||||
if v.Cursor.CurSelection[0].X > 0 {
|
||||
v.Cursor.CurSelection[0] = v.Cursor.CurSelection[0].Move(-1, v.Buf)
|
||||
v.Cursor.SetSelectionStart(v.Cursor.CurSelection[0].Move(-1, v.Buf))
|
||||
}
|
||||
}
|
||||
if i == end {
|
||||
v.Cursor.CurSelection[1] = Loc{endX, end}
|
||||
v.Cursor.SetSelectionEnd(Loc{endX, end})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -678,6 +687,9 @@ func (v *View) InsertTab(usePlugin bool) bool {
|
||||
// Insert a tab
|
||||
if v.Buf.Settings["tabstospaces"].(bool) {
|
||||
tabSize := int(v.Buf.Settings["tabsize"].(float64))
|
||||
if remainder := v.Cursor.Loc.X % tabSize; remainder != 0 {
|
||||
tabSize = tabSize - remainder
|
||||
}
|
||||
v.Buf.Insert(v.Cursor.Loc, Spaces(tabSize))
|
||||
for i := 0; i < tabSize; i++ {
|
||||
v.Cursor.Right()
|
||||
@@ -699,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
|
||||
}
|
||||
@@ -707,6 +719,8 @@ func (v *View) Save(usePlugin bool) bool {
|
||||
if v.Buf.Path == "" {
|
||||
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
|
||||
} else {
|
||||
@@ -740,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) {
|
||||
@@ -836,7 +865,7 @@ func (v *View) Copy(usePlugin bool) bool {
|
||||
}
|
||||
|
||||
if v.Cursor.HasSelection() {
|
||||
clipboard.WriteAll(v.Cursor.GetSelection())
|
||||
clipboard.WriteAll(v.Cursor.GetSelection(), "clipboard")
|
||||
v.freshClip = true
|
||||
messenger.Message("Copied selection")
|
||||
}
|
||||
@@ -859,10 +888,10 @@ func (v *View) CutLine(usePlugin bool) bool {
|
||||
}
|
||||
if v.freshClip == true {
|
||||
if v.Cursor.HasSelection() {
|
||||
if clip, err := clipboard.ReadAll(); err != nil {
|
||||
if clip, err := clipboard.ReadAll("clipboard"); err != nil {
|
||||
messenger.Error(err)
|
||||
} else {
|
||||
clipboard.WriteAll(clip + v.Cursor.GetSelection())
|
||||
clipboard.WriteAll(clip+v.Cursor.GetSelection(), "clipboard")
|
||||
}
|
||||
}
|
||||
} else if time.Since(v.lastCutTime)/time.Second > 10*time.Second || v.freshClip == false {
|
||||
@@ -887,7 +916,7 @@ func (v *View) Cut(usePlugin bool) bool {
|
||||
}
|
||||
|
||||
if v.Cursor.HasSelection() {
|
||||
clipboard.WriteAll(v.Cursor.GetSelection())
|
||||
clipboard.WriteAll(v.Cursor.GetSelection(), "clipboard")
|
||||
v.Cursor.DeleteSelection()
|
||||
v.Cursor.ResetSelection()
|
||||
v.freshClip = true
|
||||
@@ -946,18 +975,23 @@ func (v *View) Paste(usePlugin bool) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
leadingWS := GetLeadingWhitespace(v.Buf.Line(v.Cursor.Y))
|
||||
clip, _ := clipboard.ReadAll("clipboard")
|
||||
v.paste(clip)
|
||||
|
||||
if v.Cursor.HasSelection() {
|
||||
v.Cursor.DeleteSelection()
|
||||
v.Cursor.ResetSelection()
|
||||
if usePlugin {
|
||||
return PostActionCall("Paste", v)
|
||||
}
|
||||
clip, _ := clipboard.ReadAll()
|
||||
clip = strings.Replace(clip, "\n", "\n"+leadingWS, -1)
|
||||
v.Buf.Insert(v.Cursor.Loc, clip)
|
||||
v.Cursor.Loc = v.Cursor.Loc.Move(Count(clip), v.Buf)
|
||||
v.freshClip = false
|
||||
messenger.Message("Pasted clipboard")
|
||||
return true
|
||||
}
|
||||
|
||||
// PastePrimary pastes from the primary clipboard (only use on linux)
|
||||
func (v *View) PastePrimary(usePlugin bool) bool {
|
||||
if usePlugin && !PreActionCall("Paste", v) {
|
||||
return false
|
||||
}
|
||||
|
||||
clip, _ := clipboard.ReadAll("primary")
|
||||
v.paste(clip)
|
||||
|
||||
if usePlugin {
|
||||
return PostActionCall("Paste", v)
|
||||
@@ -971,8 +1005,8 @@ func (v *View) SelectAll(usePlugin bool) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
v.Cursor.CurSelection[0] = v.Buf.Start()
|
||||
v.Cursor.CurSelection[1] = v.Buf.End()
|
||||
v.Cursor.SetSelectionStart(v.Buf.Start())
|
||||
v.Cursor.SetSelectionEnd(v.Buf.End())
|
||||
// Put the cursor at the beginning
|
||||
v.Cursor.X = 0
|
||||
v.Cursor.Y = 0
|
||||
@@ -989,23 +1023,15 @@ func (v *View) OpenFile(usePlugin bool) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
if v.CanClose("Continue? (y,n,s) ", 'y', 'n', 's') {
|
||||
if v.CanClose() {
|
||||
filename, canceled := messenger.Prompt("File to open: ", "Open", FileCompletion)
|
||||
if canceled {
|
||||
return false
|
||||
}
|
||||
home, _ := homedir.Dir()
|
||||
filename = strings.Replace(filename, "~", home, 1)
|
||||
file, err := ioutil.ReadFile(filename)
|
||||
// the filename might or might not be quoted, so unquote first then join the strings.
|
||||
filename = strings.Join(SplitCommandArgs(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)
|
||||
@@ -1089,6 +1115,8 @@ func (v *View) CursorPageUp(usePlugin bool) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
v.deselect(0)
|
||||
|
||||
if v.Cursor.HasSelection() {
|
||||
v.Cursor.Loc = v.Cursor.CurSelection[0]
|
||||
v.Cursor.ResetSelection()
|
||||
@@ -1107,6 +1135,8 @@ func (v *View) CursorPageDown(usePlugin bool) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
v.deselect(0)
|
||||
|
||||
if v.Cursor.HasSelection() {
|
||||
v.Cursor.Loc = v.Cursor.CurSelection[1]
|
||||
v.Cursor.ResetSelection()
|
||||
@@ -1228,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 {
|
||||
@@ -1250,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)
|
||||
}
|
||||
@@ -1285,7 +1315,7 @@ func (v *View) Quit(usePlugin bool) bool {
|
||||
}
|
||||
|
||||
// Make sure not to quit if there are unsaved changes
|
||||
if v.CanClose("Quit anyway? (y,n,s) ", 'y', 'n', 's') {
|
||||
if v.CanClose() {
|
||||
v.CloseBuffer()
|
||||
if len(tabs[curTab].views) > 1 {
|
||||
v.splitNode.Delete()
|
||||
@@ -1322,6 +1352,39 @@ func (v *View) Quit(usePlugin bool) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// QuitAll quits the whole editor; all splits and tabs
|
||||
func (v *View) QuitAll(usePlugin bool) bool {
|
||||
if usePlugin && !PreActionCall("QuitAll", v) {
|
||||
return false
|
||||
}
|
||||
|
||||
closeAll := true
|
||||
for _, tab := range tabs {
|
||||
for _, v := range tab.views {
|
||||
if !v.CanClose() {
|
||||
closeAll = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if closeAll {
|
||||
for _, tab := range tabs {
|
||||
for _, v := range tab.views {
|
||||
v.CloseBuffer()
|
||||
}
|
||||
}
|
||||
|
||||
if usePlugin {
|
||||
PostActionCall("QuitAll", v)
|
||||
}
|
||||
|
||||
screen.Fini()
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// AddTab adds a new tab with an empty buffer
|
||||
func (v *View) AddTab(usePlugin bool) bool {
|
||||
if usePlugin && !PreActionCall("AddTab", v) {
|
||||
@@ -1382,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) {
|
||||
@@ -1420,6 +1532,62 @@ func (v *View) PreviousSplit(usePlugin bool) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
var curMacro []interface{}
|
||||
var recordingMacro bool
|
||||
|
||||
func (v *View) ToggleMacro(usePlugin bool) bool {
|
||||
if usePlugin && !PreActionCall("ToggleMacro", v) {
|
||||
return false
|
||||
}
|
||||
|
||||
recordingMacro = !recordingMacro
|
||||
|
||||
if recordingMacro {
|
||||
curMacro = []interface{}{}
|
||||
messenger.Message("Recording")
|
||||
} else {
|
||||
messenger.Message("Stopped recording")
|
||||
}
|
||||
|
||||
if usePlugin {
|
||||
return PostActionCall("ToggleMacro", v)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (v *View) PlayMacro(usePlugin bool) bool {
|
||||
if usePlugin && !PreActionCall("PlayMacro", v) {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, action := range curMacro {
|
||||
switch t := action.(type) {
|
||||
case rune:
|
||||
// Insert a character
|
||||
if v.Cursor.HasSelection() {
|
||||
v.Cursor.DeleteSelection()
|
||||
v.Cursor.ResetSelection()
|
||||
}
|
||||
v.Buf.Insert(v.Cursor.Loc, string(t))
|
||||
v.Cursor.Right()
|
||||
|
||||
for _, pl := range loadedPlugins {
|
||||
_, err := Call(pl+".onRune", string(t), v)
|
||||
if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") {
|
||||
TermMessage(err)
|
||||
}
|
||||
}
|
||||
case func(*View, bool) bool:
|
||||
t(v, true)
|
||||
}
|
||||
}
|
||||
|
||||
if usePlugin {
|
||||
return PostActionCall("PlayMacro", v)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// None is no action
|
||||
func None() bool {
|
||||
return false
|
||||
|
||||
@@ -8,19 +8,24 @@ import (
|
||||
"github.com/mitchellh/go-homedir"
|
||||
)
|
||||
|
||||
var pluginCompletions []func(string) []string
|
||||
|
||||
// This file is meant (for now) for autocompletion in command mode, not
|
||||
// while coding. This helps micro autocomplete commands and then filenames
|
||||
// for example with `vsplit filename`.
|
||||
|
||||
// FileComplete autocompletes filenames
|
||||
func FileComplete(input string) (string, []string) {
|
||||
dirs := strings.Split(input, "/")
|
||||
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], "/")
|
||||
directories := strings.Join(dirs[:len(dirs)-1], sep) + sep
|
||||
|
||||
if strings.HasPrefix(directories, "~") {
|
||||
directories = strings.Replace(directories, "~", home, 1)
|
||||
}
|
||||
@@ -28,6 +33,7 @@ func FileComplete(input string) (string, []string) {
|
||||
} else {
|
||||
files, err = ioutil.ReadDir(".")
|
||||
}
|
||||
|
||||
var suggestions []string
|
||||
if err != nil {
|
||||
return "", suggestions
|
||||
@@ -35,7 +41,7 @@ func FileComplete(input string) (string, []string) {
|
||||
for _, f := range files {
|
||||
name := f.Name()
|
||||
if f.IsDir() {
|
||||
name += "/"
|
||||
name += sep
|
||||
}
|
||||
if strings.HasPrefix(name, dirs[len(dirs)-1]) {
|
||||
suggestions = append(suggestions, name)
|
||||
@@ -45,13 +51,13 @@ func FileComplete(input string) (string, []string) {
|
||||
var chosen string
|
||||
if len(suggestions) == 1 {
|
||||
if len(dirs) > 1 {
|
||||
chosen = strings.Join(dirs[:len(dirs)-1], "/") + "/" + suggestions[0]
|
||||
chosen = strings.Join(dirs[:len(dirs)-1], sep) + sep + suggestions[0]
|
||||
} else {
|
||||
chosen = suggestions[0]
|
||||
}
|
||||
} else {
|
||||
if len(dirs) > 1 {
|
||||
chosen = strings.Join(dirs[:len(dirs)-1], "/") + "/"
|
||||
chosen = strings.Join(dirs[:len(dirs)-1], sep) + sep
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,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)
|
||||
}
|
||||
}
|
||||
@@ -122,3 +128,50 @@ func OptionComplete(input string) (string, []string) {
|
||||
}
|
||||
return chosen, suggestions
|
||||
}
|
||||
|
||||
// MakeCompletion registeres a function from a plugin for autocomplete commands
|
||||
func MakeCompletion(function string) Completion {
|
||||
pluginCompletions = append(pluginCompletions, LuaFunctionComplete(function))
|
||||
return Completion(-len(pluginCompletions))
|
||||
}
|
||||
|
||||
// PluginComplete autocompletes from plugin function
|
||||
func PluginComplete(complete Completion, input string) (chosen string, suggestions []string) {
|
||||
idx := int(-complete) - 1
|
||||
|
||||
if len(pluginCompletions) <= idx {
|
||||
return "", nil
|
||||
}
|
||||
suggestions = pluginCompletions[idx](input)
|
||||
|
||||
if len(suggestions) == 1 {
|
||||
chosen = suggestions[0]
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/zyedidia/json5/encoding/json5"
|
||||
"github.com/zyedidia/tcell"
|
||||
)
|
||||
|
||||
@@ -55,6 +55,7 @@ var bindingActions = map[string]func(*View, bool) bool{
|
||||
"IndentSelection": (*View).IndentSelection,
|
||||
"OutdentSelection": (*View).OutdentSelection,
|
||||
"Paste": (*View).Paste,
|
||||
"PastePrimary": (*View).PastePrimary,
|
||||
"SelectAll": (*View).SelectAll,
|
||||
"OpenFile": (*View).OpenFile,
|
||||
"Start": (*View).Start,
|
||||
@@ -72,11 +73,17 @@ var bindingActions = map[string]func(*View, bool) bool{
|
||||
"ShellMode": (*View).ShellMode,
|
||||
"CommandMode": (*View).CommandMode,
|
||||
"Quit": (*View).Quit,
|
||||
"QuitAll": (*View).QuitAll,
|
||||
"AddTab": (*View).AddTab,
|
||||
"PreviousTab": (*View).PreviousTab,
|
||||
"NextTab": (*View).NextTab,
|
||||
"NextSplit": (*View).NextSplit,
|
||||
"PreviousSplit": (*View).PreviousSplit,
|
||||
"Unsplit": (*View).Unsplit,
|
||||
"VSplit": (*View).VSplitBinding,
|
||||
"HSplit": (*View).HSplitBinding,
|
||||
"ToggleMacro": (*View).ToggleMacro,
|
||||
"PlayMacro": (*View).PlayMacro,
|
||||
|
||||
// This was changed to InsertNewline but I don't want to break backwards compatibility
|
||||
"InsertEnter": (*View).InsertNewline,
|
||||
@@ -201,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,
|
||||
@@ -235,7 +241,7 @@ func InitBindings() {
|
||||
return
|
||||
}
|
||||
|
||||
err = json.Unmarshal(input, &parsed)
|
||||
err = json5.Unmarshal(input, &parsed)
|
||||
if err != nil {
|
||||
TermMessage("Error reading bindings.json:", err.Error())
|
||||
}
|
||||
@@ -263,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"):
|
||||
@@ -369,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",
|
||||
@@ -393,17 +400,20 @@ func DefaultBindings() map[string]string {
|
||||
"CtrlBackslash": "NextTab",
|
||||
"Home": "StartOfLine",
|
||||
"End": "EndOfLine",
|
||||
"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",
|
||||
@@ -412,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",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@ import (
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
)
|
||||
@@ -53,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)
|
||||
|
||||
@@ -85,10 +97,36 @@ func NewBuffer(txt []byte, path string) *Buffer {
|
||||
}
|
||||
|
||||
// Put the cursor at the first spot
|
||||
cursorStartX := 0
|
||||
cursorStartY := 0
|
||||
// If -startpos LINE,COL was passed, use start position LINE,COL
|
||||
if len(*flagStartPos) > 0 {
|
||||
positions := strings.Split(*flagStartPos, ",")
|
||||
if len(positions) == 2 {
|
||||
lineNum, errPos1 := strconv.Atoi(positions[0])
|
||||
colNum, errPos2 := strconv.Atoi(positions[1])
|
||||
if errPos1 == nil && errPos2 == nil {
|
||||
cursorStartX = colNum
|
||||
cursorStartY = lineNum - 1
|
||||
// Check to avoid line overflow
|
||||
if cursorStartY > b.NumLines {
|
||||
cursorStartY = b.NumLines - 1
|
||||
} else if cursorStartY < 0 {
|
||||
cursorStartY = 0
|
||||
}
|
||||
// Check to avoid column overflow
|
||||
if cursorStartX > len(b.Line(cursorStartY)) {
|
||||
cursorStartX = len(b.Line(cursorStartY))
|
||||
} else if cursorStartX < 0 {
|
||||
cursorStartX = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
b.Cursor = Cursor{
|
||||
Loc: Loc{
|
||||
X: 0,
|
||||
Y: 0,
|
||||
X: cursorStartX,
|
||||
Y: cursorStartY,
|
||||
},
|
||||
buf: b,
|
||||
}
|
||||
@@ -219,6 +257,7 @@ func (b *Buffer) Serialize() error {
|
||||
|
||||
// SaveAs saves the buffer to a specified path (filename), creating the file if it does not exist
|
||||
func (b *Buffer) SaveAs(filename string) error {
|
||||
b.FindFileType()
|
||||
b.UpdateRules()
|
||||
b.Name = filename
|
||||
b.Path = filename
|
||||
@@ -235,6 +274,7 @@ func (b *Buffer) SaveAs(filename string) error {
|
||||
// SaveAsWithSudo is the same as SaveAs except it uses a neat trick
|
||||
// with tee to use sudo so the user doesn't have to reopen micro with sudo
|
||||
func (b *Buffer) SaveAsWithSudo(filename string) error {
|
||||
b.FindFileType()
|
||||
b.UpdateRules()
|
||||
b.Name = filename
|
||||
b.Path = filename
|
||||
|
||||
@@ -2,7 +2,6 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -16,40 +15,52 @@ 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"}
|
||||
// ColorschemeExists checks if a given colorscheme exists
|
||||
func ColorschemeExists(colorschemeName string) bool {
|
||||
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)
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -77,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)
|
||||
}
|
||||
@@ -90,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]
|
||||
@@ -101,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(strings.Join(args, " "), false)
|
||||
HandleShellCommand(JoinCommandArgs(args...), false, true)
|
||||
}
|
||||
|
||||
// Quit closes the main view
|
||||
@@ -236,46 +342,30 @@ 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
|
||||
func Replace(args []string) {
|
||||
// This is a regex to parse the replace expression
|
||||
// We allow no quotes if there are no spaces, but if you want to search
|
||||
// for or replace an expression with spaces, you can add double quotes
|
||||
r := regexp.MustCompile(`"[^"\\]*(?:\\.[^"\\]*)*"|[^\s]*`)
|
||||
replaceCmd := r.FindAllString(strings.Join(args, " "), -1)
|
||||
if len(replaceCmd) < 2 {
|
||||
if len(args) < 2 {
|
||||
// We need to find both a search and replace expression
|
||||
messenger.Error("Invalid replace statement: " + strings.Join(args, " "))
|
||||
return
|
||||
}
|
||||
|
||||
var flags string
|
||||
if len(replaceCmd) == 3 {
|
||||
if len(args) == 3 {
|
||||
// The user included some flags
|
||||
flags = replaceCmd[2]
|
||||
flags = args[2]
|
||||
}
|
||||
|
||||
search := string(replaceCmd[0])
|
||||
replace := string(replaceCmd[1])
|
||||
|
||||
// If the search and replace expressions have quotes, we need to remove those
|
||||
if strings.HasPrefix(search, `"`) && strings.HasSuffix(search, `"`) {
|
||||
search = search[1 : len(search)-1]
|
||||
}
|
||||
if strings.HasPrefix(replace, `"`) && strings.HasSuffix(replace, `"`) {
|
||||
replace = replace[1 : len(replace)-1]
|
||||
}
|
||||
|
||||
// We replace all escaped double quotes to real double quotes
|
||||
search = strings.Replace(search, `\"`, `"`, -1)
|
||||
replace = strings.Replace(replace, `\"`, `"`, -1)
|
||||
// Replace some things so users can actually insert newlines and tabs in replacements
|
||||
replace = strings.Replace(replace, "\\n", "\n", -1)
|
||||
replace = strings.Replace(replace, "\\t", "\t", -1)
|
||||
search := string(args[0])
|
||||
replace := string(args[1])
|
||||
|
||||
regex, err := regexp.Compile(search)
|
||||
if err != nil {
|
||||
@@ -287,15 +377,13 @@ func Replace(args []string) {
|
||||
view := CurView()
|
||||
|
||||
found := 0
|
||||
for {
|
||||
match := regex.FindStringIndex(view.Buf.String())
|
||||
if match == nil {
|
||||
break
|
||||
}
|
||||
found++
|
||||
if strings.Contains(flags, "c") {
|
||||
if strings.Contains(flags, "c") {
|
||||
for {
|
||||
// The 'check' flag was used
|
||||
Search(search, view, true)
|
||||
if !view.Cursor.HasSelection() {
|
||||
break
|
||||
}
|
||||
view.Relocate()
|
||||
if view.Buf.Settings["syntax"].(bool) {
|
||||
view.matches = Match(view)
|
||||
@@ -308,13 +396,14 @@ func Replace(args []string) {
|
||||
view.Cursor.ResetSelection()
|
||||
}
|
||||
messenger.Reset()
|
||||
return
|
||||
break
|
||||
}
|
||||
if choice {
|
||||
view.Cursor.DeleteSelection()
|
||||
view.Buf.Insert(FromCharPos(match[0], view.Buf), replace)
|
||||
view.Buf.Insert(view.Cursor.Loc, replace)
|
||||
view.Cursor.ResetSelection()
|
||||
messenger.Reset()
|
||||
found++
|
||||
} else {
|
||||
if view.Cursor.HasSelection() {
|
||||
searchStart = ToCharPos(view.Cursor.CurSelection[1], view.Buf)
|
||||
@@ -323,8 +412,27 @@ func Replace(args []string) {
|
||||
}
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
view.Buf.Replace(FromCharPos(match[0], view.Buf), FromCharPos(match[1], view.Buf), replace)
|
||||
}
|
||||
} else {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
view.Cursor.Relocate()
|
||||
@@ -340,8 +448,8 @@ func Replace(args []string) {
|
||||
|
||||
// RunShellCommand executes a shell command and returns the output/error
|
||||
func RunShellCommand(input string) (string, error) {
|
||||
inputCmd := strings.Split(input, " ")[0]
|
||||
args := strings.Split(input, " ")[1:]
|
||||
inputCmd := SplitCommandArgs(input)[0]
|
||||
args := SplitCommandArgs(input)[1:]
|
||||
|
||||
cmd := exec.Command(inputCmd, args...)
|
||||
outputBytes := &bytes.Buffer{}
|
||||
@@ -356,8 +464,8 @@ 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) {
|
||||
inputCmd := strings.Split(input, " ")[0]
|
||||
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
|
||||
messenger.Message("Running...")
|
||||
@@ -382,12 +490,13 @@ func HandleShellCommand(input string, openTerm bool) {
|
||||
screen.Fini()
|
||||
screen = nil
|
||||
|
||||
args := strings.Split(input, " ")[1:]
|
||||
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
|
||||
@@ -400,26 +509,35 @@ 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
|
||||
func HandleCommand(input string) {
|
||||
inputCmd := strings.Split(input, " ")[0]
|
||||
args := strings.Split(input, " ")[1:]
|
||||
args := SplitCommandArgs(input)
|
||||
inputCmd := args[0]
|
||||
|
||||
if _, ok := commands[inputCmd]; !ok {
|
||||
messenger.Error("Unknown command ", inputCmd)
|
||||
} else {
|
||||
commands[inputCmd].action(args)
|
||||
commands[inputCmd].action(args[1:])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package main
|
||||
|
||||
import "github.com/zyedidia/clipboard"
|
||||
|
||||
// The Cursor struct stores the location of the cursor in the view
|
||||
// The complicated part about the cursor is storing its location.
|
||||
// The cursor must be displayed at an x, y location, but since the buffer
|
||||
@@ -33,6 +35,24 @@ func (c *Cursor) ResetSelection() {
|
||||
c.CurSelection[1] = c.buf.Start()
|
||||
}
|
||||
|
||||
// SetSelectionStart sets the start of the selection
|
||||
func (c *Cursor) SetSelectionStart(pos Loc) {
|
||||
c.CurSelection[0] = pos
|
||||
// Copy to primary clipboard for linux
|
||||
if c.HasSelection() {
|
||||
clipboard.WriteAll(c.GetSelection(), "primary")
|
||||
}
|
||||
}
|
||||
|
||||
// SetSelectionEnd sets the end of the selection
|
||||
func (c *Cursor) SetSelectionEnd(pos Loc) {
|
||||
c.CurSelection[1] = pos
|
||||
// Copy to primary clipboard for linux
|
||||
if c.HasSelection() {
|
||||
clipboard.WriteAll(c.GetSelection(), "primary")
|
||||
}
|
||||
}
|
||||
|
||||
// HasSelection returns whether or not the user has selected anything
|
||||
func (c *Cursor) HasSelection() bool {
|
||||
return c.CurSelection[0] != c.CurSelection[1]
|
||||
@@ -43,7 +63,7 @@ func (c *Cursor) DeleteSelection() {
|
||||
if c.CurSelection[0].GreaterThan(c.CurSelection[1]) {
|
||||
c.buf.Remove(c.CurSelection[1], c.CurSelection[0])
|
||||
c.Loc = c.CurSelection[1]
|
||||
} else if c.GetSelection() == "" {
|
||||
} else if !c.HasSelection() {
|
||||
return
|
||||
} else {
|
||||
c.buf.Remove(c.CurSelection[0], c.CurSelection[1])
|
||||
@@ -62,12 +82,12 @@ func (c *Cursor) GetSelection() string {
|
||||
// SelectLine selects the current line
|
||||
func (c *Cursor) SelectLine() {
|
||||
c.Start()
|
||||
c.CurSelection[0] = c.Loc
|
||||
c.SetSelectionStart(c.Loc)
|
||||
c.End()
|
||||
if c.buf.NumLines-1 > c.Y {
|
||||
c.CurSelection[1] = c.Loc.Move(1, c.buf)
|
||||
c.SetSelectionEnd(c.Loc.Move(1, c.buf))
|
||||
} else {
|
||||
c.CurSelection[1] = c.Loc
|
||||
c.SetSelectionEnd(c.Loc)
|
||||
}
|
||||
|
||||
c.OrigSelection = c.CurSelection
|
||||
@@ -77,13 +97,13 @@ func (c *Cursor) SelectLine() {
|
||||
func (c *Cursor) AddLineToSelection() {
|
||||
if c.Loc.LessThan(c.OrigSelection[0]) {
|
||||
c.Start()
|
||||
c.CurSelection[0] = c.Loc
|
||||
c.CurSelection[1] = c.OrigSelection[1]
|
||||
c.SetSelectionStart(c.Loc)
|
||||
c.SetSelectionEnd(c.OrigSelection[1])
|
||||
}
|
||||
if c.Loc.GreaterThan(c.OrigSelection[1]) {
|
||||
c.End()
|
||||
c.CurSelection[1] = c.Loc.Move(1, c.buf)
|
||||
c.CurSelection[0] = c.OrigSelection[0]
|
||||
c.SetSelectionEnd(c.Loc.Move(1, c.buf))
|
||||
c.SetSelectionStart(c.OrigSelection[0])
|
||||
}
|
||||
|
||||
if c.Loc.LessThan(c.OrigSelection[1]) && c.Loc.GreaterThan(c.OrigSelection[0]) {
|
||||
@@ -98,8 +118,8 @@ func (c *Cursor) SelectWord() {
|
||||
}
|
||||
|
||||
if !IsWordChar(string(c.RuneUnder(c.X))) {
|
||||
c.CurSelection[0] = c.Loc
|
||||
c.CurSelection[1] = c.Loc.Move(1, c.buf)
|
||||
c.SetSelectionStart(c.Loc)
|
||||
c.SetSelectionEnd(c.Loc.Move(1, c.buf))
|
||||
c.OrigSelection = c.CurSelection
|
||||
return
|
||||
}
|
||||
@@ -110,14 +130,14 @@ func (c *Cursor) SelectWord() {
|
||||
backward--
|
||||
}
|
||||
|
||||
c.CurSelection[0] = Loc{backward, c.Y}
|
||||
c.SetSelectionStart(Loc{backward, c.Y})
|
||||
c.OrigSelection[0] = c.CurSelection[0]
|
||||
|
||||
for forward < Count(c.buf.Line(c.Y))-1 && IsWordChar(string(c.RuneUnder(forward+1))) {
|
||||
forward++
|
||||
}
|
||||
|
||||
c.CurSelection[1] = Loc{forward, c.Y}.Move(1, c.buf)
|
||||
c.SetSelectionEnd(Loc{forward, c.Y}.Move(1, c.buf))
|
||||
c.OrigSelection[1] = c.CurSelection[1]
|
||||
c.Loc = c.CurSelection[1]
|
||||
}
|
||||
@@ -136,8 +156,8 @@ func (c *Cursor) AddWordToSelection() {
|
||||
backward--
|
||||
}
|
||||
|
||||
c.CurSelection[0] = Loc{backward, c.Y}
|
||||
c.CurSelection[1] = c.OrigSelection[1]
|
||||
c.SetSelectionStart(Loc{backward, c.Y})
|
||||
c.SetSelectionEnd(c.OrigSelection[1])
|
||||
}
|
||||
|
||||
if c.Loc.GreaterThan(c.OrigSelection[1]) {
|
||||
@@ -147,8 +167,8 @@ func (c *Cursor) AddWordToSelection() {
|
||||
forward++
|
||||
}
|
||||
|
||||
c.CurSelection[1] = Loc{forward, c.Y}.Move(1, c.buf)
|
||||
c.CurSelection[0] = c.OrigSelection[0]
|
||||
c.SetSelectionEnd(Loc{forward, c.Y}.Move(1, c.buf))
|
||||
c.SetSelectionStart(c.OrigSelection[0])
|
||||
}
|
||||
|
||||
c.Loc = c.CurSelection[1]
|
||||
@@ -157,11 +177,11 @@ func (c *Cursor) AddWordToSelection() {
|
||||
// SelectTo selects from the current cursor location to the given location
|
||||
func (c *Cursor) SelectTo(loc Loc) {
|
||||
if loc.GreaterThan(c.OrigSelection[0]) {
|
||||
c.CurSelection[0] = c.OrigSelection[0]
|
||||
c.CurSelection[1] = loc
|
||||
c.SetSelectionStart(c.OrigSelection[0])
|
||||
c.SetSelectionEnd(loc)
|
||||
} else {
|
||||
c.CurSelection[0] = loc
|
||||
c.CurSelection[1] = c.OrigSelection[0]
|
||||
c.SetSelectionStart(loc)
|
||||
c.SetSelectionEnd(c.OrigSelection[0])
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,147 +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",
|
||||
"cmake",
|
||||
"coffeescript",
|
||||
"colortest",
|
||||
"conf",
|
||||
"conky",
|
||||
"csharp",
|
||||
"css",
|
||||
"cython",
|
||||
"d",
|
||||
"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",
|
||||
"nanorc",
|
||||
"nginx",
|
||||
"ocaml",
|
||||
"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())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/zyedidia/clipboard"
|
||||
"github.com/zyedidia/tcell"
|
||||
@@ -22,6 +21,7 @@ func TermMessage(msg ...interface{}) {
|
||||
screenWasNil := screen == nil
|
||||
if !screenWasNil {
|
||||
screen.Fini()
|
||||
screen = nil
|
||||
}
|
||||
|
||||
fmt.Println(msg...)
|
||||
@@ -44,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
|
||||
@@ -68,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
|
||||
}
|
||||
|
||||
@@ -93,11 +108,13 @@ func (m *Messenger) Error(msg ...interface{}) {
|
||||
if _, ok := colorscheme["error-message"]; ok {
|
||||
m.style = colorscheme["error-message"]
|
||||
}
|
||||
m.AddLog(m.message)
|
||||
m.hasMessage = true
|
||||
}
|
||||
|
||||
// YesNoPrompt asks the user a yes or no question (waits for y or n) and returns the result
|
||||
func (m *Messenger) YesNoPrompt(prompt string) (bool, bool) {
|
||||
m.hasPrompt = true
|
||||
m.Message(prompt)
|
||||
|
||||
_, h := screen.Size()
|
||||
@@ -113,11 +130,17 @@ 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
|
||||
}
|
||||
}
|
||||
@@ -126,6 +149,7 @@ func (m *Messenger) YesNoPrompt(prompt string) (bool, bool) {
|
||||
|
||||
// LetterPrompt gives the user a prompt and waits for a one letter response
|
||||
func (m *Messenger) LetterPrompt(prompt string, responses ...rune) (rune, bool) {
|
||||
m.hasPrompt = true
|
||||
m.Message(prompt)
|
||||
|
||||
_, h := screen.Size()
|
||||
@@ -142,11 +166,18 @@ 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
|
||||
return r, false
|
||||
}
|
||||
}
|
||||
case tcell.KeyCtrlC, tcell.KeyCtrlQ, tcell.KeyEscape:
|
||||
m.AddLog("\t--> (cancel)")
|
||||
m.Clear()
|
||||
m.Reset()
|
||||
m.hasPrompt = false
|
||||
return ' ', true
|
||||
}
|
||||
}
|
||||
@@ -161,6 +192,8 @@ const (
|
||||
CommandCompletion
|
||||
HelpCompletion
|
||||
OptionCompletion
|
||||
PluginCmdCompletion
|
||||
PluginNameCompletion
|
||||
)
|
||||
|
||||
// Prompt sends the user a message and waits for a response to be typed in
|
||||
@@ -189,14 +222,16 @@ 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
|
||||
case tcell.KeyTab:
|
||||
args := strings.Split(m.response, " ")
|
||||
args := SplitCommandArgs(m.response)
|
||||
currentArgNum := len(args) - 1
|
||||
currentArg := args[currentArgNum]
|
||||
var completionType Completion
|
||||
@@ -222,6 +257,12 @@ 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)
|
||||
}
|
||||
|
||||
if len(suggestions) > 1 {
|
||||
@@ -229,10 +270,7 @@ func (m *Messenger) Prompt(prompt, historyType string, completionTypes ...Comple
|
||||
}
|
||||
|
||||
if chosen != "" {
|
||||
if len(args) > 1 {
|
||||
chosen = " " + chosen
|
||||
}
|
||||
m.response = strings.Join(args[:len(args)-1], " ") + chosen
|
||||
m.response = JoinCommandArgs(append(args[:len(args)-1], chosen)...)
|
||||
m.cursorx = Count(m.response)
|
||||
}
|
||||
}
|
||||
@@ -240,18 +278,19 @@ func (m *Messenger) Prompt(prompt, historyType string, completionTypes ...Comple
|
||||
|
||||
m.HandleEvent(event, m.history[historyType])
|
||||
|
||||
messenger.Clear()
|
||||
m.Clear()
|
||||
for _, v := range tabs[curTab].views {
|
||||
v.Display()
|
||||
}
|
||||
DisplayTabs()
|
||||
messenger.Display()
|
||||
m.Display()
|
||||
if len(suggestions) > 1 {
|
||||
m.DisplaySuggestions(suggestions)
|
||||
}
|
||||
screen.Show()
|
||||
}
|
||||
|
||||
m.Clear()
|
||||
m.Reset()
|
||||
return response, canceled
|
||||
}
|
||||
@@ -260,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()
|
||||
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++
|
||||
@@ -300,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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -347,11 +425,14 @@ func (m *Messenger) DisplaySuggestions(suggestions []string) {
|
||||
func (m *Messenger) Display() {
|
||||
_, h := screen.Size()
|
||||
if m.hasMessage {
|
||||
runes := []rune(m.message + m.response)
|
||||
for x := 0; x < len(runes); x++ {
|
||||
screen.SetContent(x, h-1, runes[x], nil, m.style)
|
||||
if m.hasPrompt || globalSettings["infobar"].(bool) {
|
||||
runes := []rune(m.message + m.response)
|
||||
for x := 0; x < len(runes); x++ {
|
||||
screen.SetContent(x, h-1, runes[x], nil, m.style)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if m.hasPrompt {
|
||||
screen.ShowCursor(Count(m.message)+m.cursorx, h-1)
|
||||
screen.Show()
|
||||
|
||||
@@ -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,15 +63,16 @@ 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
|
||||
// based on the input stored in os.Args
|
||||
// based on the input stored in flag.Args()
|
||||
func LoadInput() []*Buffer {
|
||||
// There are a number of ways micro should start given its input
|
||||
|
||||
// 1. If it is given a files in os.Args, it should open those
|
||||
// 1. If it is given a files in flag.Args(), it should open those
|
||||
|
||||
// 2. If there is no input file and the input is not a terminal, that means
|
||||
// something is being piped in and the stdin should be opened in an
|
||||
@@ -82,11 +86,12 @@ func LoadInput() []*Buffer {
|
||||
var err error
|
||||
var buffers []*Buffer
|
||||
|
||||
if len(os.Args) > 1 {
|
||||
if len(flag.Args()) > 0 {
|
||||
// Option 1
|
||||
// We go through each file and load it
|
||||
for i := 1; i < len(os.Args); i++ {
|
||||
filename = os.Args[i]
|
||||
for i := 0; i < len(flag.Args()); i++ {
|
||||
filename = flag.Args()[i]
|
||||
|
||||
// Check that the file exists
|
||||
if _, e := os.Stat(filename); e == nil {
|
||||
// If it exists we load it into a buffer
|
||||
@@ -195,10 +200,24 @@ func RedrawAll() {
|
||||
}
|
||||
|
||||
// Passing -version as a flag will have micro print out the version number
|
||||
var flagVersion = flag.Bool("version", false, "Show the version number")
|
||||
var flagVersion = flag.Bool("version", false, "Show the version number and information")
|
||||
var flagStartPos = flag.String("startpos", "", "LINE,COL to start the cursor at when opening a buffer.")
|
||||
|
||||
func main() {
|
||||
flag.Usage = func() {
|
||||
fmt.Println("Usage: micro [OPTIONS] [FILE]...")
|
||||
fmt.Println("Micro's options can be set via command line arguments for quick adjustments. For real configuration, please use the bindings.json file (see 'help options').\n")
|
||||
flag.PrintDefaults()
|
||||
}
|
||||
|
||||
optionFlags := make(map[string]*string)
|
||||
|
||||
for k, v := range DefaultGlobalSettings() {
|
||||
optionFlags[k] = flag.String(k, "", fmt.Sprintf("The %s option. Default value: '%v'", k, v))
|
||||
}
|
||||
|
||||
flag.Parse()
|
||||
|
||||
if *flagVersion {
|
||||
// If -version was passed
|
||||
fmt.Println("Version:", Version)
|
||||
@@ -218,17 +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()
|
||||
|
||||
@@ -264,6 +281,14 @@ func main() {
|
||||
v.matches = Match(v)
|
||||
}
|
||||
}
|
||||
|
||||
t.Resize()
|
||||
}
|
||||
}
|
||||
|
||||
for k, v := range optionFlags {
|
||||
if *v != "" {
|
||||
SetOption(k, *v)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -284,19 +309,41 @@ func main() {
|
||||
L.SetGlobal("HandleCommand", luar.New(L, HandleCommand))
|
||||
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") {
|
||||
@@ -317,6 +364,15 @@ func main() {
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
for {
|
||||
time.Sleep(autosaveTime * time.Second)
|
||||
if globalSettings["autosave"].(bool) {
|
||||
autosave <- true
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
for {
|
||||
// Display everything
|
||||
RedrawAll()
|
||||
@@ -329,48 +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 != "" {
|
||||
// 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)
|
||||
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
|
||||
}
|
||||
|
||||
// We loop through each view in the current tab and make sure the current view
|
||||
// it 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -85,6 +79,30 @@ func LuaFunctionCommand(function string) func([]string) {
|
||||
}
|
||||
}
|
||||
|
||||
// LuaFunctionComplete returns a function which can be used for autocomplete in plugins
|
||||
func LuaFunctionComplete(function string) func(string) []string {
|
||||
return func(input string) (result []string) {
|
||||
|
||||
res, err := Call(function, input)
|
||||
if err != nil {
|
||||
TermMessage(err)
|
||||
}
|
||||
if tbl, ok := res.(*lua.LTable); !ok {
|
||||
TermMessage(function, "should return a table of strings")
|
||||
} else {
|
||||
for i := 1; i <= tbl.Len(); i++ {
|
||||
val := tbl.RawGetInt(i)
|
||||
if v, ok := val.(lua.LString); !ok {
|
||||
TermMessage(function, "should return a table of strings")
|
||||
} else {
|
||||
result = append(result, string(v))
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
func LuaFunctionJob(function string) func(string, ...string) {
|
||||
return func(output string, args ...string) {
|
||||
_, err := Call(function, unpack(append([]string{output}, args...))...)
|
||||
@@ -96,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
@@ -125,8 +125,8 @@ func Search(searchStr string, v *View, down bool) {
|
||||
return
|
||||
}
|
||||
|
||||
v.Cursor.CurSelection[0] = FromCharPos(charPos+runePos(match[0], str), v.Buf)
|
||||
v.Cursor.CurSelection[1] = FromCharPos(charPos+runePos(match[1], str), v.Buf)
|
||||
v.Cursor.SetSelectionStart(FromCharPos(charPos+runePos(match[0], str), v.Buf))
|
||||
v.Cursor.SetSelectionEnd(FromCharPos(charPos+runePos(match[1], str), v.Buf))
|
||||
v.Cursor.Loc = v.Cursor.CurSelection[1]
|
||||
if v.Relocate() {
|
||||
v.matches = Match(v)
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
@@ -10,11 +9,23 @@ import (
|
||||
"strings"
|
||||
|
||||
"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()
|
||||
@@ -30,7 +41,7 @@ func InitGlobalSettings() {
|
||||
return
|
||||
}
|
||||
|
||||
err = json.Unmarshal(input, &parsed)
|
||||
err = json5.Unmarshal(input, &parsed)
|
||||
if err != nil {
|
||||
TermMessage("Error reading settings.json:", err.Error())
|
||||
}
|
||||
@@ -70,7 +81,7 @@ func InitLocalSettings(buf *Buffer) {
|
||||
return
|
||||
}
|
||||
|
||||
err = json.Unmarshal(input, &parsed)
|
||||
err = json5.Unmarshal(input, &parsed)
|
||||
if err != nil {
|
||||
TermMessage("Error reading settings.json:", err.Error())
|
||||
}
|
||||
@@ -110,7 +121,7 @@ func WriteSettings(filename string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
err = json.Unmarshal(input, &parsed)
|
||||
err = json5.Unmarshal(input, &parsed)
|
||||
if err != nil {
|
||||
TermMessage("Error reading settings.json:", err.Error())
|
||||
}
|
||||
@@ -125,8 +136,8 @@ func WriteSettings(filename string) error {
|
||||
}
|
||||
}
|
||||
|
||||
txt, _ := json.MarshalIndent(parsed, "", " ")
|
||||
err = ioutil.WriteFile(filename, txt, 0644)
|
||||
txt, _ := json5.MarshalIndent(parsed, "", " ")
|
||||
err = ioutil.WriteFile(filename, append(txt, '\n'), 0644)
|
||||
}
|
||||
return err
|
||||
}
|
||||
@@ -165,10 +176,13 @@ 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,
|
||||
"indentchar": " ",
|
||||
"infobar": true,
|
||||
"ruler": true,
|
||||
"savecursor": false,
|
||||
"saveundo": false,
|
||||
@@ -178,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{},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -186,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,
|
||||
@@ -215,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 {
|
||||
@@ -244,6 +274,12 @@ func SetOption(option, value string) error {
|
||||
}
|
||||
}
|
||||
|
||||
if option == "infobar" {
|
||||
for _, tab := range tabs {
|
||||
tab.Resize()
|
||||
}
|
||||
}
|
||||
|
||||
if _, ok := CurView().Buf.Settings[option]; ok {
|
||||
for _, tab := range tabs {
|
||||
for _, view := range tab.views {
|
||||
@@ -262,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) {
|
||||
@@ -314,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
|
||||
@@ -146,9 +148,7 @@ func (s *SplitTree) ResizeSplits() {
|
||||
n.view.y = s.y + n.view.height*i
|
||||
n.view.x = s.x
|
||||
}
|
||||
// n.view.ToggleStatusLine()
|
||||
_, screenH := screen.Size()
|
||||
if n.view.Buf.Settings["statusline"].(bool) || (n.view.y+n.view.height) != screenH-1 {
|
||||
if n.view.Buf.Settings["statusline"].(bool) {
|
||||
n.view.height--
|
||||
}
|
||||
|
||||
|
||||
@@ -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 "
|
||||
}
|
||||
|
||||
|
||||
@@ -31,12 +31,20 @@ func NewTabFromView(v *View) *Tab {
|
||||
|
||||
w, h := screen.Size()
|
||||
t.tree.width = w
|
||||
t.tree.height = h - 1
|
||||
t.tree.height = h
|
||||
|
||||
if globalSettings["infobar"].(bool) {
|
||||
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
|
||||
}
|
||||
@@ -49,7 +57,12 @@ func (t *Tab) Cleanup() {
|
||||
func (t *Tab) Resize() {
|
||||
w, h := screen.Size()
|
||||
t.tree.width = w
|
||||
t.tree.height = h - 1
|
||||
t.tree.height = h
|
||||
|
||||
if globalSettings["infobar"].(bool) {
|
||||
t.tree.height--
|
||||
}
|
||||
|
||||
t.tree.ResizeSplits()
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -62,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 == '_')
|
||||
@@ -154,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
|
||||
}
|
||||
|
||||
@@ -162,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
|
||||
}
|
||||
@@ -217,3 +238,99 @@ func Abs(n int) int {
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// FuncName returns the name of a given function object
|
||||
func FuncName(i interface{}) string {
|
||||
return runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name()
|
||||
}
|
||||
|
||||
// SplitCommandArgs seperates multiple command arguments which may be quoted.
|
||||
// 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)
|
||||
escape := false
|
||||
|
||||
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 == ' ' && curQuote == nil {
|
||||
appendResult()
|
||||
} else {
|
||||
runeHandled := false
|
||||
appendRuneToBuff := func() {
|
||||
if curQuote != nil {
|
||||
curQuote.WriteRune(r)
|
||||
} else {
|
||||
curArg.WriteRune(r)
|
||||
}
|
||||
runeHandled = true
|
||||
}
|
||||
|
||||
if r == '"' && curQuote == nil {
|
||||
curQuote = new(bytes.Buffer)
|
||||
appendRuneToBuff()
|
||||
} else {
|
||||
if curQuote != nil && !escape {
|
||||
if r == '"' {
|
||||
appendRuneToBuff()
|
||||
finishQuote()
|
||||
} else if r == '\\' {
|
||||
appendRuneToBuff()
|
||||
escape = true
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
if !runeHandled {
|
||||
appendRuneToBuff()
|
||||
}
|
||||
}
|
||||
|
||||
escape = false
|
||||
}
|
||||
appendResult()
|
||||
return result
|
||||
}
|
||||
|
||||
// JoinCommandArgs joins multiple command arguments and quote the strings if needed.
|
||||
func JoinCommandArgs(args ...string) string {
|
||||
buf := new(bytes.Buffer)
|
||||
first := true
|
||||
for _, arg := range args {
|
||||
if first {
|
||||
first = false
|
||||
} else {
|
||||
buf.WriteRune(' ')
|
||||
}
|
||||
quoted := strconv.Quote(arg)
|
||||
if quoted[1:len(quoted)-1] != arg || strings.ContainsRune(arg, ' ') {
|
||||
buf.WriteString(quoted)
|
||||
} else {
|
||||
buf.WriteString(arg)
|
||||
}
|
||||
}
|
||||
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package main
|
||||
|
||||
import "testing"
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNumOccurences(t *testing.T) {
|
||||
var tests = []struct {
|
||||
@@ -47,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")
|
||||
}
|
||||
@@ -63,3 +66,91 @@ func TestIsWordChar(t *testing.T) {
|
||||
t.Errorf("IsWordChar(\n)) = true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestJoinAndSplitCommandArgs(t *testing.T) {
|
||||
tests := []struct {
|
||||
Query []string
|
||||
Wanted string
|
||||
}{
|
||||
{[]string{`test case`}, `"test case"`},
|
||||
{[]string{`quote "test"`}, `"quote \"test\""`},
|
||||
{[]string{`slash\\\ test`}, `"slash\\\\\\ test"`},
|
||||
{[]string{`path 1`, `path\" 2`}, `"path 1" "path\\\" 2"`},
|
||||
{[]string{`foo`}, `foo`},
|
||||
{[]string{`foo\"bar`}, `"foo\\\"bar"`},
|
||||
{[]string{``}, ``},
|
||||
{[]string{`"`}, `"\""`},
|
||||
{[]string{`a`, ``}, `a `},
|
||||
{[]string{``, ``, ``, ``}, ` `},
|
||||
{[]string{"\n"}, `"\n"`},
|
||||
{[]string{"foo\tbar"}, `"foo\tbar"`},
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
if result := JoinCommandArgs(test.Query...); test.Wanted != result {
|
||||
t.Errorf("JoinCommandArgs failed at Test %d\nGot: %q", i, result)
|
||||
}
|
||||
|
||||
if result := SplitCommandArgs(test.Wanted); !reflect.DeepEqual(test.Query, result) {
|
||||
t.Errorf("SplitCommandArgs failed at Test %d\nGot: `%q`", i, result)
|
||||
}
|
||||
}
|
||||
|
||||
splitTests := []struct {
|
||||
Query string
|
||||
Wanted []string
|
||||
}{
|
||||
{`"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 {
|
||||
if result := SplitCommandArgs(test.Query); !reflect.DeepEqual(test.Wanted, result) {
|
||||
t.Errorf("SplitCommandArgs failed at Split-Test %d\nGot: `%q`", i, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
@@ -84,7 +94,7 @@ type View struct {
|
||||
// NewView returns a new fullscreen view
|
||||
func NewView(buf *Buffer) *View {
|
||||
screenW, screenH := screen.Size()
|
||||
return NewViewWidthHeight(buf, screenW, screenH-1)
|
||||
return NewViewWidthHeight(buf, screenW, screenH)
|
||||
}
|
||||
|
||||
// NewViewWidthHeight returns a new view with the specified width and height
|
||||
@@ -145,6 +155,20 @@ func (v *View) ToggleTabbar() {
|
||||
}
|
||||
}
|
||||
|
||||
func (v *View) paste(clip string) {
|
||||
leadingWS := GetLeadingWhitespace(v.Buf.Line(v.Cursor.Y))
|
||||
|
||||
if v.Cursor.HasSelection() {
|
||||
v.Cursor.DeleteSelection()
|
||||
v.Cursor.ResetSelection()
|
||||
}
|
||||
clip = strings.Replace(clip, "\n", "\n"+leadingWS, -1)
|
||||
v.Buf.Insert(v.Cursor.Loc, clip)
|
||||
v.Cursor.Loc = v.Cursor.Loc.Move(Count(clip), v.Buf)
|
||||
v.freshClip = false
|
||||
messenger.Message("Pasted clipboard")
|
||||
}
|
||||
|
||||
// ScrollUp scrolls the view up n lines (if possible)
|
||||
func (v *View) ScrollUp(n int) {
|
||||
// Try to scroll by n but if it would overflow, scroll by 1
|
||||
@@ -168,16 +192,21 @@ 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(msg string, responses ...rune) bool {
|
||||
if v.Buf.IsModified {
|
||||
char, canceled := messenger.LetterPrompt("You have unsaved changes. "+msg, responses...)
|
||||
func (v *View) CanClose() bool {
|
||||
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' {
|
||||
return true
|
||||
} else if char == 's' {
|
||||
v.Save(true)
|
||||
return true
|
||||
} else if char == 'n' {
|
||||
return true
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -208,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 {
|
||||
@@ -217,7 +262,7 @@ func (v *View) CloseBuffer() {
|
||||
|
||||
// ReOpen reloads the current buffer
|
||||
func (v *View) ReOpen() {
|
||||
if v.CanClose("Continue? (y,n,s) ", 'y', 'n', 's') {
|
||||
if v.CanClose() {
|
||||
screen.Clear()
|
||||
v.Buf.ReOpen()
|
||||
v.Relocate()
|
||||
@@ -311,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()
|
||||
@@ -326,25 +398,30 @@ func (v *View) HandleEvent(event tcell.Event) {
|
||||
TermMessage(err)
|
||||
}
|
||||
}
|
||||
} 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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if recordingMacro {
|
||||
curMacro = append(curMacro, e.Rune())
|
||||
}
|
||||
}
|
||||
case *tcell.EventPaste:
|
||||
relocate = v.Paste(true)
|
||||
if !PreActionCall("Paste", v) {
|
||||
break
|
||||
}
|
||||
|
||||
leadingWS := GetLeadingWhitespace(v.Buf.Line(v.Cursor.Y))
|
||||
|
||||
if v.Cursor.HasSelection() {
|
||||
v.Cursor.DeleteSelection()
|
||||
v.Cursor.ResetSelection()
|
||||
}
|
||||
clip := e.Text()
|
||||
clip = strings.Replace(clip, "\n", "\n"+leadingWS, -1)
|
||||
v.Buf.Insert(v.Cursor.Loc, clip)
|
||||
v.Cursor.Loc = v.Cursor.Loc.Move(Count(clip), v.Buf)
|
||||
v.freshClip = false
|
||||
messenger.Message("Pasted clipboard")
|
||||
|
||||
PostActionCall("Paste", v)
|
||||
case *tcell.EventMouse:
|
||||
x, y := e.Position()
|
||||
x -= v.lineNumOffset - v.leftCol + v.x
|
||||
@@ -394,9 +471,13 @@ func (v *View) HandleEvent(event tcell.Event) {
|
||||
} else if v.doubleClick {
|
||||
v.Cursor.AddWordToSelection()
|
||||
} else {
|
||||
v.Cursor.CurSelection[1] = v.Cursor.Loc
|
||||
v.Cursor.SetSelectionEnd(v.Cursor.Loc)
|
||||
}
|
||||
}
|
||||
case tcell.Button2:
|
||||
// Middle mouse button was clicked,
|
||||
// We should paste primary
|
||||
v.PastePrimary(true)
|
||||
case tcell.ButtonNone:
|
||||
// Mouse event with no click
|
||||
if !v.mouseReleased {
|
||||
@@ -410,7 +491,7 @@ func (v *View) HandleEvent(event tcell.Event) {
|
||||
|
||||
if !v.doubleClick && !v.tripleClick {
|
||||
v.MoveToMouseClick(x, y)
|
||||
v.Cursor.CurSelection[1] = v.Cursor.Loc
|
||||
v.Cursor.SetSelectionEnd(v.Cursor.Loc)
|
||||
}
|
||||
v.mouseReleased = true
|
||||
}
|
||||
@@ -428,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
|
||||
@@ -466,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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -486,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}
|
||||
@@ -625,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
|
||||
|
||||
@@ -661,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() &&
|
||||
@@ -686,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)
|
||||
@@ -711,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
|
||||
|
||||
@@ -740,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"
|
||||
|
||||
20
runtime/colorschemes/bubblegum.micro
Normal file
20
runtime/colorschemes/bubblegum.micro
Normal file
@@ -0,0 +1,20 @@
|
||||
color-link default "241,231"
|
||||
color-link comment "246,231"
|
||||
color-link constant "130,231"
|
||||
color-link constant.string "136,231"
|
||||
color-link constant.number "131,231"
|
||||
color-link identifier "133,231"
|
||||
color-link statement "32,231"
|
||||
color-link preproc "28,231"
|
||||
color-link type "61,231"
|
||||
color-link special "167,231"
|
||||
color-link error "231, 160"
|
||||
color-link underlined "underline 241,231"
|
||||
color-link todo "246,231"
|
||||
|
||||
color-link statusline "241,254"
|
||||
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,12 +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.
|
||||
|
||||
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:
|
||||
|
||||
@@ -20,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,70 +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",
|
||||
"PageUp": "CursorPageUp",
|
||||
"PageDown": "CursorPageDown",
|
||||
"CtrlG": "ToggleHelp",
|
||||
"CtrlR": "ToggleRuler",
|
||||
"CtrlL": "JumpLine",
|
||||
"Delete": "Delete",
|
||||
"Esc": "ClearStatus",
|
||||
"CtrlB": "ShellMode",
|
||||
"CtrlQ": "Quit",
|
||||
"CtrlE": "CommandMode",
|
||||
"CtrlW": "NextSplit",
|
||||
|
||||
// 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",
|
||||
}
|
||||
```
|
||||
|
||||
@@ -139,6 +151,7 @@ Delete
|
||||
Center
|
||||
InsertTab
|
||||
Save
|
||||
SaveAs
|
||||
Find
|
||||
FindNext
|
||||
FindPrevious
|
||||
@@ -169,11 +182,17 @@ ClearStatus
|
||||
ShellMode
|
||||
CommandMode
|
||||
Quit
|
||||
QuitAll
|
||||
AddTab
|
||||
PreviousTab
|
||||
NextTab
|
||||
NextSplit
|
||||
Unsplit
|
||||
VSplit
|
||||
HSplit
|
||||
PreviousSplit
|
||||
ToggleMacro
|
||||
PlayMacro
|
||||
```
|
||||
|
||||
Here is the list of all possible keys you can bind:
|
||||
@@ -302,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`
|
||||
@@ -31,6 +36,11 @@ Here are the options that you can set:
|
||||
|
||||
default value: ` `
|
||||
|
||||
* `infobar`: enables the line at the bottom of the editor where messages are printed.
|
||||
This option is `global only`.
|
||||
|
||||
default value: `on`
|
||||
|
||||
* `filetype`: sets the filetype for the current buffer. This setting is `local only`
|
||||
|
||||
default value: this will be automatically set depending on the file you have open
|
||||
@@ -83,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`
|
||||
|
||||
|
||||
@@ -37,41 +37,54 @@ for example turning off `tabstospaces` only for Go files when they are opened.
|
||||
---
|
||||
|
||||
There are a number of functions and variables that are available to you in
|
||||
oder to access the inner workings of micro. Here is a list (the type signatures
|
||||
order to access the inner workings of micro. Here is a list (the type signatures
|
||||
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`
|
||||
|
||||
* `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`
|
||||
@@ -104,7 +117,89 @@ 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`
|
||||
|
||||
```lua
|
||||
local function StartsWith(String,Start)
|
||||
String = String:upper()
|
||||
Start = Start:upper()
|
||||
return string.sub(String,1,string.len(Start))==Start
|
||||
end
|
||||
|
||||
function complete(input)
|
||||
local allCompletions = {"Hello", "World", "Foo", "Bar"}
|
||||
local result = {}
|
||||
|
||||
for i,v in pairs(allCompletions) do
|
||||
if StartsWith(v, input) then
|
||||
table.insert(result, v)
|
||||
end
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
function foo(arg)
|
||||
messenger:Message(arg)
|
||||
end
|
||||
|
||||
MakeCommand("foo", "example.foo", MakeCompletion("example.complete"))
|
||||
```
|
||||
|
||||
# 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.
|
||||
|
||||
@@ -1,11 +1,22 @@
|
||||
# Tutorial
|
||||
|
||||
This is a brief intro to micro's configuration system that will will give some
|
||||
simple examples showing how to configure settings, rebind keys,
|
||||
This is a brief intro to micro's configuration system that will give some
|
||||
simple examples showing how to configure settings, rebind keys,
|
||||
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
|
||||
@@ -2,10 +2,14 @@ if GetOption("linter") == nil then
|
||||
AddOption("linter", true)
|
||||
end
|
||||
|
||||
MakeCommand("lint", "linter.runLinter", 0)
|
||||
MakeCommand("lint", "linter.lintCommand", 0)
|
||||
|
||||
function lintCommand()
|
||||
CurView():Save(false)
|
||||
runLinter()
|
||||
end
|
||||
|
||||
function runLinter()
|
||||
CurView():Save(false)
|
||||
local ft = CurView().Buf:FileType()
|
||||
local file = CurView().Buf.Path
|
||||
local devnull = "/dev/null"
|
||||
@@ -18,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
|
||||
@@ -47,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*$")
|
||||
|
||||
@@ -8,8 +8,6 @@ Micro syntax files are almost identical to Nano's, except for some key differenc
|
||||
|
||||
* Micro does not use `icolor`. Instead, for a case insensitive match, use the case insensitive flag (`i`) in the regular expression
|
||||
* For example, `icolor green ".*"` would become `color green (i) ".*"`
|
||||
* Micro does not support `start="..." end="..."`. Instead use the `s` flag to match newlines and put `.*?` in the middle
|
||||
* For example `color green start="hello" end="world"` would become `color green (s) "hello.*?world"`
|
||||
|
||||
# Using with colorschemes
|
||||
|
||||
@@ -29,6 +27,7 @@ Here is a list of the files that have been converted to properly use colorscheme
|
||||
* rust
|
||||
* java
|
||||
* javascript
|
||||
* pascal
|
||||
* python
|
||||
* ruby
|
||||
* sh
|
||||
|
||||
@@ -1,17 +1,93 @@
|
||||
## Here is an example for assembler.
|
||||
##
|
||||
## Made by Nickolay Ilyushin <nickolay02@inbox.ru>. Next line is from previous (first) version (no need for modifications :P)
|
||||
syntax "asm" "\.(S|s|asm)$"
|
||||
color red "\<[A-Z_]{2,}\>"
|
||||
color brightgreen "\.(data|subsection|text)"
|
||||
color green "\.(align|file|globl|global|hidden|section|size|type|weak)"
|
||||
color brightyellow "\.(ascii|asciz|byte|double|float|hword|int|long|short|single|struct|word)"
|
||||
brightred (i) "^[[:space:]]*[.0-9A-Z_]*:"
|
||||
color brightcyan "^[[:space:]]*#[[:space:]]*(define|undef|include|ifn?def|endif|elif|else|if|warning|error)"
|
||||
## Highlight strings (note: VERY resource intensive)
|
||||
color brightyellow "<[^= ]*>" ""(\\.|[^"])*""
|
||||
color brightyellow start=""(\\.|[^"])*\\[[:space:]]*$" end="^(\\.|[^"])*""
|
||||
## Highlight comments
|
||||
color brightblue "//.*"
|
||||
color brightblue start="/\*" end="\*/"
|
||||
## Highlight trailing whitespace
|
||||
color ,green "[[:space:]]+$"
|
||||
|
||||
# This file is made for NASM assembly
|
||||
|
||||
## Instructions
|
||||
# x86
|
||||
color statement "\b(?i)(mov|aaa|aad|aam|aas|adc|add|and|call|cbw|clc|cld|cli|cmc|cmp|cmpsb|cmpsw|cwd|daa|das|dec|div|esc|hlt|idiv|imul|in|inc|int|into|iret|ja|jae|jb|jbe|jc|je|jg|jge|jl|jle|jna|jnae|jnb|jnbe|jnc|jne|jng|jnge|jnl|jnle|jno|jnp|jns|jnz|jo|jp|jpe|jpo|js|jz|jcxz|jmp|lahf|lds|lea|les|lock|lodsb|lodsw|loop|loope|loopne|loopnz|loopz|movsb|movsw|mul|neg|nop|or|pop|popf|push|pushf|rcl|rcr|rep|repe|repne|repnz|repz|ret|retn|retf|rol|ror|sahf|sal|sar|sbb|scasb|scasw|shl|shr|stc|std|sti|stosb|stosw|sub|test|wait|xchg|xlat|xor)(?-i)\b"
|
||||
color statement "\b(?i)(bound|enter|ins|leave|outs|popa|pusha)(?-i)\b"
|
||||
color statement "\b(?i)(arpl|clts|lar|lgdt|lidt|lldt|lmsw|loadall|lsl|ltr|sgdt|sidt|sldt|smsw|str|verr|verw)(?-i)\b"
|
||||
color statement "\b(?i)(bsf|bsr|bt|btc|btr|bts|cdq|cmpsd|cwde|insd|iret|iretd|iretf|jecxz|lfs|lgs|lss|lodsd|loopw|loopew|loopnew|loopnzw|loopzw|loopd|looped|loopned|loopnzd|loopzd|cr|tr|dr|movsd|movsx|movzx|outsd|popad|popfd|pushad|pushfd|scasd|seta|setae|setb|setbe|setc|sete|setg|setge|setl|setle|setna|setnae|setnb|setnbe|setnc|setne|setng|setnge|setnl|setnle|setno|setnp|setns|setnz|seto|setp|setpe|setpo|sets|setz|shdl|shrd|stosd)(?-i)\b"
|
||||
color statement "\b(?i)(bswap|cmpxcgh|invd|invlpg|wbinvd|xadd)(?-i)\b"
|
||||
color statement "\b(?i)(cpuid|cmpxchg8b|rdmsr|rdtsc|wrmsr|rsm)(?-i)\b"
|
||||
color statement "\b(?i)(rdpmc)(?-i)\b"
|
||||
color statement "\b(?i)(syscall|sysret)(?-i)\b"
|
||||
color statement "\b(?i)(cmova|cmovae|cmovb|cmovbe|cmovc|cmove|cmovg|cmovge|cmovl|cmovle|cmovna|cmovnae|cmovnb|cmovnbe|cmovnc|cmovne|cmovng|cmovnge|cmovnle|cmovno|cmovpn|cmovns|cmovnz|cmovo|cmovp|cmovpe|cmovpo|cmovs|cmovz|sysenter|sysexit|ud2)(?-i)\b"
|
||||
color statement "\b(?i)(maskmovq|movntps|movntq|prefetch0|prefetch1|prefetch2|prefetchnta|sfence)(?-i)\b"
|
||||
color statement "\b(?i)(clflush|lfence|maskmovdqu|mfence|movntdq|movnti|movntpd|pause)(?-i)\b"
|
||||
color statement "\b(?i)(monitor|mwait)(?-i)\b"
|
||||
color statement "\b(?i)(cdqe|cqo|cmpsq|cmpxchg16b|iretq|jrcxz|lodsq|movsdx|popfq|pushfq|rdtscp|scasq|stosq|swapgs)(?-i)\b"
|
||||
color statement "\b(?i)(clgi|invlpga|skinit|stgi|vmload|vmmcall|vmrun|vmsave)(?-i)\b"
|
||||
color statement "\b(?i)(vmptrdl|vmptrst|vmclear|vmread|vmwrite|vmcall|vmlaunch|vmresume|vmxoff|vmxon)(?-i)\b"
|
||||
color statement "\b(?i)(lzcnt|popcnt)(?-i)\b"
|
||||
color statement "\b(?i)(bextr|blcfill|blci|blcic|blcmask|blcs|blsfill|blsic|t1mskc|tzmsk)(?-i)\b"
|
||||
|
||||
# x87
|
||||
color statement "\b(?i)(f2xm1|fabs|fadd|faddp|fbld|fbstp|fchs|fclex|fcom|fcomp|fcompp|fdecstp|fdisi|fdiv|fvidp|fdivr|fdivrp|feni|ffree|fiadd|ficom|ficomp|fidiv|fidivr|fild|fimul|fincstp|finit|fist|fistp|fisub|fisubr|fld|fld1|fldcw|fldenv|fldenvw|fldl2e|fldl2t|fldlg2|fldln2|fldpi|fldz|fmul|fmulp|fnclex|fndisi|fneni|fninit|fnop|fnsave|fnsavenew|fnstcw|fnstenv|fnstenvw|fnstsw|fpatan|fprem|fptan|frndint|frstor|frstorw|fsave|fsavew|fscale|fsqrt|fst|fstcw|fstenv|fstenvw|fstp|fstpsw|fsub|fsubp|fsubr|fsubrp|ftst|fwait|fxam|fxch|fxtract|fyl2x|fyl2xp1)(?-i)\b"
|
||||
color statement "\b(?i)(fsetpm)(?-i)\b"
|
||||
color statement "\b(?i)(fcos|fldenvd|fsaved|fstenvd|fprem1|frstord|fsin|fsincos|fstenvd|fucom|fucomp|fucompp)(?-i)\b"
|
||||
color statement "\b(?i)(fcmovb|fcmovbe|fcmove|fcmove|fcmovnb|fcmovnbe|fcmovne|fcmovnu|fcmovu)(?-i)\b"
|
||||
color statement "\b(?i)(fcomi|fcomip|fucomi|fucomip)(?-i)\b"
|
||||
color statement "\b(?i)(fxrstor|fxsave)(?-i)\b"
|
||||
color statement "\b(?i)(fisttp)(?-i)\b"
|
||||
color statement "\b(?i)(ffreep)(?-i)\b"
|
||||
|
||||
# SIMD
|
||||
color statement "\b(?i)(emms|movd|movq|packssdw|packsswb|packuswb|paddb|paddw|paddd|paddsb|paddsw|paddusb|paddusw|pand|pandn|por|pxor|pcmpeqb|pcmpeqw|pcmpeqd|pcmpgtb|pcmpgtw|pcmpgtd|pmaddwd|pmulhw|pmullw|psllw|pslld|psllq|psrad|psraw|psrlw|psrld|psrlq|psubb|psubw|psubd|psubsb|psubsw|psubusb|punpckhbw|punpckhwd|punpckhdq|punkcklbw|punpckldq|punpcklwd)(?-i)\b"
|
||||
color statement "\b(?i)(paveb|paddsiw|pmagw|pdistib|psubsiw|pmwzb|pmulhrw|pmvnzb|pmvlzb|pmvgezb|pmulhriw|pmachriw)(?-i)\b"
|
||||
color statement "\b(?i)(femms|pavgusb|pf2id|pfacc|pfadd|pfcmpeq|pfcmpge|pfcmpgt|pfmax|pfmin|pfmul|pfrcp|pfrcpit1|pfrcpit2|pfrsqit1|pfrsqrt|pfsub|pfsubr|pi2fd|pmulhrw|prefetch|prefetchw)(?-i)\b"
|
||||
color statement "\b(?i)(pf2iw|pfnacc|pfpnacc|pi2fw|pswapd)(?-i)\b"
|
||||
color statement "\b(?i)(pfrsqrtv|pfrcpv)(?-i)\b"
|
||||
color statement "\b(?i)(addps|addss|cmpps|cmpss|comiss|cvtpi2ps|cvtps2pi|cvtsi2ss|cvtss2si|cvttps2pi|cvttss2si|divps|divss|ldmxcsr|maxps|maxss|minps|minss|movaps|movhlps|movhps|movlhps|movlps|movmskps|movntps|movss|movups|mulps|mulss|rcpps|rcpss|rsqrtps|rsqrtss|shufps|sqrtps|sqrtss|stmxcsr|subps|subss|ucomiss|unpckhps|unpcklps)(?-i)\b"
|
||||
color statement "\b(?i)(andnps|andps|orps|pavgb|pavgw|pextrw|pinsrw|pmaxsw|pmaxub|pminsw|pminub|pmovmskb|pmulhuw|psadbw|pshufw|xorps)(?-i)\b"
|
||||
color statement "\b(?i)(movups|movss|movlps|movhlps|movlps|unpcklps|unpckhps|movhps|movlhps|prefetchnta|prefetch0|prefetch1|prefetch2|nop|movaps|cvtpi2ps|cvtsi2ss|cvtps2pi|cvttss2si|cvtps2pi|cvtss2si|ucomiss|comiss|sqrtps|sqrtss|rsqrtps|rsqrtss|rcpps|andps|orps|xorps|addps|addss|mulps|mulss|subps|subss|minps|minss|divps|divss|maxps|maxss|pshufw|ldmxcsr|stmxcsr|sfence|cmpps|cmpss|pinsrw|pextrw|shufps|pmovmskb|pminub|pmaxub|pavgb|pavgw|pmulhuw|movntq|pminsw|pmaxsw|psadbw|maskmovq)(?-i)\b"
|
||||
color statement "\b(?i)(addpd|addsd|addnpd|cmppd|cmpsd)(?-i)\b"
|
||||
color statement "\b(?i)(addpd|addsd|andnpd|andpd|cmppd|cmpsd|comisd|cvtdq2pd|cvtdq2ps|cvtpd2dq|cvtpd2pi|cvtpd2ps|cvtpi2pd|cvtps2dq|cvtps2pd|cvtsd2si|cvtsd2ss|cvtsi2sd|cvtss2sd|cvttpd2dq|cvttpd2pi|cvttps2dq|cvttsd2si|divpd|divsd|maxpd|maxsd|minpd|minsd|movapd|movhpd|movlpd|movmskpd|movsd|movupd|mulpd|mulsd|orpd|shufpd|sqrtpd|sqrtsd|subpd|subsd|ucomisd|unpckhpd|unpcklpd|xorpd)(?-i)\b"
|
||||
color statement "\b(?i)(movdq2q|movdqa|movdqu|movq2dq|paddq|psubq|pmuludq|pshufhw|pshuflw|pshufd|pslldq|psrldq|punpckhqdq|punpcklqdq)(?-i)\b"
|
||||
color statement "\b(?i)(addsubpd|addsubps|haddpd|haddps|hsubpd|hsubps|movddup|movshdup|movsldu)(?-i)\b"
|
||||
color statement "\b(?i)(lddqu)(?-i)\b"
|
||||
color statement "\b(?i)(psignw|psignd|psignb|pshufb|pmulhrsw|pmaddubsw|phsubw|phsubsw|phsubd|phaddw|phaddsw|phaddd|palignr|pabsw|pabsd|pabsb)(?-i)\b"
|
||||
color statement "\b(?i)(dpps|dppd|blendps|blendpd|blendvps|blendvpd|roundps|roundss|roundpd|roundsd|insertps|extractps)(?-i)\b"
|
||||
color statement "\b(?i)(mpsadbw|phminposuw|pmulld|pmuldq|pblendvb|pblendw|pminsb|pmaxsb|pminuw|pmaxuw|pminud|pmaxud|pminsd|pmaxsd|pinsrb|pinsrd/pinsrq|pextrb|pextrw|pextrd/pextrq|pmovsxbw|pmovzxbw|pmovsxbd|pmovzxbd|pmovsxbq|pmovzxbq|pmovsxwd|pmovzxwd|pmovsxwq|pmovzxwq|pmovsxdq|pmovzxdq|ptest|pcmpeqq|packusdw|movntdqa)(?-i)\b"
|
||||
color statement "\b(?i)(extrq|insertq|movntsd|movntss)(?-i)\b"
|
||||
color statement "\b(?i)(crc32|pcmpestri|pcmpestrm|pcmpistri|pcmpistrm|pcmpgtq)(?-i)\b"
|
||||
color statement "\b(?i)(vfmaddpd|vfmaddps|vfmaddsd|vfmaddss|vfmaddsubpd|vfmaddsubps|vfmsubaddpd|vfmsubaddps|vfmsubpd|vfmsubps|vfmsubsd|vfmsubss|vfnmaddpd|vfnmaddps|vfnmaddsd|vfnmaddss|vfnmsubps|vfnmsubsd|vfnmsubss)(?-i)\b"
|
||||
|
||||
# Crypto
|
||||
color statement "\b(?i)(aesenc|aesenclast|aesdec|aesdeclast|aeskeygenassist|aesimc)(?-i)\b"
|
||||
color statement "\b(?i)(sha1rnds4|sha1nexte|sha1msg1|sha1msg2|sha256rnds2|sha256msg1|sha256msg2)(?-i)\b"
|
||||
|
||||
# Undocumented
|
||||
color statement "\b(?i)(aam|aad|salc|icebp|loadall|loadalld|ud1)(?-i)\b"
|
||||
|
||||
## Registers
|
||||
color identifier "\b(?i)(al|ah|bl|bh|cl|ch|dl|dh|bpl|sil|r8b|r9b|r10b|r11b|dil|spl|r12b|r13b|r14b|r15)(?-i)\b"
|
||||
color identifier "\b(?i)(cw|sw|tw|fp_ds|fp_opc|fp_ip|fp_dp|fp_cs|cs|ss|ds|es|fs|gs|gdtr|idtr|tr|ldtr|ax|bx|cx|dx|bp|si|r8w|r9w|r10w|r11w|di|sp|r12w|r13w|r14w|r15w|ip)(?-i)\b"
|
||||
color identifier "\b(?i)(fp_dp|fp_ip|eax|ebx|ecx|edx|ebp|esi|r8d|r9d|r10d|r11d|edi|esp|r12d|r13d|r14d|r15d|eip|eflags|mxcsr)(?-i)\b"
|
||||
color identifier "\b(?i)(mm0|mm1|mm2|mm3|mm4|mm5|mm6|mm7|rax|rbx|rcx|rdx|rbp|rsi|r8|r9|r10|r11|rdi|rsp|r12|r13|r14|r15|rip|rflags|cr0|cr1|cr2|cr3|cr4|cr5|cr6|cr7|cr8|cr9|cr10|cr11|cr12|cr13|cr14|cr15|msw|dr0|dr1|dr2|dr3|r4|dr5|dr6|dr7|dr8|dr9|dr10|dr11|dr12|dr13|dr14|dr15)(?-i)\b"
|
||||
color identifier "\b(?i)(st0|st1|st2|st3|st4|st5|st6|st7)(?-i)\b"
|
||||
color identifier "\b(?i)(xmm0|xmm1|xmm2|xmm3|xmm4|xmm5|xmm6|xmm7|xmm8|xmm9|xmm10|xmm11|xmm12|xmm13|xmm14|xmm15)(?-i)\b"
|
||||
color identifier "\b(?i)(ymm0|ymm1|ymm2|ymm3|ymm4|ymm5|ymm6|ymm7|ymm8|ymm9|ymm10|ymm11|ymm12|ymm13|ymm14|ymm15)(?-i)\b"
|
||||
color identifier "\b(?i)(zmm0|zmm1|zmm2|zmm3|zmm4|zmm5|zmm6|zmm7|zmm8|zmm9|zmm10|zmm11|zmm12|zmm13|zmm14|zmm15|zmm16|zmm17|zmm18|zmm19|zmm20|zmm21|zmm22|zmm23|zmm24|zmm25|zmm26|zmm27|zmm28|zmm29|zmm30|zmm31)(?-i)\b"
|
||||
|
||||
## Constants
|
||||
# Number - it works
|
||||
color constant.number "\b(|h|A|0x)+[0-9]+(|h|A)+\b"
|
||||
color constant.number "\b0x[0-9 a-f A-F]+\b"
|
||||
|
||||
## Preprocessor (NASM)
|
||||
color preproc "%+(\+|\?|\?\?|)[a-z A-Z 0-9]+"
|
||||
color preproc "%\[[. a-z A-Z 0-9]*\]"
|
||||
|
||||
## Other
|
||||
color statement "\b(?i)(extern|global|section|segment|_start|\.text|\.data|\.bss)(?-i)\b"
|
||||
color statement "\b(?i)(db|dw|dd|dq|dt|ddq|do)(?-i)\b"
|
||||
color identifier "[a-z A-Z 0-9 _]+:"
|
||||
|
||||
# String
|
||||
color constant.string ""(\\.|[^"])*""
|
||||
color constant.string "'(\\.|[^'])*'"
|
||||
|
||||
## Comments
|
||||
color comment ";.*"
|
||||
|
||||
13
runtime/syntax/caddyfile.micro
Normal file
13
runtime/syntax/caddyfile.micro
Normal file
@@ -0,0 +1,13 @@
|
||||
syntax "caddyfile" "Caddyfile"
|
||||
|
||||
color identifier "^\s*\S+(\s|$)"
|
||||
color type "^([\w.:/-]+,? ?)+[,{]$"
|
||||
color constant.specialChar "\s{$"
|
||||
color constant.specialChar "^\s*}$"
|
||||
color constant.string start="\"" end="\""
|
||||
color preproc "\{(\w+|\$\w+|%\w+%)\}"
|
||||
color comment "#.*"
|
||||
|
||||
# extra and trailing spaces
|
||||
color ,red "([[:space:]]{2,}|\t){$"
|
||||
color ,red "[[:space:]]+$"
|
||||
@@ -3,8 +3,8 @@
|
||||
syntax "conf" "\.c[o]?nf$"
|
||||
## Possible errors and parameters
|
||||
## Strings
|
||||
white (i) ""(\\.|[^"])*""
|
||||
color constant.string (i) ""(\\.|[^"])*""
|
||||
## Comments
|
||||
brightblue (i) "^[[:space:]]*#.*$"
|
||||
cyan (i) "^[[:space:]]*##.*$"
|
||||
color comment (i) "^[[:space:]]*#.*$"
|
||||
color comment (i) "^[[:space:]]*##.*$"
|
||||
|
||||
|
||||
@@ -11,6 +11,12 @@ color special "!important"
|
||||
color identifier ":active|:focus|:hover|:link|:visited|:link|:after|:before|$"
|
||||
color special "(\{|\}|\(|\)|\;|:|\]|~|<|>|,)"
|
||||
|
||||
# SCSS Varaibles
|
||||
color identifier (i) "\$\{?[0-9A-Z_!@#$*?-]+\}?"
|
||||
|
||||
# SCSS Commands
|
||||
color statement "@import|@mixin|@extend"
|
||||
|
||||
# Strings
|
||||
color constant ""(\\.|[^"])*""
|
||||
color constant "'(\\.|[^'])*'"
|
||||
|
||||
19
runtime/syntax/dart.micro
Normal file
19
runtime/syntax/dart.micro
Normal file
@@ -0,0 +1,19 @@
|
||||
syntax "dart" "\.dart$"
|
||||
|
||||
color constant.number "\b[-+]?([1-9][0-9]*|0[0-7]*|0x[0-9a-fA-F]+)([uU][lL]?|[lL][uU]?)?\b"
|
||||
color constant.number "\b[-+]?([0-9]+\.[0-9]*|[0-9]*\.[0-9]+)([EePp][+-]?[0-9]+)?[fFlL]?"
|
||||
color constant.number "\b[-+]?([0-9]+[EePp][+-]?[0-9]+)[fFlL]?"
|
||||
color identifier "[A-Za-z_][A-Za-z0-9_]*[[:space:]]*[(]"
|
||||
color statement "\b(break|case|catch|continue|default|else|finally)\b"
|
||||
color statement "\b(for|function|get|if|in|as|is|new|return|set|switch|final|await|async|sync)\b"
|
||||
color statement "\b(switch|this|throw|try|var|void|while|with|import|library|part|const|export)\b"
|
||||
color constant "\b(true|false|null)\b"
|
||||
color type "\b(List|String)\b"
|
||||
color type "\b(int|num|double|bool)\b"
|
||||
color statement "[-+/*=<>!~%?:&|]"
|
||||
color constant "/[^*]([^/]|(\\/))*[^\\]/[gim]*"
|
||||
color constant "\\[0-7][0-7]?[0-7]?|\\x[0-9a-fA-F]+|\\[bfnrt'"\?\\]"
|
||||
color comment "(^|[[:space:]])//.*"
|
||||
color comment "/\*.+\*/"
|
||||
color todo "TODO:?"
|
||||
color constant.string ""(\\.|[^"])*"|'(\\.|[^'])*'"
|
||||
@@ -2,29 +2,27 @@
|
||||
##
|
||||
syntax "ebuild" "\.e(build|class)$"
|
||||
## All the standard portage functions
|
||||
color brightgreen "^src_(unpack|compile|install|test)" "^pkg_(config|nofetch|setup|(pre|post)(inst|rm))"
|
||||
color identifier "^src_(unpack|compile|install|test)" "^pkg_(config|nofetch|setup|(pre|post)(inst|rm))"
|
||||
## Highlight bash related syntax
|
||||
color green "\<(case|do|done|elif|else|esac|exit|fi|for|function|if|in|local|read|return|select|shift|then|time|until|while|continue|break)\>"
|
||||
color green "(\{|\}|\(|\)|\;|\]|\[|`|\\|\$|<|>|!|=|&|\|)"
|
||||
color green "-(e|d|f|r|g|u|w|x|L)\>"
|
||||
color green "-(eq|ne|gt|lt|ge|le|s|n|z)\>"
|
||||
color statement "\b(case|do|done|elif|else|esac|exit|fi|for|function|if|in|local|read|return|select|shift|then|time|until|while|continue|break)\b"
|
||||
color statement "(\{|\}|\(|\)|\;|\]|\[|`|\\|\$|<|>|!|=|&|\|)"
|
||||
color statement "-(e|d|f|r|g|u|w|x|L)\b"
|
||||
color statement "-(eq|ne|gt|lt|ge|le|s|n|z)\b"
|
||||
## Highlight variables ... official portage ones in red, all others in bright red
|
||||
color brightred "\$\{?[a-zA-Z_0-9]+\}?"
|
||||
color red "\<(ARCH|HOMEPAGE|DESCRIPTION|IUSE|SRC_URI|LICENSE|SLOT|KEYWORDS|FILESDIR|WORKDIR|(P|R)?DEPEND|PROVIDE|DISTDIR|RESTRICT|USERLAND)\>"
|
||||
color red "\<(S|D|T|PV|PF|P|PN|A)\>" "\<C(XX)?FLAGS\>" "\<LDFLAGS\>" "\<C(HOST|TARGET|BUILD)\>"
|
||||
color preproc "\$\{?[a-zA-Z_0-9]+\}?"
|
||||
color special "\b(ARCH|HOMEPAGE|DESCRIPTION|IUSE|SRC_URI|LICENSE|SLOT|KEYWORDS|FILESDIR|WORKDIR|(P|R)?DEPEND|PROVIDE|DISTDIR|RESTRICT|USERLAND)\b"
|
||||
color special "\b(S|D|T|PV|PF|P|PN|A)\b" "\bC(XX)?FLAGS\b" "\bLDFLAGS\b" "\bC(HOST|TARGET|BUILD)\b"
|
||||
## Highlight portage commands
|
||||
color magenta "\<use(_(with|enable))?\> [!a-zA-Z0-9_+ -]*" "inherit.*"
|
||||
color brightblue "\<e(begin|end|conf|install|make|warn|infon?|error|log|patch|new(group|user))\>"
|
||||
color brightblue "\<die\>" "\<use(_(with|enable))?\>" "\<inherit\>" "\<has\>" "\<(has|best)_version\>" "\<unpack\>"
|
||||
color brightblue "\<(do|new)(ins|s?bin|doc|lib(\.so|\.a)|man|info|exe|initd|confd|envd|pam|menu|icon)\>"
|
||||
color brightblue "\<do(python|sed|dir|hard|sym|html|jar|mo)\>" "\<keepdir\>"
|
||||
color brightblue "prepall(docs|info|man|strip)" "prep(info|lib|lib\.(so|a)|man|strip)"
|
||||
color brightblue "\<(doc|ins|exe)into\>" "\<f(owners|perms)\>" "\<(exe|ins|dir)opts\>"
|
||||
color identifier "\buse(_(with|enable))?\b [!a-zA-Z0-9_+ -]*" "inherit.*"
|
||||
color statement "\be(begin|end|conf|install|make|warn|infon?|error|log|patch|new(group|user))\b"
|
||||
color statement "\bdie\b" "\buse(_(with|enable))?\b" "\binherit\b" "\bhas\b" "\b(has|best)_version\b" "\bunpack\b"
|
||||
color statement "\b(do|new)(ins|s?bin|doc|lib(\.so|\.a)|man|info|exe|initd|confd|envd|pam|menu|icon)\b"
|
||||
color statement "\bdo(python|sed|dir|hard|sym|html|jar|mo)\b" "\bkeepdir\b"
|
||||
color statement "prepall(docs|info|man|strip)" "prep(info|lib|lib\.(so|a)|man|strip)"
|
||||
color statement "\b(doc|ins|exe)into\b" "\bf(owners|perms)\b" "\b(exe|ins|dir)opts\b"
|
||||
## Highlight common commands used in ebuilds
|
||||
color blue "\<make\>" "\<(cat|cd|chmod|chown|cp|echo|env|export|grep|let|ln|mkdir|mv|rm|sed|set|tar|touch|unset)\>"
|
||||
color type "\bmake\b" "\b(cat|cd|chmod|chown|cp|echo|env|export|grep|let|ln|mkdir|mv|rm|sed|set|tar|touch|unset)\b"
|
||||
## Highlight comments (doesnt work that well)
|
||||
color yellow "#.*$"
|
||||
color comment "#.*$"
|
||||
## Highlight strings (doesnt work that well)
|
||||
color brightyellow ""(\\.|[^\"])*"" "'(\\.|[^'])*'"
|
||||
## Trailing space is bad!
|
||||
color ,green "[[:space:]]+$"
|
||||
color constant.string ""(\\.|[^\"])*"" "'(\\.|[^'])*'"
|
||||
|
||||
@@ -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,11 +1,9 @@
|
||||
syntax "ini" "\.(ini|desktop|lfl|override)$" "(mimeapps\.list|pinforc|setup\.cfg)$" "weechat/.+\.conf$"
|
||||
header "^\[[A-Za-z]+\]$"
|
||||
|
||||
color brightcyan "\<(true|false)\>"
|
||||
color cyan "^[[:space:]]*[^=]*="
|
||||
color brightmagenta "^[[:space:]]*\[.*\]$"
|
||||
color red "[=;]"
|
||||
color yellow ""(\\.|[^"])*"|'(\\.|[^'])*'"
|
||||
color brightblack "(^|[[:space:]])#([^{].*)?$"
|
||||
color ,green "[[:space:]]+$"
|
||||
color ,red " + +| + +"
|
||||
color constant "\b(true|false)\b"
|
||||
color identifier "^[[:space:]]*[^=]*="
|
||||
color special "^[[:space:]]*\[.*\]$"
|
||||
color statement "[=;]"
|
||||
color comment "(^|[[:space:]])#([^{].*)?$"
|
||||
color constant.string ""(\\.|[^"])*"|'(\\.|[^'])*'"
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
syntax "json" "\.json$"
|
||||
header "^\{$"
|
||||
|
||||
color blue "\<[-]?[1-9][0-9]*([Ee][+-]?[0-9]+)?\>" "\<[-]?[0](\.[0-9]+)?\>"
|
||||
color cyan "\<null\>"
|
||||
color brightcyan "\<(true|false)\>"
|
||||
color yellow ""(\\.|[^"])*"|'(\\.|[^'])*'"
|
||||
color brightyellow "\"(\\"|[^"])*\"[[:space:]]*:" "'(\'|[^'])*'[[:space:]]*:"
|
||||
color magenta "\\u[0-9a-fA-F]{4}|\\[bfnrt'"/\\]"
|
||||
color constant.number "\b[-+]?([1-9][0-9]*|0[0-7]*|0x[0-9a-fA-F]+)([uU][lL]?|[lL][uU]?)?\b"
|
||||
color constant.number "\b[-+]?([0-9]+\.[0-9]*|[0-9]*\.[0-9]+)([EePp][+-]?[0-9]+)?[fFlL]?"
|
||||
color constant.number "\b[-+]?([0-9]+[EePp][+-]?[0-9]+)[fFlL]?"
|
||||
color constant "\b(null)\b"
|
||||
color constant "\b(true|false)\b"
|
||||
color constant.string ""(\\.|[^"])*"|'(\\.|[^'])*'"
|
||||
color statement "\"(\\"|[^"])*\"[[:space:]]*:" "'(\'|[^'])*'[[:space:]]*:"
|
||||
color constant "\\u[0-9a-fA-F]{4}|\\[bfnrt'"/\\]"
|
||||
color ,green "[[:space:]]+$"
|
||||
color ,red " + +| + +"
|
||||
|
||||
21
runtime/syntax/lfe.micro
Normal file
21
runtime/syntax/lfe.micro
Normal file
@@ -0,0 +1,21 @@
|
||||
syntax "lfe" "lfe$" "\.lfe$"
|
||||
|
||||
# Parens are everywhere!
|
||||
color statement "\("
|
||||
color statement "\)"
|
||||
|
||||
# Good overrides for LFE in particular
|
||||
color type "defun|define-syntax|define|defmacro|defmodule|export"
|
||||
|
||||
# Dirty base cases stolen from the generic lisp syntax
|
||||
color constant "\ [A-Za-z][A-Za-z0-9_-]+\ "
|
||||
color statement "\(([-+*/<>]|<=|>=)|'"
|
||||
color constant.number "\<[0-9]+\>"
|
||||
color constant.string "\"(\\.|[^"])*\""
|
||||
color special "['|`][A-Za-z][A-Za-z0-9_-]+"
|
||||
color constant.string "\\.?"
|
||||
color comment "(^|[[:space:]]);.*"
|
||||
|
||||
# Some warning colors because our indents are wrong.
|
||||
color ,green "[[:space:]]+$"
|
||||
color ,red " + +| + +"
|
||||
10
runtime/syntax/micro.micro
Normal file
10
runtime/syntax/micro.micro
Normal file
@@ -0,0 +1,10 @@
|
||||
# Micro syntax by <nickolay02@inbox.ru>
|
||||
syntax "micro" "\.(micro)$"
|
||||
|
||||
color statement "\b(syntax|color|color-link)\b"
|
||||
color statement "\b(start=|end=)\b"
|
||||
color identifier "\b(default|comment|identifier|constant|constant|string|constant|number|statement|preproc|type|special|underlined|error|todo|statusline|indent-char|line=number|gutter-error|gutter-warning|cursor-line)\b"
|
||||
color constant.number "\b(|h|A|0x)+[0-9]+(|h|A)+\b"
|
||||
color constant.number "\b0x[0-9 a-f A-F]+\b"
|
||||
color constant.string ""(\\.|[^"])*""
|
||||
color comment "#.*"
|
||||
24
runtime/syntax/pascal.micro
Normal file
24
runtime/syntax/pascal.micro
Normal file
@@ -0,0 +1,24 @@
|
||||
syntax "pascal" "\.pas$"
|
||||
|
||||
# color identifier "\b[\pL_][\pL_\pN]*\b"
|
||||
|
||||
color comment "//.*"
|
||||
color comment start="\(\*" end="\*\)"
|
||||
color comment start="({)(?:[^$])" end="}"
|
||||
|
||||
color special start="asm" end="end"
|
||||
|
||||
color type "\b(?i:(string|ansistring|widestring|shortstring|char|ansichar|widechar|boolean|byte|shortint|word|smallint|longword|cardinal|longint|integer|int64|single|currency|double|extended))\b"
|
||||
|
||||
color statement "\b(?i:(and|asm|array|begin|break|case|const|constructor|continue|destructor|div|do|downto|else|end|file|for|function|goto|if|implementation|in|inline|interface|label|mod|not|object|of|on|operator|or|packed|procedure|program|record|repeat|resourcestring|set|shl|shr|then|to|type|unit|until|uses|var|while|with|xor))\b"
|
||||
color statement "\b(?i:(as|class|dispose|except|exit|exports|finalization|finally|inherited|initialization|is|library|new|on|out|property|raise|self|threadvar|try))\b"
|
||||
color statement "\b(?i:(absolute|abstract|alias|assembler|cdecl|cppdecl|default|export|external|forward|generic|index|local|name|nostackframe|oldfpccall|override|pascal|private|protected|public|published|read|register|reintroduce|safecall|softfloat|specialize|stdcall|virtual|write))\b"
|
||||
|
||||
color constant "\b(?i:(false|true|nil))\b"
|
||||
color constant "\$[0-9A-Fa-f]+" "\b[+-]?[0-9]+([.]?[0-9]+)?(?i:e[+-]?[0-9]+)?"
|
||||
|
||||
color constant.string "'(?:[^']+|'')*'"
|
||||
|
||||
|
||||
|
||||
color preproc start="{\$" end="}"
|
||||
@@ -1,40 +1,30 @@
|
||||
## PHP Syntax Highlighting
|
||||
syntax "php" "\.php[2345s~]?$"
|
||||
color white start="<\?(php|=)?" end="\?>"
|
||||
# Functions
|
||||
color brightblue "([a-zA-Z0-9_-]*)\("
|
||||
# Constructs
|
||||
color brightblue "(class|extends|goto) ([a-zA-Z0-9_]*)"
|
||||
color green "[^a-z0-9_-]{1}(var|class|function|echo|case|break|default|exit|switch|if|else|elseif|endif|foreach|endforeach|@|while|public|private|protected|return|true|false|null|TRUE|FALSE|NULL|const|static|extends|as|array|require|include|require_once|include_once|define|do|continue|declare|goto|print|in|namespace|use)[^a-z0-9_-]{1}"
|
||||
color brightblue "[a-zA-Z0-9]+:"
|
||||
# Variables
|
||||
color white "\$[a-zA-Z_0-9$]*|[=!<>]"
|
||||
color white "\->[a-zA-Z_0-9$]*|[=!<>]"
|
||||
# Special Characters
|
||||
color yellow "[.,{}();]"
|
||||
color yellow "\["
|
||||
color yellow "\]"
|
||||
color yellow "[=][^>]"
|
||||
# Numbers
|
||||
color magenta "[+-]*([0-9]\.)*[0-9]+([eE][+-]?([0-9]\.)*[0-9])*"
|
||||
color magenta "0x[0-9a-zA-Z]*"
|
||||
# Special Variables
|
||||
color brightblue "(\$this|parent::|self::|\$this->)"
|
||||
# Bitwise Operations
|
||||
color magenta "(\;|\||\^){1}"
|
||||
# And/Or/SRO/etc
|
||||
color green "(\;\;|\|\||::|=>|->)"
|
||||
# Online Comments
|
||||
color brightyellow "(#.*|//.*)$"
|
||||
# STRINGS!
|
||||
color red "('[^']*')|(\"[^\"]*\")"
|
||||
# Inline Variables
|
||||
color white "\{\$[^}]*\}"
|
||||
# PHP Tags
|
||||
color red "(<\?(php)?|\?>)"
|
||||
# General HTML
|
||||
color red start="\?>" end="<\?(php|=)?"
|
||||
# trailing whitespace
|
||||
color ,green "[^[:space:]]{1}[[:space:]]+$"
|
||||
# multi-line comments
|
||||
color brightyellow start="/\*" end="\*/"
|
||||
color default start="<\?(php|=)?" end="\?>"
|
||||
|
||||
color special "([a-zA-Z0-9_-]+)\("
|
||||
|
||||
color identifier "(var|class|goto|extends|function|echo|case|break|default|exit|switch|foreach|endforeach|while|const|static|extends|as|require|include|require_once|include_once|define|do|continue|declare|goto|print|in|trait|interface|[E|e]xception|array|int|string|bool|iterable|void)[\s|\)]"
|
||||
|
||||
color identifier "[a-zA-Z\\]+::"
|
||||
|
||||
color identifier "new\s([a-zA-Z0-9\\]+)"
|
||||
color identifier "([A-Z][a-zA-Z0-9_]+)\s"
|
||||
color identifier "([A-Z0-9_]+)[;|\s|\)|,]"
|
||||
|
||||
color statement "(implements|abstract|global|public|instanceof|private|protected|static|if|else|elseif|endif|namespace|use|as|new|throw|catch|try|return)[\s|;]"
|
||||
|
||||
color constant "(true|false|null|TRUE|FALSE|NULL)"
|
||||
|
||||
color constant "[\s|=|>|\s|\(|/|+|-|\*|\[](\d+)"
|
||||
|
||||
color identifier "(\$this|parent|self|\$this->)"
|
||||
|
||||
color statement "(=>|===|!==|==|!=|&&|\|\||::|=|->|\!)"
|
||||
|
||||
color default "(\$[a-zA-Z0-9\-_]+)"
|
||||
color default "[\(|\)|/|+|-|\*|\[|,|;]"
|
||||
|
||||
color constant.string "('.*?'|\".*?\")"
|
||||
|
||||
color comment start="/\*" end="\*/"
|
||||
color comment "(#.*|//.*)$"
|
||||
|
||||
@@ -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,4 +1,4 @@
|
||||
syntax "shell" "\.sh$" "\.bash" "\.bashrc" "bashrc" "\.bash_aliases" "bash_aliases" "\.bash_functions" "bash_functions" "\.bash_profile" "bash_profile" "Pkgfile" "PKGBUILD" ".ebuild\$" "APKBUILD"
|
||||
syntax "shell" "\.sh$" "\.bash" "\.bashrc" "bashrc" "\.bash_aliases" "bash_aliases" "\.bash_functions" "bash_functions" "\.bash_profile" "bash_profile" "Pkgfile" "pkgmk.conf" "profile" "rc.conf" "PKGBUILD" ".ebuild\$" "APKBUILD"
|
||||
header "^#!.*/(env +)?(ba)?sh( |$)"
|
||||
|
||||
# Numbers
|
||||
|
||||
20
runtime/syntax/typescript.micro
Normal file
20
runtime/syntax/typescript.micro
Normal file
@@ -0,0 +1,20 @@
|
||||
syntax "typescript" "\.ts$"
|
||||
|
||||
color constant.number "\b[-+]?([1-9][0-9]*|0[0-7]*|0x[0-9a-fA-F]+)([uU][lL]?|[lL][uU]?)?\b"
|
||||
color constant.number "\b[-+]?([0-9]+\.[0-9]*|[0-9]*\.[0-9]+)([EePp][+-]?[0-9]+)?[fFlL]?"
|
||||
color constant.number "\b[-+]?([0-9]+[EePp][+-]?[0-9]+)[fFlL]?"
|
||||
color identifier "[A-Za-z_][A-Za-z0-9_]*[[:space:]]*[(]"
|
||||
color statement "\b(break|case|catch|continue|default|delete|do|else|finally)\b"
|
||||
color statement "\b(declare|interface|import|export|from|for|function|get|if|in|instanceof|new|return|set|switch)\b"
|
||||
color statement "\b(switch|this|throw|try|typeof|var|void|while|with|async|await)\b"
|
||||
color constant "\b(null|undefined|NaN)\b"
|
||||
color constant "\b(true|false)\b"
|
||||
color type "\b(Array|Boolean|Date|Enumerator|Error|Function|Math|string|number|boolean|any)\b"
|
||||
color type "\b(Number|Object|RegExp|String)\b"
|
||||
color statement "[-+/*=<>!~%?:&|]"
|
||||
color constant "/[^*]([^/]|(\\/))*[^\\]/[gim]*"
|
||||
color constant "\\[0-7][0-7]?[0-7]?|\\x[0-9a-fA-F]+|\\[bfnrt'"\?\\]"
|
||||
color comment "(^|[[:space:]])//.*"
|
||||
color comment "/\*.+\*/"
|
||||
color todo "TODO:?"
|
||||
color constant.string ""(\\.|[^"])*"|'(\\.|[^'])*'"
|
||||
@@ -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,13 +1,11 @@
|
||||
syntax "yaml" "\.ya?ml$"
|
||||
header "^---" "%YAML"
|
||||
header "%YAML"
|
||||
|
||||
color green "(^| )!!(binary|bool|float|int|map|null|omap|seq|set|str) "
|
||||
color brightcyan "\<(YES|yes|Y|y|ON|on|NO|no|N|n|OFF|off)\>"
|
||||
color brightcyan "\<(true|false)\>"
|
||||
color red ":[[:space:]]" "\[" "\]" ":[[:space:]]+[|>]" "^[[:space:]]*- "
|
||||
color brightyellow "[[:space:]][\*&][A-Za-z0-9]+"
|
||||
color yellow ""(\\.|[^"])*"|'(\\.|[^'])*'"
|
||||
color brightblack "(^|[[:space:]])#([^{].*)?$"
|
||||
color brightmagenta "^---" "^\.\.\." "^%YAML" "^%TAG"
|
||||
color ,green "[[:space:]]+$"
|
||||
color ,red " + +| + +"
|
||||
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"
|
||||
color constant "\b(true|false)\b"
|
||||
color statement ":[[:space:]]" "\[" "\]" ":[[:space:]]+[|>]" "^[[:space:]]*- "
|
||||
color identifier "[[:space:]][\*&][A-Za-z0-9]+"
|
||||
color constant.string ""(\\.|[^"])*"|'(\\.|[^'])*'"
|
||||
color comment "(^|[[:space:]])#([^{].*)?$"
|
||||
color special "^---" "^\.\.\." "^%YAML" "^%TAG"
|
||||
|
||||
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
|
||||
10
tools/build-date.go
Normal file
10
tools/build-date.go
Normal file
@@ -0,0 +1,10 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
fmt.Println(time.Now().Local().Format("January 02, 2006"))
|
||||
}
|
||||
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