Compare commits

..

3 Commits

Author SHA1 Message Date
Zachary Yedidia
d03a5fc998 Fix tcell import 2016-09-06 11:29:51 -04:00
Zachary Yedidia
ef4a385380 Fix variable name bug 2016-09-06 11:29:51 -04:00
Zachary Yedidia
a1fcbde863 Make database from terminfo if terminal entry is not found 2016-09-06 11:29:51 -04:00
87 changed files with 2124 additions and 4324 deletions

3
.gitignore vendored
View File

@@ -1,6 +1,5 @@
micro
./micro
!cmd/micro
binaries/
tmp.sh
test/
.idea/

View File

@@ -1,46 +1,43 @@
.PHONY: runtime
VERSION = $(shell go run tools/build-version.go)
VERSION = $(shell git describe --tags --abbrev=0)
HASH = $(shell git rev-parse --short HEAD)
DATE = $(shell go run tools/build-date.go)
ADDITIONAL_GO_LINKER_FLAGS = $(shell go run tools/info-plist.go "$(VERSION)")
GOBIN ?= $(GOPATH)/bin
# Builds micro after checking dependencies but without updating the runtime
build: deps
go build -ldflags "-s -w -X main.Version=$(VERSION) -X main.CommitHash=$(HASH) -X 'main.CompileDate=$(DATE)' $(ADDITIONAL_GO_LINKER_FLAGS)" ./cmd/micro
build: deps tcell
go build -ldflags "-X main.Version=$(VERSION) -X main.CommitHash=$(HASH) -X 'main.CompileDate=$(DATE)'" ./cmd/micro
# Builds micro after building the runtime and checking dependencies
build-all: runtime build
# Builds micro without checking for dependencies
build-quick:
go build -ldflags "-s -w -X main.Version=$(VERSION) -X main.CommitHash=$(HASH) -X 'main.CompileDate=$(DATE)' $(ADDITIONAL_GO_LINKER_FLAGS)" ./cmd/micro
go build -ldflags "-X main.Version=$(VERSION) -X main.CommitHash=$(HASH) -X 'main.CompileDate=$(DATE)'" ./cmd/micro
# Same as 'build' but installs to $GOBIN afterward
install: deps
go install -ldflags "-s -w -X main.Version=$(VERSION) -X main.CommitHash=$(HASH) -X 'main.CompileDate=$(DATE)' $(ADDITIONAL_GO_LINKER_FLAGS)" ./cmd/micro
# Same as 'build' but installs to $GOPATH/bin afterward
install: build
mv micro $(GOPATH)/bin
# Same as 'build-all' but installs to $GOBIN afterward
# Same as 'build-all' but installs to $GOPATH/bin afterward
install-all: runtime install
# Same as 'build-quick' but installs to $GOBIN afterward
install-quick:
go install -ldflags "-s -w -X main.Version=$(VERSION) -X main.CommitHash=$(HASH) -X 'main.CompileDate=$(DATE)' $(ADDITIONAL_GO_LINKER_FLAGS)" ./cmd/micro
# Same as 'build-quick' but installs to $GOPATH/bin afterward
install-quick: build-quick
mv micro $(GOPATH)/bin
# Updates tcell
tcell:
git -C $(GOPATH)/src/github.com/zyedidia/tcell pull
# Checks for dependencies
deps:
go get -d ./cmd/micro
update:
git pull
go get -u -d ./cmd/micro
# Builds the runtime
runtime:
go get -u github.com/jteeuwen/go-bindata/...
$(GOBIN)/go-bindata -nometadata -o runtime.go runtime/...
$(GOPATH)/bin/go-bindata -nometadata -o runtime.go runtime/...
mv runtime.go cmd/micro
test:

View File

@@ -17,8 +17,6 @@ Here is a picture of micro editing its source code.
To see more screenshots of micro, showcasing all of the default colorschemes, see [here](http://zbyedidia.webfactional.com/micro/screenshots.html).
You can also check out the website for Micro at https://micro-editor.github.io.
# Features
* Easy to use and to install
@@ -33,20 +31,17 @@ You can also check out the website for Micro at https://micro-editor.github.io.
* 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 [90 languages](runtime/syntax)!)
* Syntax highlighting (for over [75 languages](runtime/syntax)!)
* Colorscheme support
* By default, micro comes with 16, 256, and true color themes.
* True color support (set the `MICRO_TRUECOLOR` env variable to 1 to enable it)
* Snippets
* The snippet plugin can be installed with `> plugin install snippets`
* Copy and paste with the system clipboard
* Small and simple
* Easily configurable
* Macros
* Common editor things such as undo/redo, line numbers, Unicode support, softwrap...
* Common editor things such as undo/redo, line numbers, unicode support...
Although not yet implemented, I hope to add more features such as autocompletion ([#174](https://github.com/zyedidia/micro/issues/174)), and multiple cursors ([#5](https://github.com/zyedidia/micro/issues/5)) in the future.
@@ -54,7 +49,7 @@ Although not yet implemented, I hope to add more features such as autocompletion
To install micro, you can download a [prebuilt binary](https://github.com/zyedidia/micro/releases), or you can build it from source.
If you want more information about ways to install micro, see this [wiki page](https://github.com/zyedidia/micro/wiki/Installing-Micro).
If you want more information about ways to install micro, see this [wiki page](https://github.com/zyedidia/micro/wiki/Installing-Micro)
### Prebuilt binaries
@@ -67,39 +62,19 @@ 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`.
### Package Managers
You can install micro using Homebrew on Mac:
```
brew install micro
```
On Windows, you can install micro through Chocolatey:
```
choco install micro
```
### Building from source
If your operating system does not have a binary release, but does run Go, you can build from source.
If your operating system does not have binary, but does run Go, you can build from source.
Make sure that you have Go version 1.5 or greater (Go 1.4 will work if your version supports CGO) and that your `GOPATH` env variable is set (I recommand setting it to `~/go` if you don't have one).
Make sure that you have Go version 1.5 or greater (Go 1.4 will work if your version supports CGO).
```sh
go get -u github.com/zyedidia/micro/...
```
go get -d github.com/zyedidia/micro
cd $GOPATH/src/github.com/zyedidia/micro
make install
```
The binary will then be installed to `$GOPATH/bin` (or your `$GOBIN`).
You can install directly with `go get` (`go get -u github.com/zyedidia/micro/cmd/micro`) but this isn't recommended because it doesn't build micro with version information which is useful for the plugin manager.
### Linux clipboard support
On Linux, clipboard support requires the 'xclip' or 'xsel' commands to be installed.
On Linux, clipboard support requires 'xclip' or 'xsel' command to be installed.
For Ubuntu:
@@ -113,9 +88,9 @@ If you don't have xclip or xsel, micro will use an internal clipboard for copy a
If you open micro and it doesn't seem like syntax highlighting is working, this is probably because
you are using a terminal which does not support 256 color. Try changing the colorscheme to `simple`
by pressing CtrlE in micro and typing `set colorscheme 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
@@ -147,7 +122,7 @@ click to enable line selection.
# Documentation and Help
Micro has a built-in help system which you can access by pressing `Ctrl-E` and typing `help`. Additionally, you can
Micro has a built-in help system which you can access by pressing `CtrlE` and typing `help`. Additionally, you can
view the help files here:
* [main help](https://github.com/zyedidia/micro/tree/master/runtime/help/help.md)
@@ -164,6 +139,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](https://github.com/zyedidia/micro/issues) to report bugs, ask questions, or suggest new features.
You can use the Github issue tracker 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).

View File

@@ -1,11 +1,13 @@
package main
import (
"io/ioutil"
"os"
"strconv"
"strings"
"time"
"github.com/mitchellh/go-homedir"
"github.com/yuin/gopher-lua"
"github.com/zyedidia/clipboard"
)
@@ -13,7 +15,7 @@ import (
// PreActionCall executes the lua pre callback if possible
func PreActionCall(funcName string, view *View) bool {
executeAction := true
for pl := range loadedPlugins {
for _, pl := range loadedPlugins {
ret, err := Call(pl+".pre"+funcName, view)
if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") {
TermMessage(err)
@@ -29,7 +31,7 @@ func PreActionCall(funcName string, view *View) bool {
// PostActionCall executes the lua plugin callback if possible
func PostActionCall(funcName string, view *View) bool {
relocate := true
for pl := range loadedPlugins {
for _, pl := range loadedPlugins {
ret, err := Call(pl+".on"+funcName, view)
if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") {
TermMessage(err)
@@ -57,9 +59,9 @@ func (v *View) Center(usePlugin bool) bool {
return false
}
v.Topline = v.Cursor.Y - v.Height/2
if v.Topline+v.Height > v.Buf.NumLines {
v.Topline = v.Buf.NumLines - v.Height
v.Topline = v.Cursor.Y - v.height/2
if v.Topline+v.height > v.Buf.NumLines {
v.Topline = v.Buf.NumLines - v.height
}
if v.Topline < 0 {
v.Topline = 0
@@ -463,8 +465,7 @@ func (v *View) InsertNewline(usePlugin bool) bool {
v.Cursor.Right()
}
// Remove the whitespaces if keepautoindent setting is off
if IsSpacesOrTabs(v.Buf.Line(v.Cursor.Y - 1)) && !v.Buf.Settings["keepautoindent"].(bool) {
if IsSpacesOrTabs(v.Buf.Line(v.Cursor.Y - 1)) {
line := v.Buf.Line(v.Cursor.Y - 1)
v.Buf.Remove(Loc{0, v.Cursor.Y - 1}, Loc{Count(line), v.Cursor.Y - 1})
}
@@ -495,7 +496,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 it's a
// whitespace at the start of the line, we should delete as if its a
// tab (tabSize number of spaces)
lineStart := v.Buf.Line(v.Cursor.Y)[:v.Cursor.X]
tabSize := int(v.Buf.Settings["tabsize"].(float64))
@@ -588,17 +589,31 @@ func (v *View) IndentSelection(usePlugin bool) bool {
}
if v.Cursor.HasSelection() {
startY := v.Cursor.CurSelection[0].Y
endY := v.Cursor.CurSelection[1].Move(-1, v.Buf).Y
start := v.Cursor.CurSelection[0].Y
end := v.Cursor.CurSelection[1].Move(-1, v.Buf).Y
endX := v.Cursor.CurSelection[1].Move(-1, v.Buf).X
for y := startY; y <= endY; y++ {
tabsize := len(v.Buf.IndentString())
v.Buf.Insert(Loc{0, y}, v.Buf.IndentString())
if y == startY && v.Cursor.CurSelection[0].X > 0 {
v.Cursor.SetSelectionStart(v.Cursor.CurSelection[0].Move(tabsize, v.Buf))
}
if y == endY {
v.Cursor.SetSelectionEnd(Loc{endX + tabsize + 1, endY})
for i := start; i <= end; i++ {
if v.Buf.Settings["tabstospaces"].(bool) {
tabsize := int(v.Buf.Settings["tabsize"].(float64))
v.Buf.Insert(Loc{0, i}, Spaces(tabsize))
if i == start {
if v.Cursor.CurSelection[0].X > 0 {
v.Cursor.SetSelectionStart(v.Cursor.CurSelection[0].Move(tabsize, v.Buf))
}
}
if i == 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.SetSelectionStart(v.Cursor.CurSelection[0].Move(1, v.Buf))
}
}
if i == end {
v.Cursor.SetSelectionEnd(Loc{endX + 2, end})
}
}
}
v.Cursor.Relocate()
@@ -611,31 +626,6 @@ func (v *View) IndentSelection(usePlugin bool) bool {
return false
}
// OutdentLine moves the current line back one indentation
func (v *View) OutdentLine(usePlugin bool) bool {
if usePlugin && !PreActionCall("OutdentLine", v) {
return false
}
if v.Cursor.HasSelection() {
return false
}
for x := 0; x < len(v.Buf.IndentString()); x++ {
if len(GetLeadingWhitespace(v.Buf.Line(v.Cursor.Y))) == 0 {
break
}
v.Buf.Remove(Loc{0, v.Cursor.Y}, Loc{1, v.Cursor.Y})
v.Cursor.X -= 1
}
v.Cursor.Relocate()
if usePlugin {
return PostActionCall("OutdentLine", v)
}
return true
}
// OutdentSelection takes the current selection and moves it back one indent level
func (v *View) OutdentSelection(usePlugin bool) bool {
if usePlugin && !PreActionCall("OutdentSelection", v) {
@@ -643,20 +633,37 @@ func (v *View) OutdentSelection(usePlugin bool) bool {
}
if v.Cursor.HasSelection() {
startY := v.Cursor.CurSelection[0].Y
endY := v.Cursor.CurSelection[1].Move(-1, v.Buf).Y
start := v.Cursor.CurSelection[0].Y
end := v.Cursor.CurSelection[1].Move(-1, v.Buf).Y
endX := v.Cursor.CurSelection[1].Move(-1, v.Buf).X
for y := startY; y <= endY; y++ {
for x := 0; x < len(v.Buf.IndentString()); x++ {
if len(GetLeadingWhitespace(v.Buf.Line(y))) == 0 {
break
}
v.Buf.Remove(Loc{0, y}, Loc{1, y})
if y == startY && v.Cursor.CurSelection[0].X > 0 {
v.Cursor.SetSelectionStart(v.Cursor.CurSelection[0].Move(-1, v.Buf))
}
if y == endY {
v.Cursor.SetSelectionEnd(Loc{endX - x, endY})
for i := start; i <= end; i++ {
if len(GetLeadingWhitespace(v.Buf.Line(i))) > 0 {
if v.Buf.Settings["tabstospaces"].(bool) {
tabsize := int(v.Buf.Settings["tabsize"].(float64))
for j := 0; j < tabsize; j++ {
if len(GetLeadingWhitespace(v.Buf.Line(i))) == 0 {
break
}
v.Buf.Remove(Loc{0, i}, Loc{1, i})
if i == start {
if v.Cursor.CurSelection[0].X > 0 {
v.Cursor.SetSelectionStart(v.Cursor.CurSelection[0].Move(-1, v.Buf))
}
}
if i == 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.SetSelectionStart(v.Cursor.CurSelection[0].Move(-1, v.Buf))
}
}
if i == end {
v.Cursor.SetSelectionEnd(Loc{endX, end})
}
}
}
}
@@ -679,11 +686,18 @@ func (v *View) InsertTab(usePlugin bool) bool {
if v.Cursor.HasSelection() {
return false
}
tabBytes := len(v.Buf.IndentString())
bytesUntilIndent := tabBytes - (v.Cursor.GetVisualX() % tabBytes)
v.Buf.Insert(v.Cursor.Loc, v.Buf.IndentString()[:bytesUntilIndent])
for i := 0; i < bytesUntilIndent; i++ {
// 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()
}
} else {
v.Buf.Insert(v.Cursor.Loc, "\t")
v.Cursor.Right()
}
@@ -699,13 +713,21 @@ func (v *View) Save(usePlugin bool) bool {
return false
}
if v.Type == vtHelp {
if v.Help {
// We can't save the help text
return false
}
// If this is an empty buffer, ask for a filename
if v.Buf.Path == "" {
v.SaveAs(false)
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 {
return false
}
}
err := v.Buf.Save()
if err != nil {
@@ -734,36 +756,18 @@ 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) {
return false
}
searchStr := ""
if v.Cursor.HasSelection() {
searchStart = ToCharPos(v.Cursor.CurSelection[1], v.Buf)
searchStart = ToCharPos(v.Cursor.CurSelection[1], v.Buf)
searchStr = v.Cursor.GetSelection()
} else {
searchStart = ToCharPos(v.Cursor.Loc, v.Buf)
}
BeginSearch(searchStr)
BeginSearch()
if usePlugin {
return PostActionCall("Find", v)
@@ -779,13 +783,9 @@ func (v *View) FindNext(usePlugin bool) bool {
if v.Cursor.HasSelection() {
searchStart = ToCharPos(v.Cursor.CurSelection[1], v.Buf)
lastSearch = v.Cursor.GetSelection()
} else {
searchStart = ToCharPos(v.Cursor.Loc, v.Buf)
}
if lastSearch == "" {
return true
}
messenger.Message("Finding: " + lastSearch)
Search(lastSearch, v, true)
@@ -852,7 +852,7 @@ func (v *View) Copy(usePlugin bool) bool {
}
if v.Cursor.HasSelection() {
v.Cursor.CopySelection("clipboard")
clipboard.WriteAll(v.Cursor.GetSelection(), "clipboard")
v.freshClip = true
messenger.Message("Copied selection")
}
@@ -903,7 +903,7 @@ func (v *View) Cut(usePlugin bool) bool {
}
if v.Cursor.HasSelection() {
v.Cursor.CopySelection("clipboard")
clipboard.WriteAll(v.Cursor.GetSelection(), "clipboard")
v.Cursor.DeleteSelection()
v.Cursor.ResetSelection()
v.freshClip = true
@@ -918,20 +918,15 @@ func (v *View) Cut(usePlugin bool) bool {
return false
}
// DuplicateLine duplicates the current line or selection
// DuplicateLine duplicates the current line
func (v *View) DuplicateLine(usePlugin bool) bool {
if usePlugin && !PreActionCall("DuplicateLine", v) {
return false
}
if v.Cursor.HasSelection() {
v.Buf.Insert(v.Cursor.CurSelection[1], v.Cursor.GetSelection())
} else {
v.Cursor.End()
v.Buf.Insert(v.Cursor.Loc, "\n"+v.Buf.Line(v.Cursor.Y))
v.Cursor.Right()
}
v.Cursor.End()
v.Buf.Insert(v.Cursor.Loc, "\n"+v.Buf.Line(v.Cursor.Y))
v.Cursor.Right()
messenger.Message("Duplicated line")
if usePlugin {
@@ -960,84 +955,6 @@ func (v *View) DeleteLine(usePlugin bool) bool {
return true
}
// MoveLinesUp moves up the current line or selected lines if any
func (v *View) MoveLinesUp(usePlugin bool) bool {
if usePlugin && !PreActionCall("MoveLinesUp", v) {
return false
}
if v.Cursor.HasSelection() {
if v.Cursor.CurSelection[0].Y == 0 {
messenger.Message("Can not move further up")
return true
}
v.Buf.MoveLinesUp(
v.Cursor.CurSelection[0].Y,
v.Cursor.CurSelection[1].Y,
)
v.Cursor.UpN(1)
v.Cursor.CurSelection[0].Y -= 1
v.Cursor.CurSelection[1].Y -= 1
messenger.Message("Moved up selected line(s)")
} else {
if v.Cursor.Loc.Y == 0 {
messenger.Message("Can not move further up")
return true
}
v.Buf.MoveLinesUp(
v.Cursor.Loc.Y,
v.Cursor.Loc.Y+1,
)
v.Cursor.UpN(1)
messenger.Message("Moved up current line")
}
v.Buf.IsModified = true
if usePlugin {
return PostActionCall("MoveLinesUp", v)
}
return true
}
// MoveLinesDown moves down the current line or selected lines if any
func (v *View) MoveLinesDown(usePlugin bool) bool {
if usePlugin && !PreActionCall("MoveLinesDown", v) {
return false
}
if v.Cursor.HasSelection() {
if v.Cursor.CurSelection[1].Y >= len(v.Buf.lines) {
messenger.Message("Can not move further down")
return true
}
v.Buf.MoveLinesDown(
v.Cursor.CurSelection[0].Y,
v.Cursor.CurSelection[1].Y,
)
v.Cursor.DownN(1)
v.Cursor.CurSelection[0].Y += 1
v.Cursor.CurSelection[1].Y += 1
messenger.Message("Moved down selected line(s)")
} else {
if v.Cursor.Loc.Y >= len(v.Buf.lines)-1 {
messenger.Message("Can not move further down")
return true
}
v.Buf.MoveLinesDown(
v.Cursor.Loc.Y,
v.Cursor.Loc.Y+1,
)
v.Cursor.DownN(1)
messenger.Message("Moved down current line")
}
v.Buf.IsModified = true
if usePlugin {
return PostActionCall("MoveLinesDown", v)
}
return true
}
// Paste whatever is in the system clipboard into the buffer
// Delete and paste if the user has a selection
func (v *View) Paste(usePlugin bool) bool {
@@ -1094,13 +1011,30 @@ func (v *View) OpenFile(usePlugin bool) bool {
}
if v.CanClose() {
input, canceled := messenger.Prompt("> ", "open ", "Open", CommandCompletion)
if !canceled {
HandleCommand(input)
if usePlugin {
return PostActionCall("OpenFile", v)
}
filename, canceled := messenger.Prompt("File to open: ", "Open", FileCompletion)
if canceled {
return false
}
// the filename might or might not be quoted, so unquote first then join the strings.
filename = strings.Join(SplitCommandArgs(filename), " ")
home, _ := homedir.Dir()
filename = strings.Replace(filename, "~", home, 1)
file, err := ioutil.ReadFile(filename)
var buf *Buffer
if err != nil {
// File does not exist -- create an empty buffer with that name
buf = NewBuffer([]byte{}, filename)
} else {
buf = NewBuffer(file, filename)
}
v.OpenBuffer(buf)
if usePlugin {
return PostActionCall("OpenFile", v)
}
return true
}
return false
}
@@ -1125,10 +1059,10 @@ func (v *View) End(usePlugin bool) bool {
return false
}
if v.Height > v.Buf.NumLines {
if v.height > v.Buf.NumLines {
v.Topline = 0
} else {
v.Topline = v.Buf.NumLines - v.Height
v.Topline = v.Buf.NumLines - v.height
}
if usePlugin {
@@ -1143,8 +1077,8 @@ func (v *View) PageUp(usePlugin bool) bool {
return false
}
if v.Topline > v.Height {
v.ScrollUp(v.Height)
if v.Topline > v.height {
v.ScrollUp(v.height)
} else {
v.Topline = 0
}
@@ -1161,10 +1095,10 @@ func (v *View) PageDown(usePlugin bool) bool {
return false
}
if v.Buf.NumLines-(v.Topline+v.Height) > v.Height {
v.ScrollDown(v.Height)
} else if v.Buf.NumLines >= v.Height {
v.Topline = v.Buf.NumLines - v.Height
if v.Buf.NumLines-(v.Topline+v.height) > v.height {
v.ScrollDown(v.height)
} else if v.Buf.NumLines >= v.height {
v.Topline = v.Buf.NumLines - v.height
}
if usePlugin {
@@ -1185,7 +1119,7 @@ func (v *View) CursorPageUp(usePlugin bool) bool {
v.Cursor.Loc = v.Cursor.CurSelection[0]
v.Cursor.ResetSelection()
}
v.Cursor.UpN(v.Height)
v.Cursor.UpN(v.height)
if usePlugin {
return PostActionCall("CursorPageUp", v)
@@ -1205,7 +1139,7 @@ func (v *View) CursorPageDown(usePlugin bool) bool {
v.Cursor.Loc = v.Cursor.CurSelection[1]
v.Cursor.ResetSelection()
}
v.Cursor.DownN(v.Height)
v.Cursor.DownN(v.height)
if usePlugin {
return PostActionCall("CursorPageDown", v)
@@ -1219,8 +1153,8 @@ func (v *View) HalfPageUp(usePlugin bool) bool {
return false
}
if v.Topline > v.Height/2 {
v.ScrollUp(v.Height / 2)
if v.Topline > v.height/2 {
v.ScrollUp(v.height / 2)
} else {
v.Topline = 0
}
@@ -1237,11 +1171,11 @@ func (v *View) HalfPageDown(usePlugin bool) bool {
return false
}
if v.Buf.NumLines-(v.Topline+v.Height) > v.Height/2 {
v.ScrollDown(v.Height / 2)
if v.Buf.NumLines-(v.Topline+v.height) > v.height/2 {
v.ScrollDown(v.height / 2)
} else {
if v.Buf.NumLines >= v.Height {
v.Topline = v.Buf.NumLines - v.Height
if v.Buf.NumLines >= v.height {
v.Topline = v.Buf.NumLines - v.height
}
}
@@ -1278,7 +1212,7 @@ func (v *View) JumpLine(usePlugin bool) bool {
}
// Prompt for line number
linestring, canceled := messenger.Prompt("Jump to line # ", "", "LineNumber", NoCompletion)
linestring, canceled := messenger.Prompt("Jump to line # ", "LineNumber", NoCompletion)
if canceled {
return false
}
@@ -1322,7 +1256,7 @@ func (v *View) ToggleHelp(usePlugin bool) bool {
return false
}
if v.Type != vtHelp {
if !v.Help {
// Open the default help
v.openHelp("help")
} else {
@@ -1341,10 +1275,10 @@ func (v *View) ShellMode(usePlugin bool) bool {
return false
}
input, canceled := messenger.Prompt("$ ", "", "Shell", NoCompletion)
input, canceled := messenger.Prompt("$ ", "Shell", NoCompletion)
if !canceled {
// The true here is for openTerm to make the command interactive
HandleShellCommand(input, true, true)
HandleShellCommand(input, true)
if usePlugin {
return PostActionCall("ShellMode", v)
}
@@ -1358,7 +1292,7 @@ func (v *View) CommandMode(usePlugin bool) bool {
return false
}
input, canceled := messenger.Prompt("> ", "", "Command", CommandCompletion)
input, canceled := messenger.Prompt("> ", "Command", CommandCompletion)
if !canceled {
HandleCommand(input)
if usePlugin {
@@ -1369,21 +1303,6 @@ func (v *View) CommandMode(usePlugin bool) bool {
return false
}
// Escape leaves current mode / quits the editor
func (v *View) Escape(usePlugin bool) bool {
// check if user is searching, or the last search is still active
if searching || lastSearch != "" {
ExitSearch(v)
return true
}
// check if a prompt is shown, hide it and don't quit
if messenger.hasPrompt {
messenger.Reset() // FIXME
return true
}
return v.Quit(usePlugin)
}
// Quit quits the editor
// This behavior needs to be changed and should really only quit the editor if this
// is the last view
@@ -1470,10 +1389,10 @@ func (v *View) AddTab(usePlugin bool) bool {
return false
}
tab := NewTabFromView(NewView(NewBuffer(strings.NewReader(""), "")))
tab := NewTabFromView(NewView(NewBuffer([]byte{}, "")))
tab.SetNum(len(tabs))
tabs = append(tabs, tab)
curTab = len(tabs) - 1
curTab++
if len(tabs) == 2 {
for _, t := range tabs {
for _, v := range t.views {
@@ -1524,55 +1443,6 @@ 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(strings.NewReader(""), ""))
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(strings.NewReader(""), ""))
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) {
@@ -1580,10 +1450,10 @@ func (v *View) NextSplit(usePlugin bool) bool {
}
tab := tabs[curTab]
if tab.CurView < len(tab.views)-1 {
tab.CurView++
if tab.curView < len(tab.views)-1 {
tab.curView++
} else {
tab.CurView = 0
tab.curView = 0
}
if usePlugin {
@@ -1599,10 +1469,10 @@ func (v *View) PreviousSplit(usePlugin bool) bool {
}
tab := tabs[curTab]
if tab.CurView > 0 {
tab.CurView--
if tab.curView > 0 {
tab.curView--
} else {
tab.CurView = len(tab.views) - 1
tab.curView = len(tab.views) - 1
}
if usePlugin {
@@ -1614,7 +1484,6 @@ func (v *View) PreviousSplit(usePlugin bool) bool {
var curMacro []interface{}
var recordingMacro bool
// ToggleMacro toggles recording of a macro
func (v *View) ToggleMacro(usePlugin bool) bool {
if usePlugin && !PreActionCall("ToggleMacro", v) {
return false
@@ -1635,7 +1504,6 @@ func (v *View) ToggleMacro(usePlugin bool) bool {
return true
}
// PlayMacro plays back the most recently recorded macro
func (v *View) PlayMacro(usePlugin bool) bool {
if usePlugin && !PreActionCall("PlayMacro", v) {
return false
@@ -1652,7 +1520,7 @@ func (v *View) PlayMacro(usePlugin bool) bool {
v.Buf.Insert(v.Cursor.Loc, string(t))
v.Cursor.Right()
for pl := range loadedPlugins {
for _, pl := range loadedPlugins {
_, err := Call(pl+".onRune", string(t), v)
if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") {
TermMessage(err)

View File

@@ -18,14 +18,12 @@ var pluginCompletions []func(string) []string
func FileComplete(input string) (string, []string) {
var sep string = string(os.PathSeparator)
dirs := strings.Split(input, sep)
var files []os.FileInfo
var err error
if len(dirs) > 1 {
home, _ := homedir.Dir()
directories := strings.Join(dirs[:len(dirs)-1], sep) + sep
directories := strings.Join(dirs[:len(dirs)-1], sep)
if strings.HasPrefix(directories, "~") {
directories = strings.Replace(directories, "~", home, 1)
}
@@ -33,7 +31,6 @@ func FileComplete(input string) (string, []string) {
} else {
files, err = ioutil.ReadDir(".")
}
var suggestions []string
if err != nil {
return "", suggestions
@@ -84,9 +81,9 @@ func CommandComplete(input string) (string, []string) {
func HelpComplete(input string) (string, []string) {
var suggestions []string
for _, file := range ListRuntimeFiles(RTHelp) {
topic := file.Name()
for _, topic := range helpFiles {
if strings.HasPrefix(topic, input) {
suggestions = append(suggestions, topic)
}
}
@@ -149,29 +146,3 @@ func PluginComplete(complete Completion, input string) (chosen string, suggestio
}
return
}
func PluginCmdComplete(input string) (chosen string, suggestions []string) {
for _, cmd := range []string{"install", "remove", "search", "update", "list"} {
if strings.HasPrefix(cmd, input) {
suggestions = append(suggestions, cmd)
}
}
if len(suggestions) == 1 {
chosen = suggestions[0]
}
return chosen, suggestions
}
func PluginNameComplete(input string) (chosen string, suggestions []string) {
for _, pp := range GetAllPluginPackages() {
if strings.HasPrefix(pp.Name, input) {
suggestions = append(suggestions, pp.Name)
}
}
if len(suggestions) == 1 {
chosen = suggestions[0]
}
return chosen, suggestions
}

View File

@@ -5,7 +5,7 @@ import (
"os"
"strings"
"github.com/zyedidia/json5/encoding/json5"
"github.com/yosuke-furukawa/json5/encoding/json5"
"github.com/zyedidia/tcell"
)
@@ -41,7 +41,6 @@ var bindingActions = map[string]func(*View, bool) bool{
"Delete": (*View).Delete,
"InsertTab": (*View).InsertTab,
"Save": (*View).Save,
"SaveAs": (*View).SaveAs,
"Find": (*View).Find,
"FindNext": (*View).FindNext,
"FindPrevious": (*View).FindPrevious,
@@ -53,11 +52,8 @@ var bindingActions = map[string]func(*View, bool) bool{
"CutLine": (*View).CutLine,
"DuplicateLine": (*View).DuplicateLine,
"DeleteLine": (*View).DeleteLine,
"MoveLinesUp": (*View).MoveLinesUp,
"MoveLinesDown": (*View).MoveLinesDown,
"IndentSelection": (*View).IndentSelection,
"OutdentSelection": (*View).OutdentSelection,
"OutdentLine": (*View).OutdentLine,
"Paste": (*View).Paste,
"PastePrimary": (*View).PastePrimary,
"SelectAll": (*View).SelectAll,
@@ -76,7 +72,6 @@ var bindingActions = map[string]func(*View, bool) bool{
"ClearStatus": (*View).ClearStatus,
"ShellMode": (*View).ShellMode,
"CommandMode": (*View).CommandMode,
"Escape": (*View).Escape,
"Quit": (*View).Quit,
"QuitAll": (*View).QuitAll,
"AddTab": (*View).AddTab,
@@ -84,9 +79,6 @@ var bindingActions = map[string]func(*View, bool) bool{
"NextTab": (*View).NextTab,
"NextSplit": (*View).NextSplit,
"PreviousSplit": (*View).PreviousSplit,
"Unsplit": (*View).Unsplit,
"VSplit": (*View).VSplitBinding,
"HSplit": (*View).HSplitBinding,
"ToggleMacro": (*View).ToggleMacro,
"PlayMacro": (*View).PlayMacro,
@@ -213,11 +205,12 @@ 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,
"Backspace": tcell.KeyBackspace2,
"Backspace2": tcell.KeyBackspace2,
// I renamed these keys to PageUp and PageDown but I don't want to break someone's keybindings
"PgUp": tcell.KeyPgUp,
@@ -274,8 +267,7 @@ modSearch:
case strings.HasPrefix(k, "-"):
// We optionally support dashes between modifiers
k = k[1:]
case strings.HasPrefix(k, "Ctrl") && k != "CtrlH":
// CtrlH technically does not have a 'Ctrl' modifier because it is really backspace
case strings.HasPrefix(k, "Ctrl"):
k = k[4:]
modifiers |= tcell.ModCtrl
case strings.HasPrefix(k, "Alt"):
@@ -341,25 +333,13 @@ func findAction(v string) (action func(*View, bool) bool) {
func BindKey(k, v string) {
key, ok := findKey(k)
if !ok {
TermMessage("Unknown keybinding: " + k)
return
}
if v == "ToggleHelp" {
helpBinding = k
}
if helpBinding == k && v != "ToggleHelp" {
helpBinding = ""
}
actionNames := strings.Split(v, ",")
if actionNames[0] == "UnbindKey" {
delete(bindings, key)
if len(actionNames) == 1 {
actionNames = make([]string, 0, 0)
} else {
actionNames = append(actionNames[:0], actionNames[1:]...)
}
}
actions := make([]func(*View, bool) bool, 0, len(actionNames))
for _, actionName := range actionNames {
actions = append(actions, findAction(actionName))
@@ -381,8 +361,6 @@ func DefaultBindings() map[string]string {
"ShiftRight": "SelectRight",
"AltLeft": "WordLeft",
"AltRight": "WordRight",
"AltUp": "MoveLinesUp",
"AltDown": "MoveLinesDown",
"AltShiftRight": "SelectWordRight",
"AltShiftLeft": "SelectWordLeft",
"CtrlLeft": "StartOfLine",
@@ -394,12 +372,13 @@ func DefaultBindings() map[string]string {
"CtrlShiftUp": "SelectToStart",
"CtrlShiftDown": "SelectToEnd",
"Enter": "InsertNewline",
"CtrlH": "Backspace",
"Space": "InsertSpace",
"Backspace": "Backspace",
"Alt-CtrlH": "DeleteWordLeft",
"Backspace2": "Backspace",
"Alt-Backspace": "DeleteWordLeft",
"Alt-Backspace2": "DeleteWordLeft",
"Tab": "IndentSelection,InsertTab",
"Backtab": "OutdentSelection,OutdentLine",
"Backtab": "OutdentSelection",
"CtrlO": "OpenFile",
"CtrlS": "Save",
"CtrlF": "Find",
@@ -426,6 +405,7 @@ func DefaultBindings() map[string]string {
"CtrlR": "ToggleRuler",
"CtrlL": "JumpLine",
"Delete": "Delete",
"Esc": "ClearStatus",
"CtrlB": "ShellMode",
"CtrlQ": "Quit",
"CtrlE": "CommandMode",
@@ -440,13 +420,5 @@ 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": "Escape",
}
}

View File

@@ -3,19 +3,15 @@ package main
import (
"bytes"
"encoding/gob"
"io"
"io/ioutil"
"os"
"os/exec"
"os/signal"
"path/filepath"
"regexp"
"strconv"
"strings"
"time"
"unicode/utf8"
"github.com/mitchellh/go-homedir"
)
// Buffer stores the text for files that are loaded into the text editor
@@ -31,10 +27,8 @@ type Buffer struct {
// Path to the file on disk
Path string
// Absolute path to the file on disk
AbsPath string
// Name of the buffer on the status line
name string
Name string
// Whether or not the buffer has been modified since it was opened
IsModified bool
@@ -59,24 +53,10 @@ type SerializedBuffer struct {
ModTime time.Time
}
func NewBufferFromString(text, path string) *Buffer {
return NewBuffer(strings.NewReader(text), path)
}
// NewBuffer creates a new buffer from a given reader with a given path
func NewBuffer(reader io.Reader, path string) *Buffer {
if path != "" {
for _, tab := range tabs {
for _, view := range tab.views {
if view.Buf.Path == path {
return view.Buf
}
}
}
}
// NewBuffer creates a new buffer from `txt` with path and name `path`
func NewBuffer(txt []byte, path string) *Buffer {
b := new(Buffer)
b.LineArray = NewLineArray(reader)
b.LineArray = NewLineArray(txt)
b.Settings = DefaultLocalSettings()
for k, v := range globalSettings {
@@ -85,10 +65,13 @@ func NewBuffer(reader io.Reader, path string) *Buffer {
}
}
absPath, _ := filepath.Abs(path)
b.Path = path
b.AbsPath = absPath
b.Name = path
// If the file doesn't have a path to disk then we give it no name
if path == "" {
b.Name = "No name"
}
// The last time this file was modified
b.ModTime, _ = GetModTime(b.Path)
@@ -143,7 +126,8 @@ func NewBuffer(reader io.Reader, path string) *Buffer {
if b.Settings["savecursor"].(bool) || b.Settings["saveundo"].(bool) {
// If either savecursor or saveundo is turned on, we need to load the serialized information
// from ~/.config/micro/buffers
file, err := os.Open(configDir + "/buffers/" + EscapePath(b.AbsPath))
absPath, _ := filepath.Abs(b.Path)
file, err := os.Open(configDir + "/buffers/" + EscapePath(absPath))
if err == nil {
var buffer SerializedBuffer
decoder := gob.NewDecoder(file)
@@ -172,16 +156,6 @@ func NewBuffer(reader io.Reader, path string) *Buffer {
return b
}
func (b *Buffer) GetName() string {
if b.name == "" {
if b.Path == "" {
return "No name"
}
return b.Path
}
return b.name
}
// UpdateRules updates the syntax rules and filetype for this buffer
// This is called when the colorscheme changes
func (b *Buffer) UpdateRules() {
@@ -198,14 +172,6 @@ func (b *Buffer) FileType() string {
return b.Settings["filetype"].(string)
}
// IndentString returns a string representing one level of indentation
func (b *Buffer) IndentString() string {
if b.Settings["tabstospaces"].(bool) {
return Spaces(int(b.Settings["tabsize"].(float64)))
}
return "\t"
}
// CheckModTime makes sure that the file this buffer points to hasn't been updated
// by an external program since it was last read
// If it has, we ask the user if they would like to reload the file
@@ -262,7 +228,8 @@ func (b *Buffer) SaveWithSudo() error {
// Serialize serializes the buffer to configDir/buffers
func (b *Buffer) Serialize() error {
if b.Settings["savecursor"].(bool) || b.Settings["saveundo"].(bool) {
file, err := os.Create(configDir + "/buffers/" + EscapePath(b.AbsPath))
absPath, _ := filepath.Abs(b.Path)
file, err := os.Create(configDir + "/buffers/" + EscapePath(absPath))
if err == nil {
enc := gob.NewEncoder(file)
gob.Register(TextEvent{})
@@ -282,35 +249,15 @@ func (b *Buffer) Serialize() error {
func (b *Buffer) SaveAs(filename string) error {
b.FindFileType()
b.UpdateRules()
dir, _ := homedir.Dir()
b.Path = strings.Replace(filename, "~", dir, 1)
if b.Settings["rmtrailingws"].(bool) {
r, _ := regexp.Compile(`[ \t]+$`)
for lineNum, line := range b.Lines(0, b.NumLines) {
indices := r.FindStringIndex(line)
if indices == nil {
continue
}
startLoc := Loc{indices[0], lineNum}
b.deleteToEnd(startLoc)
}
b.Cursor.Relocate()
}
if b.Settings["eofnewline"].(bool) {
end := b.End()
if b.RuneAt(Loc{end.X - 1, end.Y}) != '\n' {
b.Insert(end, "\n")
}
}
str := b.String()
data := []byte(str)
b.Name = filename
b.Path = filename
data := []byte(b.String())
err := ioutil.WriteFile(filename, data, 0644)
if err == nil {
b.IsModified = false
b.ModTime, _ = GetModTime(filename)
return b.Serialize()
}
b.ModTime, _ = GetModTime(filename)
return err
}
@@ -319,6 +266,7 @@ func (b *Buffer) SaveAs(filename string) error {
func (b *Buffer) SaveAsWithSudo(filename string) error {
b.FindFileType()
b.UpdateRules()
b.Name = filename
b.Path = filename
// The user may have already used sudo in which case we won't need the password
@@ -375,11 +323,6 @@ func (b *Buffer) remove(start, end Loc) string {
b.Update()
return sub
}
func (b *Buffer) deleteToEnd(start Loc) {
b.IsModified = true
b.LineArray.DeleteToEnd(start)
b.Update()
}
// Start returns the location of the first character in the buffer
func (b *Buffer) Start() Loc {
@@ -391,20 +334,8 @@ func (b *Buffer) End() Loc {
return Loc{utf8.RuneCount(b.lines[b.NumLines-1]), b.NumLines - 1}
}
// RuneAt returns the rune at a given location in the buffer
func (b *Buffer) RuneAt(loc Loc) rune {
line := []rune(b.Line(loc.Y))
if len(line) > 0 {
return line[loc.X]
}
return '\n'
}
// Line returns a single line
func (b *Buffer) Line(n int) string {
if n >= len(b.lines) {
return ""
}
return string(b.lines[n])
}
@@ -422,48 +353,3 @@ func (b *Buffer) Lines(start, end int) []string {
func (b *Buffer) Len() int {
return Count(b.String())
}
// MoveLinesUp moves the range of lines up one row
func (b *Buffer) MoveLinesUp(start int, end int) {
// 0 < start < end <= len(b.lines)
if start < 1 || start >= end || end > len(b.lines) {
return // what to do? FIXME
}
if end == len(b.lines) {
b.Insert(
Loc{
utf8.RuneCount(b.lines[end-1]),
end - 1,
},
"\n"+b.Line(start-1),
)
} else {
b.Insert(
Loc{0, end},
b.Line(start-1)+"\n",
)
}
b.Remove(
Loc{0, start - 1},
Loc{0, start},
)
}
// MoveLinesDown moves the range of lines down one row
func (b *Buffer) MoveLinesDown(start int, end int) {
// 0 <= start < end < len(b.lines)
// if end == len(b.lines), we can't do anything here because the
// last line is unaccessible, FIXME
if start < 0 || start >= end || end >= len(b.lines)-1 {
return // what to do? FIXME
}
b.Insert(
Loc{0, start},
b.Line(end)+"\n",
)
end++
b.Remove(
Loc{0, end},
Loc{0, end + 1},
)
}

View File

@@ -2,6 +2,7 @@ package main
import (
"fmt"
"io/ioutil"
"regexp"
"strconv"
"strings"
@@ -15,54 +16,67 @@ type Colorscheme map[string]tcell.Style
// The current colorscheme
var colorscheme Colorscheme
var preInstalledColors = []string{"default", "simple", "solarized", "solarized-tc", "atom-dark-tc", "monokai", "gruvbox", "zenburn", "bubblegum"}
// ColorschemeExists checks if a given colorscheme exists
func ColorschemeExists(colorschemeName string) bool {
return FindRuntimeFile(RTColorscheme, colorschemeName) != nil
files, _ := ioutil.ReadDir(configDir)
for _, f := range files {
if f.Name() == colorschemeName+".micro" {
return true
}
}
for _, name := range preInstalledColors {
if name == colorschemeName {
return true
}
}
return false
}
// 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))
LoadColorscheme(globalSettings["colorscheme"].(string), configDir+"/colorschemes")
}
// LoadColorscheme loads the given colorscheme from a directory
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 {
TermMessage("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)
func LoadColorscheme(colorschemeName, dir string) {
files, _ := ioutil.ReadDir(dir)
found := false
for _, f := range files {
if f.Name() == colorschemeName+".micro" {
text, err := ioutil.ReadFile(dir + "/" + f.Name())
if err != nil {
fmt.Println("Error loading colorscheme:", err)
continue
}
colorscheme = ParseColorscheme(string(text))
found = true
}
}
for _, name := range preInstalledColors {
if name == colorschemeName {
data, err := Asset("runtime/colorschemes/" + name + ".micro")
if err != nil {
TermMessage("Unable to load pre-installed colorscheme " + name)
continue
}
colorscheme = ParseColorscheme(string(data))
found = true
}
}
if !found {
TermMessage(colorschemeName, "is not a valid colorscheme")
}
}
// ParseColorscheme parses the text definition for a colorscheme and returns the corresponding object
@@ -88,12 +102,7 @@ func ParseColorscheme(text string) Colorscheme {
link := string(matches[1])
colors := string(matches[2])
style := StringToStyle(colors)
c[link] = style
if link == "default" {
defStyle = style
}
c[link] = StringToStyle(colors)
} else {
fmt.Println("Color-link statement is not valid:", line)
}
@@ -106,7 +115,8 @@ 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, bg string
var fg string
bg := "default"
split := strings.Split(str, ",")
if len(split) > 1 {
fg, bg = split[0], split[1]
@@ -116,19 +126,7 @@ func StringToStyle(str string) tcell.Style {
fg = strings.TrimSpace(fg)
bg = strings.TrimSpace(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)
style := defStyle.Foreground(StringToColor(fg)).Background(StringToColor(bg))
if strings.Contains(str, "bold") {
style = style.Bold(true)
}

View File

@@ -2,12 +2,10 @@ package main
import (
"bytes"
"fmt"
"io"
"io/ioutil"
"os"
"os/exec"
"os/signal"
"path/filepath"
"regexp"
"strings"
@@ -26,30 +24,19 @@ type StrCommand struct {
var commands map[string]Command
var commandActions map[string]func([]string)
func init() {
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,
"Eval": Eval,
"ToggleLog": ToggleLog,
"Plugin": PluginCmd,
"Reload": Reload,
"Cd": Cd,
"Pwd": Pwd,
"Open": Open,
}
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,
}
// InitCommands initializes the default commands
@@ -94,156 +81,9 @@ 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}},
"reload": {"Reload", []Completion{NoCompletion}},
"cd": {"Cd", []Completion{FileCompletion}},
"pwd": {"Pwd", []Completion{NoCompletion}},
"open": {"Open", []Completion{FileCompletion}},
}
}
// 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.
if _, ok := loadedPlugins[plugin]; ok {
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{})
}
}
case "available":
packages := GetAllPluginPackages()
messenger.AddLog("Available Plugins:")
for _, pkg := range packages {
messenger.AddLog(pkg.Name)
}
if CurView().Type != vtLog {
ToggleLog([]string{})
}
}
} else {
messenger.Error("Not enough arguments")
}
}
func Cd(args []string) {
if len(args) > 0 {
home, _ := homedir.Dir()
path := strings.Replace(args[0], "~", home, 1)
os.Chdir(path)
for _, tab := range tabs {
for _, view := range tab.views {
wd, _ := os.Getwd()
view.Buf.Path, _ = MakeRelative(view.Buf.AbsPath, wd)
if p, _ := filepath.Abs(view.Buf.Path); !strings.Contains(p, wd) {
view.Buf.Path = view.Buf.AbsPath
}
}
}
}
}
func Pwd(args []string) {
wd, err := os.Getwd()
if err != nil {
messenger.Message(err.Error())
} else {
messenger.Message(wd)
}
}
func Open(args []string) {
if len(args) > 0 {
filename := args[0]
// the filename might or might not be quoted, so unquote first then join the strings.
filename = strings.Join(SplitCommandArgs(filename), " ")
CurView().Open(filename)
} else {
messenger.Error("No filename")
}
}
func ToggleLog(args []string) {
buffer := messenger.getBuffer()
if CurView().Type != vtLog {
CurView().HSplit(buffer)
CurView().Type = vtLog
RedrawAll()
buffer.Cursor.Loc = buffer.Start()
CurView().Relocate()
buffer.Cursor.Loc = buffer.End()
CurView().Relocate()
} else {
CurView().Quit(true)
}
}
func Reload(args []string) {
LoadAll()
}
// Help tries to open the given help page in a horizontal split
func Help(args []string) {
if len(args) < 1 {
@@ -251,7 +91,7 @@ func Help(args []string) {
CurView().openHelp("help")
} else {
helpPage := args[0]
if FindRuntimeFile(RTHelp, helpPage) != nil {
if _, ok := helpPages[helpPage]; ok {
CurView().openHelp(helpPage)
} else {
messenger.Error("Sorry, no help for ", helpPage)
@@ -263,18 +103,17 @@ func Help(args []string) {
// If no file is given, it opens an empty buffer in a new split
func VSplit(args []string) {
if len(args) == 0 {
CurView().VSplit(NewBuffer(strings.NewReader(""), ""))
CurView().VSplit(NewBuffer([]byte{}, ""))
} else {
filename := args[0]
home, _ := homedir.Dir()
filename = strings.Replace(filename, "~", home, 1)
file, err := os.Open(filename)
defer file.Close()
file, err := ioutil.ReadFile(filename)
var buf *Buffer
if err != nil {
// File does not exist -- create an empty buffer with that name
buf = NewBuffer(strings.NewReader(""), filename)
buf = NewBuffer([]byte{}, filename)
} else {
buf = NewBuffer(file, filename)
}
@@ -286,18 +125,17 @@ func VSplit(args []string) {
// If no file is given, it opens an empty buffer in a new split
func HSplit(args []string) {
if len(args) == 0 {
CurView().HSplit(NewBuffer(strings.NewReader(""), ""))
CurView().HSplit(NewBuffer([]byte{}, ""))
} else {
filename := args[0]
home, _ := homedir.Dir()
filename = strings.Replace(filename, "~", home, 1)
file, err := os.Open(filename)
defer file.Close()
file, err := ioutil.ReadFile(filename)
var buf *Buffer
if err != nil {
// File does not exist -- create an empty buffer with that name
buf = NewBuffer(strings.NewReader(""), filename)
buf = NewBuffer([]byte{}, filename)
} else {
buf = NewBuffer(file, filename)
}
@@ -305,18 +143,6 @@ 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 {
@@ -325,13 +151,12 @@ func NewTab(args []string) {
filename := args[0]
home, _ := homedir.Dir()
filename = strings.Replace(filename, "~", home, 1)
file, _ := os.Open(filename)
defer file.Close()
file, _ := ioutil.ReadFile(filename)
tab := NewTabFromView(NewView(NewBuffer(file, filename)))
tab.SetNum(len(tabs))
tabs = append(tabs, tab)
curTab = len(tabs) - 1
curTab++
if len(tabs) == 2 {
for _, t := range tabs {
for _, v := range t.views {
@@ -400,7 +225,7 @@ func Bind(args []string) {
// Run runs a shell command in the background
func Run(args []string) {
// Run a shell command in the background (openTerm is false)
HandleShellCommand(JoinCommandArgs(args...), false, true)
HandleShellCommand(JoinCommandArgs(args...), false)
}
// Quit closes the main view
@@ -411,12 +236,8 @@ func Quit(args []string) {
// Save saves the buffer in the main view
func Save(args []string) {
if len(args) == 0 {
// Save the main view
CurView().Save(true)
} else {
CurView().Buf.SaveAs(args[0])
}
// Save the main view
CurView().Save(true)
}
// Replace runs search and replace
@@ -436,7 +257,7 @@ func Replace(args []string) {
search := string(args[0])
replace := string(args[1])
regex, err := regexp.Compile("(?m)" + search)
regex, err := regexp.Compile(search)
if err != nil {
// There was an error with the user's regex
messenger.Error(err.Error())
@@ -483,27 +304,13 @@ func Replace(args []string) {
}
}
} else {
bufStr := view.Buf.String()
matches := regex.FindAllStringIndex(bufStr, -1)
if matches != nil && len(matches) > 0 {
prevMatchCount := runePos(matches[0][0], bufStr)
searchCount := runePos(matches[0][1], bufStr) - prevMatchCount
from := FromCharPos(matches[0][0], view.Buf)
to := from.Move(searchCount, view.Buf)
adjust := Count(replace) - searchCount
view.Buf.Replace(from, to, replace)
if len(matches) > 1 {
for _, match := range matches[1:] {
found++
matchCount := runePos(match[0], bufStr)
searchCount = runePos(match[1], bufStr) - matchCount
from = from.Move(matchCount-prevMatchCount+adjust, view.Buf)
to = from.Move(searchCount, view.Buf)
view.Buf.Replace(from, to, replace)
prevMatchCount = matchCount
adjust = Count(replace) - searchCount
}
for {
match := regex.FindStringIndex(view.Buf.String())
if match == nil {
break
}
found++
view.Buf.Replace(FromCharPos(match[0], view.Buf), FromCharPos(match[1], view.Buf), replace)
}
}
view.Cursor.Relocate()
@@ -535,7 +342,7 @@ func RunShellCommand(input string) (string, error) {
// HandleShellCommand runs the shell command
// The openTerm argument specifies whether a terminal should be opened (for viewing output
// or interacting with stdin)
func HandleShellCommand(input string, openTerm bool, waitToFinish bool) string {
func HandleShellCommand(input string, openTerm bool) {
inputCmd := SplitCommandArgs(input)[0]
if !openTerm {
// Simply run the command in the background and notify the user when it's done
@@ -564,10 +371,9 @@ func HandleShellCommand(input string, openTerm bool, waitToFinish bool) string {
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 = io.MultiWriter(os.Stdout, &outputBuf)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
// This is a trap for Ctrl-C so that it doesn't kill micro
@@ -580,25 +386,16 @@ func HandleShellCommand(input string, openTerm bool, waitToFinish bool) string {
}
}()
// Start the command
cmd.Start()
err := cmd.Wait()
cmd.Wait()
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("")
}
// 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

View File

@@ -29,15 +29,6 @@ func (c *Cursor) Goto(b Cursor) {
c.OrigSelection, c.CurSelection = b.OrigSelection, b.CurSelection
}
// CopySelection copies the user's selection to either "primary" or "clipboard"
func (c *Cursor) CopySelection(target string) {
if c.HasSelection() {
if target != "primary" || c.buf.Settings["useprimary"].(bool) {
clipboard.WriteAll(c.GetSelection(), target)
}
}
}
// ResetSelection resets the user's selection
func (c *Cursor) ResetSelection() {
c.CurSelection[0] = c.buf.Start()
@@ -47,11 +38,19 @@ func (c *Cursor) ResetSelection() {
// 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
@@ -74,13 +73,10 @@ func (c *Cursor) DeleteSelection() {
// GetSelection returns the cursor's selection
func (c *Cursor) GetSelection() string {
if InBounds(c.CurSelection[0], c.buf) && InBounds(c.CurSelection[1], c.buf) {
if c.CurSelection[0].GreaterThan(c.CurSelection[1]) {
return c.buf.Substr(c.CurSelection[1], c.CurSelection[0])
}
return c.buf.Substr(c.CurSelection[0], c.CurSelection[1])
if c.CurSelection[0].GreaterThan(c.CurSelection[1]) {
return c.buf.Substr(c.CurSelection[1], c.CurSelection[0])
}
return ""
return c.buf.Substr(c.CurSelection[0], c.CurSelection[1])
}
// SelectLine selects the current line

View File

@@ -1,11 +1,9 @@
package main
import (
"strings"
"time"
dmp "github.com/sergi/go-diff/diffmatchpatch"
"github.com/yuin/gopher-lua"
)
const (
@@ -116,17 +114,6 @@ 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)
}

25
cmd/micro/help.go Normal file
View File

@@ -0,0 +1,25 @@
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)
}
}

View File

@@ -1,10 +1,11 @@
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
@@ -26,19 +27,153 @@ type SyntaxRule struct {
style tcell.Style
}
var syntaxKeys [][2]*regexp.Regexp
var syntaxFiles map[[2]*regexp.Regexp]FileTypeRules
// These syntax files are pre installed and embedded in the resulting binary by go-bindata
var preInstalledSynFiles = []string{
"Dockerfile",
"apacheconf",
"arduino",
"asciidoc",
"asm",
"awk",
"c",
"caddyfile",
"cmake",
"coffeescript",
"colortest",
"conf",
"conky",
"csharp",
"css",
"cython",
"d",
"dart",
"dot",
"erb",
"fish",
"fortran",
"gdscript",
"gentoo-ebuild",
"gentoo-etc-portage",
"git-commit",
"git-config",
"git-rebase-todo",
"glsl",
"go",
"golo",
"groff",
"haml",
"haskell",
"html",
"ini",
"inputrc",
"java",
"javascript",
"json",
"keymap",
"kickstart",
"ledger",
"lilypond",
"lisp",
"lua",
"makefile",
"man",
"markdown",
"mpdconf",
"micro",
"nanorc",
"nginx",
"ocaml",
"pascal",
"patch",
"peg",
"perl",
"perl6",
"php",
"pkg-config",
"pkgbuild",
"po",
"pov",
"privoxy-action",
"privoxy-config",
"privoxy-filter",
"puppet",
"python",
"r",
"reST",
"rpmspec",
"ruby",
"rust",
"scala",
"sed",
"sh",
"sls",
"sql",
"swift",
"systemd",
"tcl",
"tex",
"vala",
"vi",
"xml",
"xresources",
"yaml",
"yum",
"zsh",
}
// LoadSyntaxFiles loads the syntax files from the default directory (configDir)
func LoadSyntaxFiles() {
InitColorscheme()
syntaxFiles = make(map[[2]*regexp.Regexp]FileTypeRules)
for _, f := range ListRuntimeFiles(RTSyntax) {
data, err := f.Data()
// 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("Error loading syntax file " + f.Name() + ": " + err.Error())
} else {
LoadSyntaxFile(string(data), f.Name())
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)
}
}
}
@@ -125,7 +260,6 @@ func LoadSyntaxFile(text, filename string) {
if syntaxRegex != nil {
// Add the current rules to the syntaxFiles variable
regexes := [2]*regexp.Regexp{syntaxRegex, headerRegex}
syntaxKeys = append(syntaxKeys, regexes)
syntaxFiles[regexes] = FileTypeRules{filetype, filename, text}
}
}
@@ -261,16 +395,13 @@ func LoadRulesFromFile(text, filename string) []SyntaxRule {
// FindFileType finds the filetype for the given buffer
func FindFileType(buf *Buffer) string {
for _, r := range syntaxKeys {
if r[1] != nil && r[1].MatchString(buf.Line(0)) {
// The header statement matches the first line
return syntaxFiles[r].filetype
}
}
for _, r := range syntaxKeys {
for r := range syntaxFiles {
if r[0] != nil && r[0].MatchString(buf.Path) {
// The syntax statement matches the extension
return syntaxFiles[r].filetype
} else if r[1] != nil && r[1].MatchString(buf.Line(0)) {
// The header statement matches the first line
return syntaxFiles[r].filetype
}
}
return "Unknown"
@@ -279,7 +410,7 @@ func FindFileType(buf *Buffer) string {
// GetRules finds the syntax rules that should be used for the buffer
// and returns them. It also returns the filetype of the file
func GetRules(buf *Buffer) []SyntaxRule {
for _, r := range syntaxKeys {
for r := range syntaxFiles {
if syntaxFiles[r].filetype == buf.FileType() {
return LoadRulesFromFile(syntaxFiles[r].text, syntaxFiles[r].filename)
}
@@ -298,7 +429,7 @@ func Match(v *View) SyntaxMatches {
rules := v.Buf.rules
viewStart := v.Topline
viewEnd := v.Topline + v.Height
viewEnd := v.Topline + v.height
if viewEnd > buf.NumLines {
viewEnd = buf.NumLines
}
@@ -315,7 +446,7 @@ func Match(v *View) SyntaxMatches {
// We don't actually check the entire buffer, just from synLinesUp to synLinesDown
totalStart := v.Topline - synLinesUp
totalEnd := v.Topline + v.Height + synLinesDown
totalEnd := v.Topline + v.height + synLinesDown
if totalStart < 0 {
totalStart = 0
}
@@ -343,7 +474,7 @@ func Match(v *View) SyntaxMatches {
continue
}
lineNum -= viewStart
if lineNum >= 0 && lineNum < v.Height {
if lineNum >= 0 && lineNum < v.height {
matches[lineNum][colNum] = rule.style
}
}

View File

@@ -4,6 +4,7 @@ import (
"bytes"
"io"
"os/exec"
"strings"
)
// Jobs are the way plugins can run processes in the background
@@ -39,17 +40,15 @@ func (f *CallbackFile) Write(data []byte) (int, error) {
return f.Writer.Write(data)
}
// JobStart starts a shell command in the background with the given callbacks
// JobStart starts a process in the background with the given callbacks
// It returns an *exec.Cmd as the job id
func JobStart(cmd string, onStdout, onStderr, onExit string, userargs ...string) *exec.Cmd {
return JobSpawn("sh", []string{"-c", cmd}, onStdout, onStderr, onExit, userargs...)
}
split := strings.Split(cmd, " ")
args := split[1:]
cmdName := split[0]
// JobSpawn starts a process with args in the background with the given callbacks
// It returns an *exec.Cmd as the job id
func JobSpawn(cmdName string, cmdArgs []string, onStdout, onStderr, onExit string, userargs ...string) *exec.Cmd {
// Set up everything correctly if the functions have been provided
proc := exec.Command(cmdName, cmdArgs...)
proc := exec.Command(cmdName, args...)
var outbuf bytes.Buffer
if onStdout != "" {
proc.Stdout = &CallbackFile{&outbuf, LuaFunctionJob(onStdout), userargs}

View File

@@ -1,9 +1,7 @@
package main
import (
"bufio"
"bytes"
"io"
"unicode/utf8"
)
@@ -35,23 +33,14 @@ type LineArray struct {
}
// NewLineArray returns a new line array from an array of bytes
func NewLineArray(reader io.Reader) *LineArray {
func NewLineArray(text []byte) *LineArray {
la := new(LineArray)
br := bufio.NewReader(reader)
i := 0
for {
data, err := br.ReadBytes('\n')
if err != nil {
if err == io.EOF {
la.lines = append(la.lines, data[:len(data)])
}
// Last line was read
break
} else {
la.lines = append(la.lines, data[:len(data)-1])
}
i++
// Split the bytes into lines
split := bytes.Split(text, []byte("\n"))
la.lines = make([][]byte, len(split))
for i := range split {
la.lines[i] = make([]byte, len(split[i]))
copy(la.lines[i], split[i])
}
return la

View File

@@ -28,27 +28,6 @@ func ToCharPos(start Loc, buf *Buffer) int {
return loc
}
// InBounds returns whether the given location is a valid character position in the given buffer
func InBounds(pos Loc, buf *Buffer) bool {
if pos.Y < 0 || pos.Y >= buf.NumLines || pos.X < 0 || pos.X > Count(buf.Line(pos.Y)) {
return false
}
return true
}
// ByteOffset is just like ToCharPos except it counts bytes instead of runes
func ByteOffset(pos Loc, buf *Buffer) int {
x, y := pos.X, pos.Y
loc := 0
for i := 0; i < y; i++ {
// + 1 for the newline
loc += len(buf.Line(i)) + 1
}
loc += len(buf.Line(y)[:x])
return loc
}
// Loc stores a location
type Loc struct {
X, Y int

View File

@@ -6,7 +6,6 @@ import (
"fmt"
"os"
"strconv"
"strings"
"github.com/zyedidia/clipboard"
"github.com/zyedidia/tcell"
@@ -22,7 +21,6 @@ func TermMessage(msg ...interface{}) {
screenWasNil := screen == nil
if !screenWasNil {
screen.Fini()
screen = nil
}
fmt.Println(msg...)
@@ -45,7 +43,6 @@ 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
@@ -70,85 +67,38 @@ 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(strings.NewReader(""), "")
m.log.name = "Log"
}
return m.log
}
// Message sends a message to the user
func (m *Messenger) Message(msg ...interface{}) {
displayMessage := fmt.Sprint(msg...)
// only display a new message if there isn't an active prompt
// this is to prevent overwriting an existing prompt to the user
if m.hasPrompt == false {
// if there is no active prompt then style and display the message as normal
m.message = displayMessage
buf := new(bytes.Buffer)
fmt.Fprint(buf, msg...)
m.message = buf.String()
m.style = defStyle
m.style = defStyle
if _, ok := colorscheme["message"]; ok {
m.style = colorscheme["message"]
}
m.hasMessage = true
if _, ok := colorscheme["message"]; ok {
m.style = colorscheme["message"]
}
// add the message to the log regardless of active prompts
m.AddLog(displayMessage)
m.hasMessage = true
}
// Error sends an error message to the user
func (m *Messenger) Error(msg ...interface{}) {
buf := new(bytes.Buffer)
fmt.Fprint(buf, msg...)
m.message = buf.String()
m.style = defStyle.
Foreground(tcell.ColorBlack).
Background(tcell.ColorMaroon)
// only display a new message if there isn't an active prompt
// this is to prevent overwriting an existing prompt to the user
if m.hasPrompt == false {
// if there is no active prompt then style and display the message as normal
m.message = buf.String()
m.style = defStyle.
Foreground(tcell.ColorBlack).
Background(tcell.ColorMaroon)
if _, ok := colorscheme["error-message"]; ok {
m.style = colorscheme["error-message"]
}
m.hasMessage = true
if _, ok := colorscheme["error-message"]; ok {
m.style = colorscheme["error-message"]
}
// add the message to the log regardless of active prompts
m.AddLog(buf.String())
}
func (m *Messenger) PromptText(msg ...interface{}) {
displayMessage := fmt.Sprint(msg...)
// if there is no active prompt then style and display the message as normal
m.message = displayMessage
m.style = defStyle
if _, ok := colorscheme["message"]; ok {
m.style = colorscheme["message"]
}
m.hasMessage = true
// add the message to the log regardless of active prompts
m.AddLog(displayMessage)
}
// 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.PromptText(prompt)
m.Message(prompt)
_, h := screen.Size()
for {
@@ -163,16 +113,13 @@ 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
}
@@ -183,7 +130,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.PromptText(prompt)
m.Message(prompt)
_, h := screen.Size()
for {
@@ -199,7 +146,6 @@ 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
@@ -207,7 +153,6 @@ func (m *Messenger) LetterPrompt(prompt string, responses ...rune) (rune, bool)
}
}
case tcell.KeyCtrlC, tcell.KeyCtrlQ, tcell.KeyEscape:
m.AddLog("\t--> (cancel)")
m.Clear()
m.Reset()
m.hasPrompt = false
@@ -225,15 +170,13 @@ const (
CommandCompletion
HelpCompletion
OptionCompletion
PluginCmdCompletion
PluginNameCompletion
)
// Prompt sends the user a message and waits for a response to be typed in
// This function blocks the main loop while waiting for input
func (m *Messenger) Prompt(prompt, placeholder, historyType string, completionTypes ...Completion) (string, bool) {
func (m *Messenger) Prompt(prompt, historyType string, completionTypes ...Completion) (string, bool) {
m.hasPrompt = true
m.PromptText(prompt)
m.Message(prompt)
if _, ok := m.history[historyType]; !ok {
m.history[historyType] = []string{""}
} else {
@@ -241,9 +184,7 @@ func (m *Messenger) Prompt(prompt, placeholder, historyType string, completionTy
}
m.historyNum = len(m.history[historyType]) - 1
response, canceled := placeholder, true
m.response = response
m.cursorx = Count(placeholder)
response, canceled := "", true
RedrawAll()
for m.hasPrompt {
@@ -257,11 +198,9 @@ func (m *Messenger) Prompt(prompt, placeholder, historyType string, completionTy
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
@@ -292,10 +231,6 @@ func (m *Messenger) Prompt(prompt, placeholder, historyType string, completionTy
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)
}
@@ -334,58 +269,36 @@ func (m *Messenger) Prompt(prompt, placeholder, historyType string, completionTy
func (m *Messenger) HandleEvent(event tcell.Event, history []string) {
switch e := event.(type) {
case *tcell.EventKey:
if e.Key() != tcell.KeyRune || e.Modifiers() != 0 {
for key, actions := range bindings {
if e.Key() == key.keyCode {
if e.Key() == tcell.KeyRune {
if e.Rune() != key.r {
continue
}
}
if e.Modifiers() == key.modifiers {
for _, action := range actions {
funcName := FuncName(action)
switch funcName {
case "main.(*View).CursorUp":
if m.historyNum > 0 {
m.historyNum--
m.response = history[m.historyNum]
m.cursorx = Count(m.response)
}
case "main.(*View).CursorDown":
if m.historyNum < len(history)-1 {
m.historyNum++
m.response = history[m.historyNum]
m.cursorx = Count(m.response)
}
case "main.(*View).CursorLeft":
if m.cursorx > 0 {
m.cursorx--
}
case "main.(*View).CursorRight":
if m.cursorx < Count(m.response) {
m.cursorx++
}
case "main.(*View).CursorStart", "main.(*View).StartOfLine":
m.cursorx = 0
case "main.(*View).CursorEnd", "main.(*View).EndOfLine":
m.cursorx = Count(m.response)
case "main.(*View).Backspace":
if m.cursorx > 0 {
m.response = string([]rune(m.response)[:m.cursorx-1]) + string([]rune(m.response)[m.cursorx:])
m.cursorx--
}
case "main.(*View).Paste":
clip, _ := clipboard.ReadAll("clipboard")
m.response = Insert(m.response, m.cursorx, clip)
m.cursorx += Count(clip)
}
}
}
}
}
}
switch e.Key() {
case tcell.KeyUp:
if m.historyNum > 0 {
m.historyNum--
m.response = history[m.historyNum]
m.cursorx = Count(m.response)
}
case tcell.KeyDown:
if m.historyNum < len(history)-1 {
m.historyNum++
m.response = history[m.historyNum]
m.cursorx = Count(m.response)
}
case tcell.KeyLeft:
if m.cursorx > 0 {
m.cursorx--
}
case tcell.KeyRight:
if m.cursorx < Count(m.response) {
m.cursorx++
}
case tcell.KeyBackspace2, tcell.KeyBackspace:
if m.cursorx > 0 {
m.response = string([]rune(m.response)[:m.cursorx-1]) + string([]rune(m.response)[m.cursorx:])
m.cursorx--
}
case tcell.KeyCtrlV:
clip, _ := clipboard.ReadAll("clipboard")
m.response = Insert(m.response, m.cursorx, clip)
m.cursorx += Count(clip)
case tcell.KeyRune:
m.response = Insert(m.response, m.cursorx, string(e.Rune()))
m.cursorx++
@@ -396,23 +309,6 @@ 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)
}
}
}
}
}

View File

@@ -5,13 +5,11 @@ import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"runtime"
"strings"
"time"
"github.com/go-errors/errors"
"layeh.com/gopher-luar"
"github.com/layeh/gopher-luar"
"github.com/mattn/go-isatty"
"github.com/mitchellh/go-homedir"
"github.com/yuin/gopher-lua"
@@ -25,7 +23,6 @@ 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 (
@@ -46,7 +43,7 @@ var (
// Version is the version number or commit hash
// These variables should be set by the linker when compiling
Version = "0.0.0-unknown"
Version = "Unknown"
CommitHash = "Unknown"
CompileDate = "Unknown"
@@ -63,8 +60,7 @@ var (
// Channel of jobs running in the background
jobs chan JobFunction
// Event channel
events chan tcell.Event
autosave chan bool
events chan tcell.Event
)
// LoadInput determines which files should be loaded into buffers
@@ -93,27 +89,17 @@ func LoadInput() []*Buffer {
filename = flag.Args()[i]
// Check that the file exists
var input *os.File
if _, e := os.Stat(filename); e == nil {
// If it exists we load it into a buffer
input, err = os.Open(filename)
stat, _ := input.Stat()
defer input.Close()
input, err = ioutil.ReadFile(filename)
if err != nil {
TermMessage(err)
continue
}
if stat.IsDir() {
TermMessage("Cannot read", filename, "because it is a directory")
continue
input = []byte{}
filename = ""
}
}
// If the file didn't exist, input will be empty, and we'll open an empty buffer
if input != nil {
buffers = append(buffers, NewBuffer(input, filename))
} else {
buffers = append(buffers, NewBuffer(strings.NewReader(""), filename))
}
buffers = append(buffers, NewBuffer(input, filename))
}
} else if !isatty.IsTerminal(os.Stdin.Fd()) {
// Option 2
@@ -124,10 +110,10 @@ func LoadInput() []*Buffer {
TermMessage("Error reading from stdin: ", err)
input = []byte{}
}
buffers = append(buffers, NewBuffer(strings.NewReader(string(input)), filename))
buffers = append(buffers, NewBuffer(input, filename))
} else {
// Option 3, just open an empty buffer
buffers = append(buffers, NewBuffer(strings.NewReader(string(input)), filename))
buffers = append(buffers, NewBuffer(input, filename))
}
return buffers
@@ -177,9 +163,29 @@ func InitScreen() {
os.Setenv("TERM", "xterm-truecolor")
}
os.Setenv("TCELLDB", configDir+"/.tcelldb")
// Initilize tcell
var err error
screen, err = tcell.NewScreen()
if err != nil && err.Error() == "terminal entry not found" {
var termDB []byte
termDB, err = MkInfo()
if err != nil {
fmt.Println("Terminal entry not found")
fmt.Println("Error when trying to read terminfo: ", err)
os.Exit(1)
}
if _, e := os.Stat(configDir); e == nil {
ioutil.WriteFile(configDir+"/.tcelldb", termDB, 0644)
}
screen, err = tcell.NewScreen()
}
if err != nil {
fmt.Println(err)
os.Exit(1)
@@ -209,31 +215,6 @@ func RedrawAll() {
screen.Show()
}
func LoadAll() {
// 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()
LoadSyntaxFiles()
for _, tab := range tabs {
for _, v := range tab.views {
v.Buf.UpdateRules()
if v.Buf.Settings["syntax"].(bool) {
v.matches = Match(v)
}
}
}
}
// Passing -version as a flag will have micro print out 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.")
@@ -241,7 +222,7 @@ var flagStartPos = flag.String("startpos", "", "LINE,COL to start the cursor at
func main() {
flag.Usage = func() {
fmt.Println("Usage: micro [OPTIONS] [FILE]...")
fmt.Print("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\n")
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()
}
@@ -272,15 +253,18 @@ 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()
@@ -304,10 +288,6 @@ func main() {
// Now we load the input
buffers := LoadInput()
if len(buffers) == 0 {
screen.Fini()
os.Exit(1)
}
for _, buf := range buffers {
// For each buffer we create a new tab and place the view in that tab
tab := NewTabFromView(NewView(buf))
@@ -316,9 +296,10 @@ func main() {
for _, t := range tabs {
for _, v := range t.views {
v.Center(false)
if globalSettings["syntax"].(bool) {
v.matches = Match(v)
}
}
t.Resize()
}
}
@@ -346,46 +327,20 @@ func main() {
L.SetGlobal("HandleShellCommand", luar.New(L, HandleShellCommand))
L.SetGlobal("GetLeadingWhitespace", luar.New(L, GetLeadingWhitespace))
L.SetGlobal("MakeCompletion", luar.New(L, MakeCompletion))
L.SetGlobal("NewBuffer", luar.New(L, NewBufferFromString))
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("DirectoryName", luar.New(L, filepath.Dir))
L.SetGlobal("configDir", luar.New(L, configDir))
L.SetGlobal("Reload", luar.New(L, LoadAll))
L.SetGlobal("ByteOffset", luar.New(L, ByteOffset))
L.SetGlobal("ToCharPos", luar.New(L, ToCharPos))
// Used for asynchronous jobs
L.SetGlobal("JobStart", luar.New(L, JobStart))
L.SetGlobal("JobSpawn", luar.New(L, JobSpawn))
L.SetGlobal("JobSend", luar.New(L, JobSend))
L.SetGlobal("JobStop", luar.New(L, JobStop))
// 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, 100)
autosave = make(chan bool)
LoadPlugins()
// Load the syntax files, including the colorscheme
LoadSyntaxFiles()
jobs = make(chan JobFunction, 100)
events = make(chan tcell.Event)
for _, t := range tabs {
for _, v := range t.views {
v.Buf.FindFileType()
v.Buf.UpdateRules()
for pl := range loadedPlugins {
for _, pl := range loadedPlugins {
_, err := Call(pl+".onViewOpen", v)
if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") {
TermMessage(err)
@@ -405,15 +360,6 @@ func main() {
}
}()
go func() {
for {
time.Sleep(autosaveTime * time.Second)
if globalSettings["autosave"].(bool) {
autosave <- true
}
}
}()
for {
// Display everything
RedrawAll()
@@ -426,61 +372,50 @@ 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:
}
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
}
switch e := event.(type) {
case *tcell.EventMouse:
if e.Buttons() == tcell.Button1 {
// If the user left clicked we check a couple things
_, h := screen.Size()
x, y := e.Position()
if y == h-1 && messenger.message != "" && globalSettings["infobar"].(bool) {
// If the user clicked in the bottom bar, and there is a message down there
// we copy it to the clipboard.
// Often error messages are displayed down there so it can be useful to easily
// copy the message
clipboard.WriteAll(messenger.message, "primary")
continue
}
if CurView().mouseReleased {
// We loop through each view in the current tab and make sure the current view
// is the one being clicked in
for _, v := range tabs[curTab].views {
if x >= v.x && x < v.x+v.Width && y >= v.y && y < v.y+v.Height {
tabs[curTab].CurView = v.Num
}
if CurView().mouseReleased {
// We loop through each view in the current tab and make sure the current view
// is the one being clicked in
for _, v := range tabs[curTab].views {
if x >= v.x && x < v.x+v.width && y >= v.y && y < v.y+v.height {
tabs[curTab].curView = v.Num
}
}
}
}
}
// This function checks the mouse event for the possibility of changing the current tab
// If the tab was changed it returns true
if TabbarHandleMouseEvent(event) {
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
}
// 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
}
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)
}
}
}

669
cmd/micro/mkinfo.go Normal file
View File

@@ -0,0 +1,669 @@
// Copyright 2016 The TCell Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use file except in compliance with the License.
// You may obtain a copy of the license at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// This command is used to generate suitable configuration files in either
// go syntax or in JSON. It defaults to JSON output on stdout. If no
// term values are specified on the command line, then $TERM is used.
//
// Usage is like this:
//
// mkinfo [-go file.go] [-json file.json] [-quiet] [-nofatal] [<term>...]
//
// -go specifiles Go output into the named file. Use - for stdout.
// -json specifies JSON output in the named file. Use - for stdout
// -nofatal indicates that errors loading definitions should not be fatal
//
package main
import (
"encoding/json"
"errors"
"fmt"
"io"
"os"
"regexp"
"runtime"
"sort"
"strings"
"time"
"github.com/zyedidia/tcell"
)
// #include <curses.h>
// #include <term.h>
// #cgo LDFLAGS: -lcurses
//
// void noenv() {
// use_env(FALSE);
// }
//
// char *tigetstr_good(char *name) {
// char *r;
// r = tigetstr(name);
// if (r == (char *)-1) {
// r = NULL;
// }
// return (r);
// }
import "C"
func tigetnum(s string) int {
n := C.tigetnum(C.CString(s))
return int(n)
}
func tigetflag(s string) bool {
n := C.tigetflag(C.CString(s))
return n != 0
}
func tigetstr(s string) string {
// NB: If the string is invalid, we'll get back -1, which causes
// no end of grief. So make sure your capability strings are correct!
cs := C.tigetstr_good(C.CString(s))
if cs == nil {
return ""
}
return C.GoString(cs)
}
// This program is used to collect data from the system's terminfo library,
// and write it into Go source code. That is, we maintain our terminfo
// capabilities encoded in the program. It should never need to be run by
// an end user, but developers can use this to add codes for additional
// terminal types.
//
// If a terminal name ending with -truecolor is given, and we cannot find
// one, we will try to fabricte one from either the -256color (if present)
// or the unadorned base name, adding the XTerm specific 24-bit color
// escapes. We believe that all 24-bit capable terminals use the same
// escape sequences, and terminfo has yet to evolve to support this.
func getinfo(name string) (*tcell.Terminfo, error) {
addTrueColor := false
rsn := C.int(0)
C.noenv()
rv, _ := C.setupterm(C.CString(name), 1, &rsn)
if rv == C.ERR {
if strings.HasSuffix(name, "-truecolor") {
base := name[:len(name)-len("-truecolor")]
// Probably -256color is closest to what we want
rv, _ = C.setupterm(C.CString(base+"-256color"), 1,
&rsn)
// Otherwise try the base
if rv == C.ERR {
rv, _ = C.setupterm(C.CString(base), 1, &rsn)
}
if rv != C.ERR {
addTrueColor = true
}
}
}
if rv == C.ERR {
switch rsn {
case 1:
return nil, errors.New("hardcopy terminal")
case 0:
return nil, errors.New("terminal definition not found")
case -1:
return nil, errors.New("terminfo database missing")
default:
return nil, errors.New("setupterm failed (other)")
}
}
t := &tcell.Terminfo{}
t.Name = name
t.Colors = tigetnum("colors")
t.Columns = tigetnum("cols")
t.Lines = tigetnum("lines")
t.Bell = tigetstr("bel")
t.Clear = tigetstr("clear")
t.EnterCA = tigetstr("smcup")
t.ExitCA = tigetstr("rmcup")
t.ShowCursor = tigetstr("cnorm")
t.HideCursor = tigetstr("civis")
t.AttrOff = tigetstr("sgr0")
t.Underline = tigetstr("smul")
t.Bold = tigetstr("bold")
t.Blink = tigetstr("blink")
t.Dim = tigetstr("dim")
t.Reverse = tigetstr("rev")
t.EnterKeypad = tigetstr("smkx")
t.ExitKeypad = tigetstr("rmkx")
t.SetFg = tigetstr("setaf")
t.SetBg = tigetstr("setab")
t.SetCursor = tigetstr("cup")
t.CursorBack1 = tigetstr("cub1")
t.CursorUp1 = tigetstr("cuu1")
t.KeyF1 = tigetstr("kf1")
t.KeyF2 = tigetstr("kf2")
t.KeyF3 = tigetstr("kf3")
t.KeyF4 = tigetstr("kf4")
t.KeyF5 = tigetstr("kf5")
t.KeyF6 = tigetstr("kf6")
t.KeyF7 = tigetstr("kf7")
t.KeyF8 = tigetstr("kf8")
t.KeyF9 = tigetstr("kf9")
t.KeyF10 = tigetstr("kf10")
t.KeyF11 = tigetstr("kf11")
t.KeyF12 = tigetstr("kf12")
t.KeyF13 = tigetstr("kf13")
t.KeyF14 = tigetstr("kf14")
t.KeyF15 = tigetstr("kf15")
t.KeyF16 = tigetstr("kf16")
t.KeyF17 = tigetstr("kf17")
t.KeyF18 = tigetstr("kf18")
t.KeyF19 = tigetstr("kf19")
t.KeyF20 = tigetstr("kf20")
t.KeyF21 = tigetstr("kf21")
t.KeyF22 = tigetstr("kf22")
t.KeyF23 = tigetstr("kf23")
t.KeyF24 = tigetstr("kf24")
t.KeyF25 = tigetstr("kf25")
t.KeyF26 = tigetstr("kf26")
t.KeyF27 = tigetstr("kf27")
t.KeyF28 = tigetstr("kf28")
t.KeyF29 = tigetstr("kf29")
t.KeyF30 = tigetstr("kf30")
t.KeyF31 = tigetstr("kf31")
t.KeyF32 = tigetstr("kf32")
t.KeyF33 = tigetstr("kf33")
t.KeyF34 = tigetstr("kf34")
t.KeyF35 = tigetstr("kf35")
t.KeyF36 = tigetstr("kf36")
t.KeyF37 = tigetstr("kf37")
t.KeyF38 = tigetstr("kf38")
t.KeyF39 = tigetstr("kf39")
t.KeyF40 = tigetstr("kf40")
t.KeyF41 = tigetstr("kf41")
t.KeyF42 = tigetstr("kf42")
t.KeyF43 = tigetstr("kf43")
t.KeyF44 = tigetstr("kf44")
t.KeyF45 = tigetstr("kf45")
t.KeyF46 = tigetstr("kf46")
t.KeyF47 = tigetstr("kf47")
t.KeyF48 = tigetstr("kf48")
t.KeyF49 = tigetstr("kf49")
t.KeyF50 = tigetstr("kf50")
t.KeyF51 = tigetstr("kf51")
t.KeyF52 = tigetstr("kf52")
t.KeyF53 = tigetstr("kf53")
t.KeyF54 = tigetstr("kf54")
t.KeyF55 = tigetstr("kf55")
t.KeyF56 = tigetstr("kf56")
t.KeyF57 = tigetstr("kf57")
t.KeyF58 = tigetstr("kf58")
t.KeyF59 = tigetstr("kf59")
t.KeyF60 = tigetstr("kf60")
t.KeyF61 = tigetstr("kf61")
t.KeyF62 = tigetstr("kf62")
t.KeyF63 = tigetstr("kf63")
t.KeyF64 = tigetstr("kf64")
t.KeyInsert = tigetstr("kich1")
t.KeyDelete = tigetstr("kdch1")
t.KeyBackspace = tigetstr("kbs")
t.KeyHome = tigetstr("khome")
t.KeyEnd = tigetstr("kend")
t.KeyUp = tigetstr("kcuu1")
t.KeyDown = tigetstr("kcud1")
t.KeyRight = tigetstr("kcuf1")
t.KeyLeft = tigetstr("kcub1")
t.KeyPgDn = tigetstr("knp")
t.KeyPgUp = tigetstr("kpp")
t.KeyBacktab = tigetstr("kcbt")
t.KeyExit = tigetstr("kext")
t.KeyCancel = tigetstr("kcan")
t.KeyPrint = tigetstr("kprt")
t.KeyHelp = tigetstr("khlp")
t.KeyClear = tigetstr("kclr")
t.AltChars = tigetstr("acsc")
t.EnterAcs = tigetstr("smacs")
t.ExitAcs = tigetstr("rmacs")
t.EnableAcs = tigetstr("enacs")
t.Mouse = tigetstr("kmous")
t.KeyShfRight = tigetstr("kRIT")
t.KeyShfLeft = tigetstr("kLFT")
t.KeyShfHome = tigetstr("kHOM")
t.KeyShfEnd = tigetstr("kEND")
// Terminfo lacks descriptions for a bunch of modified keys,
// but modern XTerm and emulators often have them. Let's add them,
// if the shifted right and left arrows are defined.
if t.KeyShfRight == "\x1b[1;2C" && t.KeyShfLeft == "\x1b[1;2D" {
t.KeyShfUp = "\x1b[1;2A"
t.KeyShfDown = "\x1b[1;2B"
t.KeyMetaUp = "\x1b[1;9A"
t.KeyMetaDown = "\x1b[1;9B"
t.KeyMetaRight = "\x1b[1;9C"
t.KeyMetaLeft = "\x1b[1;9D"
t.KeyAltUp = "\x1b[1;3A"
t.KeyAltDown = "\x1b[1;3B"
t.KeyAltRight = "\x1b[1;3C"
t.KeyAltLeft = "\x1b[1;3D"
t.KeyCtrlUp = "\x1b[1;5A"
t.KeyCtrlDown = "\x1b[1;5B"
t.KeyCtrlRight = "\x1b[1;5C"
t.KeyCtrlLeft = "\x1b[1;5D"
t.KeyAltShfUp = "\x1b[1;4A"
t.KeyAltShfDown = "\x1b[1;4B"
t.KeyAltShfRight = "\x1b[1;4C"
t.KeyAltShfLeft = "\x1b[1;4D"
t.KeyMetaShfUp = "\x1b[1;10A"
t.KeyMetaShfDown = "\x1b[1;10B"
t.KeyMetaShfRight = "\x1b[1;10C"
t.KeyMetaShfLeft = "\x1b[1;10D"
t.KeyCtrlShfUp = "\x1b[1;6A"
t.KeyCtrlShfDown = "\x1b[1;6B"
t.KeyCtrlShfRight = "\x1b[1;6C"
t.KeyCtrlShfLeft = "\x1b[1;6D"
}
// And also for Home and End
if t.KeyShfHome == "\x1b[1;2H" && t.KeyShfEnd == "\x1b[1;2F" {
t.KeyCtrlHome = "\x1b[1;5H"
t.KeyCtrlEnd = "\x1b[1;5F"
t.KeyAltHome = "\x1b[1;9H"
t.KeyAltEnd = "\x1b[1;9F"
t.KeyCtrlShfHome = "\x1b[1;6H"
t.KeyCtrlShfEnd = "\x1b[1;6F"
t.KeyAltShfHome = "\x1b[1;4H"
t.KeyAltShfEnd = "\x1b[1;4F"
t.KeyMetaShfHome = "\x1b[1;10H"
t.KeyMetaShfEnd = "\x1b[1;10F"
}
// And the same thing for rxvt and workalikes (Eterm, aterm, etc.)
// It seems that urxvt at least send ESC as ALT prefix for these,
// although some places seem to indicate a separate ALT key sesquence.
if t.KeyShfRight == "\x1b[c" && t.KeyShfLeft == "\x1b[d" {
t.KeyShfUp = "\x1b[a"
t.KeyShfDown = "\x1b[b"
t.KeyCtrlUp = "\x1b[Oa"
t.KeyCtrlDown = "\x1b[Ob"
t.KeyCtrlRight = "\x1b[Oc"
t.KeyCtrlLeft = "\x1b[Od"
}
if t.KeyShfHome == "\x1b[7$" && t.KeyShfEnd == "\x1b[8$" {
t.KeyCtrlHome = "\x1b[7^"
t.KeyCtrlEnd = "\x1b[8^"
}
// If the kmous entry is present, then we need to record the
// the codes to enter and exit mouse mode. Sadly, this is not
// part of the terminfo databases anywhere that I've found, but
// is an extension. The escape codes are documented in the XTerm
// manual, and all terminals that have kmous are expected to
// use these same codes, unless explicitly configured otherwise
// vi XM. Note that in any event, we only known how to parse either
// x11 or SGR mouse events -- if your terminal doesn't support one
// of these two forms, you maybe out of luck.
t.MouseMode = tigetstr("XM")
if t.Mouse != "" && t.MouseMode == "" {
// we anticipate that all xterm mouse tracking compatible
// terminals understand mouse tracking (1000), but we hope
// that those that don't understand any-event tracking (1003)
// will at least ignore it. Likewise we hope that terminals
// that don't understand SGR reporting (1006) just ignore it.
t.MouseMode = "%?%p1%{1}%=%t%'h'%Pa%e%'l'%Pa%;" +
"\x1b[?1000%ga%c\x1b[?1003%ga%c\x1b[?1006%ga%c"
}
// We only support colors in ANSI 8 or 256 color mode.
if t.Colors < 8 || t.SetFg == "" {
t.Colors = 0
}
if t.SetCursor == "" {
return nil, errors.New("terminal not cursor addressable")
}
// For padding, we lookup the pad char. If that isn't present,
// and npc is *not* set, then we assume a null byte.
t.PadChar = tigetstr("pad")
if t.PadChar == "" {
if !tigetflag("npc") {
t.PadChar = "\u0000"
}
}
// For some terminals we fabricate a -truecolor entry, that may
// not exist in terminfo.
if addTrueColor {
t.SetFgRGB = "\x1b[38;2;%p1%d;%p2%d;%p3%dm"
t.SetBgRGB = "\x1b[48;2;%p1%d;%p2%d;%p3%dm"
t.SetFgBgRGB = "\x1b[38;2;%p1%d;%p2%d;%p3%d;" +
"48;2;%p4%d;%p5%d;%p6%dm"
}
// For terminals that use "standard" SGR sequences, lets combine the
// foreground and background together.
if strings.HasPrefix(t.SetFg, "\x1b[") &&
strings.HasPrefix(t.SetBg, "\x1b[") &&
strings.HasSuffix(t.SetFg, "m") &&
strings.HasSuffix(t.SetBg, "m") {
fg := t.SetFg[:len(t.SetFg)-1]
r := regexp.MustCompile("%p1")
bg := r.ReplaceAllString(t.SetBg[2:], "%p2")
t.SetFgBg = fg + ";" + bg
}
return t, nil
}
func dotGoAddInt(w io.Writer, n string, i int) {
if i == 0 {
// initialized to 0, ignore
return
}
fmt.Fprintf(w, " %-13s %d,\n", n+":", i)
}
func dotGoAddStr(w io.Writer, n string, s string) {
if s == "" {
return
}
fmt.Fprintf(w, " %-13s %q,\n", n+":", s)
}
func dotGoAddArr(w io.Writer, n string, a []string) {
if len(a) == 0 {
return
}
fmt.Fprintf(w, " %-13s []string{", n+":")
did := false
for _, b := range a {
if did {
fmt.Fprint(w, ", ")
}
did = true
fmt.Fprintf(w, "%q", b)
}
fmt.Fprintln(w, "},")
}
func dotGoHeader(w io.Writer) {
fmt.Fprintf(w, "// Generated by %s (%s/%s) on %s.\n",
os.Args[0],
runtime.GOOS, runtime.GOARCH,
time.Now().Format(time.UnixDate))
fmt.Fprintln(w, "// DO NOT HAND-EDIT")
fmt.Fprintln(w, "")
fmt.Fprintln(w, "package tcell")
fmt.Fprintln(w, "")
fmt.Fprintln(w, "func init() {")
}
func dotGoTrailer(w io.Writer) {
fmt.Fprintln(w, "}")
}
func dotGoInfo(w io.Writer, t *tcell.Terminfo) {
fmt.Fprintln(w, " AddTerminfo(&Terminfo{")
dotGoAddStr(w, "Name", t.Name)
dotGoAddArr(w, "Aliases", t.Aliases)
dotGoAddInt(w, "Columns", t.Columns)
dotGoAddInt(w, "Lines", t.Lines)
dotGoAddInt(w, "Colors", t.Colors)
dotGoAddStr(w, "Bell", t.Bell)
dotGoAddStr(w, "Clear", t.Clear)
dotGoAddStr(w, "EnterCA", t.EnterCA)
dotGoAddStr(w, "ExitCA", t.ExitCA)
dotGoAddStr(w, "ShowCursor", t.ShowCursor)
dotGoAddStr(w, "HideCursor", t.HideCursor)
dotGoAddStr(w, "AttrOff", t.AttrOff)
dotGoAddStr(w, "Underline", t.Underline)
dotGoAddStr(w, "Bold", t.Bold)
dotGoAddStr(w, "Dim", t.Dim)
dotGoAddStr(w, "Blink", t.Blink)
dotGoAddStr(w, "Reverse", t.Reverse)
dotGoAddStr(w, "EnterKeypad", t.EnterKeypad)
dotGoAddStr(w, "ExitKeypad", t.ExitKeypad)
dotGoAddStr(w, "SetFg", t.SetFg)
dotGoAddStr(w, "SetBg", t.SetBg)
dotGoAddStr(w, "SetFgBg", t.SetFgBg)
dotGoAddStr(w, "PadChar", t.PadChar)
dotGoAddStr(w, "AltChars", t.AltChars)
dotGoAddStr(w, "EnterAcs", t.EnterAcs)
dotGoAddStr(w, "ExitAcs", t.ExitAcs)
dotGoAddStr(w, "EnableAcs", t.EnableAcs)
dotGoAddStr(w, "SetFgRGB", t.SetFgRGB)
dotGoAddStr(w, "SetBgRGB", t.SetBgRGB)
dotGoAddStr(w, "SetFgBgRGB", t.SetFgBgRGB)
dotGoAddStr(w, "Mouse", t.Mouse)
dotGoAddStr(w, "MouseMode", t.MouseMode)
dotGoAddStr(w, "SetCursor", t.SetCursor)
dotGoAddStr(w, "CursorBack1", t.CursorBack1)
dotGoAddStr(w, "CursorUp1", t.CursorUp1)
dotGoAddStr(w, "KeyUp", t.KeyUp)
dotGoAddStr(w, "KeyDown", t.KeyDown)
dotGoAddStr(w, "KeyRight", t.KeyRight)
dotGoAddStr(w, "KeyLeft", t.KeyLeft)
dotGoAddStr(w, "KeyInsert", t.KeyInsert)
dotGoAddStr(w, "KeyDelete", t.KeyDelete)
dotGoAddStr(w, "KeyBackspace", t.KeyBackspace)
dotGoAddStr(w, "KeyHome", t.KeyHome)
dotGoAddStr(w, "KeyEnd", t.KeyEnd)
dotGoAddStr(w, "KeyPgUp", t.KeyPgUp)
dotGoAddStr(w, "KeyPgDn", t.KeyPgDn)
dotGoAddStr(w, "KeyF1", t.KeyF1)
dotGoAddStr(w, "KeyF2", t.KeyF2)
dotGoAddStr(w, "KeyF3", t.KeyF3)
dotGoAddStr(w, "KeyF4", t.KeyF4)
dotGoAddStr(w, "KeyF5", t.KeyF5)
dotGoAddStr(w, "KeyF6", t.KeyF6)
dotGoAddStr(w, "KeyF7", t.KeyF7)
dotGoAddStr(w, "KeyF8", t.KeyF8)
dotGoAddStr(w, "KeyF9", t.KeyF9)
dotGoAddStr(w, "KeyF10", t.KeyF10)
dotGoAddStr(w, "KeyF11", t.KeyF11)
dotGoAddStr(w, "KeyF12", t.KeyF12)
dotGoAddStr(w, "KeyF13", t.KeyF13)
dotGoAddStr(w, "KeyF14", t.KeyF14)
dotGoAddStr(w, "KeyF15", t.KeyF15)
dotGoAddStr(w, "KeyF16", t.KeyF16)
dotGoAddStr(w, "KeyF17", t.KeyF17)
dotGoAddStr(w, "KeyF18", t.KeyF18)
dotGoAddStr(w, "KeyF19", t.KeyF19)
dotGoAddStr(w, "KeyF20", t.KeyF20)
dotGoAddStr(w, "KeyF21", t.KeyF21)
dotGoAddStr(w, "KeyF22", t.KeyF22)
dotGoAddStr(w, "KeyF23", t.KeyF23)
dotGoAddStr(w, "KeyF24", t.KeyF24)
dotGoAddStr(w, "KeyF25", t.KeyF25)
dotGoAddStr(w, "KeyF26", t.KeyF26)
dotGoAddStr(w, "KeyF27", t.KeyF27)
dotGoAddStr(w, "KeyF28", t.KeyF28)
dotGoAddStr(w, "KeyF29", t.KeyF29)
dotGoAddStr(w, "KeyF30", t.KeyF30)
dotGoAddStr(w, "KeyF31", t.KeyF31)
dotGoAddStr(w, "KeyF32", t.KeyF32)
dotGoAddStr(w, "KeyF33", t.KeyF33)
dotGoAddStr(w, "KeyF34", t.KeyF34)
dotGoAddStr(w, "KeyF35", t.KeyF35)
dotGoAddStr(w, "KeyF36", t.KeyF36)
dotGoAddStr(w, "KeyF37", t.KeyF37)
dotGoAddStr(w, "KeyF38", t.KeyF38)
dotGoAddStr(w, "KeyF39", t.KeyF39)
dotGoAddStr(w, "KeyF40", t.KeyF40)
dotGoAddStr(w, "KeyF41", t.KeyF41)
dotGoAddStr(w, "KeyF42", t.KeyF42)
dotGoAddStr(w, "KeyF43", t.KeyF43)
dotGoAddStr(w, "KeyF44", t.KeyF44)
dotGoAddStr(w, "KeyF45", t.KeyF45)
dotGoAddStr(w, "KeyF46", t.KeyF46)
dotGoAddStr(w, "KeyF47", t.KeyF47)
dotGoAddStr(w, "KeyF48", t.KeyF48)
dotGoAddStr(w, "KeyF49", t.KeyF49)
dotGoAddStr(w, "KeyF50", t.KeyF50)
dotGoAddStr(w, "KeyF51", t.KeyF51)
dotGoAddStr(w, "KeyF52", t.KeyF52)
dotGoAddStr(w, "KeyF53", t.KeyF53)
dotGoAddStr(w, "KeyF54", t.KeyF54)
dotGoAddStr(w, "KeyF55", t.KeyF55)
dotGoAddStr(w, "KeyF56", t.KeyF56)
dotGoAddStr(w, "KeyF57", t.KeyF57)
dotGoAddStr(w, "KeyF58", t.KeyF58)
dotGoAddStr(w, "KeyF59", t.KeyF59)
dotGoAddStr(w, "KeyF60", t.KeyF60)
dotGoAddStr(w, "KeyF61", t.KeyF61)
dotGoAddStr(w, "KeyF62", t.KeyF62)
dotGoAddStr(w, "KeyF63", t.KeyF63)
dotGoAddStr(w, "KeyF64", t.KeyF64)
dotGoAddStr(w, "KeyCancel", t.KeyCancel)
dotGoAddStr(w, "KeyPrint", t.KeyPrint)
dotGoAddStr(w, "KeyExit", t.KeyExit)
dotGoAddStr(w, "KeyHelp", t.KeyHelp)
dotGoAddStr(w, "KeyClear", t.KeyClear)
dotGoAddStr(w, "KeyBacktab", t.KeyBacktab)
dotGoAddStr(w, "KeyShfLeft", t.KeyShfLeft)
dotGoAddStr(w, "KeyShfRight", t.KeyShfRight)
dotGoAddStr(w, "KeyShfUp", t.KeyShfUp)
dotGoAddStr(w, "KeyShfDown", t.KeyShfDown)
dotGoAddStr(w, "KeyCtrlLeft", t.KeyCtrlLeft)
dotGoAddStr(w, "KeyCtrlRight", t.KeyCtrlRight)
dotGoAddStr(w, "KeyCtrlUp", t.KeyCtrlUp)
dotGoAddStr(w, "KeyCtrlDown", t.KeyCtrlDown)
dotGoAddStr(w, "KeyMetaLeft", t.KeyMetaLeft)
dotGoAddStr(w, "KeyMetaRight", t.KeyMetaRight)
dotGoAddStr(w, "KeyMetaUp", t.KeyMetaUp)
dotGoAddStr(w, "KeyMetaDown", t.KeyMetaDown)
dotGoAddStr(w, "KeyAltLeft", t.KeyAltLeft)
dotGoAddStr(w, "KeyAltRight", t.KeyAltRight)
dotGoAddStr(w, "KeyAltUp", t.KeyAltUp)
dotGoAddStr(w, "KeyAltDown", t.KeyAltDown)
dotGoAddStr(w, "KeyAltShfLeft", t.KeyAltShfLeft)
dotGoAddStr(w, "KeyAltShfRight", t.KeyAltShfRight)
dotGoAddStr(w, "KeyAltShfUp", t.KeyAltShfUp)
dotGoAddStr(w, "KeyAltShfDown", t.KeyAltShfDown)
dotGoAddStr(w, "KeyMetaShfLeft", t.KeyMetaShfLeft)
dotGoAddStr(w, "KeyMetaShfRight", t.KeyMetaShfRight)
dotGoAddStr(w, "KeyMetaShfUp", t.KeyMetaShfUp)
dotGoAddStr(w, "KeyMetaShfDown", t.KeyMetaShfDown)
dotGoAddStr(w, "KeyCtrlShfLeft", t.KeyCtrlShfLeft)
dotGoAddStr(w, "KeyCtrlShfRight", t.KeyCtrlShfRight)
dotGoAddStr(w, "KeyCtrlShfUp", t.KeyCtrlShfUp)
dotGoAddStr(w, "KeyCtrlShfDown", t.KeyCtrlShfDown)
dotGoAddStr(w, "KeyShfHome", t.KeyShfHome)
dotGoAddStr(w, "KeyShfEnd", t.KeyShfEnd)
dotGoAddStr(w, "KeyCtrlHome", t.KeyCtrlHome)
dotGoAddStr(w, "KeyCtrlEnd", t.KeyCtrlEnd)
dotGoAddStr(w, "KeyMetaHome", t.KeyMetaHome)
dotGoAddStr(w, "KeyMetaEnd", t.KeyMetaEnd)
dotGoAddStr(w, "KeyAltHome", t.KeyAltHome)
dotGoAddStr(w, "KeyAltEnd", t.KeyAltEnd)
dotGoAddStr(w, "KeyCtrlShfHome", t.KeyCtrlShfHome)
dotGoAddStr(w, "KeyCtrlShfEnd", t.KeyCtrlShfEnd)
dotGoAddStr(w, "KeyMetaShfHome", t.KeyMetaShfHome)
dotGoAddStr(w, "KeyMetaShfEnd", t.KeyMetaShfEnd)
dotGoAddStr(w, "KeyAltShfHome", t.KeyAltShfHome)
dotGoAddStr(w, "KeyAltShfEnd", t.KeyAltShfEnd)
fmt.Fprintln(w, " })")
}
func MkInfo() ([]byte, error) {
jsonfile := ""
gofile := ""
nofatal := true
quiet := true
var e error
js := []byte{}
args := []string{os.Getenv("TERM")}
tdata := make(map[string]*tcell.Terminfo)
adata := make(map[string]string)
for _, term := range args {
if arr := strings.SplitN(term, "=", 2); len(arr) == 2 {
adata[arr[0]] = arr[1]
} else if t, e := getinfo(term); e != nil {
if !quiet {
fmt.Fprintf(os.Stderr,
"Failed loading %s: %v\n", term, e)
}
if !nofatal {
os.Exit(1)
}
} else {
tdata[t.Name] = t
}
}
for alias, canon := range adata {
if t, ok := tdata[canon]; ok {
t.Aliases = append(t.Aliases, alias)
// sort aliases to avoid extra diffs
sort.Strings(t.Aliases)
} else {
if !quiet {
fmt.Fprintf(os.Stderr,
"Alias %s missing canonical %s\n",
alias, canon)
}
if !nofatal {
os.Exit(1)
}
}
}
if gofile != "" {
w := os.Stdout
if gofile != "-" {
if w, e = os.Create(gofile); e != nil {
return []byte{}, fmt.Errorf("Failed: %v", e)
}
}
dotGoHeader(w)
for _, term := range args {
if t := tdata[term]; t != nil {
dotGoInfo(w, t)
}
}
dotGoTrailer(w)
if w != os.Stdout {
w.Close()
}
} else if jsonfile != "" {
w := os.Stdout
if jsonfile != "-" {
if w, e = os.Create(jsonfile); e != nil {
fmt.Fprintf(os.Stderr, "Failed: %v", e)
}
}
for _, term := range args {
if t := tdata[term]; t != nil {
js, e = json.Marshal(t)
fmt.Fprintln(w, string(js))
}
// arguably if there is more than one term, this
// should be a javascript array, but that's not how
// we load it. We marshal objects one at a time from
// the file.
}
if e != nil {
return []byte{}, fmt.Errorf("Failed: %v", e)
}
} else {
for _, term := range args {
if t := tdata[term]; t != nil {
js, e := json.Marshal(tdata[term])
if e != nil {
return []byte{}, fmt.Errorf("Failed ", e)
}
return js, nil
}
}
}
return []byte{}, nil
}

View File

@@ -6,11 +6,17 @@ import (
"os"
"strings"
"layeh.com/gopher-luar"
"github.com/layeh/gopher-luar"
"github.com/yuin/gopher-lua"
)
var loadedPlugins map[string]string
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,
@@ -112,39 +118,51 @@ func LuaFunctionJob(function string) func(string, ...string) {
}
}
// luaPluginName convert a human-friendly plugin name into a valid lua variable name.
func luaPluginName(name string) string {
return strings.Replace(name, "-", "_", -1)
}
// 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"
loadedPlugins = make(map[string]string)
for _, plugin := range ListRuntimeFiles(RTPlugin) {
pluginName := plugin.Name()
if _, ok := loadedPlugins[pluginName]; ok {
continue
if err := L.DoString(pluginDef + string(data)); err != nil {
TermMessage(err)
continue
}
loadedPlugins = append(loadedPlugins, pluginName)
}
}
}
}
data, err := plugin.Data()
if err != nil {
TermMessage("Error loading plugin: " + pluginName)
continue
for _, pluginName := range preInstalledPlugins {
alreadyExists := false
for _, pl := range loadedPlugins {
if pl == pluginName {
alreadyExists = true
break
}
}
if !alreadyExists {
plugin := "runtime/plugins/" + pluginName + "/" + pluginName + ".lua"
data, err := Asset(plugin)
if err != nil {
TermMessage("Error loading pre-installed 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
}
pluginLuaName := luaPluginName(pluginName)
pluginDef := "\nlocal P = {}\n" + pluginLuaName + " = P\nsetmetatable(" + pluginLuaName + ", {__index = _G})\nsetfenv(1, P)\n"
if err := L.DoString(pluginDef + string(data)); err != nil {
TermMessage(err)
continue
loadedPlugins = append(loadedPlugins, pluginName)
}
loadedPlugins[pluginName] = pluginLuaName
}
if _, err := os.Stat(configDir + "/init.lua"); err == nil {
@@ -153,6 +171,6 @@ func LoadPlugins() {
if err := L.DoString(pluginDef + string(data)); err != nil {
TermMessage(err)
}
loadedPlugins["init"] = "init"
loadedPlugins = append(loadedPlugins, "init")
}
}

View File

@@ -1,615 +0,0 @@
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
)
// 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
// PluginDependency descripes a dependency to another plugin or micro itself.
type PluginDependency struct {
Name string
Range semver.Range
}
// PluginDependencies is a slice of PluginDependency
type PluginDependencies []*PluginDependency
func (pp *PluginPackage) String() string {
buf := new(bytes.Buffer)
buf.WriteString("Plugin: ")
buf.WriteString(pp.Name)
buf.WriteRune('\n')
if pp.Author != "" {
buf.WriteString("Author: ")
buf.WriteString(pp.Author)
buf.WriteRune('\n')
}
if pp.Description != "" {
buf.WriteRune('\n')
buf.WriteString(pp.Description)
}
return buf.String()
}
func fetchAllSources(count int, fetcher func(i int) PluginPackages) PluginPackages {
wgQuery := new(sync.WaitGroup)
wgQuery.Add(count)
results := make(chan PluginPackages)
wgDone := new(sync.WaitGroup)
wgDone.Add(1)
var packages PluginPackages
for i := 0; i < count; i++ {
go func(i int) {
results <- fetcher(i)
wgQuery.Done()
}(i)
}
go func() {
packages = make(PluginPackages, 0)
for res := range results {
packages = append(packages, res...)
}
wgDone.Done()
}()
wgQuery.Wait()
close(results)
wgDone.Wait()
return packages
}
// Fetch retrieves all available PluginPackages from the given channels
func (pc PluginChannels) Fetch() 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()
}
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 (pv PluginVersions) Less(i, j int) bool {
return pv[i].Version.GT(pv[j].Version)
}
// Match returns true if the package matches a given search text
func (pp PluginPackage) Match(text string) bool {
text = strings.ToLower(text)
for _, t := range pp.Tags {
if strings.ToLower(t) == text {
return true
}
}
if strings.Contains(strings.ToLower(pp.Name), text) {
return true
}
if strings.Contains(strings.ToLower(pp.Description), text) {
return true
}
return false
}
// IsInstallable returns true if the package can be installed.
func (pp PluginPackage) IsInstallable() 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, lpname := range loadedPlugins {
version := GetInstalledPluginVersion(lpname)
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 ""
}
// DownloadAndInstall downloads and installs the given plugin and version
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()
target, err := os.Create(targetName)
if err != nil {
return err
}
defer target.Close()
if _, err = io.Copy(target, content); err != nil {
return err
}
}
}
return nil
}
func (pl PluginPackages) Get(name string) *PluginPackage {
for _, p := range pl {
if p.Name == name {
return p
}
}
return nil
}
func (pl PluginPackages) GetAllVersions(name string) PluginVersions {
result := make(PluginVersions, 0)
p := pl.Get(name)
if p != nil {
for _, v := range p.Versions {
result = append(result, v)
}
}
return result
}
func (req PluginDependencies) Join(other PluginDependencies) PluginDependencies {
m := make(map[string]*PluginDependency)
for _, r := range req {
m[r.Name] = r
}
for _, o := range other {
cur, ok := m[o.Name]
if ok {
m[o.Name] = &PluginDependency{
o.Name,
o.Range.AND(cur.Range),
}
} else {
m[o.Name] = o
}
}
result := make(PluginDependencies, 0, len(m))
for _, v := range m {
result = append(result, v)
}
return result
}
// Resolve resolves dependencies between different plugins
func (all PluginPackages) Resolve(selectedVersions PluginVersions, open PluginDependencies) (PluginVersions, error) {
if len(open) == 0 {
return selectedVersions, nil
}
currentRequirement, stillOpen := open[0], open[1:]
if currentRequirement != nil {
if selVersion := selectedVersions.find(currentRequirement.Name); selVersion != nil {
if currentRequirement.Range(selVersion.Version) {
return all.Resolve(selectedVersions, stillOpen)
}
return nil, fmt.Errorf("unable to find a matching version for \"%s\"", currentRequirement.Name)
}
availableVersions := all.GetAllVersions(currentRequirement.Name)
sort.Sort(availableVersions)
for _, version := range availableVersions {
if currentRequirement.Range(version.Version) {
resolved, err := all.Resolve(append(selectedVersions, version), stillOpen.Join(version.Require))
if err == nil {
return resolved, nil
}
}
}
return nil, fmt.Errorf("unable to find a matching version for \"%s\"", currentRequirement.Name)
}
return selectedVersions, nil
}
func (pv PluginVersions) install() {
anyInstalled := false
currentlyInstalled := GetInstalledVersions(true)
for _, sel := range pv {
if sel.pack.Name != CorePluginName {
shouldInstall := true
if pv := currentlyInstalled.find(sel.pack.Name); pv != nil {
if pv.Version.NE(sel.Version) {
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)
return
}
delete(loadedPlugins, name)
}
// Install installs the plugin
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()
}
// UpdatePlugins updates the given plugins
func UpdatePlugins(plugins []string) {
// if no plugins are specified, update all installed plugins.
if len(plugins) == 0 {
for name := range loadedPlugins {
plugins = append(plugins, name)
}
}
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()
}

View File

@@ -1,55 +0,0 @@
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)
}
}

View File

@@ -1,189 +0,0 @@
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 {
realpath, _ := filepath.EvalSymlinks(filepath.Join(configDir, "plugins", f.Name()))
realpathStat, _ := os.Stat(realpath)
if realpathStat.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

View File

@@ -21,14 +21,12 @@ var (
)
// BeginSearch starts a search
func BeginSearch(searchStr string) {
func BeginSearch() {
searchHistory = append(searchHistory, "")
messenger.historyNum = len(searchHistory) - 1
searching = true
messenger.response = searchStr
messenger.cursorx = Count(searchStr)
messenger.Message("Find: ")
messenger.hasPrompt = true
messenger.Message("Find: ")
}
// EndSearch stops the current search
@@ -43,27 +41,13 @@ func EndSearch() {
}
}
// exit the search mode, reset active search phrase, and clear status bar
func ExitSearch(v *View) {
lastSearch = ""
searching = false
messenger.hasPrompt = false
messenger.Clear()
messenger.Reset()
v.Cursor.ResetSelection()
}
// HandleSearchEvent takes an event and a view and will do a real time match from the messenger's output
// to the current buffer. It searches down the buffer.
func HandleSearchEvent(event tcell.Event, v *View) {
switch e := event.(type) {
case *tcell.EventKey:
switch e.Key() {
case tcell.KeyEscape:
// Exit the search mode
ExitSearch(v)
return
case tcell.KeyCtrlQ, tcell.KeyCtrlC, tcell.KeyEnter:
case tcell.KeyCtrlQ, tcell.KeyCtrlC, tcell.KeyEscape, tcell.KeyEnter:
// Done
EndSearch()
return

View File

@@ -8,24 +8,13 @@ import (
"strconv"
"strings"
"github.com/yosuke-furukawa/json5/encoding/json5"
"github.com/zyedidia/glob"
"github.com/zyedidia/json5/encoding/json5"
)
type optionValidator func(string, interface{}) error
// The options that the user can set
var globalSettings map[string]interface{}
// Options with validators
var optionValidators = map[string]optionValidator{
"tabsize": validatePositiveValue,
"scrollmargin": validateNonNegativeValue,
"scrollspeed": validateNonNegativeValue,
"colorscheme": validateColorscheme,
"colorcolumn": validateNonNegativeValue,
}
// InitGlobalSettings initializes the options map and sets all options to their default values
func InitGlobalSettings() {
defaults := DefaultGlobalSettings()
@@ -175,34 +164,21 @@ func GetOption(name string) interface{} {
// Note that colorscheme is a global only option
func DefaultGlobalSettings() map[string]interface{} {
return map[string]interface{}{
"autoindent": true,
"keepautoindent": false,
"autosave": false,
"colorcolumn": float64(0),
"colorscheme": "default",
"cursorline": true,
"eofnewline": false,
"rmtrailingws": false,
"ignorecase": false,
"indentchar": " ",
"infobar": true,
"ruler": true,
"savecursor": false,
"saveundo": false,
"scrollspeed": float64(2),
"scrollmargin": float64(3),
"softwrap": false,
"splitRight": true,
"splitBottom": true,
"statusline": true,
"syntax": true,
"tabsize": float64(4),
"tabstospaces": false,
"pluginchannels": []string{
"https://raw.githubusercontent.com/micro-editor/plugin-channel/master/channel.json",
},
"pluginrepos": []string{},
"useprimary": true,
"autoindent": true,
"colorscheme": "zenburn",
"cursorline": true,
"ignorecase": false,
"indentchar": " ",
"infobar": true,
"ruler": true,
"savecursor": false,
"saveundo": false,
"scrollspeed": float64(2),
"scrollmargin": float64(3),
"statusline": true,
"syntax": true,
"tabsize": float64(4),
"tabstospaces": false,
}
}
@@ -210,29 +186,20 @@ func DefaultGlobalSettings() map[string]interface{} {
// Note that filetype is a local only option
func DefaultLocalSettings() map[string]interface{} {
return map[string]interface{}{
"autoindent": true,
"keepautoindent": false,
"autosave": false,
"colorcolumn": float64(0),
"cursorline": true,
"eofnewline": false,
"rmtrailingws": false,
"filetype": "Unknown",
"ignorecase": false,
"indentchar": " ",
"ruler": true,
"savecursor": false,
"saveundo": false,
"scrollspeed": float64(2),
"scrollmargin": float64(3),
"softwrap": false,
"splitRight": true,
"splitBottom": true,
"statusline": true,
"syntax": true,
"tabsize": float64(4),
"tabstospaces": false,
"useprimary": true,
"autoindent": true,
"cursorline": true,
"filetype": "Unknown",
"ignorecase": false,
"indentchar": " ",
"ruler": true,
"savecursor": false,
"saveundo": false,
"scrollspeed": float64(2),
"scrollmargin": float64(3),
"statusline": true,
"syntax": true,
"tabsize": float64(4),
"tabstospaces": false,
}
}
@@ -241,6 +208,12 @@ func DefaultLocalSettings() map[string]interface{} {
// is local only it will set the local version
// Use setlocal to force an option to be set locally
func SetOption(option, value string) error {
if option == "colorscheme" {
if !ColorschemeExists(value) {
return errors.New(value + " is not a valid colorscheme")
}
}
if _, ok := globalSettings[option]; !ok {
if _, ok := CurView().Buf.Settings[option]; !ok {
return errors.New("Invalid option")
@@ -249,33 +222,23 @@ 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")
}
nativeValue = b
globalSettings[option] = b
} else if kind == reflect.String {
nativeValue = value
globalSettings[option] = value
} else if kind == reflect.Float64 {
i, err := strconv.Atoi(value)
if err != nil {
return errors.New("Invalid value")
}
nativeValue = float64(i)
} else {
return errors.New("Option has unsupported value type")
globalSettings[option] = float64(i)
}
if err := optionIsValid(option, nativeValue); err != nil {
return err
}
globalSettings[option] = nativeValue
if option == "colorscheme" {
LoadSyntaxFiles()
for _, tab := range tabs {
@@ -312,33 +275,23 @@ 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")
}
nativeValue = b
buf.Settings[option] = b
} else if kind == reflect.String {
nativeValue = value
buf.Settings[option] = value
} else if kind == reflect.Float64 {
i, err := strconv.Atoi(value)
if err != nil {
return errors.New("Invalid value")
}
nativeValue = float64(i)
} else {
return errors.New("Option has unsupported value type")
buf.Settings[option] = float64(i)
}
if err := optionIsValid(option, nativeValue); err != nil {
return err
}
buf.Settings[option] = nativeValue
if option == "statusline" {
view.ToggleStatusLine()
if buf.Settings["syntax"].(bool) {
@@ -374,55 +327,3 @@ 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
}

View File

@@ -1,30 +1,24 @@
package main
// SpltType specifies whether a split is horizontal or vertical
type SplitType bool
const (
// VerticalSplit type
VerticalSplit = false
// HorizontalSplit type
VerticalSplit = false
HorizontalSplit = true
)
// A Node on the split tree
type Node interface {
VSplit(buf *Buffer, splitIndex int)
HSplit(buf *Buffer, splitIndex int)
VSplit(buf *Buffer)
HSplit(buf *Buffer)
String() string
}
// A LeafNode is an actual split so it contains a view
type LeafNode struct {
view *View
parent *SplitTree
}
// NewLeafNode returns a new leaf node containing the given view
func NewLeafNode(v *View, parent *SplitTree) *LeafNode {
n := new(LeafNode)
n.view = v
@@ -33,7 +27,6 @@ func NewLeafNode(v *View, parent *SplitTree) *LeafNode {
return n
}
// A SplitTree is a Node itself and it contains other nodes
type SplitTree struct {
kind SplitType
@@ -43,122 +36,64 @@ type SplitTree struct {
x int
y int
width int
height int
lockWidth bool
lockHeight bool
width int
height int
tabNum int
}
// VSplit creates a vertical split
func (l *LeafNode) VSplit(buf *Buffer, splitIndex int) {
if splitIndex < 0 {
splitIndex = 0
}
func (l *LeafNode) VSplit(buf *Buffer) {
tab := tabs[l.parent.tabNum]
if l.parent.kind == VerticalSplit {
if splitIndex > len(l.parent.children) {
splitIndex = len(l.parent.children)
}
newView := NewView(buf)
newView.TabNum = l.parent.tabNum
newView.Num = len(tab.views)
l.parent.children = append(l.parent.children, NewLeafNode(newView, l.parent))
l.parent.children = append(l.parent.children, nil)
copy(l.parent.children[splitIndex+1:], l.parent.children[splitIndex:])
l.parent.children[splitIndex] = NewLeafNode(newView, l.parent)
tab.views = append(tab.views, nil)
copy(tab.views[splitIndex+1:], tab.views[splitIndex:])
tab.views[splitIndex] = newView
tab.CurView = splitIndex
tab.curView++
tab.views = append(tab.views, newView)
} else {
if splitIndex > 1 {
splitIndex = 1
}
s := new(SplitTree)
s.kind = VerticalSplit
s.parent = l.parent
s.tabNum = l.parent.tabNum
newView := NewView(buf)
newView.TabNum = l.parent.tabNum
if splitIndex == 1 {
s.children = []Node{l, NewLeafNode(newView, s)}
} else {
s.children = []Node{NewLeafNode(newView, s), l}
}
l.parent.children[search(l.parent.children, l)] = s
l.parent = s
tab.views = append(tab.views, nil)
copy(tab.views[splitIndex+1:], tab.views[splitIndex:])
tab.views[splitIndex] = newView
tab.CurView = splitIndex
}
tab.Resize()
}
// HSplit creates a horizontal split
func (l *LeafNode) HSplit(buf *Buffer, splitIndex int) {
if splitIndex < 0 {
splitIndex = 0
}
tab := tabs[l.parent.tabNum]
if l.parent.kind == HorizontalSplit {
if splitIndex > len(l.parent.children) {
splitIndex = len(l.parent.children)
}
newView := NewView(buf)
newView.TabNum = l.parent.tabNum
l.parent.children = append(l.parent.children, nil)
copy(l.parent.children[splitIndex+1:], l.parent.children[splitIndex:])
l.parent.children[splitIndex] = NewLeafNode(newView, l.parent)
tab.views = append(tab.views, nil)
copy(tab.views[splitIndex+1:], tab.views[splitIndex:])
tab.views[splitIndex] = newView
tab.CurView = splitIndex
} else {
if splitIndex > 1 {
splitIndex = 1
}
s := new(SplitTree)
s.kind = HorizontalSplit
s.tabNum = l.parent.tabNum
s.parent = l.parent
newView := NewView(buf)
newView.TabNum = l.parent.tabNum
newView.Num = len(tab.views)
if splitIndex == 1 {
s.children = []Node{l, NewLeafNode(newView, s)}
} else {
s.children = []Node{NewLeafNode(newView, s), l}
}
s.children = []Node{l, NewLeafNode(newView, s)}
l.parent.children[search(l.parent.children, l)] = s
l.parent = s
tab.views = append(tab.views, nil)
copy(tab.views[splitIndex+1:], tab.views[splitIndex:])
tab.views[splitIndex] = newView
tab.CurView = splitIndex
tab.curView++
tab.views = append(tab.views, newView)
}
}
func (l *LeafNode) HSplit(buf *Buffer) {
tab := tabs[l.parent.tabNum]
if l.parent.kind == HorizontalSplit {
newView := NewView(buf)
newView.TabNum = l.parent.tabNum
newView.Num = len(tab.views)
l.parent.children = append(l.parent.children, NewLeafNode(newView, l.parent))
tab.curView++
tab.views = append(tab.views, newView)
} else {
s := new(SplitTree)
s.kind = HorizontalSplit
s.parent = l.parent
newView := NewView(buf)
newView.TabNum = l.parent.tabNum
newView.Num = len(tab.views)
s.children = []Node{l, NewLeafNode(newView, s)}
l.parent.children[search(l.parent.children, l)] = s
l.parent = s
tab.curView++
tab.views = append(tab.views, newView)
}
tab.Resize()
}
// Delete deletes a split
func (l *LeafNode) Delete() {
i := search(l.parent.children, l)
@@ -175,12 +110,11 @@ func (l *LeafNode) Delete() {
for i, v := range tab.views {
v.Num = i
}
if tab.CurView > 0 {
tab.CurView--
if tab.curView > 0 {
tab.curView--
}
}
// Cleanup rearranges all the parents after a split has been deleted
func (s *SplitTree) Cleanup() {
for i, node := range s.children {
if n, ok := node.(*SplitTree); ok {
@@ -196,85 +130,41 @@ func (s *SplitTree) Cleanup() {
}
}
// ResizeSplits resizes all the splits correctly
func (s *SplitTree) ResizeSplits() {
lockedWidth := 0
lockedHeight := 0
lockedChildren := 0
for _, node := range s.children {
for i, node := range s.children {
if n, ok := node.(*LeafNode); ok {
if s.kind == VerticalSplit {
if n.view.LockWidth {
lockedWidth += n.view.Width
lockedChildren++
}
} else {
if n.view.LockHeight {
lockedHeight += n.view.Height
lockedChildren++
}
}
} else if n, ok := node.(*SplitTree); ok {
if s.kind == VerticalSplit {
if n.lockWidth {
lockedWidth += n.width
lockedChildren++
}
} else {
if n.lockHeight {
lockedHeight += n.height
lockedChildren++
}
}
}
}
x, y := 0, 0
for _, node := range s.children {
if n, ok := node.(*LeafNode); ok {
if s.kind == VerticalSplit {
if !n.view.LockWidth {
n.view.Width = (s.width - lockedWidth) / (len(s.children) - lockedChildren)
}
n.view.Height = s.height
n.view.width = s.width / len(s.children)
n.view.height = s.height
n.view.x = s.x + x
n.view.x = s.x + n.view.width*i
n.view.y = s.y
x += n.view.Width
} else {
if !n.view.LockHeight {
n.view.Height = (s.height - lockedHeight) / (len(s.children) - lockedChildren)
}
n.view.Width = s.width
n.view.height = s.height / len(s.children)
n.view.width = s.width
n.view.y = s.y + y
n.view.y = s.y + n.view.height*i
n.view.x = s.x
y += n.view.Height
}
if n.view.Buf.Settings["statusline"].(bool) {
n.view.Height--
n.view.height--
}
n.view.ToggleTabbar()
n.view.matches = Match(n.view)
} else if n, ok := node.(*SplitTree); ok {
if s.kind == VerticalSplit {
if !n.lockWidth {
n.width = (s.width - lockedWidth) / (len(s.children) - lockedChildren)
}
n.width = s.width / len(s.children)
n.height = s.height
n.x = s.x + x
n.x = s.x + n.width*i
n.y = s.y
x += n.width
} else {
if !n.lockHeight {
n.height = (s.height - lockedHeight) / (len(s.children) - lockedChildren)
}
n.height = s.height / len(s.children)
n.width = s.width
n.y = s.y + y
n.y = s.y + n.height*i
n.x = s.x
y += n.height
}
n.ResizeSplits()
}
@@ -282,7 +172,7 @@ func (s *SplitTree) ResizeSplits() {
}
func (l *LeafNode) String() string {
return l.view.Buf.GetName()
return l.view.Buf.Name
}
func search(haystack []Node, needle Node) int {
@@ -303,11 +193,8 @@ func findView(haystack []*View, needle *View) int {
return 0
}
// VSplit is here just to make SplitTree fit the Node interface
func (s *SplitTree) VSplit(buf *Buffer, splitIndex int) {}
// HSplit is here just to make SplitTree fit the Node interface
func (s *SplitTree) HSplit(buf *Buffer, splitIndex int) {}
func (s *SplitTree) VSplit(buf *Buffer) {}
func (s *SplitTree) HSplit(buf *Buffer) {}
func (s *SplitTree) String() string {
str := "["

View File

@@ -15,9 +15,9 @@ type Statusline struct {
// Display draws the statusline to the screen
func (sline *Statusline) Display() {
// We'll draw the line at the lowest line in the view
y := sline.view.Height + sline.view.y
y := sline.view.height + sline.view.y
file := sline.view.Buf.GetName()
file := sline.view.Buf.Name
// If the buffer is dirty (has been modified) write a little '+'
if sline.view.Buf.IsModified {
@@ -36,12 +36,9 @@ func (sline *Statusline) Display() {
// Add the filetype
file += " " + sline.view.Buf.FileType()
rightText := ""
if len(helpBinding) > 0 {
rightText = helpBinding + " for help "
if sline.view.Type == vtHelp {
rightText = helpBinding + " to close help "
}
rightText := helpBinding + " for help "
if sline.view.Help {
rightText = helpBinding + " to close help "
}
statusLineStyle := defStyle.Reverse(true)
@@ -56,11 +53,11 @@ func (sline *Statusline) Display() {
screen.SetContent(viewX, y, ' ', nil, statusLineStyle)
viewX++
}
for x := 0; x < sline.view.Width; x++ {
for x := 0; x < sline.view.width; x++ {
if x < len(fileRunes) {
screen.SetContent(viewX+x, y, fileRunes[x], nil, statusLineStyle)
} else if x >= sline.view.Width-len(rightText) && x < len(rightText)+sline.view.Width-len(rightText) {
screen.SetContent(viewX+x, y, []rune(rightText)[x-sline.view.Width+len(rightText)], nil, statusLineStyle)
} else if x >= sline.view.width-len(rightText) && x < len(rightText)+sline.view.width-len(rightText) {
screen.SetContent(viewX+x, y, []rune(rightText)[x-sline.view.width+len(rightText)], nil, statusLineStyle)
} else {
screen.SetContent(viewX+x, y, ' ', nil, statusLineStyle)
}

View File

@@ -12,7 +12,9 @@ type Tab struct {
// multiple views with splits
views []*View
// This is the current view for this tab
CurView int
curView int
// Generally this is the name of the current view's buffer
name string
tree *SplitTree
}
@@ -35,14 +37,11 @@ func NewTabFromView(v *View) *Tab {
t.tree.height--
}
t.Resize()
return t
}
// SetNum sets all this tab's views to have the correct tab number
func (t *Tab) SetNum(num int) {
t.tree.tabNum = num
for _, v := range t.views {
v.TabNum = num
}
@@ -62,16 +61,12 @@ func (t *Tab) Resize() {
}
t.tree.ResizeSplits()
for i, v := range t.views {
v.Num = i
}
}
// CurView returns the current view
func CurView() *View {
curTab := tabs[curTab]
return curTab.views[curTab.CurView]
return curTab.views[curTab.curView]
}
// TabbarString returns the string that should be displayed in the tabbar
@@ -87,7 +82,7 @@ func TabbarString() (string, map[int]int) {
} else {
str += " "
}
str += t.views[t.CurView].Buf.GetName()
str += t.views[t.curView].Buf.Name
if i == curTab {
str += "]"
} else {

View File

@@ -23,7 +23,7 @@ func Count(s string) int {
return utf8.RuneCountInString(s)
}
// NumOccurrences counts the number of occurrences of a byte in a string
// NumOccurrences counts the number of occurences of a byte in a string
func NumOccurrences(s string, c byte) int {
var n int
for i := 0; i < len(s); i++ {
@@ -65,7 +65,7 @@ func Max(a, b int) int {
func IsWordChar(str string) bool {
if len(str) > 1 {
// Unicode
return true
return false
}
c := str[0]
return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c == '_')
@@ -91,18 +91,6 @@ func Insert(str string, pos int, value string) string {
return string([]rune(str)[:pos]) + value + string([]rune(str)[pos:])
}
// MakeRelative will attempt to make a relative path between path and base
func MakeRelative(path, base string) (string, error) {
if len(path) > 0 {
rel, err := filepath.Rel(base, path)
if err != nil {
return path, err
}
return rel, nil
}
return path, nil
}
// GetLeadingWhitespace returns the leading whitespace of the given string
func GetLeadingWhitespace(str string) string {
ws := ""
@@ -169,19 +157,7 @@ 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)
lineIdx := 0
for _, ch := range str {
switch ch {
case '\t':
ts := tabsize - (lineIdx % tabsize)
sw += ts
lineIdx += ts
case '\n':
lineIdx = 0
default:
lineIdx++
}
}
sw += NumOccurrences(str, '\t') * (tabsize - 1)
return sw
}
@@ -189,22 +165,16 @@ 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 - (lineIdx % tabsize)
w = tabsize
} else {
w = runewidth.RuneWidth(ch)
}
if w > 1 {
count += (w - 1)
}
if ch == '\n' {
lineIdx = 0
} else {
lineIdx += w
}
}
return count
}
@@ -256,68 +226,46 @@ func FuncName(i interface{}) string {
return runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name()
}
// SplitCommandArgs separates multiple command arguments which may be quoted.
// 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
curArg := new(bytes.Buffer)
inQuote := false
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()
inQuote = false
escape = false
if strings.HasPrefix(str, `"`) && strings.HasSuffix(str, `"`) {
if unquoted, err := strconv.Unquote(str); err == nil {
str = unquoted
}
}
result = append(result, str)
curArg.Reset()
}
for _, r := range input {
if r == ' ' && curQuote == nil {
if r == ' ' && !inQuote {
appendResult()
} else {
runeHandled := false
appendRuneToBuff := func() {
if curQuote != nil {
curQuote.WriteRune(r)
} else {
curArg.WriteRune(r)
}
runeHandled = true
}
curArg.WriteRune(r)
if r == '"' && curQuote == nil {
curQuote = new(bytes.Buffer)
appendRuneToBuff()
if r == '"' && !inQuote {
inQuote = true
} else {
if curQuote != nil && !escape {
if inQuote && !escape {
if r == '"' {
appendRuneToBuff()
finishQuote()
} else if r == '\\' {
appendRuneToBuff()
inQuote = false
}
if r == '\\' {
escape = true
continue
}
}
}
if !runeHandled {
appendRuneToBuff()
}
}
escape = false

View File

@@ -50,15 +50,15 @@ func TestIsWordChar(t *testing.T) {
if IsWordChar("_") == false {
t.Errorf("IsWordChar(_) = false")
}
if IsWordChar("ß") == false {
t.Errorf("IsWordChar(ß) = false")
}
if IsWordChar("~") == true {
t.Errorf("IsWordChar(~) = true")
}
if IsWordChar(" ") == true {
t.Errorf("IsWordChar( ) = true")
}
if IsWordChar("ß") == true {
t.Errorf("IsWordChar(ß) = true")
}
if IsWordChar(")") == true {
t.Errorf("IsWordChar()) = true")
}
@@ -100,14 +100,9 @@ func TestJoinAndSplitCommandArgs(t *testing.T) {
Query string
Wanted []string
}{
{`"hallo""Welt"`, []string{`halloWelt`}},
{`"hallo" "Welt"`, []string{`hallo`, `Welt`}},
{`"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 {
@@ -116,41 +111,3 @@ func TestJoinAndSplitCommandArgs(t *testing.T) {
}
}
}
func TestStringWidth(t *testing.T) {
tabsize := 4
if w := StringWidth("1\t2", tabsize); w != 5 {
t.Error("StringWidth 1 Failed. Got", w)
}
if w := StringWidth("\t", tabsize); w != 4 {
t.Error("StringWidth 2 Failed. Got", w)
}
if w := StringWidth("1\t", tabsize); w != 4 {
t.Error("StringWidth 3 Failed. Got", w)
}
if w := StringWidth("\t\t", tabsize); w != 8 {
t.Error("StringWidth 4 Failed. Got", w)
}
if w := StringWidth("12\t2\t", tabsize); w != 8 {
t.Error("StringWidth 5 Failed. Got", w)
}
}
func TestWidthOfLargeRunes(t *testing.T) {
tabsize := 4
if w := WidthOfLargeRunes("1\t2", tabsize); w != 2 {
t.Error("WidthOfLargeRunes 1 Failed. Got", w)
}
if w := WidthOfLargeRunes("\t", tabsize); w != 3 {
t.Error("WidthOfLargeRunes 2 Failed. Got", w)
}
if w := WidthOfLargeRunes("1\t", tabsize); w != 2 {
t.Error("WidthOfLargeRunes 3 Failed. Got", w)
}
if w := WidthOfLargeRunes("\t\t", tabsize); w != 6 {
t.Error("WidthOfLargeRunes 4 Failed. Got", w)
}
if w := WidthOfLargeRunes("12\t2\t", tabsize); w != 3 {
t.Error("WidthOfLargeRunes 5 Failed. Got", w)
}
}

View File

@@ -1,24 +1,14 @@
package main
import (
"os"
"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.
@@ -31,15 +21,16 @@ type View struct {
// The leftmost column, used for horizontal scrolling
leftCol int
// Percentage of the terminal window that this view takes up (from 0 to 100)
widthPercent int
heightPercent int
// Specifies whether or not this view holds a help buffer
Type ViewType
Help bool
// Actual width and height
Width int
Height int
LockWidth bool
LockHeight bool
// Actual with and height
width int
height int
// Where this view is located
x, y int
@@ -103,8 +94,8 @@ func NewViewWidthHeight(buf *Buffer, w, h int) *View {
v.x, v.y = 0, 0
v.Width = w
v.Height = h
v.width = w
v.height = h
v.ToggleTabbar()
@@ -117,10 +108,10 @@ func NewViewWidthHeight(buf *Buffer, w, h int) *View {
}
if v.Buf.Settings["statusline"].(bool) {
v.Height--
v.height--
}
for pl := range loadedPlugins {
for _, pl := range loadedPlugins {
_, err := Call(pl+".onViewOpen", v)
if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") {
TermMessage(err)
@@ -131,27 +122,25 @@ func NewViewWidthHeight(buf *Buffer, w, h int) *View {
return v
}
// ToggleStatusLine creates an extra row for the statusline if necessary
func (v *View) ToggleStatusLine() {
if v.Buf.Settings["statusline"].(bool) {
v.Height--
v.height--
} else {
v.Height++
v.height++
}
}
// ToggleTabbar creates an extra row for the tabbar if necessary
func (v *View) ToggleTabbar() {
if len(tabs) > 1 {
if v.y == 0 {
// Include one line for the tab bar at the top
v.Height--
v.height--
v.y = 1
}
} else {
if v.y == 1 {
v.y = 0
v.Height++
v.height++
}
}
}
@@ -183,9 +172,9 @@ func (v *View) ScrollUp(n int) {
// ScrollDown scrolls the view down n lines (if possible)
func (v *View) ScrollDown(n int) {
// Try to scroll by n but if it would overflow, scroll by 1
if v.Topline+n <= v.Buf.NumLines-v.Height {
if v.Topline+n <= v.Buf.NumLines-v.height {
v.Topline += n
} else if v.Topline < v.Buf.NumLines-v.Height {
} else if v.Topline < v.Buf.NumLines-v.height {
v.Topline++
}
}
@@ -193,15 +182,10 @@ func (v *View) ScrollDown(n int) {
// CanClose returns whether or not the view can be closed
// If there are unsaved changes, the user will be asked if the view can be closed
// causing them to lose the unsaved changes
// The message is what to print after saying "You have unsaved changes. "
func (v *View) CanClose() bool {
if v.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.GetName()+" before closing? (y,n,esc) ", 'y', 'n')
}
if v.Buf.IsModified {
char, canceled := messenger.LetterPrompt("Save changes to "+v.Buf.Name+" before closing? (y,n,esc) ", 'y', 'n')
if !canceled {
if char == 'y' {
v.Save(true)
@@ -238,24 +222,6 @@ func (v *View) OpenBuffer(buf *Buffer) {
v.lastClickTime = time.Time{}
}
// Open opens the given file in the view
func (v *View) Open(filename string) {
home, _ := homedir.Dir()
filename = strings.Replace(filename, "~", home, 1)
file, err := os.Open(filename)
defer file.Close()
var buf *Buffer
if err != nil {
messenger.Message(err.Error())
// File does not exist -- create an empty buffer with that name
buf = NewBuffer(strings.NewReader(""), 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 {
@@ -274,117 +240,22 @@ func (v *View) ReOpen() {
}
// HSplit opens a horizontal split with the given buffer
func (v *View) HSplit(buf *Buffer) {
i := 0
if v.Buf.Settings["splitBottom"].(bool) {
i = 1
}
v.splitNode.HSplit(buf, v.Num+i)
func (v *View) HSplit(buf *Buffer) bool {
v.splitNode.HSplit(buf)
tabs[v.TabNum].Resize()
return false
}
// VSplit opens a vertical split with the given buffer
func (v *View) VSplit(buf *Buffer) {
i := 0
if v.Buf.Settings["splitRight"].(bool) {
i = 1
}
v.splitNode.VSplit(buf, v.Num+i)
}
// HSplitIndex opens a horizontal split with the given buffer at the given index
func (v *View) HSplitIndex(buf *Buffer, splitIndex int) {
v.splitNode.HSplit(buf, splitIndex)
}
// VSplitIndex opens a vertical split with the given buffer at the given index
func (v *View) VSplitIndex(buf *Buffer, splitIndex int) {
v.splitNode.VSplit(buf, splitIndex)
}
// GetSoftWrapLocation gets the location of a visual click on the screen and converts it to col,line
func (v *View) GetSoftWrapLocation(vx, vy int) (int, int) {
if !v.Buf.Settings["softwrap"].(bool) {
if vy >= v.Buf.NumLines {
vy = v.Buf.NumLines - 1
}
vx = v.Cursor.GetCharPosInLine(vy, vx)
return vx, vy
}
screenX, screenY := 0, v.Topline
for lineN := v.Topline; lineN < v.Bottomline(); lineN++ {
line := v.Buf.Line(lineN)
if lineN >= v.Buf.NumLines {
return 0, v.Buf.NumLines - 1
}
colN := 0
for _, ch := range line {
if screenX >= v.Width-v.lineNumOffset {
screenX = 0
screenY++
}
if screenX == vx && screenY == vy {
return colN, lineN
}
if ch == '\t' {
screenX += int(v.Buf.Settings["tabsize"].(float64)) - 1
}
screenX++
colN++
}
if screenY == vy {
return colN, lineN
}
screenX = 0
screenY++
}
return 0, 0
}
func (v *View) Bottomline() int {
if !v.Buf.Settings["softwrap"].(bool) {
return v.Topline + v.Height
}
screenX, screenY := 0, 0
numLines := 0
for lineN := v.Topline; lineN < v.Topline+v.Height; lineN++ {
line := v.Buf.Line(lineN)
colN := 0
for _, ch := range line {
if screenX >= v.Width-v.lineNumOffset {
screenX = 0
screenY++
}
if ch == '\t' {
screenX += int(v.Buf.Settings["tabsize"].(float64)) - 1
}
screenX++
colN++
}
screenX = 0
screenY++
numLines++
if screenY >= v.Height {
break
}
}
return numLines + v.Topline
func (v *View) VSplit(buf *Buffer) bool {
v.splitNode.VSplit(buf)
tabs[v.TabNum].Resize()
return false
}
// Relocate moves the view window so that the cursor is in view
// This is useful if the user has scrolled far away, and then starts typing
func (v *View) Relocate() bool {
height := v.Bottomline() - v.Topline
ret := false
cy := v.Cursor.Y
scrollmargin := int(v.Buf.Settings["scrollmargin"].(float64))
@@ -395,24 +266,22 @@ func (v *View) Relocate() bool {
v.Topline = cy
ret = true
}
if cy > v.Topline+height-1-scrollmargin && cy < v.Buf.NumLines-scrollmargin {
v.Topline = cy - height + 1 + scrollmargin
if cy > v.Topline+v.height-1-scrollmargin && cy < v.Buf.NumLines-scrollmargin {
v.Topline = cy - v.height + 1 + scrollmargin
ret = true
} else if cy >= v.Buf.NumLines-scrollmargin && cy > height {
v.Topline = v.Buf.NumLines - height
} else if cy >= v.Buf.NumLines-scrollmargin && cy > v.height {
v.Topline = v.Buf.NumLines - v.height
ret = true
}
if !v.Buf.Settings["softwrap"].(bool) {
cx := v.Cursor.GetVisualX()
if cx < v.leftCol {
v.leftCol = cx
ret = true
}
if cx+v.lineNumOffset+1 > v.leftCol+v.Width {
v.leftCol = cx - v.Width + v.lineNumOffset + 1
ret = true
}
cx := v.Cursor.GetVisualX()
if cx < v.leftCol {
v.leftCol = cx
ret = true
}
if cx+v.lineNumOffset+1 > v.leftCol+v.width {
v.leftCol = cx - v.width + v.lineNumOffset + 1
ret = true
}
return ret
}
@@ -420,9 +289,12 @@ func (v *View) Relocate() bool {
// MoveToMouseClick moves the cursor to location x, y assuming x, y were given
// by a mouse click
func (v *View) MoveToMouseClick(x, y int) {
if y-v.Topline > v.Height-1 {
if y-v.Topline > v.height-1 {
v.ScrollDown(1)
y = v.Height + v.Topline - 1
y = v.height + v.Topline - 1
}
if y >= v.Buf.NumLines {
y = v.Buf.NumLines - 1
}
if y < 0 {
y = 0
@@ -431,8 +303,7 @@ func (v *View) MoveToMouseClick(x, y int) {
x = 0
}
x, y = v.GetSoftWrapLocation(x, y)
// x = v.Cursor.GetCharPosInLine(y, x)
x = v.Cursor.GetCharPosInLine(y, x)
if x > Count(v.Buf.Line(y)) {
x = Count(v.Buf.Line(y))
}
@@ -454,34 +325,7 @@ func (v *View) HandleEvent(event tcell.Event) {
// Window resized
tabs[v.TabNum].Resize()
case *tcell.EventKey:
// 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 {
if e.Key() == tcell.KeyRune && (e.Modifiers() == 0 || e.Modifiers() == tcell.ModShift) {
// Insert a character
if v.Cursor.HasSelection() {
v.Cursor.DeleteSelection()
@@ -490,7 +334,7 @@ func (v *View) HandleEvent(event tcell.Event) {
v.Buf.Insert(v.Cursor.Loc, string(e.Rune()))
v.Cursor.Right()
for pl := range loadedPlugins {
for _, pl := range loadedPlugins {
_, err := Call(pl+".onRune", string(e.Rune()), v)
if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") {
TermMessage(err)
@@ -500,13 +344,46 @@ func (v *View) HandleEvent(event tcell.Event) {
if recordingMacro {
curMacro = append(curMacro, e.Rune())
}
} else {
for key, actions := range bindings {
if e.Key() == key.keyCode {
if e.Key() == tcell.KeyRune {
if e.Rune() != key.r {
continue
}
}
if e.Modifiers() == key.modifiers {
relocate = false
for _, action := range actions {
relocate = action(v, true) || relocate
funcName := FuncName(action)
if funcName != "main.(*View).ToggleMacro" && funcName != "main.(*View).PlayMacro" {
if recordingMacro {
curMacro = append(curMacro, action)
}
}
}
}
}
}
}
case *tcell.EventPaste:
if !PreActionCall("Paste", v) {
break
}
v.paste(e.Text())
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:
@@ -532,7 +409,6 @@ func (v *View) HandleEvent(event tcell.Event) {
v.doubleClick = false
v.Cursor.SelectLine()
v.Cursor.CopySelection("primary")
} else {
// Double click
v.lastClickTime = time.Now()
@@ -541,7 +417,6 @@ func (v *View) HandleEvent(event tcell.Event) {
v.tripleClick = false
v.Cursor.SelectWord()
v.Cursor.CopySelection("primary")
}
} else {
v.doubleClick = false
@@ -561,7 +436,6 @@ func (v *View) HandleEvent(event tcell.Event) {
v.Cursor.AddWordToSelection()
} else {
v.Cursor.SetSelectionEnd(v.Cursor.Loc)
v.Cursor.CopySelection("primary")
}
}
case tcell.Button2:
@@ -582,7 +456,6 @@ func (v *View) HandleEvent(event tcell.Event) {
if !v.doubleClick && !v.tripleClick {
v.MoveToMouseClick(x, y)
v.Cursor.SetSelectionEnd(v.Cursor.Loc)
v.Cursor.CopySelection("primary")
}
v.mouseReleased = true
}
@@ -600,6 +473,9 @@ 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
@@ -635,38 +511,26 @@ func (v *View) ClearAllGutterMessages() {
// Opens the given help page in a new horizontal split
func (v *View) openHelp(helpPage string) {
if data, err := FindRuntimeFile(RTHelp, helpPage).Data(); err != nil {
TermMessage("Unable to load help text", helpPage, "\n", err)
if v.Help {
helpBuffer := NewBuffer([]byte(helpPages[helpPage]), helpPage+".md")
helpBuffer.Name = "Help"
v.OpenBuffer(helpBuffer)
} else {
helpBuffer := NewBuffer(strings.NewReader(string(data)), helpPage+".md")
helpBuffer.name = "Help"
if v.Type == vtHelp {
v.OpenBuffer(helpBuffer)
} else {
v.HSplit(helpBuffer)
CurView().Type = vtHelp
}
helpBuffer := NewBuffer([]byte(helpPages[helpPage]), helpPage+".md")
helpBuffer.Name = "Help"
v.HSplit(helpBuffer)
CurView().Help = true
}
}
func (v *View) drawCell(x, y int, ch rune, combc []rune, style tcell.Style) {
if x >= v.x && x < v.x+v.Width && y >= v.y && y < v.y+v.Height {
if x >= v.x && x < v.x+v.width && y >= v.y && y < v.y+v.height {
screen.SetContent(x, y, ch, combc, 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}
@@ -699,22 +563,17 @@ func (v *View) DisplayView() {
}
// These represent the current screen coordinates
screenX, screenY := v.x, v.y-1
screenX, screenY := 0, 0
highlightStyle := defStyle
curLineN := 0
// ViewLine is the current line from the top of the viewport
for viewLine := 0; viewLine < v.Height; viewLine++ {
screenY++
for viewLine := 0; viewLine < v.height; viewLine++ {
screenY = v.y + viewLine
screenX = v.x
// This is the current line number of the buffer that we are drawing
curLineN = viewLine + v.Topline
if screenY-v.y >= v.Height {
break
}
curLineN := viewLine + v.Topline
if v.x != 0 {
// Draw the split divider
@@ -724,7 +583,7 @@ func (v *View) DisplayView() {
// If the buffer is smaller than the view height we have to clear all this space
if curLineN >= v.Buf.NumLines {
for i := screenX; i < v.x+v.Width; i++ {
for i := screenX; i < v.x+v.width; i++ {
v.drawCell(i, screenY, ' ', nil, defStyle)
}
@@ -779,14 +638,14 @@ func (v *View) DisplayView() {
}
}
lineNumStyle := defStyle
if v.Buf.Settings["ruler"] == true {
// Write the line number
lineNumStyle := defStyle
if style, ok := colorscheme["line-number"]; ok {
lineNumStyle = style
}
if style, ok := colorscheme["current-line-number"]; ok {
if curLineN == v.Cursor.Y && tabs[curTab].CurView == v.Num && !v.Cursor.HasSelection() {
if curLineN == v.Cursor.Y && tabs[curTab].curView == v.Num && !v.Cursor.HasSelection() {
lineNumStyle = style
}
}
@@ -811,31 +670,7 @@ 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 {
if v.Buf.Settings["softwrap"].(bool) {
if screenX-v.x >= v.Width {
screenY++
x := 0
if hasGutterMessages {
v.drawCell(v.x+x, screenY, ' ', nil, defStyle)
x++
v.drawCell(v.x+x, screenY, ' ', nil, defStyle)
x++
}
for i := 0; i < v.lineNumOffset; i++ {
screen.SetContent(v.x+i+x, screenY, ' ', nil, lineNumStyle)
}
screenX = v.x + v.lineNumOffset
}
}
if tabs[curTab].CurView == v.Num && !v.Cursor.HasSelection() && v.Cursor.Y == curLineN && colN == v.Cursor.X {
v.DisplayCursor(screenX-v.leftCol, screenY)
}
lineStyle := defStyle
if v.Buf.Settings["syntax"].(bool) {
@@ -858,7 +693,7 @@ func (v *View) DisplayView() {
// We need to display the background of the linestyle with the correct color if cursorline is enabled
// and this is the current view and there is no selection on this line and the cursor is on this line
if v.Buf.Settings["cursorline"].(bool) && tabs[curTab].CurView == v.Num && !v.Cursor.HasSelection() && v.Cursor.Y == curLineN {
if v.Buf.Settings["cursorline"].(bool) && tabs[curTab].curView == v.Num && !v.Cursor.HasSelection() && v.Cursor.Y == curLineN {
if style, ok := colorscheme["cursor-line"]; ok {
fg, _, _ := style.Decompose()
lineStyle = lineStyle.Background(fg)
@@ -871,7 +706,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 && v.Buf.Settings["indentchar"].(string) != " " {
if style, ok := colorscheme["indent-char"]; ok {
lineIndentStyle = style
}
if v.Cursor.HasSelection() &&
@@ -884,7 +719,7 @@ func (v *View) DisplayView() {
lineIndentStyle = style
}
}
if v.Buf.Settings["cursorline"].(bool) && tabs[curTab].CurView == v.Num && !v.Cursor.HasSelection() && v.Cursor.Y == curLineN {
if v.Buf.Settings["cursorline"].(bool) && tabs[curTab].curView == v.Num && !v.Cursor.HasSelection() && v.Cursor.Y == curLineN {
if style, ok := colorscheme["cursor-line"]; ok {
fg, _, _ := style.Decompose()
lineIndentStyle = lineIndentStyle.Background(fg)
@@ -896,15 +731,13 @@ 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
visLoc := strWidth
remainder := tabSize - (visLoc % tabSize)
for i := 0; i < remainder-1; i++ {
tabSize := int(v.Buf.Settings["tabsize"].(float64))
for i := 0; i < tabSize-1; i++ {
screenX++
if screenX-v.x-v.leftCol >= v.lineNumOffset {
v.drawCell(screenX-v.leftCol, screenY, ' ', nil, lineStyle)
}
}
strWidth += remainder
} else if runewidth.RuneWidth(ch) > 1 {
if screenX-v.x-v.leftCol >= v.lineNumOffset {
v.drawCell(screenX, screenY, ch, nil, lineStyle)
@@ -915,12 +748,10 @@ func (v *View) DisplayView() {
v.drawCell(screenX-v.leftCol, screenY, '<', nil, lineStyle)
}
}
strWidth += StringWidth(string(ch), tabSize)
} else {
if screenX-v.x-v.leftCol >= v.lineNumOffset {
v.drawCell(screenX-v.leftCol, screenY, ch, nil, lineStyle)
}
strWidth += StringWidth(string(ch), tabSize)
}
charNum = charNum.Move(1, v.Buf)
screenX++
@@ -928,10 +759,6 @@ func (v *View) DisplayView() {
}
// Here we are at a newline
if tabs[curTab].CurView == v.Num && !v.Cursor.HasSelection() && v.Cursor.Y == curLineN && colN == v.Cursor.X {
v.DisplayCursor(screenX-v.leftCol, screenY)
}
// The newline may be selected, in which case we should draw the selection style
// with a space to represent it
if v.Cursor.HasSelection() &&
@@ -949,22 +776,15 @@ func (v *View) DisplayView() {
charNum = charNum.Move(1, v.Buf)
for i := 0; i < v.Width; i++ {
for i := 0; i < v.width; i++ {
lineStyle := defStyle
if v.Buf.Settings["cursorline"].(bool) && tabs[curTab].CurView == v.Num && !v.Cursor.HasSelection() && v.Cursor.Y == curLineN {
if v.Buf.Settings["cursorline"].(bool) && tabs[curTab].curView == v.Num && !v.Cursor.HasSelection() && v.Cursor.Y == curLineN {
if style, ok := colorscheme["cursor-line"]; ok {
fg, _, _ := style.Decompose()
lineStyle = lineStyle.Background(fg)
}
}
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)
}
}
@@ -972,24 +792,27 @@ func (v *View) DisplayView() {
}
// DisplayCursor draws the current buffer's cursor to the screen
func (v *View) DisplayCursor(x, y int) {
// screen.ShowCursor(v.x+v.Cursor.GetVisualX()+v.lineNumOffset-v.leftCol, y)
screen.ShowCursor(x, y)
func (v *View) DisplayCursor() {
// Don't draw the cursor if it is out of the viewport or if it has a selection
if (v.Cursor.Y-v.Topline < 0 || v.Cursor.Y-v.Topline > v.height-1) || v.Cursor.HasSelection() {
screen.HideCursor()
} else {
screen.ShowCursor(v.x+v.Cursor.GetVisualX()+v.lineNumOffset-v.leftCol, v.Cursor.Y-v.Topline+v.y)
}
}
// Display renders the view, the cursor, and statusline
func (v *View) Display() {
v.DisplayView()
// Don't draw the cursor if it is out of the viewport or if it has a selection
if (v.Cursor.Y-v.Topline < 0 || v.Cursor.Y-v.Topline > v.Height-1) || v.Cursor.HasSelection() {
screen.HideCursor()
if v.Num == tabs[curTab].curView {
v.DisplayCursor()
}
_, screenH := screen.Size()
if v.Buf.Settings["statusline"].(bool) {
v.sline.Display()
} else if (v.y + v.Height) != screenH-1 {
for x := 0; x < v.Width; x++ {
screen.SetContent(v.x+x, v.y+v.Height, '-', nil, defStyle.Reverse(true))
} else if (v.y + v.height) != screenH-1 {
for x := 0; x < v.width; x++ {
screen.SetContent(v.x+x, v.y+v.height, '-', nil, defStyle.Reverse(true))
}
}
}

View File

@@ -4,7 +4,6 @@ color-link identifier "#F9EE98,#1D1F21"
color-link constant "#FF73FD,#1D1F21"
color-link constant.string "#A8FF60,#1D1F21"
color-link statement "#96CBFE,#1D1F21"
color-link symbol "#96CBFE,#1DF121"
color-link preproc "#62B1FE,#1D1F21"
color-link type "#C6C5FE,#1D1F21"
color-link special "#A6E22E,#1D1F21"
@@ -18,4 +17,3 @@ 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"

View File

@@ -5,7 +5,6 @@ color-link constant.string "136,231"
color-link constant.number "131,231"
color-link identifier "133,231"
color-link statement "32,231"
color-link symbol "32,231"
color-link preproc "28,231"
color-link type "61,231"
color-link special "167,231"
@@ -18,4 +17,3 @@ 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"

View File

@@ -1,23 +1,20 @@
# This is the monokai colorscheme
color-link default "#F8F8F2,#282828"
color-link comment "#75715E,#282828"
color-link identifier "#66D9EF,#282828"
color-link constant "#AE81FF,#282828"
color-link constant.string "#E6DB74,#282828"
color-link constant.string.char "#BDE6AD,#282828"
color-link statement "#F92672,#282828"
color-link symbol "#F92672,#282828"
color-link preproc "#CB4B16,#282828"
color-link type "#66D9EF,#282828"
color-link special "#A6E22E,#282828"
color-link underlined "#D33682,#282828"
color-link error "bold #CB4B16,#282828"
color-link todo "bold #D33682,#282828"
color-link statusline "#282828,#F8F8F2"
color-link indent-char "#505050,#282828"
color-link line-number "#AAAAAA,#323232"
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"
color-link default "188,237"
color-link comment "108,237"
color-link constant.string "174,237"
color-link constant.number "116,237"
color-link constant "181,237"
color-link identifier "223,237"
color-link statement "223,237"
color-link preproc "223,237"
color-link type "187,237"
color-link special "181,237"
color-link underlined "188,237"
color-link error "115,236"
color-link todo "bold 254,237"
color-link statusline "186,236"
color-link indent-char "238,237"
color-link line-number "188,238"
color-link gutter-error "237,174"
color-link gutter-warning "174,237"
color-link cursor-line "238"
color-link current-line-number "188,237"

View File

@@ -1,19 +0,0 @@
color-link default "#ebdbb2,#282828"
color-link comment "#928374,#282828"
color-link symbol "#d79921,#282828"
color-link constant "#d3869b,#282828"
color-link constant.string "#b8bb26,#282828"
color-link constant.string.char "#b8bb26,#282828"
color-link identifier "#8ec07c,#282828"
color-link statement "#fb4934,#282828"
color-link preproc "#fb4934,235"
color-link type "#fb4934,#282828"
color-link special "#d79921,#282828"
color-link underlined "underline #282828"
color-link error "#9d0006,#282828"
color-link gutter-error "#fb4934,#282828"
color-link gutter-warning "#d79921,#282828"
color-link line-number "#665c54,#282828"
color-link current-line-number "#665c54,#3c3836"
color-link cursor-line "#3c3836"
color-link color-column "#79740e"

View File

@@ -4,7 +4,6 @@ color-link constant "175,235"
color-link constant.string "142,235"
color-link identifier "109,235"
color-link statement "124,235"
color-link symbol "124,235"
color-link preproc "72,235"
color-link type "214,235"
color-link special "172,235"
@@ -14,4 +13,3 @@ 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"

View File

@@ -3,9 +3,7 @@ color-link comment "#75715E,#282828"
color-link identifier "#66D9EF,#282828"
color-link constant "#AE81FF,#282828"
color-link constant.string "#E6DB74,#282828"
color-link constant.string.char "#BDE6AD,#282828"
color-link statement "#F92672,#282828"
color-link symbol "#F92672,#282828"
color-link preproc "#CB4B16,#282828"
color-link type "#66D9EF,#282828"
color-link special "#A6E22E,#282828"
@@ -19,4 +17,3 @@ 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"

View File

@@ -2,7 +2,6 @@ color-link comment "blue"
color-link constant "red"
color-link identifier "cyan"
color-link statement "yellow"
color-link symbol "yellow"
color-link preproc "magenta"
color-link type "green"
color-link special "magenta"
@@ -15,4 +14,3 @@ 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"

View File

@@ -3,7 +3,6 @@ color-link comment "#586E75,#002833"
color-link identifier "#268BD2,#002833"
color-link constant "#2AA198,#002833"
color-link statement "#859900,#002833"
color-link symbol "#859900,#002833"
color-link preproc "#CB4B16,#002833"
color-link type "#B58900,#002833"
color-link special "#DC322F,#002833"
@@ -17,4 +16,3 @@ 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"

View File

@@ -2,7 +2,6 @@ color-link comment "brightgreen"
color-link constant "cyan"
color-link identifier "blue"
color-link statement "green"
color-link symbol "green"
color-link preproc "brightred"
color-link type "yellow"
color-link special "red"
@@ -16,4 +15,3 @@ 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"

View File

@@ -5,7 +5,6 @@ color-link constant.number "116,237"
color-link constant "181,237"
color-link identifier "223,237"
color-link statement "223,237"
color-link symbol "223,237"
color-link preproc "223,237"
color-link type "187,237"
color-link special "181,237"
@@ -18,5 +17,4 @@ 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"

View File

@@ -12,24 +12,25 @@ Micro comes with a number of colorschemes by default. Here is the list:
* simple: this is the simplest colorscheme. It uses 16 colors which are
set by your terminal
* 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.
It's also the default colorscheme.
* zenburn: this is micro's default colorscheme because it looks very good
and works in 256 color terminals.
this colorscheme also has the name 'default'
* zenburn: The 'zenburn' colorscheme and works well with 256 color terminals
* solarized: this is the solarized colorscheme.
* 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
make sure your terminal supports true color before using it and that the
* 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
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.
It requires true color to look good.
To enable one of these colorschemes just press CtrlE in micro and type `set colorscheme solarized`.
To enable one of these colorschemes just run the command `set colorscheme solarized`.
(or whichever one you choose).
---
@@ -95,7 +96,6 @@ Here is a list of the colorscheme groups that you can use:
* identifier
* constant
* statement
* symbol
* preproc
* type
* special
@@ -108,8 +108,6 @@ 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.

View File

@@ -5,13 +5,12 @@ Here are the possible commands that you can use.
* `quit`: Quits micro.
* `save filename?`: Saves the current buffer. If the filename is provided it will
'save as' the filename.
* `save`: Saves the current buffer.
* `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.
@@ -24,10 +23,6 @@ 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.
@@ -35,43 +30,19 @@ 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.
* `plugin available`: list plugins available for download (this includes
any plugins that may be already installed).
* `reload`: reloads all runtime files.
* `cd path`: Change the working directory to the given `path`.
* `pwd`: Print the current working directory.
* `open filename`: Open a file in the current buffer.
---
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.

View File

@@ -3,32 +3,18 @@
Micro is a terminal-based text editor that aims to be easy to use and intuitive,
while also taking advantage of the full capabilities of modern terminals.
*Press CtrlQ to quit, and CtrlS to save.*
If you want to see all the keybindings press CtrlE and type `help keybindings`.
See the next section for more information about documentation and help.
### Quick-start
Press CtrlQ to quit, and CtrlS to save. Press CtrlE to start typing commands
and you can see which commands are available by pressing tab, or by
viewing the help topic `> help commands`. When I write `> ...` I mean press
CtrlE and then type whatever is there.
Move the cursor around with the mouse or the arrow keys.
If the colorscheme doesn't look good, you can change it with `> set colorscheme ...`.
You can press tab to see the available colorschemes, or see more information with
`> help colors`.
Press CtrlW to move between splits, and type `> vsplit filename` or `> hsplit filename`
to open a new split.
### Accessing more help
Micro has a built-in help system much like Vim's (although less extensive).
To use it, press CtrlE to access command mode and type in `help` followed by a topic.
Typing `help` followed by nothing will open this page.
To use it, press CtrlE to access command mode and type in help followed by a topic.
Typing help followed by nothing will open this page.
Here are the possible help topics that you can read:
@@ -40,7 +26,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,

View File

@@ -1,87 +1,78 @@
# Keybindings
Here are the default keybindings in json format. You can rebind them to your liking, following the same format.
Here are the default keybindings in json form which is also how
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",
"AltUp": "MoveLinesUp",
"AltDown": "MoveLinesDown",
"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": "Escape",
"Up": "CursorUp",
"Down": "CursorDown",
"Right": "CursorRight",
"Left": "CursorLeft",
"ShiftUp": "SelectUp",
"ShiftDown": "SelectDown",
"ShiftLeft": "SelectLeft",
"ShiftRight": "SelectRight",
"AltLeft": "WordLeft",
"AltRight": "WordRight",
"AltShiftRight": "SelectWordRight",
"AltShiftLeft": "SelectWordLeft",
"CtrlLeft": "StartOfLine",
"CtrlRight": "EndOfLine",
"CtrlShiftLeft": "SelectToStartOfLine",
"CtrlShiftRight": "SelectToEndOfLine",
"CtrlUp": "CursorStart",
"CtrlDown": "CursorEnd",
"CtrlShiftUp": "SelectToStart",
"CtrlShiftDown": "SelectToEnd",
"Enter": "InsertEnter",
"Space": "InsertSpace",
"Backspace": "Backspace",
"Backspace2": "Backspace",
"Alt-Backspace": "DeleteWordLeft",
"Alt-Backspace2": "DeleteWordLeft",
"Tab": "InsertTab,IndentSelection",
"CtrlO": "OpenFile",
"CtrlS": "Save",
"CtrlF": "Find",
"CtrlN": "FindNext",
"CtrlP": "FindPrevious",
"CtrlZ": "Undo",
"CtrlY": "Redo",
"CtrlC": "Copy",
"CtrlX": "Cut",
"CtrlK": "CutLine",
"CtrlD": "DuplicateLine",
"CtrlV": "Paste",
"CtrlA": "SelectAll",
"CtrlT": "AddTab",
"CtrlRightSq": "PreviousTab",
"CtrlBackslash": "NextTab",
"Home": "Start",
"End": "End",
"CtrlHome": "CursorStart",
"CtrlEnd": "CursorEnd",
"PageUp": "CursorPageUp",
"PageDown": "CursorPageDown",
"CtrlG": "ToggleHelp",
"CtrlR": "ToggleRuler",
"CtrlL": "JumpLine",
"Delete": "Delete",
"Esc": "ClearStatus",
"CtrlB": "ShellMode",
"CtrlQ": "Quit",
"CtrlE": "CommandMode",
"CtrlW": "NextSplit",
"CtrlU": "ToggleMacro",
"CtrlJ": "PlayMacro",
// Emacs-style keybindings
"Alt-f": "WordRight",
"Alt-b": "WordLeft",
"Alt-a": "StartOfLine",
"Alt-e": "EndOfLine",
"Alt-p": "CursorUp",
"Alt-n": "CursorDown"
}
```
@@ -115,11 +106,6 @@ and quit you can bind it like so:
}
```
# Unbinding keys
It is also possible to disable any of the default key bindings by use of the
`UnbindKey` action in the user's `bindings.json` file.
# Bindable actions and bindable keys
The list of default keybindings contains most of the possible actions and keys
@@ -146,8 +132,6 @@ WordRight
WordLeft
SelectWordRight
SelectWordLeft
MoveLinesUp
MoveLinesDown
DeleteWordRight
DeleteWordLeft
SelectToStartOfLine
@@ -159,7 +143,6 @@ Delete
Center
InsertTab
Save
SaveAs
Find
FindNext
FindPrevious
@@ -195,13 +178,9 @@ AddTab
PreviousTab
NextTab
NextSplit
Unsplit
VSplit
HSplit
PreviousSplit
ToggleMacro
PlayMacro
UnbindKey
```
Here is the list of all possible keys you can bind:
@@ -330,11 +309,9 @@ 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

View File

@@ -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,19 +23,6 @@ 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`
* `eofnewline`: micro will automatically add a newline to the file.
default value: `false`
* `rmtrailingws`: micro will automatically trim trailing whitespaces at eol.
default value: `false`
* `tabsize`: sets the tab size to `option`
default value: `4`
@@ -101,54 +88,24 @@ Here are the options that you can set:
default value: `2`
* `softwrap`: should micro wrap lines that are too long to fit on the screen
default value: `off`
* `splitRight`: when a vertical split is created, should it be created to the right of
the current split?
default value: `on`
* `splitBottom`: when a horizontal split is created, should it be created below the
current split?
default value: `on`
* `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: ` `
* `useprimary` (only useful on Linux): defines whether or not micro will use the primary clipboard to copy selections
in the background. This does not affect the normal clipboard using Ctrl-C and Ctrl-V.
default value: `on`
---
Default plugin options:
* `autoclose`: Automatically close `{}` `()` `[]` `""` `''`. Provided by the `autoclose` plugin
* `linter`: lint languages on save (supported languages are C, D, Go, Java,
Javascript, Lua). Provided by the `linter` plugin.
default value: `on`
* `linter`: Automatically lint when the file is saved. Provided by the `linter` plugin
* `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.
default value: `on`

View File

@@ -43,73 +43,45 @@ 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
* `NewBuffer(text, path string) *Buffer`: creates a new buffer from a given reader with a given path
* `GetLeadingWhitespace() bool`: returns the leading whitespace of the given string
* `IsWordChar(str string) bool`: returns whether or not the string is a 'word character'
* `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
* `DirectoryName(path string)` returns all but the last element of path ,typically the path's directory
* `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, view *View)`: sets the given option to
the value locally in the given buffer
* `SetLocalOption(option, value string, buffer *Buffer)`: sets the given option to
the value locally in the given buffer.
* `BindKey(key, action string)`: binds `key` to `action`
* `BindKey(key, action string)`: binds `key` to `action`.
* `MakeCommand(name, function string, completions ...Completion)`:
creates a command with `name` which will call `function` when executed.
Use 0 for completions to get NoCompletion.
* `MakeCompletion(function string)`:
creates a `Completion` to use with `MakeCommand`
creates a `Completion` to use with `MakeCommand`.
* `CurView()`: returns the current view
* `HandleCommand(cmd string)`: runs the given command
* `HandleShellCommand(shellCmd string, interactive bool, 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.
* `HandleShellCommand(shellCmd string, interactive bool)`: runs the given shell
command
* `ToCharPos(loc Loc, buf *Buffer) int`: returns the character position of a given x, y location
* `Reload`: (Re)load everything
* `ByteOffset(loc Loc, buf *Buffer) int`: exactly like `ToCharPos` except it it counts bytes instead of runes
* `JobSpawn(cmdName string, cmdArgs []string, onStdout, onStderr, onExit string, userargs ...string)`:
Starts running the given process in the background. `onStdout` `onStderr` and `onExit`
* `JobStart(cmd string, onStdout, onStderr, onExit string, userargs ...string)`:
Starts running the given shell command in the background. `onStdout` `onStderr` and `onExit`
are callbacks to lua functions which will be called when the given actions happen
to the background process.
`userargs` are the arguments which will get passed to the callback functions
* `JobStart(cmd string, onStdout, onStderr, onExit string, userargs ...string)`:
Starts running the given shell command in the background. Note that the command execute
is first parsed by a shell when using this command. It is executed with `sh -c`.
* `JobSend(cmd *exec.Cmd, data string)`: send a string into the stdin of the job process
* `JobStop(cmd *exec.Cmd)`: kill a job
@@ -135,21 +107,6 @@ 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 to your plugin
called `test`, you would create a `test.md` file, and call the function:
```lua
AddRuntimeFile("test", "help", "test.md")
```
Use `AddRuntimeFilesFromDirectory(name, type, dir, pattern)` to add a number of files
to the runtime.
To read the content of a runtime file use `ReadRuntimeFile(fileType, name string)`
or `ListRuntimeFiles(fileType string)` for all runtime files.
# Autocomplete command arguments
See this example to learn how to use `MakeCompletion` and `MakeCommand`
@@ -182,44 +139,5 @@ MakeCommand("foo", "example.foo", MakeCompletion("example.complete"))
# Default plugins
For examples of plugins, see the default `autoclose` and `linter` plugins
(stored in the normal micro core repo under `runtime/plugins`) as well as
any plugins that are stored in the official channel [here](https://github.com/micro-editor/plugin-channel).
# 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.
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).

View File

@@ -6,17 +6,6 @@ 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
@@ -91,7 +80,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) -- the first true means don't run it in the background
HandleShellCommand("go run " .. buf.Path, true) -- true means don't run it in the background
end
end

View File

@@ -6,7 +6,7 @@ if GetOption("autoclose") == nil then
AddOption("autoclose", true)
end
local autoclosePairs = {"\"\"", "''", "``", "()", "{}", "[]"}
local autoclosePairs = {"\"\"", "''", "()", "{}", "[]"}
local autoNewlinePairs = {"()", "{}", "[]"}
function onRune(r, v)

View File

@@ -1,9 +0,0 @@
function onViewOpen(view)
local ft = view.Buf.Settings["filetype"]
if ft == "makefile" or ft == "go" then
SetOption("tabstospaces", "off")
elseif ft == "python" then
SetOption("tabstospaces", "on")
end
end

52
runtime/plugins/go/go.lua Normal file
View File

@@ -0,0 +1,52 @@
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

View File

@@ -13,34 +13,24 @@ function runLinter()
local ft = CurView().Buf:FileType()
local file = CurView().Buf.Path
local devnull = "/dev/null"
local temp = os.getenv("TMPDIR")
if OS == "windows" then
devnull = "NUL"
temp = os.getenv("TEMP")
end
if ft == "go" then
lint("gobuild", "go", {"build", "-o", devnull}, "%f:%l: %m")
lint("golint", "golint", {CurView().Buf.Path}, "%f:%l:%d+: %m")
lint("gobuild", "go build -o " .. devnull, "%f:%l: %m")
lint("golint", "golint " .. CurView().Buf.Path, "%f:%l:%d+: %m")
elseif ft == "lua" then
lint("luacheck", "luacheck", {"--no-color", file}, "%f:%l:%d+: %m")
lint("luacheck", "luacheck --no-color " .. file, "%f:%l:%d+: %m")
elseif ft == "python" then
lint("pyflakes", "pyflakes", {file}, "%f:%l:.-:? %m")
lint("mypy", "mypy", {file}, "%f:%l: %m")
lint("pylint", "pylint", {"--output-format=parseable", "--reports=no", 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 == "swift" then
lint("switfc", "xcrun", {"swiftc", file}, "%f:%l:%d+:.+: %m")
elseif ft == "Objective-C" then
lint("clang", "xcrun", {"clang", "-fsyntax-only", "-Wall", "-Wextra", file}, "%f:%l:%d+:.+: %m")
lint("gcc", "gcc -fsyntax-only -Wall -Wextra " .. file, "%f:%l:%d+:.+: %m")
elseif ft == "d" then
lint("dmd", "dmd", {"-color=off", "-o-", "-w", "-wi", "-c", file}, "%f%(%l%):.+: %m")
lint("dmd", "dmd -color=off -o- -w -wi -c " .. file, "%f%(%l%):.+: %m")
elseif ft == "java" then
lint("javac", "javac", {"-d", temp, file}, "%f:%l: error: %m")
lint("javac", "javac " .. file, "%f:%l: error: %m")
elseif ft == "javascript" then
lint("jshint", "jshint", {file}, "%f: line %l,.+, %m")
elseif ft == "nim" then
lint("nim", "nim", {"check", "--listFullPaths", "--stdout", "--hints:off", file}, "%f.%l, %d+. %m")
lint("jshint", "jshint " .. file, "%f: line %l,.+, %m")
end
end
@@ -52,16 +42,16 @@ function onSave(view)
end
end
function lint(linter, cmd, args, errorformat)
function lint(linter, cmd, errorformat)
CurView():ClearGutterMessages(linter)
JobSpawn(cmd, args, "", "", "linter.onExit", linter, errorformat)
JobStart(cmd, "", "", "linter.onExit", linter, errorformat)
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*$")

View File

@@ -2,19 +2,25 @@
syntax "dockerfile" "Dockerfile[^/]*$" "\.dockerfile$"
## Keywords
color keyword (i) "^(FROM|MAINTAINER|RUN|CMD|LABEL|EXPOSE|ENV|ADD|COPY|ENTRYPOINT|VOLUME|USER|WORKDIR|ONBUILD|ARG|HEALTHCHECK|STOPSIGNAL|SHELL)[[:space:]]"
red (i) "^(FROM|MAINTAINER|RUN|CMD|LABEL|EXPOSE|ENV|ADD|COPY|ENTRYPOINT|VOLUME|USER|WORKDIR|ONBUILD)[[:space:]]"
## Brackets & parenthesis
color statement "(\(|\)|\[|\])"
color brightgreen "(\(|\)|\[|\])"
## Double ampersand
color special "&&"
color brightmagenta "&&"
## Comments
color comment (i) "^[[:space:]]*#.*$"
cyan (i) "^[[:space:]]*#.*$"
## Blank space at EOL
color ,green "[[:space:]]+$"
## Strings, single-quoted
color constant.string "'([^']|(\\'))*'" "%[qw]\{[^}]*\}" "%[qw]\([^)]*\)" "%[qw]<[^>]*>" "%[qw]\[[^]]*\]" "%[qw]\$[^$]*\$" "%[qw]\^[^^]*\^" "%[qw]![^!]*!"
color brightwhite "'([^']|(\\'))*'" "%[qw]\{[^}]*\}" "%[qw]\([^)]*\)" "%[qw]<[^>]*>" "%[qw]\[[^]]*\]" "%[qw]\$[^$]*\$" "%[qw]\^[^^]*\^" "%[qw]![^!]*!"
## Strings, double-quoted
color constant.string ""([^"]|(\\"))*"" "%[QW]?\{[^}]*\}" "%[QW]?\([^)]*\)" "%[QW]?<[^>]*>" "%[QW]?\[[^]]*\]" "%[QW]?\$[^$]*\$" "%[QW]?\^[^^]*\^" "%[QW]?![^!]*!"
color brightwhite ""([^"]|(\\"))*"" "%[QW]?\{[^}]*\}" "%[QW]?\([^)]*\)" "%[QW]?<[^>]*>" "%[QW]?\[[^]]*\]" "%[QW]?\$[^$]*\$" "%[QW]?\^[^^]*\^" "%[QW]?![^!]*!"
## Single and double quotes
color brightyellow "('|\")"

View File

@@ -32,8 +32,6 @@ Here is a list of the files that have been converted to properly use colorscheme
* ruby
* sh
* git
* tex
* solidity
# License

View File

@@ -21,7 +21,7 @@ color statement "[.:;,+*|=!\%]" "<" ">" "/" "-" "&"
#Parenthetical Color
# color magenta "[(){}]" "\[" "\]"
color constant.number "\b[0-9]+\b" "\b0x[0-9A-Fa-f]+\b"
color constant.number "\b[0-9]+\b"
##
## String highlighting. You will in general want your brightblacks and

View File

@@ -1,34 +0,0 @@
## Crystal Syntax file.
##
syntax "crystal" "\.cr$" "Gemfile" "config.ru" "Rakefile" "Capfile" "Vagrantfile"
header "^#!.*/(env +)?crystal( |$)"
## Asciibetical list of reserved words
color statement "\b(BEGIN|END|abstract|alias|and|begin|break|case|class|def|defined\?|do|else|elsif|end|ensure|enum|false|for|fun|if|in|include|lib|loop|macro|module|next|nil|not|of|or|pointerof|private|protected|raise|redo|require|rescue|retry|return|self|sizeof|spawn|struct|super|then|true|type|undef|union|uninitialized|unless|until|when|while|yield)\b"
## Constants
color constant "(\$|@|@@)?\b[A-Z]+[0-9A-Z_a-z]*"
color constant.number "\b[0-9]+\b"
## Crystal "symbols"
color constant (i) "([ ]|^):[0-9A-Z_]+\b"
## Some unique things we want to stand out
color constant "\b(__FILE__|__LINE__)\b"
## Regular expressions
color constant "/([^/]|(\\/))*/[iomx]*" "%r\{([^}]|(\\}))*\}[iomx]*"
## Shell command expansion is in `backticks` or like %x{this}. These are
## "double-quotish" (to use a perlism).
color constant.string "`[^`]*`" "%x\{[^}]*\}"
## Strings, double-quoted
color constant.string ""([^"]|(\\"))*"" "%[QW]?\{[^}]*\}" "%[QW]?\([^)]*\)" "%[QW]?<[^>]*>" "%[QW]?\[[^]]*\]" "%[QW]?\$[^$]*\$" "%[QW]?\^[^^]*\^" "%[QW]?![^!]*!"
## Expression substitution. These go inside double-quoted strings,
## "like #{this}".
color special "#\{[^}]*\}"
## Characters are single-quoted
color constant.string.char "'([^']|(\\'))*'" "%[qw]\{[^}]*\}" "%[qw]\([^)]*\)" "%[qw]<[^>]*>" "%[qw]\[[^]]*\]" "%[qw]\$[^$]*\$" "%[qw]\^[^^]*\^" "%[qw]![^!]*!"
## Comments
color comment "#[^{].*$" "#$"
color comment "##[^{].*$" "##$"
## "Here" docs
color constant start="<<-?'?EOT'?" end="^EOT"
## Some common markers
color todo "(XXX|TODO|FIXME|\?\?\?)"

View File

@@ -1,34 +1,19 @@
syntax "go" "\.go$"
# Conditionals and control flow
color statement "\b(break|case|continue|default|else|for|go|goto|if|range|return|switch)\b"
color statement "\b(package|import|const|var|type|struct|func|go|defer|iota)\b"
color statement "\b(append|cap|close|complex|copy|delete|imag|len)\b"
color statement "\b(make|new|panic|print|println|protect|real|recover)\b"
color type "\b(u?int(8|16|32|64)?|float(32|64)|complex(64|128))\b"
color type "\b(uintptr|byte|rune|string|interface|bool|map|chan|error)\b"
color statement "\b(package|import|const|var|type|struct|func|go|defer|nil|iota)\b"
color statement "\b(for|range|if|else|case|default|switch|return)\b"
color statement "\b(go|goto|break|continue)\b"
color constant "\b(true|false)\b"
color statement "[-+/*=<>!~%&|^]|:="
# Types
color special "[a-zA-Z0-9]*\("
color brightyellow "(,|\.)"
color type "\b(u?int(8|16|32|64)?|float(32|64)|complex(64|128))\b"
color type "\b(uintptr|byte|rune|string|interface|bool|map|chan|error)\b"
color constant "\b(true|false|nil)\b"
# Brackets n shit
color statement "(\{|\})"
color statement "(\(|\))"
color statement "(\[|\])"
color statement "!"
color statement ","
# Numbers and strings
color constant.number "\b([0-9]+|0x[0-9a-fA-F]*)\b|'.'"
color constant.number "\b([0-9]+|0x[0-9a-fA-F]*)\b|'.'"
color constant.string ""(\\.|[^"])*"|'(\\.|[^'])*'"
color constant.specialChar "\\[abfnrtv'\"\\]"
color constant.specialChar "\\([0-7]{3}|x[A-Fa-f0-9]{2}|u[A-Fa-f0-9]{4}|U[A-Fa-f0-9]{8})"
color constant.string "`[^`]*`"
color constant.specialChar """
color constant.specialChar "'"
# Comments & TODOs
color constant.specialChar "\\[abfnrtv'\"\\]"
color constant.specialChar "\\([0-7]{3}|x[A-Fa-f0-9]{2}|u[A-Fa-f0-9]{4}|U[A-Fa-f0-9]{8})"
color constant.string "`[^`]*`"
color comment "(^|[[:space:]])//.*"
#color comment start="/\*" end="\*/"
color todo "(TODO|XXX|FIXME):?"
color comment start="/\*" end="\*/"
color todo "TODO:?"

View File

@@ -1,7 +1,7 @@
## Here is a short improved example for HTML.
##
syntax "html" "\.htm[l]?$"
color identifier "<.*?>"
color identifier start="<" end=">"
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)="

View File

@@ -1,6 +1,6 @@
##############################################################################
#
# Lua syntax highlighting for Micro.
# Lua syntax highlighting for Nano.
#
# Author: Matthew Wild <mwild1 (at) gmail.com>
# License: GPL 2 or later
@@ -14,12 +14,12 @@
# Automatically use for '.lua' files
syntax "lua" ".*\.lua$"
# Operators
color statement ":|\*\*|\*|/|%|\+|-|\^|>|>=|<|<=|~=|=|\.\.|\b(not|and|or)\b"
# Statements
color statement "\b(do|end|while|repeat|until|if|elseif|then|else|for|in|function|local|return)\b"
# Logic
color statement "\b(not|and|or)\b"
# Keywords
color statement "\b(debug|string|math|table|io|coroutine|os|utf8|bit32)\b\."
color statement "\b(_ENV|_G|_VERSION|assert|collectgarbage|dofile|error|getfenv|getmetatable|ipairs|load|loadfile|module|next|pairs|pcall|print|rawequal|rawget|rawlen|rawset|require|select|setfenv|setmetatable|tonumber|tostring|type|unpack|xpcall)\s*\("
@@ -49,7 +49,7 @@ color statement "(\b(dofile|require|include)|%q|%!|%Q|%r|%x)\b"
color constant.number "\b([0-9]+)\b"
# Symbols
color symbol "(\(|\)|\[|\]|\{|\}|\*\*|\*|/|%|\+|-|\^|>|>=|<|<=|~=|=|\.\.)"
color statement "(\(|\)|\[|\]|\{|\})"
# Strings
color constant.string "\"(\\.|[^\\\"])*\"|'(\\.|[^\\'])*'"

View File

@@ -1,13 +0,0 @@
syntax "mail" "(.*/mutt-.*|\.eml)$"
header "^From .* \d+:\d+:\d+ \d+"
color yellow "^From .*"
color identifier "^[^[:space:]]+:"
color preproc "^List-(Id|Archive|Subscribe|Unsubscribe|Post|Help):"
color constant "^(To|From):"
color constant.string "^Subject:.*"
color statement "<?[^@[:space:]]+@[^[:space:]]+>?"
color default start="^\n\n" end=".*"
color comment "^>.*$"

View File

@@ -1,13 +1,10 @@
# Micro syntax by <nickolay02@inbox.ru>
syntax "micro" "\.(micro)$"
color statement "\b(syntax|color(-link)?)\b"
color statement "\b(syntax|color|color-link)\b"
color statement "\b(start=|end=)\b"
color identifier "\b(default|comment|symbol|identifier|constant(.string(.char)?|.number)?|statement|preproc|type|special|underlined|error|todo|statusline|indent-char|(current-)?line-number|gutter-error|gutter-warning|cursor-line|color-column)\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 comment "#.*$"
color constant.string ""(\\.|[^"])*""
color constant.number "#[0-9 A-F a-f]+"
color comment "#.*"

View File

@@ -1,28 +0,0 @@
syntax "nim" "\.nim$"
color preproc "[\{\|]\b(atom|lit|sym|ident|call|lvalue|sideeffect|nosideeffect|param|genericparam|module|type|let|var|const|result|proc|method|iterator|converter|macro|template|field|enumfield|forvar|label|nk[a-zA-Z]+|alias|noalias)\b[\}\|]"
color statement "\b(addr|and|as|asm|atomic|bind|block|break|case|cast|concept|const|continue|converter|defer|discard|distinct|div|do|elif|else|end|enum|except|export|finally|for|from|func|generic|if|import|in|include|interface|is|isnot|iterator|let|macro|method|mixin|mod|nil|not|notin|object|of|or|out|proc|ptr|raise|ref|return|shl|shr|static|template|try|tuple|type|using|var|when|while|with|without|xor|yield)\b"
color statement "\b(deprecated|noSideEffect|constructor|destructor|override|procvar|compileTime|noReturn|acyclic|final|shallow|pure|asmNoStackFrame|error|fatal|warning|hint|line|linearScanEnd|computedGoto|unroll|immediate|checks|boundsChecks|overflowChecks|nilChecks|assertations|warnings|hints|optimization|patterns|callconv|push|pop|global|pragma|experimental|bitsize|volatile|noDecl|header|incompleteStruct|compile|link|passC|passL|emit|importc|importcpp|importobjc|codegenDecl|injectStmt|intdefine|strdefine|varargs|exportc|extern|bycopy|byref|union|packed|unchecked|dynlib|cdecl|thread|gcsafe|threadvar|guard|locks|compileTime)\b"
color statement "[=\+\-\*/<>@\$~&%\|!\?\^\.:\\]+"
color special "\{\." "\.\}" "\[\." "\.\]" "\(\." "\.\)" ";" "," "`"
color statement "\.\."
color type "\b(int|cint|int8|int16|int32|int64|uint|uint8|uint16|uint32|uint64|float|float32|float64|bool|char|enum|string|cstring|array|openarray|seq|varargs|tuple|object|set|void|auto|cshort|range|nil|T|untyped|typedesc)\b"
color type "'[iI](8|16|32|64)?\b" "'[uU](8|16|32|64)?\b" "'[fF](32|64|128)?\b" "'[dD]\b"
color constant.number "\b[0-9]+\b"
color constant.number "\b0[xX][0-9][0-9_]+\b"
color constant.number "\b0[ocC][0-7][0-7_]+\b"
color constant.number "\b0[bB][01][01_]+\b"
color constant.number "\b[0-9_]((\.?)[0-9_]+)?[eE][+\-][0-9][0-9_]+\b"
color constant.string ""(\\.|[^"])*"|'(\\.|[^'])*'"
color comment "[[:space:]]*#.*$"
color comment start="\#\[" end="\]\#"
color todo "(TODO|FIXME|XXX):?"

View File

@@ -1,37 +0,0 @@
## Here is an example for Obj-C.
##
syntax "Objective-C" "\.(m|mm|h)$"
color type "\b(float|double|CGFloat|id|bool|BOOL|Boolean|char|int|short|long|sizeof|enum|void|static|const|struct|union|typedef|extern|(un)?signed|inline|Class|SEL|IMP|NS(U)?Integer)\b"
color type "\b((s?size)|((u_?)?int(8|16|32|64|ptr)))_t\b"
color type "\b[A-Z][A-Z][[:alnum:]]*\b"
color type "\b[A-Za-z0-9_]*_t\b"
color type "\bdispatch_[a-zA-Z0-9_]*_t\b"
color statement "__attribute__[[:space:]]*\(\([^)]*\)\)" "__(aligned|asm|builtin|hidden|inline|packed|restrict|section|typeof|weak)__" "__unused" "_Nonnull" "_Nullable" "__block" "__builtin.*"
color statement "\b(class|namespace|template|public|protected|private|typename|this|friend|virtual|using|mutable|volatile|register|explicit)\b"
color statement "\b(for|if|while|do|else|case|default|switch)\b"
color statement "\b(try|throw|catch|operator|new|delete)\b"
color statement "\b(goto|continue|break|return)\b"
color statement "\b(nonatomic|atomic|readonly|readwrite|strong|weak|assign)\b"
color statement "@(encode|end|interface|implementation|class|selector|protocol|synchronized|try|catch|finally|property|optional|required|import|autoreleasepool)"
color preproc "^[[:space:]]*#[[:space:]]*(define|include|import|(un|ifn?)def|endif|el(if|se)|if|warning|error|pragma).*$"
color preproc "__[A-Z0-9_]*__"
color special "^[[:space:]]*[#|@][[:space:]]*(import|include)[[:space:]]*[\"|<].*\/?[>|\"][[:space:]]*$"
color statement "[.:;,+*|=!\%\[\]]" "<" ">" "/" "-" "&"
color constant.number "\b(-?)?[0-9]+\b" "\b\[0-9]+\.[0-9]+\b" "\b0x[0-9A-F]+\b"
color constant "@\[(\\.|[^\]])*\]" "@\{(\\.|[^\}])*\}" "@\((\\.|[^\)])*\)"
color constant "\b<(\\.[^\>])*\>\b"
color constant "\b(nil|NULL|YES|NO|TRUE|true|FALSE|false|self)\b"
color constant "\bk[[:alnum]]*\b"
color constant.string "\"(\\.|[^\"])*\"" "@\"(\\.|[^\"])*\"" "'.'"
color comment "//.*"
color comment start="/\*" end="\*/"

View File

@@ -1,20 +1,25 @@
syntax "ocaml" "\.mli?$"
# Numbers
## Integers
### Binary
color constant.number "-?0[bB][01][01_]*"
### Octal
color constant.number "-?0[oO][0-7][0-7_]*"
### Decimal
color constant.number "-?\d[\d_]*"
### Hexadecimal
color constant.number "-?0[xX][0-9a-fA-F][0-9a-fA-F_]*"
## Real
### Decimal
color constant.number "-?\d[\d_]*.\d[\d_]*([eE][+-]\d[\d_]*.\d[\d_]*)?"
### Hexadecimal
color constant.number "-?0[xX][0-9a-fA-F][0-9a-fA-F_]*.[0-9a-fA-F][0-9a-fA-F_]*([pP][+-][0-9a-fA-F][0-9a-fA-F_]*.[0-9a-fA-F][0-9a-fA-F_]*)?"
# Comments
color comment start="\(\*" end="\*\)"
#uid
color red "\<[A-Z][0-9a-z_]{2,}\>"
#declarations
color green "\<(let|val|method|in|and|rec|private|virtual|constraint)\>"
#structure items
color red "\<(type|open|class|module|exception|external)\>"
#patterns
color blue "\<(fun|function|functor|match|try|with)\>"
#patterns-modifiers
color yellow "\<(as|when|of)\>"
#conditions
color cyan "\<(if|then|else)\>"
#blocs
color magenta "\<(begin|end|object|struct|sig|for|while|do|done|to|downto)\>"
#constantes
color green "\<(true|false)\>"
#modules/classes
color green "\<(include|inherit|initializer)\>"
#expr modifiers
color yellow "\<(new|ref|mutable|lazy|assert|raise)\>"
#comments
color white start="\(\*" end="\*\)"
#strings (no multiline handling yet)
color brightblack ""[^\"]*""

View File

@@ -2,6 +2,12 @@ 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"
@@ -9,15 +15,10 @@ color statement "\b(?i:(as|class|dispose|except|exit|exports|finalization|finall
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 special start="asm" end="end"
color constant.number "\$[0-9A-Fa-f]+" "\b[+-]?[0-9]+([.]?[0-9]+)?(?i:e[+-]?[0-9]+)?"
color constant.string "#[0-9]{1,}"
color constant.string "'(?:[^']+|'')*'"
color preproc start="{\$" end="}"
color comment "//.*"
color comment start="\(\*" end="\*\)"
color comment start="({)(?:[^$])" end="}"
color preproc start="{\$" end="}"

View File

@@ -24,9 +24,7 @@ color statement "(=>|===|!==|==|!=|&&|\|\||::|=|->|\!)"
color default "(\$[a-zA-Z0-9\-_]+)"
color default "[\(|\)|/|+|-|\*|\[|,|;]"
color constant.string ""(\\.|[^"])*"|'(\\.|[^'])*'"
color constant.specialChar "\\[abfnrtv'\"\\]"
color constant.string "('.*?'|\".*?\")"
color comment "(^|[[:space:]])//.*"
color comment "(^|[[:space:]])#.*"
color comment start="/\*" end="\*/"
color comment "(#.*|//.*)$"

View File

@@ -0,0 +1,23 @@
## 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)\>"

View File

@@ -1,31 +0,0 @@
syntax "pony" "\.pony$"
color statement "\b(type|interface|trait|primitive|class|struct|actor)\b"
color statement "\b(compiler_intrinsic)\b"
color statement "\b(use)\b"
color statement "\b(var|let|embed)\b"
color statement "\b(new|be|fun)\b"
color statement "\b(iso|trn|ref|val|box|tag|consume)\b"
color statement "\b(break|continue|return|error)\b"
color statement "\b(if|then|elseif|else|end|match|where|try|with|as|recover|object|lambda|as|digestof|ifdef)\b"
color statement "\b(while|do|repeat|until|for|in)\b"
color statement "(\?|=>)"
color statement "(\||\&|\,|\^)"
color statement "(\-|\+|\*|/|\!|%|<<|>>)"
color statement "(==|!=|<=|>=|<|>)"
color statement "\b(is|isnt|not|and|or|xor)\b"
color type "\b(_*[A-Z][_a-zA-Z0-9\']*)\b"
color constant "\b(this)\b"
color constant "\b(true|false)\b"
color constant.number "\b((0b[0-1_]*)|(0o[0-7_]*)|(0x[0-9a-fA-F_]*)|([0-9_]+(\.[0-9_]+)?((e|E)(\\+|-)?[0-9_]+)?))\b"
color constant.string ""(\\.|[^"])*""
color comment start=""""[^"]*" end="""""
color comment "(^|[[:space:]])//.*"
color comment start="/\*" end="\*/"
color todo "TODO:?"

View File

@@ -0,0 +1,43 @@
## Here is an example for Python.
##
syntax "python" "\.py$"
header "^#!.*/(env +)?python( |$)"
## built-in objects
color constant "\b(None|self|True|False)\b"
## built-in attributes
color constant "\b(__builtin__|__dict__|__methods__|__members__|__class__|__bases__|__import__|__name__|__doc__|__self__|__debug__)\b"
## built-in functions
color identifier "\b(abs|append|apply|buffer|callable|chr|clear|close|closed|cmp|coerce|compile|complex|conjugate|copy|count|delattr|dir|divmod|eval|execfile|extend|fileno|filter|float|flush|get|getattr|globals|has_key|hasattr|hash|hex|id|index|input|insert|int|intern|isatty|isinstance|issubclass|items|keys|len|list|locals|long|map|max|min|mode|name|oct|open|ord|pop|pow|range|raw_input|read|readline|readlines|reduce|reload|remove|repr|reverse|round|seek|setattr|slice|softspace|sort|str|tell|truncate|tuple|type|unichr|unicode|update|values|vars|write|writelines|xrange|zip)\b"
## special method names
color identifier "\b(__abs__|__add__|__and__|__call__|__cmp__|__coerce__|__complex__|__concat__|__contains__|__del__|__delattr__|__delitem__|__delslice__|__div__|__divmod__|__float__|__getattr__|__getitem__|__getslice__|__hash__|__hex__|__init__|__int__|__inv__|__invert__|__len__|__long__|__lshift__|__mod__|__mul__|__neg__|__nonzero__|__oct__|__or__|__pos__|__pow__|__radd__|__rand__|__rcmp__|__rdiv__|__rdivmod__|__repeat__|__repr__|__rlshift__|__rmod__|__rmul__|__ror__|__rpow__|__rrshift__|__rshift__|__rsub__|__rxor__|__setattr__|__setitem__|__setslice__|__str__|__sub__|__xor__)\b"
## exception classes
# color cyan "\b(Exception|StandardError|ArithmeticError|LookupError|EnvironmentError|AssertionError|AttributeError|EOFError|FloatingPointError|IOError|ImportError|IndexError|KeyError|KeyboardInterrupt|MemoryError|NameError|NotImplementedError|OSError|OverflowError|RuntimeError|SyntaxError|SystemError|SystemExit|TypeError|UnboundLocalError|UnicodeError|ValueError|WindowsError|ZeroDivisionError)\b"
## types
color type "\b(NoneType|TypeType|IntType|LongType|FloatType|ComplexType|StringType|UnicodeType|BufferType|TupleType|ListType|DictType|FunctionType|LambdaType|CodeType|ClassType|UnboundMethodType|InstanceType|MethodType|BuiltinFunctionType|BuiltinMethodType|ModuleType|FileType|XRangeType|TracebackType|FrameType|SliceType|EllipsisType)\b"
## definitions
color identifier "def [a-zA-Z_0-9]+"
## keywords
color statement "\b(and|as|assert|break|class|continue|def|del|elif|else|except|exec|finally|for|from|global|if|import|in|is|lambda|map|not|or|pass|print|raise|return|try|with|while|yield)\b"
## decorators
color brightgreen "@.*[(]"
## operators
color statement "[.:;,+*|=!\%@]" "<" ">" "/" "-" "&"
## parentheses
color statement "[(){}]" "\[" "\]"
## numbers
color constant.number "\b[0-9]+\b"
## strings
color constant.string ""(\\.|[^"])*"|'(\\.|[^'])*'"
## brightblacks
color comment "#.*$"
## block brightblacks
color comment start=""""([^"]|$)" end="""""
color comment start="'''([^']|$)" end="'''"

View File

@@ -1,46 +0,0 @@
## Here is an example for Python.
##
syntax "python" "\.py$"
header "^#!.*/(env +)?python( |$)"
## built-in objects
color constant "\b(None|self|True|False)\b"
## built-in attributes
color constant "\b(__bases__|__builtin__|__class__|__debug__|__dict__|__doc__|__file__|__members__|__methods__|__name__|__self__)\b"
## built-in functions
color identifier "\b(abs|apply|callable|chr|cmp|compile|delattr|dir|divmod|eval|exec|execfile|filter|format|getattr|globals|hasattr|hash|help|hex|id|input|intern|isinstance|issubclass|len|locals|max|min|next|oct|open|ord|pow|range|raw_input|reduce|reload|repr|round|setattr|unichr|vars|zip|__import__)\b"
## some standard library methods / attributes
#color identifier "\b(append|clear|close|closed|coerce|conjugate|copy|count|extend|fileno|flush|get|has_key|index|insert|items|read|readline|readlines|isatty|keys|mode|name|pop|remove|reverse|seek|softspace|sort|tell|truncate|write|writelines|update|values)\b"
## special method names
color identifier "\b(__abs__|__add__|__and__|__call__|__cmp__|__coerce__|__complex__|__concat__|__contains__|__del__|__delattr__|__delitem__|__dict__|__delslice__|__div__|__divmod__|__float__|__getattr__|__getitem__|__getslice__|__hash__|__hex__|__init__|__int__|__inv__|__invert__|__len__|__long__|__lshift__|__mod__|__mul__|__neg__|__nonzero__|__oct__|__or__|__pos__|__pow__|__radd__|__rand__|__rcmp__|__rdiv__|__rdivmod__|__repeat__|__repr__|__rlshift__|__rmod__|__rmul__|__ror__|__rpow__|__rrshift__|__rshift__|__rsub__|__rxor__|__setattr__|__setitem__|__setslice__|__str__|__sub__|__xor__)\b"
## exception classes
# color cyan "\b(Exception|StandardError|ArithmeticError|LookupError|EnvironmentError|AssertionError|AttributeError|EOFError|FloatingPointError|IOError|ImportError|IndexError|KeyError|KeyboardInterrupt|MemoryError|NameError|NotImplementedError|OSError|OverflowError|RuntimeError|SyntaxError|SystemError|SystemExit|TypeError|UnboundLocalError|UnicodeError|ValueError|WindowsError|ZeroDivisionError)\b"
## types
color type "\b(basestring|bool|buffer|bytearray|bytes|classmethod|complex|dict|enumerate|file|float|frozenset|int|list|long|map|memoryview|object|property|reversed|set|slice|staticmethod|str|super|tuple|type|unicode|xrange)\b"
#color type "\b(NoneType|TypeType|IntType|LongType|FloatType|ComplexType|StringType|UnicodeType|BufferType|TupleType|ListType|DictType|FunctionType|LambdaType|CodeType|ClassType|UnboundMethodType|InstanceType|MethodType|BuiltinFunctionType|BuiltinMethodType|ModuleType|FileType|XRangeType|TracebackType|FrameType|SliceType|EllipsisType)\b"
## definitions
color identifier "def [a-zA-Z_0-9]+"
## keywords
color statement "\b(and|as|assert|break|class|continue|def|del|elif|else|except|finally|for|from|global|if|import|in|is|lambda|not|or|pass|print|raise|return|try|while|with|yield)\b"
## decorators
color brightgreen "@.*[(]"
## operators
color statement "[.:;,+*|=!\%@]" "<" ">" "/" "-" "&"
## parentheses
color statement "[(){}]" "\[" "\]"
## numbers
color constant.number "\b[0-9]+\b"
## strings
color constant.string ""(\\.|[^"])*"|'(\\.|[^'])*'"
## brightblacks
color comment "#.*$"
## block brightblacks
color comment start=""""([^"]|$)" end="""""
color comment start="'''([^']|$)" end="'''"

View File

@@ -1,46 +0,0 @@
## Here is an example for Python.
##
syntax "python3" "\.py3$"
header "^#!.*/(env +)?python3$"
## built-in objects
color constant "\b(None|self|True|False)\b"
## built-in attributes
color constant "\b(__bases__|__builtin__|__class__|__debug__|__dict__|__doc__|__file__|__members__|__methods__|__name__|__self__)\b"
## built-in functions
color identifier "\b(abs|all|any|ascii|bin|callable|chr|compile|delattr|dir|divmod|eval|exec|format|getattr|globals|hasattr|hash|help|hex|id|input|isinstance|issubclass|iter|len|locals|max|min|next|oct|open|ord|pow|print|repr|round|setattr|sorted|sum|vars|__import__)\b"
## some standard library methods / attributes
#color identifier "\b(append|clear|close|closed|coerce|conjugate|copy|count|extend|fileno|flush|get|has_key|index|insert|items|read|readline|readlines|isatty|keys|mode|name|pop|remove|reverse|seek|softspace|sort|tell|truncate|write|writelines|update|values)\b"
## special method names
color identifier "\b(__abs__|__add__|__and__|__call__|__cmp__|__coerce__|__complex__|__concat__|__contains__|__del__|__delattr__|__delitem__|__delslice__|__div__|__divmod__|__float__|__getattr__|__getitem__|__getslice__|__hash__|__hex__|__init__|__int__|__inv__|__invert__|__len__|__dict__|__long__|__lshift__|__mod__|__mul__|__neg__|__next__|__nonzero__|__oct__|__or__|__pos__|__pow__|__radd__|__rand__|__rcmp__|__rdiv__|__rdivmod__|__repeat__|__repr__|__rlshift__|__rmod__|__rmul__|__ror__|__rpow__|__rrshift__|__rshift__|__rsub__|__rxor__|__setattr__|__setitem__|__setslice__|__str__|__sub__|__xor__)\b"
## exception classes
# color cyan "\b(Exception|StandardError|ArithmeticError|LookupError|EnvironmentError|AssertionError|AttributeError|EOFError|FloatingPointError|IOError|ImportError|IndexError|KeyError|KeyboardInterrupt|MemoryError|NameError|NotImplementedError|OSError|OverflowError|RuntimeError|SyntaxError|SystemError|SystemExit|TypeError|UnboundLocalError|UnicodeError|ValueError|WindowsError|ZeroDivisionError)\b"
## types
color type "\b(bool|bytearray|bytes|classmethod|complex|dict|enumerate|filter|float|frozenset|int|list|map|memoryview|object|property|range|reversed|set|slice|staticmethod|str|super|tuple|type|zip)\b"
#color type "\b(NoneType|TypeType|IntType|LongType|FloatType|ComplexType|StringType|UnicodeType|BufferType|TupleType|ListType|DictType|FunctionType|LambdaType|CodeType|ClassType|UnboundMethodType|InstanceType|MethodType|BuiltinFunctionType|BuiltinMethodType|ModuleType|FileType|TracebackType|FrameType|SliceType|EllipsisType)\b"
## definitions
color identifier "def [a-zA-Z_0-9]+"
## keywords
color statement "\b(and|as|assert|break|class|continue|def|del|elif|else|except|finally|for|from|global|if|import|in|is|lambda|nonlocal|not|or|pass|raise|return|try|while|with|yield)\b"
## decorators
color brightgreen "@.*[(]"
## operators
color statement "[.:;,+*|=!\%@]" "<" ">" "/" "-" "&"
## parentheses
color statement "[(){}]" "\[" "\]"
## numbers
color constant.number "\b[0-9]+\b"
## strings
color constant.string ""(\\.|[^"])*"|'(\\.|[^'])*'"
## brightblacks
color comment "#.*$"
## block brightblacks
color comment start=""""([^"]|$)" end="""""
color comment start="'''([^']|$)" end="'''"

View File

@@ -1,41 +0,0 @@
# Solidity syntax for Micro
# Copyright (C) 2016 Nicolai Søborg
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
syntax "solidity" "\.sol$"
color preproc "\b(contract|library|pragma)\b"
color constant.number "\b[-]?([0-9]+|0x[0-9a-fA-F]+)\b"
color identifier "[a-zA-Z][_a-zA-Z0-9]*[[:space:]]*"
color statement "\b(assembly|break|continue|do|for|function|if|else|new|return|returns|while)\b"
color special "\b(\.send|throw)\b" # make sure they are very visible
color keyword "\b(anonymous|constant|indexed|payable|public|private|external|internal)\b"
color constant "\b(block(\.(blockhash|coinbase|difficulty|gaslimit|number|timestamp))?|msg(\.(data|gas|sender|value))?|now|tx(\.(gasprice|origin))?)\b"
color constant "\b(keccak256|sha3|sha256|ripemd160|ecrecover|addmod|mulmod|this|super|selfdestruct|\.balance)\b"
color constant "\b(true|false)\b"
color constant "\b(wei|szabo|finney|ether|seconds|minutes|hours|days|weeks|years)\b"
color type "\b(address|bool|mapping|string|var|int(\d*)|uint(\d*)|byte(\d*)|fixed(\d*)|ufixed(\d*))\b"
color error "\b(abstract|after|case|catch|default|final|in|inline|interface|let|match|null|of|pure|relocatable|static|switch|try|type|typeof|view)\b"
color operator "[-+/*=<>!~%?:&|]"
color comment "(^|[[:space:]])//.*"
color comment "/\*.+\*/"
color todo "TODO:?"
color constant.string ""(\\.|[^"])*"|'(\\.|[^'])*'"

View File

@@ -1,21 +1,8 @@
## TeX
## Here is a short example for TeX files.
##
syntax "tex" "\.tex$" "bib" "\.bib$" "cls" "\.cls$"
## colorize the identifiers of {<identifier>} and [<identifier>]
color identifier start="\{" end="\}"
color identifier start="\[" end="\]"
## numbers
color constant.number "\b[0-9]+(\.[0-9]+)?([[:space:]](pt|mm|cm|in|ex|em|bp|pc|dd|cc|nd|nc|sp))?\b"
## let brackets have the default color again
color default "[{}\[\]]"
color special "[&\\]"
## macros
color statement "\\@?[a-zA-Z_]+"
## commments
color comment "%.*"
color comment start="\\begin\{comment\}" end="\\end\{comment\}"
color yellow "\$[^$]*\$"
green (i) "\\.|\\[A-Z]*"
color magenta "[{}]"
color blue "%.*"
color blue start="\\begin\{comment\}" end="\\end\{comment\}"

View File

@@ -1,21 +0,0 @@
syntax "toml" "\.toml$"
# Keys
color statement "(.*)[[:space:]]="
color special "="
# Bracket thingies
color special "(\[|\])"
# Numbers and strings
color constant.number "\b([0-9]+|0x[0-9a-fA-F]*)\b|'.'"
color constant.string ""(\\.|[^"])*"|'(\\.|[^'])*'"
color constant.specialChar "\\[abfnrtv'\"\\]"
color constant.specialChar "\\([0-7]{3}|x[A-Fa-f0-9]{2}|u[A-Fa-f0-9]{4}|U[A-Fa-f0-9]{8})"
color constant.string "`[^`]*`"
color constant.specialChar """
color constant.specialChar "'"
# Comments & TODOs
color comment "(^|[[:space:]])#.*"
color todo "(TODO|XXX|FIXME):?"

View File

@@ -4,15 +4,13 @@ color constant.number "\b[-+]?([1-9][0-9]*|0[0-7]*|0x[0-9a-fA-F]+)([uU][lL]?|[
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(abstract|as|async|await|break|case|catch|class|const|constructor|continue)\b"
color statement "\b(debugger|declare|default|delete|do|else|enum|export|extends|finally|for|from)\b"
color statement "\b(function|get|if|implements|import|in|instanceof|interface|is|let|module|namespace)\b"
color statement "\b(new|of|package|private|protected|public|require|return|set|static|super|switch)\b"
color statement "\b(this|throw|try|type|typeof|var|void|while|with|yield)\b"
color constant "\b(false|true|null|undefined|NaN)\b"
color type "\b(Array|Boolean|Date|Enumerator|Error|Function|Math)\b"
color type "\b(Number|Object|RegExp|String|Symbol)\b"
color type "\b(any|boolean|never|number|string|symbol)\b"
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'"\?\\]"

View File

@@ -1,50 +0,0 @@
## vhdl
syntax "vhdl" "\.vhdl?$"
## types
color type (i) "\b(string|integer|natural|positive|(un)?signed|std_u?logic(_vector)?|bit(_vector)?|boolean|u?x01z?|array|range)\b"
## identifiers (component-, library-names etc.)
color identifier (i) "library[[:space:]]+[a-zA-Z_0-9]+"
color identifier (i) "use[[:space:]]+[a-zA-Z_0-9\.]+"
color identifier (i) "component[[:space:]]+[a-zA-Z_0-9]+"
color identifier (i) "(architecture|configuration)[[:space:]]+[a-zA-Z_0-9]+[[:space:]]+of[[:space:]]+[a-zA-Z_0-9]+"
color identifier (i) "(entity|package)[[:space:]]+[a-zA-Z_0-9]+[[:space:]]+is"
color identifier (i) "end[[:space:]]+((architecture|entity|component|process|package|generate)[[:space:]]+)?[a-zA-Z_0-9]+"
## reserved words
color statement (i) "\b(abs|access|after|alias|all|and|architecture|assert|attribute)\b"
color statement (i) "\b(begin|block|body|buffer|bus|case|component|configuration|constant)\b"
color statement (i) "\b(disconnect|downto|else|elsif|end|entity|exit)\b"
color statement (i) "\b(file|for|function|generate|generic|guarded)\b"
color statement (i) "\b(if|impure|in|inertial|inout|is)\b"
color statement (i) "\b(label|library|linkage|literal|loop|map|mod)\b"
color statement (i) "\b(nand|new|next|nor|not|null|of|on|open|or|others|out)\b"
color statement (i) "\b(package|port|postponed|procedure|process|pure)\b"
color statement (i) "\b(range|record|register|reject|rem|report|return|rol|ror)\b"
color statement (i) "\b(select|severity|shared|signal|sla|sll|sra|srl|subtype)\b"
color statement (i) "\b(then|to|transport|type|unaffected|units|until|use)\b"
color statement (i) "\b(variable|wait|when|while|with|xnor|xor)\b"
## attributes
color statement (i) "'(base|left|right|high|low|pos|val|succ|pred|leftof|rightof|image|(last_)?value)"
color statement (i) "'((reverse_)?range|length|ascending|event|stable)"
color statement (i) "'(simple|path|instance)_name"
## library functions
color statement (i) "\b(std_match|(rising|falling)_edge|is_x)\b"
color statement (i) "\bto_(unsigned|signed|integer|u?x01z?|stdu?logic(vector)?)\b"
## operators
color statement "(\+|-|\*|/|&|<|>|=|\.|:)"
## constants
color constant.number (i) "'([0-1]|u|x|z|w|l|h|-)'" "[box]?"([0-1a-fA-F]|u|x|z|w|l|h|-)+""
color constant.number (i) "\b[0-9\._]+(e[-]?[0-9]+)?( ?[fpnum]?s)?\b"
color constant (i) "\b(true|false)\b"
## severity levels
color constant (i) "\b(note|warning|error|failure)\b"
color constant.string ""[^"]*""
## Comment highlighting
color comment "--.*"

View File

@@ -1,7 +1,11 @@
## Here is an example for xml files.
##
syntax "xml" "\.(xml|sgml?|rng|plist)$"
color identifier "<.*?>"
color comment start="<!DOCTYPE" end="[/]?>"
color comment start="<!--" end="-->"
color special "&[^;]*;"
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 "&[^;]*;"

View File

@@ -1,12 +1,11 @@
syntax "yaml" "\.ya?ml$"
header "%YAML"
header "^---" "%YAML"
color type "(^| )!!(binary|bool|float|int|map|null|omap|seq|set|str) "
color constant "\b(YES|yes|Y|y|ON|on|NO|no|N|n|OFF|off)\b"
color constant "\b(true|false)\b"
color statement ":[[:space:]]" "\[" "\]" ":[[:space:]]+[|>]" "^[[:space:]]*- "
color identifier "[[:space:]][\*&][A-Za-z0-9]+"
color type "([-\w]+:\s+)|([-\w]+:$)"
color constant.string ""(\\.|[^"])*"|'(\\.|[^'])*'"
color comment "(^|[[:space:]])#([^{].*)?$"
color special "^---" "^\.\.\." "^%YAML" "^%TAG"

View File

@@ -1,19 +0,0 @@
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

View File

@@ -1,65 +0,0 @@
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("0.0.0-unknown")
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())
}

View File

@@ -1,69 +1,60 @@
# Source tar
./vendor-src.sh micro-$1-src
cd ..
mkdir -p binaries
mkdir -p micro-$1
mv micro-$1-src.tar.gz binaries
mv micro-$1-src.zip binaries
cp LICENSE micro-$1
cp README.md micro-$1
HASH="$(git rev-parse --short HEAD)"
VERSION="$(go run tools/build-version.go)"
DATE="$(go run tools/build-date.go)"
ADDITIONAL_GO_LINKER_FLAGS="$(go run tools/info-plist.go $VERSION)"
# Mac
echo "OSX 64"
GOOS=darwin GOARCH=amd64 go build -ldflags "-s -w -X main.Version=$1 -X main.CommitHash=$HASH -X 'main.CompileDate=$DATE' $ADDITIONAL_GO_LINKER_FLAGS" -o micro-$1/micro ./cmd/micro
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
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 "-s -w -X main.Version=$1 -X main.CommitHash=$HASH -X 'main.CompileDate=$DATE'" -o micro-$1/micro ./cmd/micro
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
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 "-s -w -X main.Version=$1 -X main.CommitHash=$HASH -X 'main.CompileDate=$DATE'" -o micro-$1/micro ./cmd/micro
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
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 "-s -w -X main.Version=$1 -X main.CommitHash=$HASH -X 'main.CompileDate=$DATE'" -o micro-$1/micro ./cmd/micro
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
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 "-s -w -X main.Version=$1 -X main.CommitHash=$HASH -X 'main.CompileDate=$DATE'" -o micro-$1/micro ./cmd/micro
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
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 "-s -w -X main.Version=$1 -X main.CommitHash=$HASH -X 'main.CompileDate=$DATE'" -o micro-$1/micro ./cmd/micro
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
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 "-s -w -X main.Version=$1 -X main.CommitHash=$HASH -X 'main.CompileDate=$DATE'" -o micro-$1/micro ./cmd/micro
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
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 "-s -w -X main.Version=$1 -X main.CommitHash=$HASH -X 'main.CompileDate=$DATE'" -o micro-$1/micro ./cmd/micro
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
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 "-s -w -X main.Version=$1 -X main.CommitHash=$HASH -X 'main.CompileDate=$DATE'" -o micro-$1/micro ./cmd/micro
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
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 "-s -w -X main.Version=$1 -X main.CommitHash=$HASH -X 'main.CompileDate=$DATE'" -o micro-$1/micro ./cmd/micro
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
tar -czf micro-$1-freebsd32.tar.gz micro-$1
mv micro-$1-freebsd32.tar.gz binaries
@@ -71,11 +62,11 @@ rm micro-$1/micro
# Windows
echo "Windows 64"
GOOS=windows GOARCH=amd64 go build -ldflags "-s -w -X main.Version=$1 -X main.CommitHash=$HASH -X 'main.CompileDate=$DATE'" -o micro-$1/micro.exe ./cmd/micro
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
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 "-s -w -X main.Version=$1 -X main.CommitHash=$HASH -X 'main.CompileDate=$DATE'" -o micro-$1/micro.exe ./cmd/micro
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
zip -r -q -T micro-$1-win32.zip micro-$1
mv micro-$1-win32.zip binaries

View File

@@ -1,45 +0,0 @@
package main
import (
"os"
"fmt"
"runtime"
"io/ioutil"
)
func check(e error) {
if e != nil {
panic(e)
}
}
func main() {
if runtime.GOOS == "darwin" {
if len(os.Args) == 2 {
raw_info_plist_string := `<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleIdentifier</key>
<string>io.github.micro-editor</string>
<key>CFBundleName</key>
<string>micro</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>` + os.Args[1] + `</string>
</dict>
</plist>
`
info_plist_data := []byte(raw_info_plist_string)
err := ioutil.WriteFile("/tmp/micro-info.plist", info_plist_data, 0644)
check(err)
fmt.Println("-linkmode external -extldflags -Wl,-sectcreate,__TEXT,__info_plist,/tmp/micro-info.plist")
} else {
panic("missing argument for version number!")
}
}
}

View File

@@ -118,19 +118,3 @@ github-release upload \
--tag nightly \
--name "micro-$1-win32.zip" \
--file binaries/micro-$1-win32.zip
echo "Uploading vendored tarball"
github-release upload \
--user zyedidia \
--repo micro \
--tag nightly \
--name "micro-$1-src.tar.gz" \
--file binaries/micro-$1-src.tar.gz
echo "Uploading vendored zip"
github-release upload \
--user zyedidia \
--repo micro \
--tag nightly \
--name "micro-$1-src.zip" \
--file binaries/micro-$1-src.zip

View File

@@ -115,19 +115,3 @@ github-release upload \
--tag $tag \
--name "micro-$1-win32.zip" \
--file binaries/micro-$1-win32.zip
echo "Uploading vendored tarball"
github-release upload \
--user zyedidia \
--repo micro \
--tag $tag \
--name "micro-$1-src.tar.gz" \
--file binaries/micro-$1-src.tar.gz
echo "Uploading vendored zip"
github-release upload \
--user zyedidia \
--repo micro \
--tag $tag \
--name "micro-$1-src.zip" \
--file binaries/micro-$1-src.zip

View File

@@ -115,19 +115,3 @@ github-release upload \
--name "micro-$1-win32.zip" \
--file binaries/micro-$1-win32.zip
echo "Uploading vendored tarball"
github-release upload \
--user zyedidia \
--repo micro \
--tag $tag \
--name "micro-$1-src.tar.gz" \
--file binaries/micro-$1-src.tar.gz
echo "Uploading vendored zip"
github-release upload \
--user zyedidia \
--repo micro \
--tag $tag \
--name "micro-$1-src.zip" \
--file binaries/micro-$1-src.zip

View File

@@ -1,14 +0,0 @@
cd ../cmd/micro
govendor init
govendor add +e
cd ../../..
tar czf "$1".tar.gz micro
zip -rq "$1".zip micro
mv "$1".tar.gz micro
mv "$1".zip micro
cd micro/cmd/micro
rm -rf vendor