]> git.lizzy.rs Git - micro.git/commitdiff
Start refactor
authorZachary Yedidia <zyedidia@gmail.com>
Sun, 26 Aug 2018 03:06:44 +0000 (23:06 -0400)
committerZachary Yedidia <zyedidia@gmail.com>
Wed, 25 Dec 2019 22:05:10 +0000 (17:05 -0500)
52 files changed:
.gitignore
Makefile
cmd/micro/actionhandler.go [new file with mode: 0644]
cmd/micro/actions.go [deleted file]
cmd/micro/actions_other.go [deleted file]
cmd/micro/actions_posix.go [deleted file]
cmd/micro/autocomplete.go [deleted file]
cmd/micro/bindings.go [deleted file]
cmd/micro/buffer.go
cmd/micro/buffer_test.go [deleted file]
cmd/micro/cellview.go [deleted file]
cmd/micro/colorscheme.go
cmd/micro/colorscheme_test.go [new file with mode: 0644]
cmd/micro/command.go [deleted file]
cmd/micro/cursor.go
cmd/micro/cursor_test.go [new file with mode: 0644]
cmd/micro/debug.go [new file with mode: 0644]
cmd/micro/eventhandler.go
cmd/micro/highlighter.go [deleted file]
cmd/micro/job.go [deleted file]
cmd/micro/keymenu.go [deleted file]
cmd/micro/lineArray.go [deleted file]
cmd/micro/line_array.go [new file with mode: 0644]
cmd/micro/line_array_test.go [new file with mode: 0644]
cmd/micro/loc.go
cmd/micro/lua.go
cmd/micro/message.go [new file with mode: 0644]
cmd/micro/messenger.go [deleted file]
cmd/micro/micro.go
cmd/micro/plugin.go [deleted file]
cmd/micro/pluginmanager.go [deleted file]
cmd/micro/pluginmanager_test.go [deleted file]
cmd/micro/profile.go [new file with mode: 0644]
cmd/micro/rtfiles.go
cmd/micro/rtfiles_test.go [new file with mode: 0644]
cmd/micro/runtime.go
cmd/micro/scrollbar.go [deleted file]
cmd/micro/search.go [deleted file]
cmd/micro/settings.go
cmd/micro/shell.go [deleted file]
cmd/micro/shell_supported.go [deleted file]
cmd/micro/shell_unsupported.go [deleted file]
cmd/micro/split_tree.go [deleted file]
cmd/micro/stack.go
cmd/micro/stack_test.go [new file with mode: 0644]
cmd/micro/statusline.go
cmd/micro/tab.go [deleted file]
cmd/micro/terminal.go [deleted file]
cmd/micro/util.go
cmd/micro/util_test.go
cmd/micro/view.go [deleted file]
cmd/micro/window.go [new file with mode: 0644]

index 08c1a639abfd0bac5bf28d81727439a57f01c247..acfa073027e48fd895125dead902e3d5e54a5284 100644 (file)
@@ -7,3 +7,6 @@ tmp.sh
 test/
 .idea/
 packages/
+todo.txt
+test.txt
+log.txt
index e03ee2fc7ed7a3045410cf210fcc53db1b27a4e5..08a70768025b8e43b4d04eddbe6ea21cac8077f3 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -9,28 +9,29 @@ ADDITIONAL_GO_LINKER_FLAGS := $(shell GOOS=$(shell go env GOHOSTOS) \
        GOARCH=$(shell go env GOHOSTARCH) \
        go run tools/info-plist.go "$(VERSION)")
 GOBIN ?= $(shell go env GOPATH)/bin
+GOVARS := -X main.Version=$(VERSION) -X main.CommitHash=$(HASH) -X 'main.CompileDate=$(DATE)' -X main.Debug=OFF
 
 # Builds micro after checking dependencies but without updating the runtime
 build: update
-       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 "-s -w $(GOVARS) $(ADDITIONAL_GO_LINKER_FLAGS)" ./cmd/micro
 
 # Builds micro after building the runtime and checking dependencies
 build-all: runtime build
 
 # Builds micro without checking for dependencies
 build-quick:
-       go build -ldflags "-s -w -X main.Version=$(VERSION) -X main.CommitHash=$(HASH) -X 'main.CompileDate=$(DATE)' $(ADDITIONAL_GO_LINKER_FLAGS)" ./cmd/micro
+       go build -ldflags "-s -w $(GOVARS) $(ADDITIONAL_GO_LINKER_FLAGS)" ./cmd/micro
 
 # Same as 'build' but installs to $GOBIN afterward
 install: update
-       go install -ldflags "-s -w -X main.Version=$(VERSION) -X main.CommitHash=$(HASH) -X 'main.CompileDate=$(DATE)' $(ADDITIONAL_GO_LINKER_FLAGS)" ./cmd/micro
+       go install -ldflags "-s -w $(GOVARS) $(ADDITIONAL_GO_LINKER_FLAGS)" ./cmd/micro
 
 # Same as 'build-all' but installs to $GOBIN 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
+       go install -ldflags "-s -w $(GOVARS) $(ADDITIONAL_GO_LINKER_FLAGS)"  ./cmd/micro
 
 update:
        git pull
diff --git a/cmd/micro/actionhandler.go b/cmd/micro/actionhandler.go
new file mode 100644 (file)
index 0000000..3cceabd
--- /dev/null
@@ -0,0 +1,40 @@
+package main
+
+import "time"
+
+// The ActionHandler connects the buffer and the window
+// It provides a cursor (or multiple) and defines a set of actions
+// that can be taken on the buffer
+// The ActionHandler can access the window for necessary info about
+// visual positions for mouse clicks and scrolling
+type ActionHandler struct {
+       Buf *Buffer
+       Win *Window
+
+       // Since tcell doesn't differentiate between a mouse release event
+       // and a mouse move event with no keys pressed, we need to keep
+       // track of whether or not the mouse was pressed (or not released) last event to determine
+       // mouse release events
+       mouseReleased bool
+
+       // We need to keep track of insert key press toggle
+       isOverwriteMode bool
+       // This stores when the last click was
+       // This is useful for detecting double and triple clicks
+       lastClickTime time.Time
+       lastLoc       Loc
+
+       // lastCutTime stores when the last ctrl+k was issued.
+       // It is used for clearing the clipboard to replace it with fresh cut lines.
+       lastCutTime time.Time
+
+       // freshClip returns true if the clipboard has never been pasted.
+       freshClip bool
+
+       // Was the last mouse event actually a double click?
+       // Useful for detecting triple clicks -- if a double click is detected
+       // but the last mouse event was actually a double click, it's a triple click
+       doubleClick bool
+       // Same here, just to keep track for mouse move events
+       tripleClick bool
+}
diff --git a/cmd/micro/actions.go b/cmd/micro/actions.go
deleted file mode 100644 (file)
index 83eec14..0000000
+++ /dev/null
@@ -1,2328 +0,0 @@
-package main
-
-import (
-       "fmt"
-       "os"
-       "regexp"
-       "strconv"
-       "strings"
-       "time"
-       "unicode/utf8"
-
-       "github.com/yuin/gopher-lua"
-       "github.com/zyedidia/clipboard"
-       "github.com/zyedidia/micro/cmd/micro/shellwords"
-       "github.com/zyedidia/tcell"
-)
-
-// PreActionCall executes the lua pre callback if possible
-func PreActionCall(funcName string, view *View, args ...interface{}) bool {
-       executeAction := true
-       for pl := range loadedPlugins {
-               ret, err := Call(pl+".pre"+funcName, append([]interface{}{view}, args...)...)
-               if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") {
-                       TermMessage(err)
-                       continue
-               }
-               if ret == lua.LFalse {
-                       executeAction = false
-               }
-       }
-       return executeAction
-}
-
-// PostActionCall executes the lua plugin callback if possible
-func PostActionCall(funcName string, view *View, args ...interface{}) bool {
-       relocate := true
-       for pl := range loadedPlugins {
-               ret, err := Call(pl+".on"+funcName, append([]interface{}{view}, args...)...)
-               if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") {
-                       TermMessage(err)
-                       continue
-               }
-               if ret == lua.LFalse {
-                       relocate = false
-               }
-       }
-       return relocate
-}
-
-func (v *View) deselect(index int) bool {
-       if v.Cursor.HasSelection() {
-               v.Cursor.Loc = v.Cursor.CurSelection[index]
-               v.Cursor.ResetSelection()
-               v.Cursor.StoreVisualX()
-               return true
-       }
-       return false
-}
-
-// MousePress is the event that should happen when a normal click happens
-// This is almost always bound to left click
-func (v *View) MousePress(usePlugin bool, e *tcell.EventMouse) bool {
-       if usePlugin && !PreActionCall("MousePress", v, e) {
-               return false
-       }
-
-       x, y := e.Position()
-       x -= v.lineNumOffset - v.leftCol + v.x
-       y += v.Topline - v.y
-
-       // This is usually bound to left click
-       v.MoveToMouseClick(x, y)
-       if v.mouseReleased {
-               if len(v.Buf.cursors) > 1 {
-                       for i := 1; i < len(v.Buf.cursors); i++ {
-                               v.Buf.cursors[i] = nil
-                       }
-                       v.Buf.cursors = v.Buf.cursors[:1]
-                       v.Buf.UpdateCursors()
-                       v.Cursor.ResetSelection()
-                       v.Relocate()
-               }
-               if time.Since(v.lastClickTime)/time.Millisecond < doubleClickThreshold && (x == v.lastLoc.X && y == v.lastLoc.Y) {
-                       if v.doubleClick {
-                               // Triple click
-                               v.lastClickTime = time.Now()
-
-                               v.tripleClick = true
-                               v.doubleClick = false
-
-                               v.Cursor.SelectLine()
-                               v.Cursor.CopySelection("primary")
-                       } else {
-                               // Double click
-                               v.lastClickTime = time.Now()
-
-                               v.doubleClick = true
-                               v.tripleClick = false
-
-                               v.Cursor.SelectWord()
-                               v.Cursor.CopySelection("primary")
-                       }
-               } else {
-                       v.doubleClick = false
-                       v.tripleClick = false
-                       v.lastClickTime = time.Now()
-
-                       v.Cursor.OrigSelection[0] = v.Cursor.Loc
-                       v.Cursor.CurSelection[0] = v.Cursor.Loc
-                       v.Cursor.CurSelection[1] = v.Cursor.Loc
-               }
-               v.mouseReleased = false
-       } else if !v.mouseReleased {
-               if v.tripleClick {
-                       v.Cursor.AddLineToSelection()
-               } else if v.doubleClick {
-                       v.Cursor.AddWordToSelection()
-               } else {
-                       v.Cursor.SetSelectionEnd(v.Cursor.Loc)
-                       v.Cursor.CopySelection("primary")
-               }
-       }
-
-       v.lastLoc = Loc{x, y}
-
-       if usePlugin {
-               PostActionCall("MousePress", v, e)
-       }
-       return false
-}
-
-// ScrollUpAction scrolls the view up
-func (v *View) ScrollUpAction(usePlugin bool) bool {
-       if v.mainCursor() {
-               if usePlugin && !PreActionCall("ScrollUp", v) {
-                       return false
-               }
-
-               scrollspeed := int(v.Buf.Settings["scrollspeed"].(float64))
-               v.ScrollUp(scrollspeed)
-
-               if usePlugin {
-                       PostActionCall("ScrollUp", v)
-               }
-       }
-       return false
-}
-
-// ScrollDownAction scrolls the view up
-func (v *View) ScrollDownAction(usePlugin bool) bool {
-       if v.mainCursor() {
-               if usePlugin && !PreActionCall("ScrollDown", v) {
-                       return false
-               }
-
-               scrollspeed := int(v.Buf.Settings["scrollspeed"].(float64))
-               v.ScrollDown(scrollspeed)
-
-               if usePlugin {
-                       PostActionCall("ScrollDown", v)
-               }
-       }
-       return false
-}
-
-// Center centers the view on the cursor
-func (v *View) Center(usePlugin bool) bool {
-       if usePlugin && !PreActionCall("Center", v) {
-               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
-       }
-       if v.Topline < 0 {
-               v.Topline = 0
-       }
-
-       if usePlugin {
-               return PostActionCall("Center", v)
-       }
-       return true
-}
-
-// CursorUp moves the cursor up
-func (v *View) CursorUp(usePlugin bool) bool {
-       if usePlugin && !PreActionCall("CursorUp", v) {
-               return false
-       }
-
-       v.deselect(0)
-       v.Cursor.Up()
-
-       if usePlugin {
-               return PostActionCall("CursorUp", v)
-       }
-       return true
-}
-
-// CursorDown moves the cursor down
-func (v *View) CursorDown(usePlugin bool) bool {
-       if usePlugin && !PreActionCall("CursorDown", v) {
-               return false
-       }
-
-       v.deselect(1)
-       v.Cursor.Down()
-
-       if usePlugin {
-               return PostActionCall("CursorDown", v)
-       }
-       return true
-}
-
-// CursorLeft moves the cursor left
-func (v *View) CursorLeft(usePlugin bool) bool {
-       if usePlugin && !PreActionCall("CursorLeft", v) {
-               return false
-       }
-
-       if v.Cursor.HasSelection() {
-               v.Cursor.Loc = v.Cursor.CurSelection[0]
-               v.Cursor.ResetSelection()
-               v.Cursor.StoreVisualX()
-       } else {
-               tabstospaces := v.Buf.Settings["tabstospaces"].(bool)
-               tabmovement := v.Buf.Settings["tabmovement"].(bool)
-               if tabstospaces && tabmovement {
-                       tabsize := int(v.Buf.Settings["tabsize"].(float64))
-                       line := v.Buf.Line(v.Cursor.Y)
-                       if v.Cursor.X-tabsize >= 0 && line[v.Cursor.X-tabsize:v.Cursor.X] == Spaces(tabsize) && IsStrWhitespace(line[0:v.Cursor.X-tabsize]) {
-                               for i := 0; i < tabsize; i++ {
-                                       v.Cursor.Left()
-                               }
-                       } else {
-                               v.Cursor.Left()
-                       }
-               } else {
-                       v.Cursor.Left()
-               }
-       }
-
-       if usePlugin {
-               return PostActionCall("CursorLeft", v)
-       }
-       return true
-}
-
-// CursorRight moves the cursor right
-func (v *View) CursorRight(usePlugin bool) bool {
-       if usePlugin && !PreActionCall("CursorRight", v) {
-               return false
-       }
-
-       if v.Cursor.HasSelection() {
-               v.Cursor.Loc = v.Cursor.CurSelection[1]
-               v.Cursor.ResetSelection()
-               v.Cursor.StoreVisualX()
-       } else {
-               tabstospaces := v.Buf.Settings["tabstospaces"].(bool)
-               tabmovement := v.Buf.Settings["tabmovement"].(bool)
-               if tabstospaces && tabmovement {
-                       tabsize := int(v.Buf.Settings["tabsize"].(float64))
-                       line := v.Buf.Line(v.Cursor.Y)
-                       if v.Cursor.X+tabsize < Count(line) && line[v.Cursor.X:v.Cursor.X+tabsize] == Spaces(tabsize) && IsStrWhitespace(line[0:v.Cursor.X]) {
-                               for i := 0; i < tabsize; i++ {
-                                       v.Cursor.Right()
-                               }
-                       } else {
-                               v.Cursor.Right()
-                       }
-               } else {
-                       v.Cursor.Right()
-               }
-       }
-
-       if usePlugin {
-               return PostActionCall("CursorRight", v)
-       }
-       return true
-}
-
-// WordRight moves the cursor one word to the right
-func (v *View) WordRight(usePlugin bool) bool {
-       if usePlugin && !PreActionCall("WordRight", v) {
-               return false
-       }
-
-       v.Cursor.WordRight()
-
-       if usePlugin {
-               return PostActionCall("WordRight", v)
-       }
-       return true
-}
-
-// WordLeft moves the cursor one word to the left
-func (v *View) WordLeft(usePlugin bool) bool {
-       if usePlugin && !PreActionCall("WordLeft", v) {
-               return false
-       }
-
-       v.Cursor.WordLeft()
-
-       if usePlugin {
-               return PostActionCall("WordLeft", v)
-       }
-       return true
-}
-
-// SelectUp selects up one line
-func (v *View) SelectUp(usePlugin bool) bool {
-       if usePlugin && !PreActionCall("SelectUp", v) {
-               return false
-       }
-
-       if !v.Cursor.HasSelection() {
-               v.Cursor.OrigSelection[0] = v.Cursor.Loc
-       }
-       v.Cursor.Up()
-       v.Cursor.SelectTo(v.Cursor.Loc)
-
-       if usePlugin {
-               return PostActionCall("SelectUp", v)
-       }
-       return true
-}
-
-// SelectDown selects down one line
-func (v *View) SelectDown(usePlugin bool) bool {
-       if usePlugin && !PreActionCall("SelectDown", v) {
-               return false
-       }
-
-       if !v.Cursor.HasSelection() {
-               v.Cursor.OrigSelection[0] = v.Cursor.Loc
-       }
-       v.Cursor.Down()
-       v.Cursor.SelectTo(v.Cursor.Loc)
-
-       if usePlugin {
-               return PostActionCall("SelectDown", v)
-       }
-       return true
-}
-
-// SelectLeft selects the character to the left of the cursor
-func (v *View) SelectLeft(usePlugin bool) bool {
-       if usePlugin && !PreActionCall("SelectLeft", v) {
-               return false
-       }
-
-       loc := v.Cursor.Loc
-       count := v.Buf.End()
-       if loc.GreaterThan(count) {
-               loc = count
-       }
-       if !v.Cursor.HasSelection() {
-               v.Cursor.OrigSelection[0] = loc
-       }
-       v.Cursor.Left()
-       v.Cursor.SelectTo(v.Cursor.Loc)
-
-       if usePlugin {
-               return PostActionCall("SelectLeft", v)
-       }
-       return true
-}
-
-// SelectRight selects the character to the right of the cursor
-func (v *View) SelectRight(usePlugin bool) bool {
-       if usePlugin && !PreActionCall("SelectRight", v) {
-               return false
-       }
-
-       loc := v.Cursor.Loc
-       count := v.Buf.End()
-       if loc.GreaterThan(count) {
-               loc = count
-       }
-       if !v.Cursor.HasSelection() {
-               v.Cursor.OrigSelection[0] = loc
-       }
-       v.Cursor.Right()
-       v.Cursor.SelectTo(v.Cursor.Loc)
-
-       if usePlugin {
-               return PostActionCall("SelectRight", v)
-       }
-       return true
-}
-
-// SelectWordRight selects the word to the right of the cursor
-func (v *View) SelectWordRight(usePlugin bool) bool {
-       if usePlugin && !PreActionCall("SelectWordRight", v) {
-               return false
-       }
-
-       if !v.Cursor.HasSelection() {
-               v.Cursor.OrigSelection[0] = v.Cursor.Loc
-       }
-       v.Cursor.WordRight()
-       v.Cursor.SelectTo(v.Cursor.Loc)
-
-       if usePlugin {
-               return PostActionCall("SelectWordRight", v)
-       }
-       return true
-}
-
-// SelectWordLeft selects the word to the left of the cursor
-func (v *View) SelectWordLeft(usePlugin bool) bool {
-       if usePlugin && !PreActionCall("SelectWordLeft", v) {
-               return false
-       }
-
-       if !v.Cursor.HasSelection() {
-               v.Cursor.OrigSelection[0] = v.Cursor.Loc
-       }
-       v.Cursor.WordLeft()
-       v.Cursor.SelectTo(v.Cursor.Loc)
-
-       if usePlugin {
-               return PostActionCall("SelectWordLeft", v)
-       }
-       return true
-}
-
-// StartOfLine moves the cursor to the start of the line
-func (v *View) StartOfLine(usePlugin bool) bool {
-       if usePlugin && !PreActionCall("StartOfLine", v) {
-               return false
-       }
-
-       v.deselect(0)
-
-       if v.Cursor.X != 0 {
-               v.Cursor.Start()
-       } else {
-               v.Cursor.StartOfText()
-       }
-
-       if usePlugin {
-               return PostActionCall("StartOfLine", v)
-       }
-       return true
-}
-
-// EndOfLine moves the cursor to the end of the line
-func (v *View) EndOfLine(usePlugin bool) bool {
-       if usePlugin && !PreActionCall("EndOfLine", v) {
-               return false
-       }
-
-       v.deselect(0)
-
-       v.Cursor.End()
-
-       if usePlugin {
-               return PostActionCall("EndOfLine", v)
-       }
-       return true
-}
-
-// SelectLine selects the entire current line
-func (v *View) SelectLine(usePlugin bool) bool {
-       if usePlugin && !PreActionCall("SelectLine", v) {
-               return false
-       }
-
-       v.Cursor.SelectLine()
-
-       if usePlugin {
-               return PostActionCall("SelectLine", v)
-       }
-       return true
-}
-
-// SelectToStartOfLine selects to the start of the current line
-func (v *View) SelectToStartOfLine(usePlugin bool) bool {
-       if usePlugin && !PreActionCall("SelectToStartOfLine", v) {
-               return false
-       }
-
-       if !v.Cursor.HasSelection() {
-               v.Cursor.OrigSelection[0] = v.Cursor.Loc
-       }
-       v.Cursor.Start()
-       v.Cursor.SelectTo(v.Cursor.Loc)
-
-       if usePlugin {
-               return PostActionCall("SelectToStartOfLine", v)
-       }
-       return true
-}
-
-// SelectToEndOfLine selects to the end of the current line
-func (v *View) SelectToEndOfLine(usePlugin bool) bool {
-       if usePlugin && !PreActionCall("SelectToEndOfLine", v) {
-               return false
-       }
-
-       if !v.Cursor.HasSelection() {
-               v.Cursor.OrigSelection[0] = v.Cursor.Loc
-       }
-       v.Cursor.End()
-       v.Cursor.SelectTo(v.Cursor.Loc)
-
-       if usePlugin {
-               return PostActionCall("SelectToEndOfLine", v)
-       }
-       return true
-}
-
-// ParagraphPrevious moves the cursor to the previous empty line, or beginning of the buffer if there's none
-func (v *View) ParagraphPrevious(usePlugin bool) bool {
-       if usePlugin && !PreActionCall("ParagraphPrevious", v) {
-               return false
-       }
-       var line int
-       for line = v.Cursor.Y; line > 0; line-- {
-               if len(v.Buf.lines[line].data) == 0 && line != v.Cursor.Y {
-                       v.Cursor.X = 0
-                       v.Cursor.Y = line
-                       break
-               }
-       }
-       // If no empty line found. move cursor to end of buffer
-       if line == 0 {
-               v.Cursor.Loc = v.Buf.Start()
-       }
-
-       if usePlugin {
-               return PostActionCall("ParagraphPrevious", v)
-       }
-       return true
-}
-
-// ParagraphNext moves the cursor to the next empty line, or end of the buffer if there's none
-func (v *View) ParagraphNext(usePlugin bool) bool {
-       if usePlugin && !PreActionCall("ParagraphNext", v) {
-               return false
-       }
-
-       var line int
-       for line = v.Cursor.Y; line < len(v.Buf.lines); line++ {
-               if len(v.Buf.lines[line].data) == 0 && line != v.Cursor.Y {
-                       v.Cursor.X = 0
-                       v.Cursor.Y = line
-                       break
-               }
-       }
-       // If no empty line found. move cursor to end of buffer
-       if line == len(v.Buf.lines) {
-               v.Cursor.Loc = v.Buf.End()
-       }
-
-       if usePlugin {
-               return PostActionCall("ParagraphNext", v)
-       }
-       return true
-}
-
-// Retab changes all tabs to spaces or all spaces to tabs depending
-// on the user's settings
-func (v *View) Retab(usePlugin bool) bool {
-       if usePlugin && !PreActionCall("Retab", v) {
-               return false
-       }
-
-       toSpaces := v.Buf.Settings["tabstospaces"].(bool)
-       tabsize := int(v.Buf.Settings["tabsize"].(float64))
-       dirty := false
-
-       for i := 0; i < v.Buf.NumLines; i++ {
-               l := v.Buf.Line(i)
-
-               ws := GetLeadingWhitespace(l)
-               if ws != "" {
-                       if toSpaces {
-                               ws = strings.Replace(ws, "\t", Spaces(tabsize), -1)
-                       } else {
-                               ws = strings.Replace(ws, Spaces(tabsize), "\t", -1)
-                       }
-               }
-
-               l = strings.TrimLeft(l, " \t")
-               v.Buf.lines[i].data = []byte(ws + l)
-               dirty = true
-       }
-
-       v.Buf.IsModified = dirty
-
-       if usePlugin {
-               return PostActionCall("Retab", v)
-       }
-       return true
-}
-
-// CursorStart moves the cursor to the start of the buffer
-func (v *View) CursorStart(usePlugin bool) bool {
-       if usePlugin && !PreActionCall("CursorStart", v) {
-               return false
-       }
-
-       v.deselect(0)
-
-       v.Cursor.X = 0
-       v.Cursor.Y = 0
-
-       if usePlugin {
-               return PostActionCall("CursorStart", v)
-       }
-       return true
-}
-
-// CursorEnd moves the cursor to the end of the buffer
-func (v *View) CursorEnd(usePlugin bool) bool {
-       if usePlugin && !PreActionCall("CursorEnd", v) {
-               return false
-       }
-
-       v.deselect(0)
-
-       v.Cursor.Loc = v.Buf.End()
-       v.Cursor.StoreVisualX()
-
-       if usePlugin {
-               return PostActionCall("CursorEnd", v)
-       }
-       return true
-}
-
-// SelectToStart selects the text from the cursor to the start of the buffer
-func (v *View) SelectToStart(usePlugin bool) bool {
-       if usePlugin && !PreActionCall("SelectToStart", v) {
-               return false
-       }
-
-       if !v.Cursor.HasSelection() {
-               v.Cursor.OrigSelection[0] = v.Cursor.Loc
-       }
-       v.CursorStart(false)
-       v.Cursor.SelectTo(v.Buf.Start())
-
-       if usePlugin {
-               return PostActionCall("SelectToStart", v)
-       }
-       return true
-}
-
-// SelectToEnd selects the text from the cursor to the end of the buffer
-func (v *View) SelectToEnd(usePlugin bool) bool {
-       if usePlugin && !PreActionCall("SelectToEnd", v) {
-               return false
-       }
-
-       if !v.Cursor.HasSelection() {
-               v.Cursor.OrigSelection[0] = v.Cursor.Loc
-       }
-       v.CursorEnd(false)
-       v.Cursor.SelectTo(v.Buf.End())
-
-       if usePlugin {
-               return PostActionCall("SelectToEnd", v)
-       }
-       return true
-}
-
-// InsertSpace inserts a space
-func (v *View) InsertSpace(usePlugin bool) bool {
-       if usePlugin && !PreActionCall("InsertSpace", v) {
-               return false
-       }
-
-       if v.Cursor.HasSelection() {
-               v.Cursor.DeleteSelection()
-               v.Cursor.ResetSelection()
-       }
-       v.Buf.Insert(v.Cursor.Loc, " ")
-       // v.Cursor.Right()
-
-       if usePlugin {
-               return PostActionCall("InsertSpace", v)
-       }
-       return true
-}
-
-// InsertNewline inserts a newline plus possible some whitespace if autoindent is on
-func (v *View) InsertNewline(usePlugin bool) bool {
-       if usePlugin && !PreActionCall("InsertNewline", v) {
-               return false
-       }
-
-       // Insert a newline
-       if v.Cursor.HasSelection() {
-               v.Cursor.DeleteSelection()
-               v.Cursor.ResetSelection()
-       }
-
-       ws := GetLeadingWhitespace(v.Buf.Line(v.Cursor.Y))
-       cx := v.Cursor.X
-       v.Buf.Insert(v.Cursor.Loc, "\n")
-       // v.Cursor.Right()
-
-       if v.Buf.Settings["autoindent"].(bool) {
-               if cx < len(ws) {
-                       ws = ws[0:cx]
-               }
-               v.Buf.Insert(v.Cursor.Loc, ws)
-               // for i := 0; i < len(ws); i++ {
-               //      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) {
-                       line := v.Buf.Line(v.Cursor.Y - 1)
-                       v.Buf.Remove(Loc{0, v.Cursor.Y - 1}, Loc{Count(line), v.Cursor.Y - 1})
-               }
-       }
-       v.Cursor.LastVisualX = v.Cursor.GetVisualX()
-
-       if usePlugin {
-               return PostActionCall("InsertNewline", v)
-       }
-       return true
-}
-
-// Backspace deletes the previous character
-func (v *View) Backspace(usePlugin bool) bool {
-       if usePlugin && !PreActionCall("Backspace", v) {
-               return false
-       }
-
-       // Delete a character
-       if v.Cursor.HasSelection() {
-               v.Cursor.DeleteSelection()
-               v.Cursor.ResetSelection()
-       } else if v.Cursor.Loc.GreaterThan(v.Buf.Start()) {
-               // We have to do something a bit hacky here because we want to
-               // delete the line by first moving left and then deleting backwards
-               // but the undo redo would place the cursor in the wrong place
-               // So instead we move left, save the position, move back, delete
-               // 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
-               // tab (tabSize number of spaces)
-               lineStart := sliceEnd(v.Buf.LineBytes(v.Cursor.Y), v.Cursor.X)
-               tabSize := int(v.Buf.Settings["tabsize"].(float64))
-               if v.Buf.Settings["tabstospaces"].(bool) && IsSpaces(lineStart) && utf8.RuneCount(lineStart) != 0 && utf8.RuneCount(lineStart)%tabSize == 0 {
-                       loc := v.Cursor.Loc
-                       v.Buf.Remove(loc.Move(-tabSize, v.Buf), loc)
-               } else {
-                       loc := v.Cursor.Loc
-                       v.Buf.Remove(loc.Move(-1, v.Buf), loc)
-               }
-       }
-       v.Cursor.LastVisualX = v.Cursor.GetVisualX()
-
-       if usePlugin {
-               return PostActionCall("Backspace", v)
-       }
-       return true
-}
-
-// DeleteWordRight deletes the word to the right of the cursor
-func (v *View) DeleteWordRight(usePlugin bool) bool {
-       if usePlugin && !PreActionCall("DeleteWordRight", v) {
-               return false
-       }
-
-       v.SelectWordRight(false)
-       if v.Cursor.HasSelection() {
-               v.Cursor.DeleteSelection()
-               v.Cursor.ResetSelection()
-       }
-
-       if usePlugin {
-               return PostActionCall("DeleteWordRight", v)
-       }
-       return true
-}
-
-// DeleteWordLeft deletes the word to the left of the cursor
-func (v *View) DeleteWordLeft(usePlugin bool) bool {
-       if usePlugin && !PreActionCall("DeleteWordLeft", v) {
-               return false
-       }
-
-       v.SelectWordLeft(false)
-       if v.Cursor.HasSelection() {
-               v.Cursor.DeleteSelection()
-               v.Cursor.ResetSelection()
-       }
-
-       if usePlugin {
-               return PostActionCall("DeleteWordLeft", v)
-       }
-       return true
-}
-
-// Delete deletes the next character
-func (v *View) Delete(usePlugin bool) bool {
-       if usePlugin && !PreActionCall("Delete", v) {
-               return false
-       }
-
-       if v.Cursor.HasSelection() {
-               v.Cursor.DeleteSelection()
-               v.Cursor.ResetSelection()
-       } else {
-               loc := v.Cursor.Loc
-               if loc.LessThan(v.Buf.End()) {
-                       v.Buf.Remove(loc, loc.Move(1, v.Buf))
-               }
-       }
-
-       if usePlugin {
-               return PostActionCall("Delete", v)
-       }
-       return true
-}
-
-// IndentSelection indents the current selection
-func (v *View) IndentSelection(usePlugin bool) bool {
-       if usePlugin && !PreActionCall("IndentSelection", v) {
-               return false
-       }
-
-       if v.Cursor.HasSelection() {
-               start := v.Cursor.CurSelection[0]
-               end := v.Cursor.CurSelection[1]
-               if end.Y < start.Y {
-                       start, end = end, start
-                       v.Cursor.SetSelectionStart(start)
-                       v.Cursor.SetSelectionEnd(end)
-               }
-
-               startY := start.Y
-               endY := end.Move(-1, v.Buf).Y
-               endX := end.Move(-1, v.Buf).X
-               tabsize := len(v.Buf.IndentString())
-               for y := startY; y <= endY; y++ {
-                       v.Buf.Insert(Loc{0, y}, v.Buf.IndentString())
-                       if y == startY && start.X > 0 {
-                               v.Cursor.SetSelectionStart(start.Move(tabsize, v.Buf))
-                       }
-                       if y == endY {
-                               v.Cursor.SetSelectionEnd(Loc{endX + tabsize + 1, endY})
-                       }
-               }
-               v.Cursor.Relocate()
-
-               if usePlugin {
-                       return PostActionCall("IndentSelection", v)
-               }
-               return true
-       }
-       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.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) {
-               return false
-       }
-
-       if v.Cursor.HasSelection() {
-               start := v.Cursor.CurSelection[0]
-               end := v.Cursor.CurSelection[1]
-               if end.Y < start.Y {
-                       start, end = end, start
-                       v.Cursor.SetSelectionStart(start)
-                       v.Cursor.SetSelectionEnd(end)
-               }
-
-               startY := start.Y
-               endY := end.Move(-1, v.Buf).Y
-               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})
-                       }
-               }
-               v.Cursor.Relocate()
-
-               if usePlugin {
-                       return PostActionCall("OutdentSelection", v)
-               }
-               return true
-       }
-       return false
-}
-
-// InsertTab inserts a tab or spaces
-func (v *View) InsertTab(usePlugin bool) bool {
-       if usePlugin && !PreActionCall("InsertTab", v) {
-               return false
-       }
-
-       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++ {
-       //      v.Cursor.Right()
-       // }
-
-       if usePlugin {
-               return PostActionCall("InsertTab", v)
-       }
-       return true
-}
-
-// SaveAll saves all open buffers
-func (v *View) SaveAll(usePlugin bool) bool {
-       if v.mainCursor() {
-               if usePlugin && !PreActionCall("SaveAll", v) {
-                       return false
-               }
-
-               for _, t := range tabs {
-                       for _, v := range t.Views {
-                               v.Save(false)
-                       }
-               }
-
-               if usePlugin {
-                       return PostActionCall("SaveAll", v)
-               }
-       }
-       return false
-}
-
-// Save the buffer to disk
-func (v *View) Save(usePlugin bool) bool {
-       if v.mainCursor() {
-               if usePlugin && !PreActionCall("Save", v) {
-                       return false
-               }
-
-               if v.Type.Scratch == true {
-                       // We can't save any view type with scratch set. eg help and log text
-                       return false
-               }
-               // If this is an empty buffer, ask for a filename
-               if v.Buf.Path == "" {
-                       v.SaveAs(false)
-               } else {
-                       v.saveToFile(v.Buf.Path)
-               }
-
-               if usePlugin {
-                       return PostActionCall("Save", v)
-               }
-       }
-       return false
-}
-
-// This function saves the buffer to `filename` and changes the buffer's path and name
-// to `filename` if the save is successful
-func (v *View) saveToFile(filename string) {
-       err := v.Buf.SaveAs(filename)
-       if err != nil {
-               if strings.HasSuffix(err.Error(), "permission denied") {
-                       choice, _ := messenger.YesNoPrompt("Permission denied. Do you want to save this file using sudo? (y,n)")
-                       if choice {
-                               err = v.Buf.SaveAsWithSudo(filename)
-                               if err != nil {
-                                       messenger.Error(err.Error())
-                               } else {
-                                       v.Buf.Path = filename
-                                       v.Buf.name = filename
-                                       messenger.Message("Saved " + filename)
-                               }
-                       }
-                       messenger.Reset()
-                       messenger.Clear()
-               } else {
-                       messenger.Error(err.Error())
-               }
-       } else {
-               v.Buf.Path = filename
-               v.Buf.name = filename
-               messenger.Message("Saved " + filename)
-       }
-}
-
-// SaveAs saves the buffer to disk with the given name
-func (v *View) SaveAs(usePlugin bool) bool {
-       if v.mainCursor() {
-               if usePlugin && !PreActionCall("SaveAs", v) {
-                       return 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.
-                       args, err := shellwords.Split(filename)
-                       filename = strings.Join(args, " ")
-                       if err != nil {
-                               messenger.Error("Error parsing arguments: ", err)
-                               return false
-                       }
-                       v.saveToFile(filename)
-               }
-
-               if usePlugin {
-                       PostActionCall("SaveAs", v)
-               }
-       }
-       return false
-}
-
-// Find opens a prompt and searches forward for the input
-func (v *View) Find(usePlugin bool) bool {
-       if v.mainCursor() {
-               if usePlugin && !PreActionCall("Find", v) {
-                       return false
-               }
-
-               searchStr := ""
-               if v.Cursor.HasSelection() {
-                       searchStart = v.Cursor.CurSelection[1]
-                       searchStart = v.Cursor.CurSelection[1]
-                       searchStr = v.Cursor.GetSelection()
-               } else {
-                       searchStart = v.Cursor.Loc
-               }
-               BeginSearch(searchStr)
-
-               if usePlugin {
-                       return PostActionCall("Find", v)
-               }
-       }
-       return true
-}
-
-// FindNext searches forwards for the last used search term
-func (v *View) FindNext(usePlugin bool) bool {
-       if usePlugin && !PreActionCall("FindNext", v) {
-               return false
-       }
-
-       if v.Cursor.HasSelection() {
-               searchStart = v.Cursor.CurSelection[1]
-               // lastSearch = v.Cursor.GetSelection()
-       } else {
-               searchStart = v.Cursor.Loc
-       }
-       if lastSearch == "" {
-               return true
-       }
-       messenger.Message("Finding: " + lastSearch)
-       Search(lastSearch, v, true)
-
-       if usePlugin {
-               return PostActionCall("FindNext", v)
-       }
-       return true
-}
-
-// FindPrevious searches backwards for the last used search term
-func (v *View) FindPrevious(usePlugin bool) bool {
-       if usePlugin && !PreActionCall("FindPrevious", v) {
-               return false
-       }
-
-       if v.Cursor.HasSelection() {
-               searchStart = v.Cursor.CurSelection[0]
-       } else {
-               searchStart = v.Cursor.Loc
-       }
-       messenger.Message("Finding: " + lastSearch)
-       Search(lastSearch, v, false)
-
-       if usePlugin {
-               return PostActionCall("FindPrevious", v)
-       }
-       return true
-}
-
-// Undo undoes the last action
-func (v *View) Undo(usePlugin bool) bool {
-       if usePlugin && !PreActionCall("Undo", v) {
-               return false
-       }
-
-       if v.Buf.curCursor == 0 {
-               v.Buf.clearCursors()
-       }
-
-       v.Buf.Undo()
-       messenger.Message("Undid action")
-
-       if usePlugin {
-               return PostActionCall("Undo", v)
-       }
-       return true
-}
-
-// Redo redoes the last action
-func (v *View) Redo(usePlugin bool) bool {
-       if usePlugin && !PreActionCall("Redo", v) {
-               return false
-       }
-
-       if v.Buf.curCursor == 0 {
-               v.Buf.clearCursors()
-       }
-
-       v.Buf.Redo()
-       messenger.Message("Redid action")
-
-       if usePlugin {
-               return PostActionCall("Redo", v)
-       }
-       return true
-}
-
-// Copy the selection to the system clipboard
-func (v *View) Copy(usePlugin bool) bool {
-       if v.mainCursor() {
-               if usePlugin && !PreActionCall("Copy", v) {
-                       return false
-               }
-
-               if v.Cursor.HasSelection() {
-                       v.Cursor.CopySelection("clipboard")
-                       v.freshClip = true
-                       messenger.Message("Copied selection")
-               }
-
-               if usePlugin {
-                       return PostActionCall("Copy", v)
-               }
-       }
-       return true
-}
-
-// CutLine cuts the current line to the clipboard
-func (v *View) CutLine(usePlugin bool) bool {
-       if usePlugin && !PreActionCall("CutLine", v) {
-               return false
-       }
-
-       v.Cursor.SelectLine()
-       if !v.Cursor.HasSelection() {
-               return false
-       }
-       if v.freshClip == true {
-               if v.Cursor.HasSelection() {
-                       if clip, err := clipboard.ReadAll("clipboard"); err != nil {
-                               messenger.Error(err)
-                       } else {
-                               clipboard.WriteAll(clip+v.Cursor.GetSelection(), "clipboard")
-                       }
-               }
-       } else if time.Since(v.lastCutTime)/time.Second > 10*time.Second || v.freshClip == false {
-               v.Copy(true)
-       }
-       v.freshClip = true
-       v.lastCutTime = time.Now()
-       v.Cursor.DeleteSelection()
-       v.Cursor.ResetSelection()
-       messenger.Message("Cut line")
-
-       if usePlugin {
-               return PostActionCall("CutLine", v)
-       }
-       return true
-}
-
-// Cut the selection to the system clipboard
-func (v *View) Cut(usePlugin bool) bool {
-       if usePlugin && !PreActionCall("Cut", v) {
-               return false
-       }
-
-       if v.Cursor.HasSelection() {
-               v.Cursor.CopySelection("clipboard")
-               v.Cursor.DeleteSelection()
-               v.Cursor.ResetSelection()
-               v.freshClip = true
-               messenger.Message("Cut selection")
-
-               if usePlugin {
-                       return PostActionCall("Cut", v)
-               }
-               return true
-       } else {
-               return v.CutLine(usePlugin)
-       }
-}
-
-// DuplicateLine duplicates the current line or selection
-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()
-       }
-
-       messenger.Message("Duplicated line")
-
-       if usePlugin {
-               return PostActionCall("DuplicateLine", v)
-       }
-       return true
-}
-
-// DeleteLine deletes the current line
-func (v *View) DeleteLine(usePlugin bool) bool {
-       if usePlugin && !PreActionCall("DeleteLine", v) {
-               return false
-       }
-
-       v.Cursor.SelectLine()
-       if !v.Cursor.HasSelection() {
-               return false
-       }
-       v.Cursor.DeleteSelection()
-       v.Cursor.ResetSelection()
-       messenger.Message("Deleted line")
-
-       if usePlugin {
-               return PostActionCall("DeleteLine", v)
-       }
-       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
-               }
-               start := v.Cursor.CurSelection[0].Y
-               end := v.Cursor.CurSelection[1].Y
-               if start > end {
-                       end, start = start, end
-               }
-
-               v.Buf.MoveLinesUp(
-                       start,
-                       end,
-               )
-               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,
-               )
-               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
-               }
-               start := v.Cursor.CurSelection[0].Y
-               end := v.Cursor.CurSelection[1].Y
-               if start > end {
-                       end, start = start, end
-               }
-
-               v.Buf.MoveLinesDown(
-                       start,
-                       end,
-               )
-               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,
-               )
-               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 {
-       if usePlugin && !PreActionCall("Paste", v) {
-               return false
-       }
-
-       clip, _ := clipboard.ReadAll("clipboard")
-       v.paste(clip)
-
-       if usePlugin {
-               return PostActionCall("Paste", v)
-       }
-       return true
-}
-
-// PastePrimary pastes from the primary clipboard (only use on linux)
-func (v *View) PastePrimary(usePlugin bool) bool {
-       if usePlugin && !PreActionCall("Paste", v) {
-               return false
-       }
-
-       clip, _ := clipboard.ReadAll("primary")
-       v.paste(clip)
-
-       if usePlugin {
-               return PostActionCall("Paste", v)
-       }
-       return true
-}
-
-// JumpToMatchingBrace moves the cursor to the matching brace if it is
-// currently on a brace
-func (v *View) JumpToMatchingBrace(usePlugin bool) bool {
-       if usePlugin && !PreActionCall("JumpToMatchingBrace", v) {
-               return false
-       }
-
-       for _, bp := range bracePairs {
-               r := v.Cursor.RuneUnder(v.Cursor.X)
-               if r == bp[0] || r == bp[1] {
-                       matchingBrace := v.Buf.FindMatchingBrace(bp, v.Cursor.Loc)
-                       v.Cursor.GotoLoc(matchingBrace)
-               }
-       }
-
-       if usePlugin {
-               return PostActionCall("JumpToMatchingBrace", v)
-       }
-       return true
-}
-
-// SelectAll selects the entire buffer
-func (v *View) SelectAll(usePlugin bool) bool {
-       if usePlugin && !PreActionCall("SelectAll", v) {
-               return false
-       }
-
-       v.Cursor.SetSelectionStart(v.Buf.Start())
-       v.Cursor.SetSelectionEnd(v.Buf.End())
-       // Put the cursor at the beginning
-       v.Cursor.X = 0
-       v.Cursor.Y = 0
-
-       if usePlugin {
-               return PostActionCall("SelectAll", v)
-       }
-       return true
-}
-
-// OpenFile opens a new file in the buffer
-func (v *View) OpenFile(usePlugin bool) bool {
-       if v.mainCursor() {
-               if usePlugin && !PreActionCall("OpenFile", v) {
-                       return false
-               }
-
-               if v.CanClose() {
-                       input, canceled := messenger.Prompt("> ", "open ", "Open", CommandCompletion)
-                       if !canceled {
-                               HandleCommand(input)
-                               if usePlugin {
-                                       return PostActionCall("OpenFile", v)
-                               }
-                       }
-               }
-       }
-       return false
-}
-
-// Start moves the viewport to the start of the buffer
-func (v *View) Start(usePlugin bool) bool {
-       if v.mainCursor() {
-               if usePlugin && !PreActionCall("Start", v) {
-                       return false
-               }
-
-               v.Topline = 0
-
-               if usePlugin {
-                       return PostActionCall("Start", v)
-               }
-       }
-       return false
-}
-
-// End moves the viewport to the end of the buffer
-func (v *View) End(usePlugin bool) bool {
-       if v.mainCursor() {
-               if usePlugin && !PreActionCall("End", v) {
-                       return false
-               }
-
-               if v.Height > v.Buf.NumLines {
-                       v.Topline = 0
-               } else {
-                       v.Topline = v.Buf.NumLines - v.Height
-               }
-
-               if usePlugin {
-                       return PostActionCall("End", v)
-               }
-       }
-       return false
-}
-
-// PageUp scrolls the view up a page
-func (v *View) PageUp(usePlugin bool) bool {
-       if v.mainCursor() {
-               if usePlugin && !PreActionCall("PageUp", v) {
-                       return false
-               }
-
-               if v.Topline > v.Height {
-                       v.ScrollUp(v.Height)
-               } else {
-                       v.Topline = 0
-               }
-
-               if usePlugin {
-                       return PostActionCall("PageUp", v)
-               }
-       }
-       return false
-}
-
-// PageDown scrolls the view down a page
-func (v *View) PageDown(usePlugin bool) bool {
-       if v.mainCursor() {
-               if usePlugin && !PreActionCall("PageDown", v) {
-                       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 usePlugin {
-                       return PostActionCall("PageDown", v)
-               }
-       }
-       return false
-}
-
-// SelectPageUp selects up one page
-func (v *View) SelectPageUp(usePlugin bool) bool {
-       if usePlugin && !PreActionCall("SelectPageUp", v) {
-               return false
-       }
-
-       if !v.Cursor.HasSelection() {
-               v.Cursor.OrigSelection[0] = v.Cursor.Loc
-       }
-       v.Cursor.UpN(v.Height)
-       v.Cursor.SelectTo(v.Cursor.Loc)
-
-       if usePlugin {
-               return PostActionCall("SelectPageUp", v)
-       }
-       return true
-}
-
-// SelectPageDown selects down one page
-func (v *View) SelectPageDown(usePlugin bool) bool {
-       if usePlugin && !PreActionCall("SelectPageDown", v) {
-               return false
-       }
-
-       if !v.Cursor.HasSelection() {
-               v.Cursor.OrigSelection[0] = v.Cursor.Loc
-       }
-       v.Cursor.DownN(v.Height)
-       v.Cursor.SelectTo(v.Cursor.Loc)
-
-       if usePlugin {
-               return PostActionCall("SelectPageDown", v)
-       }
-       return true
-}
-
-// CursorPageUp places the cursor a page up
-func (v *View) CursorPageUp(usePlugin bool) bool {
-       if usePlugin && !PreActionCall("CursorPageUp", v) {
-               return false
-       }
-
-       v.deselect(0)
-
-       if v.Cursor.HasSelection() {
-               v.Cursor.Loc = v.Cursor.CurSelection[0]
-               v.Cursor.ResetSelection()
-               v.Cursor.StoreVisualX()
-       }
-       v.Cursor.UpN(v.Height)
-
-       if usePlugin {
-               return PostActionCall("CursorPageUp", v)
-       }
-       return true
-}
-
-// CursorPageDown places the cursor a page up
-func (v *View) CursorPageDown(usePlugin bool) bool {
-       if usePlugin && !PreActionCall("CursorPageDown", v) {
-               return false
-       }
-
-       v.deselect(0)
-
-       if v.Cursor.HasSelection() {
-               v.Cursor.Loc = v.Cursor.CurSelection[1]
-               v.Cursor.ResetSelection()
-               v.Cursor.StoreVisualX()
-       }
-       v.Cursor.DownN(v.Height)
-
-       if usePlugin {
-               return PostActionCall("CursorPageDown", v)
-       }
-       return true
-}
-
-// HalfPageUp scrolls the view up half a page
-func (v *View) HalfPageUp(usePlugin bool) bool {
-       if v.mainCursor() {
-               if usePlugin && !PreActionCall("HalfPageUp", v) {
-                       return false
-               }
-
-               if v.Topline > v.Height/2 {
-                       v.ScrollUp(v.Height / 2)
-               } else {
-                       v.Topline = 0
-               }
-
-               if usePlugin {
-                       return PostActionCall("HalfPageUp", v)
-               }
-       }
-       return false
-}
-
-// HalfPageDown scrolls the view down half a page
-func (v *View) HalfPageDown(usePlugin bool) bool {
-       if v.mainCursor() {
-               if usePlugin && !PreActionCall("HalfPageDown", v) {
-                       return false
-               }
-
-               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 usePlugin {
-                       return PostActionCall("HalfPageDown", v)
-               }
-       }
-       return false
-}
-
-// ToggleRuler turns line numbers off and on
-func (v *View) ToggleRuler(usePlugin bool) bool {
-       if v.mainCursor() {
-               if usePlugin && !PreActionCall("ToggleRuler", v) {
-                       return false
-               }
-
-               if v.Buf.Settings["ruler"] == false {
-                       v.Buf.Settings["ruler"] = true
-                       messenger.Message("Enabled ruler")
-               } else {
-                       v.Buf.Settings["ruler"] = false
-                       messenger.Message("Disabled ruler")
-               }
-
-               if usePlugin {
-                       return PostActionCall("ToggleRuler", v)
-               }
-       }
-       return false
-}
-
-// JumpLine jumps to a line and moves the view accordingly.
-func (v *View) JumpLine(usePlugin bool) bool {
-       if usePlugin && !PreActionCall("JumpLine", v) {
-               return false
-       }
-
-       // Prompt for line number
-       message := fmt.Sprintf("Jump to line:col (1 - %v) # ", v.Buf.NumLines)
-       input, canceled := messenger.Prompt(message, "", "LineNumber", NoCompletion)
-       if canceled {
-               return false
-       }
-       var lineInt int
-       var colInt int
-       var err error
-       if strings.Contains(input, ":") {
-               split := strings.Split(input, ":")
-               lineInt, err = strconv.Atoi(split[0])
-               if err != nil {
-                       messenger.Message("Invalid line number")
-                       return false
-               }
-               colInt, err = strconv.Atoi(split[1])
-               if err != nil {
-                       messenger.Message("Invalid column number")
-                       return false
-               }
-       } else {
-               lineInt, err = strconv.Atoi(input)
-               if err != nil {
-                       messenger.Message("Invalid line number")
-                       return false
-               }
-       }
-       lineInt--
-       // Move cursor and view if possible.
-       if lineInt < v.Buf.NumLines && lineInt >= 0 {
-               v.Cursor.X = colInt
-               v.Cursor.Y = lineInt
-
-               if usePlugin {
-                       return PostActionCall("JumpLine", v)
-               }
-               return true
-       }
-       messenger.Error("Only ", v.Buf.NumLines, " lines to jump")
-       return false
-}
-
-// ClearStatus clears the messenger bar
-func (v *View) ClearStatus(usePlugin bool) bool {
-       if v.mainCursor() {
-               if usePlugin && !PreActionCall("ClearStatus", v) {
-                       return false
-               }
-
-               messenger.Message("")
-
-               if usePlugin {
-                       return PostActionCall("ClearStatus", v)
-               }
-       }
-       return false
-}
-
-// ToggleHelp toggles the help screen
-func (v *View) ToggleHelp(usePlugin bool) bool {
-       if v.mainCursor() {
-               if usePlugin && !PreActionCall("ToggleHelp", v) {
-                       return false
-               }
-
-               if v.Type != vtHelp {
-                       // Open the default help
-                       v.openHelp("help")
-               } else {
-                       v.Quit(true)
-               }
-
-               if usePlugin {
-                       return PostActionCall("ToggleHelp", v)
-               }
-       }
-       return true
-}
-
-// ToggleKeyMenu toggles the keymenu option and resizes all tabs
-func (v *View) ToggleKeyMenu(usePlugin bool) bool {
-       if v.mainCursor() {
-               if usePlugin && !PreActionCall("ToggleBindings", v) {
-                       return false
-               }
-
-               globalSettings["keymenu"] = !globalSettings["keymenu"].(bool)
-               for _, tab := range tabs {
-                       tab.Resize()
-               }
-
-               if usePlugin {
-                       return PostActionCall("ToggleBindings", v)
-               }
-       }
-       return true
-}
-
-// ShellMode opens a terminal to run a shell command
-func (v *View) ShellMode(usePlugin bool) bool {
-       if v.mainCursor() {
-               if usePlugin && !PreActionCall("ShellMode", v) {
-                       return false
-               }
-
-               input, canceled := messenger.Prompt("$ ", "", "Shell", NoCompletion)
-               if !canceled {
-                       // The true here is for openTerm to make the command interactive
-                       HandleShellCommand(input, true, true)
-                       if usePlugin {
-                               return PostActionCall("ShellMode", v)
-                       }
-               }
-       }
-       return false
-}
-
-// CommandMode lets the user enter a command
-func (v *View) CommandMode(usePlugin bool) bool {
-       if v.mainCursor() {
-               if usePlugin && !PreActionCall("CommandMode", v) {
-                       return false
-               }
-
-               input, canceled := messenger.Prompt("> ", "", "Command", CommandCompletion)
-               if !canceled {
-                       HandleCommand(input)
-                       if usePlugin {
-                               return PostActionCall("CommandMode", v)
-                       }
-               }
-       }
-
-       return false
-}
-
-// ToggleOverwriteMode lets the user toggle the text overwrite mode
-func (v *View) ToggleOverwriteMode(usePlugin bool) bool {
-       if v.mainCursor() {
-               if usePlugin && !PreActionCall("ToggleOverwriteMode", v) {
-                       return false
-               }
-
-               v.isOverwriteMode = !v.isOverwriteMode
-
-               if usePlugin {
-                       return PostActionCall("ToggleOverwriteMode", v)
-               }
-       }
-       return false
-}
-
-// Escape leaves current mode
-func (v *View) Escape(usePlugin bool) bool {
-       if v.mainCursor() {
-               // 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 false
-}
-
-// Quit this will close the current tab or view that is open
-func (v *View) Quit(usePlugin bool) bool {
-       if v.mainCursor() {
-               if usePlugin && !PreActionCall("Quit", v) {
-                       return false
-               }
-
-               // Make sure not to quit if there are unsaved changes
-               if v.CanClose() {
-                       v.CloseBuffer()
-                       if len(tabs[curTab].Views) > 1 {
-                               v.splitNode.Delete()
-                               tabs[v.TabNum].Cleanup()
-                               tabs[v.TabNum].Resize()
-                       } else if len(tabs) > 1 {
-                               if len(tabs[v.TabNum].Views) == 1 {
-                                       tabs = tabs[:v.TabNum+copy(tabs[v.TabNum:], tabs[v.TabNum+1:])]
-                                       for i, t := range tabs {
-                                               t.SetNum(i)
-                                       }
-                                       if curTab >= len(tabs) {
-                                               curTab--
-                                       }
-                                       if curTab == 0 {
-                                               CurView().ToggleTabbar()
-                                       }
-                               }
-                       } else {
-                               if usePlugin {
-                                       PostActionCall("Quit", v)
-                               }
-
-                               screen.Fini()
-                               messenger.SaveHistory()
-                               os.Exit(0)
-                       }
-               }
-
-               if usePlugin {
-                       return PostActionCall("Quit", v)
-               }
-       }
-       return false
-}
-
-// QuitAll quits the whole editor; all splits and tabs
-func (v *View) QuitAll(usePlugin bool) bool {
-       if v.mainCursor() {
-               if usePlugin && !PreActionCall("QuitAll", v) {
-                       return false
-               }
-
-               closeAll := true
-               for _, tab := range tabs {
-                       for _, v := range tab.Views {
-                               if !v.CanClose() {
-                                       closeAll = false
-                               }
-                       }
-               }
-
-               if closeAll {
-                       // only quit if all of the buffers can be closed and the user confirms that they actually want to quit everything
-                       shouldQuit, _ := messenger.YesNoPrompt("Do you want to quit micro (all open files will be closed)?")
-
-                       if shouldQuit {
-                               for _, tab := range tabs {
-                                       for _, v := range tab.Views {
-                                               v.CloseBuffer()
-                                       }
-                               }
-
-                               if usePlugin {
-                                       PostActionCall("QuitAll", v)
-                               }
-
-                               screen.Fini()
-                               messenger.SaveHistory()
-                               os.Exit(0)
-                       }
-               }
-       }
-
-       return false
-}
-
-// AddTab adds a new tab with an empty buffer
-func (v *View) AddTab(usePlugin bool) bool {
-       if v.mainCursor() {
-               if usePlugin && !PreActionCall("AddTab", v) {
-                       return false
-               }
-
-               tab := NewTabFromView(NewView(NewBufferFromString("", "")))
-               tab.SetNum(len(tabs))
-               tabs = append(tabs, tab)
-               curTab = len(tabs) - 1
-               if len(tabs) == 2 {
-                       for _, t := range tabs {
-                               for _, v := range t.Views {
-                                       v.ToggleTabbar()
-                               }
-                       }
-               }
-
-               if usePlugin {
-                       return PostActionCall("AddTab", v)
-               }
-       }
-       return true
-}
-
-// PreviousTab switches to the previous tab in the tab list
-func (v *View) PreviousTab(usePlugin bool) bool {
-       if v.mainCursor() {
-               if usePlugin && !PreActionCall("PreviousTab", v) {
-                       return false
-               }
-
-               if curTab > 0 {
-                       curTab--
-               } else if curTab == 0 {
-                       curTab = len(tabs) - 1
-               }
-
-               if usePlugin {
-                       return PostActionCall("PreviousTab", v)
-               }
-       }
-       return false
-}
-
-// NextTab switches to the next tab in the tab list
-func (v *View) NextTab(usePlugin bool) bool {
-       if v.mainCursor() {
-               if usePlugin && !PreActionCall("NextTab", v) {
-                       return false
-               }
-
-               if curTab < len(tabs)-1 {
-                       curTab++
-               } else if curTab == len(tabs)-1 {
-                       curTab = 0
-               }
-
-               if usePlugin {
-                       return PostActionCall("NextTab", v)
-               }
-       }
-       return false
-}
-
-// VSplitBinding opens an empty vertical split
-func (v *View) VSplitBinding(usePlugin bool) bool {
-       if v.mainCursor() {
-               if usePlugin && !PreActionCall("VSplit", v) {
-                       return false
-               }
-
-               v.VSplit(NewBufferFromString("", ""))
-
-               if usePlugin {
-                       return PostActionCall("VSplit", v)
-               }
-       }
-       return false
-}
-
-// HSplitBinding opens an empty horizontal split
-func (v *View) HSplitBinding(usePlugin bool) bool {
-       if v.mainCursor() {
-               if usePlugin && !PreActionCall("HSplit", v) {
-                       return false
-               }
-
-               v.HSplit(NewBufferFromString("", ""))
-
-               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 v.mainCursor() {
-               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 v.mainCursor() {
-               if usePlugin && !PreActionCall("NextSplit", v) {
-                       return false
-               }
-
-               tab := tabs[curTab]
-               if tab.CurView < len(tab.Views)-1 {
-                       tab.CurView++
-               } else {
-                       tab.CurView = 0
-               }
-
-               if usePlugin {
-                       return PostActionCall("NextSplit", v)
-               }
-       }
-       return false
-}
-
-// PreviousSplit changes the view to the previous split
-func (v *View) PreviousSplit(usePlugin bool) bool {
-       if v.mainCursor() {
-               if usePlugin && !PreActionCall("PreviousSplit", v) {
-                       return false
-               }
-
-               tab := tabs[curTab]
-               if tab.CurView > 0 {
-                       tab.CurView--
-               } else {
-                       tab.CurView = len(tab.Views) - 1
-               }
-
-               if usePlugin {
-                       return PostActionCall("PreviousSplit", v)
-               }
-       }
-       return false
-}
-
-var curMacro []interface{}
-var recordingMacro bool
-
-// ToggleMacro toggles recording of a macro
-func (v *View) ToggleMacro(usePlugin bool) bool {
-       if v.mainCursor() {
-               if usePlugin && !PreActionCall("ToggleMacro", v) {
-                       return false
-               }
-
-               recordingMacro = !recordingMacro
-
-               if recordingMacro {
-                       curMacro = []interface{}{}
-                       messenger.Message("Recording")
-               } else {
-                       messenger.Message("Stopped recording")
-               }
-
-               if usePlugin {
-                       return PostActionCall("ToggleMacro", v)
-               }
-       }
-       return true
-}
-
-// PlayMacro plays back the most recently recorded macro
-func (v *View) PlayMacro(usePlugin bool) bool {
-       if usePlugin && !PreActionCall("PlayMacro", v) {
-               return false
-       }
-
-       for _, action := range curMacro {
-               switch t := action.(type) {
-               case rune:
-                       // Insert a character
-                       if v.Cursor.HasSelection() {
-                               v.Cursor.DeleteSelection()
-                               v.Cursor.ResetSelection()
-                       }
-                       v.Buf.Insert(v.Cursor.Loc, string(t))
-                       // v.Cursor.Right()
-
-                       for pl := range loadedPlugins {
-                               _, err := Call(pl+".onRune", string(t), v)
-                               if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") {
-                                       TermMessage(err)
-                               }
-                       }
-               case func(*View, bool) bool:
-                       t(v, true)
-               }
-       }
-
-       if usePlugin {
-               return PostActionCall("PlayMacro", v)
-       }
-       return true
-}
-
-// SpawnMultiCursor creates a new multiple cursor at the next occurrence of the current selection or current word
-func (v *View) SpawnMultiCursor(usePlugin bool) bool {
-       spawner := v.Buf.cursors[len(v.Buf.cursors)-1]
-       // You can only spawn a cursor from the main cursor
-       if v.Cursor == spawner {
-               if usePlugin && !PreActionCall("SpawnMultiCursor", v) {
-                       return false
-               }
-
-               if !spawner.HasSelection() {
-                       spawner.SelectWord()
-               } else {
-                       c := &Cursor{
-                               buf: v.Buf,
-                       }
-
-                       sel := spawner.GetSelection()
-
-                       searchStart = spawner.CurSelection[1]
-                       v.Cursor = c
-                       Search(regexp.QuoteMeta(sel), v, true)
-
-                       for _, cur := range v.Buf.cursors {
-                               if c.Loc == cur.Loc {
-                                       return false
-                               }
-                       }
-                       v.Buf.cursors = append(v.Buf.cursors, c)
-                       v.Buf.UpdateCursors()
-                       v.Relocate()
-                       v.Cursor = spawner
-               }
-
-               if usePlugin {
-                       PostActionCall("SpawnMultiCursor", v)
-               }
-       }
-
-       return false
-}
-
-// SpawnMultiCursorSelect adds a cursor at the beginning of each line of a selection
-func (v *View) SpawnMultiCursorSelect(usePlugin bool) bool {
-       if v.Cursor == &v.Buf.Cursor {
-               if usePlugin && !PreActionCall("SpawnMultiCursorSelect", v) {
-                       return false
-               }
-
-               // Avoid cases where multiple cursors already exist, that would create problems
-               if len(v.Buf.cursors) > 1 {
-                       return false
-               }
-
-               var startLine int
-               var endLine int
-
-               a, b := v.Cursor.CurSelection[0].Y, v.Cursor.CurSelection[1].Y
-               if a > b {
-                       startLine, endLine = b, a
-               } else {
-                       startLine, endLine = a, b
-               }
-
-               if v.Cursor.HasSelection() {
-                       v.Cursor.ResetSelection()
-                       v.Cursor.GotoLoc(Loc{0, startLine})
-
-                       for i := startLine; i <= endLine; i++ {
-                               c := &Cursor{
-                                       buf: v.Buf,
-                               }
-                               c.GotoLoc(Loc{0, i})
-                               v.Buf.cursors = append(v.Buf.cursors, c)
-                       }
-                       v.Buf.MergeCursors()
-                       v.Buf.UpdateCursors()
-               } else {
-                       return false
-               }
-
-               if usePlugin {
-                       PostActionCall("SpawnMultiCursorSelect", v)
-               }
-
-               messenger.Message("Added cursors from selection")
-       }
-       return false
-}
-
-// MouseMultiCursor is a mouse action which puts a new cursor at the mouse position
-func (v *View) MouseMultiCursor(usePlugin bool, e *tcell.EventMouse) bool {
-       if v.Cursor == &v.Buf.Cursor {
-               if usePlugin && !PreActionCall("SpawnMultiCursorAtMouse", v, e) {
-                       return false
-               }
-               x, y := e.Position()
-               x -= v.lineNumOffset - v.leftCol + v.x
-               y += v.Topline - v.y
-
-               c := &Cursor{
-                       buf: v.Buf,
-               }
-               v.Cursor = c
-               v.MoveToMouseClick(x, y)
-               v.Relocate()
-               v.Cursor = &v.Buf.Cursor
-
-               v.Buf.cursors = append(v.Buf.cursors, c)
-               v.Buf.MergeCursors()
-               v.Buf.UpdateCursors()
-
-               if usePlugin {
-                       PostActionCall("SpawnMultiCursorAtMouse", v)
-               }
-       }
-       return false
-}
-
-// SkipMultiCursor moves the current multiple cursor to the next available position
-func (v *View) SkipMultiCursor(usePlugin bool) bool {
-       cursor := v.Buf.cursors[len(v.Buf.cursors)-1]
-
-       if v.mainCursor() {
-               if usePlugin && !PreActionCall("SkipMultiCursor", v) {
-                       return false
-               }
-               sel := cursor.GetSelection()
-
-               searchStart = cursor.CurSelection[1]
-               v.Cursor = cursor
-               Search(regexp.QuoteMeta(sel), v, true)
-               v.Relocate()
-               v.Cursor = cursor
-
-               if usePlugin {
-                       PostActionCall("SkipMultiCursor", v)
-               }
-       }
-       return false
-}
-
-// RemoveMultiCursor removes the latest multiple cursor
-func (v *View) RemoveMultiCursor(usePlugin bool) bool {
-       end := len(v.Buf.cursors)
-       if end > 1 {
-               if v.mainCursor() {
-                       if usePlugin && !PreActionCall("RemoveMultiCursor", v) {
-                               return false
-                       }
-
-                       v.Buf.cursors[end-1] = nil
-                       v.Buf.cursors = v.Buf.cursors[:end-1]
-                       v.Buf.UpdateCursors()
-                       v.Relocate()
-
-                       if usePlugin {
-                               return PostActionCall("RemoveMultiCursor", v)
-                       }
-                       return true
-               }
-       } else {
-               v.RemoveAllMultiCursors(usePlugin)
-       }
-       return false
-}
-
-// RemoveAllMultiCursors removes all cursors except the base cursor
-func (v *View) RemoveAllMultiCursors(usePlugin bool) bool {
-       if v.mainCursor() {
-               if usePlugin && !PreActionCall("RemoveAllMultiCursors", v) {
-                       return false
-               }
-
-               v.Buf.clearCursors()
-               v.Relocate()
-
-               if usePlugin {
-                       return PostActionCall("RemoveAllMultiCursors", v)
-               }
-               return true
-       }
-       return false
-}
diff --git a/cmd/micro/actions_other.go b/cmd/micro/actions_other.go
deleted file mode 100644 (file)
index 10a099e..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-// +build plan9 nacl windows
-
-package main
-
-func (v *View) Suspend(usePlugin bool) bool {
-       messenger.Error("Suspend is only supported on Posix")
-
-       return false
-}
diff --git a/cmd/micro/actions_posix.go b/cmd/micro/actions_posix.go
deleted file mode 100644 (file)
index eb67d68..0000000
+++ /dev/null
@@ -1,37 +0,0 @@
-// +build linux darwin dragonfly solaris openbsd netbsd freebsd
-
-package main
-
-import "syscall"
-
-// Suspend sends micro to the background. This is the same as pressing CtrlZ in most unix programs.
-// This only works on linux and has no default binding.
-// This code was adapted from the suspend code in nsf/godit
-func (v *View) Suspend(usePlugin bool) bool {
-       if usePlugin && !PreActionCall("Suspend", v) {
-               return false
-       }
-
-       screenWasNil := screen == nil
-
-       if !screenWasNil {
-               screen.Fini()
-               screen = nil
-       }
-
-       // suspend the process
-       pid := syscall.Getpid()
-       err := syscall.Kill(pid, syscall.SIGSTOP)
-       if err != nil {
-               TermMessage(err)
-       }
-
-       if !screenWasNil {
-               InitScreen()
-       }
-
-       if usePlugin {
-               return PostActionCall("Suspend", v)
-       }
-       return true
-}
diff --git a/cmd/micro/autocomplete.go b/cmd/micro/autocomplete.go
deleted file mode 100644 (file)
index 3ecc3f1..0000000
+++ /dev/null
@@ -1,249 +0,0 @@
-package main
-
-import (
-       "io/ioutil"
-       "os"
-       "strings"
-)
-
-var pluginCompletions []func(string) []string
-
-// This file is meant (for now) for autocompletion in command mode, not
-// while coding. This helps micro autocomplete commands and then filenames
-// for example with `vsplit filename`.
-
-// FileComplete autocompletes filenames
-func FileComplete(input string) (string, []string) {
-       var sep string = string(os.PathSeparator)
-       dirs := strings.Split(input, sep)
-
-       var files []os.FileInfo
-       var err error
-       if len(dirs) > 1 {
-               directories := strings.Join(dirs[:len(dirs)-1], sep) + sep
-
-               directories = ReplaceHome(directories)
-               files, err = ioutil.ReadDir(directories)
-       } else {
-               files, err = ioutil.ReadDir(".")
-       }
-
-       var suggestions []string
-       if err != nil {
-               return "", suggestions
-       }
-       for _, f := range files {
-               name := f.Name()
-               if f.IsDir() {
-                       name += sep
-               }
-               if strings.HasPrefix(name, dirs[len(dirs)-1]) {
-                       suggestions = append(suggestions, name)
-               }
-       }
-
-       var chosen string
-       if len(suggestions) == 1 {
-               if len(dirs) > 1 {
-                       chosen = strings.Join(dirs[:len(dirs)-1], sep) + sep + suggestions[0]
-               } else {
-                       chosen = suggestions[0]
-               }
-       } else {
-               if len(dirs) > 1 {
-                       chosen = strings.Join(dirs[:len(dirs)-1], sep) + sep
-               }
-       }
-
-       return chosen, suggestions
-}
-
-// CommandComplete autocompletes commands
-func CommandComplete(input string) (string, []string) {
-       var suggestions []string
-       for cmd := range commands {
-               if strings.HasPrefix(cmd, input) {
-                       suggestions = append(suggestions, cmd)
-               }
-       }
-
-       var chosen string
-       if len(suggestions) == 1 {
-               chosen = suggestions[0]
-       }
-       return chosen, suggestions
-}
-
-// HelpComplete autocompletes help topics
-func HelpComplete(input string) (string, []string) {
-       var suggestions []string
-
-       for _, file := range ListRuntimeFiles(RTHelp) {
-               topic := file.Name()
-               if strings.HasPrefix(topic, input) {
-                       suggestions = append(suggestions, topic)
-               }
-       }
-
-       var chosen string
-       if len(suggestions) == 1 {
-               chosen = suggestions[0]
-       }
-       return chosen, suggestions
-}
-
-// ColorschemeComplete tab-completes names of colorschemes.
-func ColorschemeComplete(input string) (string, []string) {
-       var suggestions []string
-       files := ListRuntimeFiles(RTColorscheme)
-
-       for _, f := range files {
-               if strings.HasPrefix(f.Name(), input) {
-                       suggestions = append(suggestions, f.Name())
-               }
-       }
-
-       var chosen string
-       if len(suggestions) == 1 {
-               chosen = suggestions[0]
-       }
-
-       return chosen, suggestions
-}
-
-func contains(s []string, e string) bool {
-       for _, a := range s {
-               if a == e {
-                       return true
-               }
-       }
-       return false
-}
-
-// OptionComplete autocompletes options
-func OptionComplete(input string) (string, []string) {
-       var suggestions []string
-       localSettings := DefaultLocalSettings()
-       for option := range globalSettings {
-               if strings.HasPrefix(option, input) {
-                       suggestions = append(suggestions, option)
-               }
-       }
-       for option := range localSettings {
-               if strings.HasPrefix(option, input) && !contains(suggestions, option) {
-                       suggestions = append(suggestions, option)
-               }
-       }
-
-       var chosen string
-       if len(suggestions) == 1 {
-               chosen = suggestions[0]
-       }
-       return chosen, suggestions
-}
-
-// OptionValueComplete completes values for various options
-func OptionValueComplete(inputOpt, input string) (string, []string) {
-       inputOpt = strings.TrimSpace(inputOpt)
-       var suggestions []string
-       localSettings := DefaultLocalSettings()
-       var optionVal interface{}
-       for k, option := range globalSettings {
-               if k == inputOpt {
-                       optionVal = option
-               }
-       }
-       for k, option := range localSettings {
-               if k == inputOpt {
-                       optionVal = option
-               }
-       }
-
-       switch optionVal.(type) {
-       case bool:
-               if strings.HasPrefix("on", input) {
-                       suggestions = append(suggestions, "on")
-               } else if strings.HasPrefix("true", input) {
-                       suggestions = append(suggestions, "true")
-               }
-               if strings.HasPrefix("off", input) {
-                       suggestions = append(suggestions, "off")
-               } else if strings.HasPrefix("false", input) {
-                       suggestions = append(suggestions, "false")
-               }
-       case string:
-               switch inputOpt {
-               case "colorscheme":
-                       _, suggestions = ColorschemeComplete(input)
-               case "fileformat":
-                       if strings.HasPrefix("unix", input) {
-                               suggestions = append(suggestions, "unix")
-                       }
-                       if strings.HasPrefix("dos", input) {
-                               suggestions = append(suggestions, "dos")
-                       }
-               case "sucmd":
-                       if strings.HasPrefix("sudo", input) {
-                               suggestions = append(suggestions, "sudo")
-                       }
-                       if strings.HasPrefix("doas", input) {
-                               suggestions = append(suggestions, "doas")
-                       }
-               }
-       }
-
-       var chosen string
-       if len(suggestions) == 1 {
-               chosen = suggestions[0]
-       }
-       return chosen, suggestions
-}
-
-// MakeCompletion registers a function from a plugin for autocomplete commands
-func MakeCompletion(function string) Completion {
-       pluginCompletions = append(pluginCompletions, LuaFunctionComplete(function))
-       return Completion(-len(pluginCompletions))
-}
-
-// PluginComplete autocompletes from plugin function
-func PluginComplete(complete Completion, input string) (chosen string, suggestions []string) {
-       idx := int(-complete) - 1
-
-       if len(pluginCompletions) <= idx {
-               return "", nil
-       }
-       suggestions = pluginCompletions[idx](input)
-
-       if len(suggestions) == 1 {
-               chosen = suggestions[0]
-       }
-       return
-}
-
-// PluginCmdComplete completes with possible choices for the `> plugin` command
-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
-}
-
-// PluginnameComplete completes with the names of loaded plugins
-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
-}
diff --git a/cmd/micro/bindings.go b/cmd/micro/bindings.go
deleted file mode 100644 (file)
index e3a0284..0000000
+++ /dev/null
@@ -1,616 +0,0 @@
-package main
-
-import (
-       "fmt"
-       "io/ioutil"
-       "os"
-       "strings"
-       "unicode"
-
-       "github.com/flynn/json5"
-       "github.com/zyedidia/tcell"
-)
-
-var bindingsStr map[string]string
-var bindings map[Key][]func(*View, bool) bool
-var mouseBindings map[Key][]func(*View, bool, *tcell.EventMouse) bool
-var helpBinding string
-var kmenuBinding string
-
-var mouseBindingActions = map[string]func(*View, bool, *tcell.EventMouse) bool{
-       "MousePress":       (*View).MousePress,
-       "MouseMultiCursor": (*View).MouseMultiCursor,
-}
-
-var bindingActions = map[string]func(*View, bool) bool{
-       "CursorUp":               (*View).CursorUp,
-       "CursorDown":             (*View).CursorDown,
-       "CursorPageUp":           (*View).CursorPageUp,
-       "CursorPageDown":         (*View).CursorPageDown,
-       "CursorLeft":             (*View).CursorLeft,
-       "CursorRight":            (*View).CursorRight,
-       "CursorStart":            (*View).CursorStart,
-       "CursorEnd":              (*View).CursorEnd,
-       "SelectToStart":          (*View).SelectToStart,
-       "SelectToEnd":            (*View).SelectToEnd,
-       "SelectUp":               (*View).SelectUp,
-       "SelectDown":             (*View).SelectDown,
-       "SelectLeft":             (*View).SelectLeft,
-       "SelectRight":            (*View).SelectRight,
-       "WordRight":              (*View).WordRight,
-       "WordLeft":               (*View).WordLeft,
-       "SelectWordRight":        (*View).SelectWordRight,
-       "SelectWordLeft":         (*View).SelectWordLeft,
-       "DeleteWordRight":        (*View).DeleteWordRight,
-       "DeleteWordLeft":         (*View).DeleteWordLeft,
-       "SelectLine":             (*View).SelectLine,
-       "SelectToStartOfLine":    (*View).SelectToStartOfLine,
-       "SelectToEndOfLine":      (*View).SelectToEndOfLine,
-       "ParagraphPrevious":      (*View).ParagraphPrevious,
-       "ParagraphNext":          (*View).ParagraphNext,
-       "InsertNewline":          (*View).InsertNewline,
-       "InsertSpace":            (*View).InsertSpace,
-       "Backspace":              (*View).Backspace,
-       "Delete":                 (*View).Delete,
-       "InsertTab":              (*View).InsertTab,
-       "Save":                   (*View).Save,
-       "SaveAll":                (*View).SaveAll,
-       "SaveAs":                 (*View).SaveAs,
-       "Find":                   (*View).Find,
-       "FindNext":               (*View).FindNext,
-       "FindPrevious":           (*View).FindPrevious,
-       "Center":                 (*View).Center,
-       "Undo":                   (*View).Undo,
-       "Redo":                   (*View).Redo,
-       "Copy":                   (*View).Copy,
-       "Cut":                    (*View).Cut,
-       "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,
-       "OpenFile":               (*View).OpenFile,
-       "Start":                  (*View).Start,
-       "End":                    (*View).End,
-       "PageUp":                 (*View).PageUp,
-       "PageDown":               (*View).PageDown,
-       "SelectPageUp":           (*View).SelectPageUp,
-       "SelectPageDown":         (*View).SelectPageDown,
-       "HalfPageUp":             (*View).HalfPageUp,
-       "HalfPageDown":           (*View).HalfPageDown,
-       "StartOfLine":            (*View).StartOfLine,
-       "EndOfLine":              (*View).EndOfLine,
-       "ToggleHelp":             (*View).ToggleHelp,
-       "ToggleKeyMenu":          (*View).ToggleKeyMenu,
-       "ToggleRuler":            (*View).ToggleRuler,
-       "JumpLine":               (*View).JumpLine,
-       "ClearStatus":            (*View).ClearStatus,
-       "ShellMode":              (*View).ShellMode,
-       "CommandMode":            (*View).CommandMode,
-       "ToggleOverwriteMode":    (*View).ToggleOverwriteMode,
-       "Escape":                 (*View).Escape,
-       "Quit":                   (*View).Quit,
-       "QuitAll":                (*View).QuitAll,
-       "AddTab":                 (*View).AddTab,
-       "PreviousTab":            (*View).PreviousTab,
-       "NextTab":                (*View).NextTab,
-       "NextSplit":              (*View).NextSplit,
-       "PreviousSplit":          (*View).PreviousSplit,
-       "Unsplit":                (*View).Unsplit,
-       "VSplit":                 (*View).VSplitBinding,
-       "HSplit":                 (*View).HSplitBinding,
-       "ToggleMacro":            (*View).ToggleMacro,
-       "PlayMacro":              (*View).PlayMacro,
-       "Suspend":                (*View).Suspend,
-       "ScrollUp":               (*View).ScrollUpAction,
-       "ScrollDown":             (*View).ScrollDownAction,
-       "SpawnMultiCursor":       (*View).SpawnMultiCursor,
-       "SpawnMultiCursorSelect": (*View).SpawnMultiCursorSelect,
-       "RemoveMultiCursor":      (*View).RemoveMultiCursor,
-       "RemoveAllMultiCursors":  (*View).RemoveAllMultiCursors,
-       "SkipMultiCursor":        (*View).SkipMultiCursor,
-       "JumpToMatchingBrace":    (*View).JumpToMatchingBrace,
-
-       // This was changed to InsertNewline but I don't want to break backwards compatibility
-       "InsertEnter": (*View).InsertNewline,
-}
-
-var bindingMouse = map[string]tcell.ButtonMask{
-       "MouseLeft":       tcell.Button1,
-       "MouseMiddle":     tcell.Button2,
-       "MouseRight":      tcell.Button3,
-       "MouseWheelUp":    tcell.WheelUp,
-       "MouseWheelDown":  tcell.WheelDown,
-       "MouseWheelLeft":  tcell.WheelLeft,
-       "MouseWheelRight": tcell.WheelRight,
-}
-
-var bindingKeys = map[string]tcell.Key{
-       "Up":             tcell.KeyUp,
-       "Down":           tcell.KeyDown,
-       "Right":          tcell.KeyRight,
-       "Left":           tcell.KeyLeft,
-       "UpLeft":         tcell.KeyUpLeft,
-       "UpRight":        tcell.KeyUpRight,
-       "DownLeft":       tcell.KeyDownLeft,
-       "DownRight":      tcell.KeyDownRight,
-       "Center":         tcell.KeyCenter,
-       "PageUp":         tcell.KeyPgUp,
-       "PageDown":       tcell.KeyPgDn,
-       "Home":           tcell.KeyHome,
-       "End":            tcell.KeyEnd,
-       "Insert":         tcell.KeyInsert,
-       "Delete":         tcell.KeyDelete,
-       "Help":           tcell.KeyHelp,
-       "Exit":           tcell.KeyExit,
-       "Clear":          tcell.KeyClear,
-       "Cancel":         tcell.KeyCancel,
-       "Print":          tcell.KeyPrint,
-       "Pause":          tcell.KeyPause,
-       "Backtab":        tcell.KeyBacktab,
-       "F1":             tcell.KeyF1,
-       "F2":             tcell.KeyF2,
-       "F3":             tcell.KeyF3,
-       "F4":             tcell.KeyF4,
-       "F5":             tcell.KeyF5,
-       "F6":             tcell.KeyF6,
-       "F7":             tcell.KeyF7,
-       "F8":             tcell.KeyF8,
-       "F9":             tcell.KeyF9,
-       "F10":            tcell.KeyF10,
-       "F11":            tcell.KeyF11,
-       "F12":            tcell.KeyF12,
-       "F13":            tcell.KeyF13,
-       "F14":            tcell.KeyF14,
-       "F15":            tcell.KeyF15,
-       "F16":            tcell.KeyF16,
-       "F17":            tcell.KeyF17,
-       "F18":            tcell.KeyF18,
-       "F19":            tcell.KeyF19,
-       "F20":            tcell.KeyF20,
-       "F21":            tcell.KeyF21,
-       "F22":            tcell.KeyF22,
-       "F23":            tcell.KeyF23,
-       "F24":            tcell.KeyF24,
-       "F25":            tcell.KeyF25,
-       "F26":            tcell.KeyF26,
-       "F27":            tcell.KeyF27,
-       "F28":            tcell.KeyF28,
-       "F29":            tcell.KeyF29,
-       "F30":            tcell.KeyF30,
-       "F31":            tcell.KeyF31,
-       "F32":            tcell.KeyF32,
-       "F33":            tcell.KeyF33,
-       "F34":            tcell.KeyF34,
-       "F35":            tcell.KeyF35,
-       "F36":            tcell.KeyF36,
-       "F37":            tcell.KeyF37,
-       "F38":            tcell.KeyF38,
-       "F39":            tcell.KeyF39,
-       "F40":            tcell.KeyF40,
-       "F41":            tcell.KeyF41,
-       "F42":            tcell.KeyF42,
-       "F43":            tcell.KeyF43,
-       "F44":            tcell.KeyF44,
-       "F45":            tcell.KeyF45,
-       "F46":            tcell.KeyF46,
-       "F47":            tcell.KeyF47,
-       "F48":            tcell.KeyF48,
-       "F49":            tcell.KeyF49,
-       "F50":            tcell.KeyF50,
-       "F51":            tcell.KeyF51,
-       "F52":            tcell.KeyF52,
-       "F53":            tcell.KeyF53,
-       "F54":            tcell.KeyF54,
-       "F55":            tcell.KeyF55,
-       "F56":            tcell.KeyF56,
-       "F57":            tcell.KeyF57,
-       "F58":            tcell.KeyF58,
-       "F59":            tcell.KeyF59,
-       "F60":            tcell.KeyF60,
-       "F61":            tcell.KeyF61,
-       "F62":            tcell.KeyF62,
-       "F63":            tcell.KeyF63,
-       "F64":            tcell.KeyF64,
-       "CtrlSpace":      tcell.KeyCtrlSpace,
-       "CtrlA":          tcell.KeyCtrlA,
-       "CtrlB":          tcell.KeyCtrlB,
-       "CtrlC":          tcell.KeyCtrlC,
-       "CtrlD":          tcell.KeyCtrlD,
-       "CtrlE":          tcell.KeyCtrlE,
-       "CtrlF":          tcell.KeyCtrlF,
-       "CtrlG":          tcell.KeyCtrlG,
-       "CtrlH":          tcell.KeyCtrlH,
-       "CtrlI":          tcell.KeyCtrlI,
-       "CtrlJ":          tcell.KeyCtrlJ,
-       "CtrlK":          tcell.KeyCtrlK,
-       "CtrlL":          tcell.KeyCtrlL,
-       "CtrlM":          tcell.KeyCtrlM,
-       "CtrlN":          tcell.KeyCtrlN,
-       "CtrlO":          tcell.KeyCtrlO,
-       "CtrlP":          tcell.KeyCtrlP,
-       "CtrlQ":          tcell.KeyCtrlQ,
-       "CtrlR":          tcell.KeyCtrlR,
-       "CtrlS":          tcell.KeyCtrlS,
-       "CtrlT":          tcell.KeyCtrlT,
-       "CtrlU":          tcell.KeyCtrlU,
-       "CtrlV":          tcell.KeyCtrlV,
-       "CtrlW":          tcell.KeyCtrlW,
-       "CtrlX":          tcell.KeyCtrlX,
-       "CtrlY":          tcell.KeyCtrlY,
-       "CtrlZ":          tcell.KeyCtrlZ,
-       "CtrlLeftSq":     tcell.KeyCtrlLeftSq,
-       "CtrlBackslash":  tcell.KeyCtrlBackslash,
-       "CtrlRightSq":    tcell.KeyCtrlRightSq,
-       "CtrlCarat":      tcell.KeyCtrlCarat,
-       "CtrlUnderscore": tcell.KeyCtrlUnderscore,
-       "CtrlPageUp":     tcell.KeyCtrlPgUp,
-       "CtrlPageDown":   tcell.KeyCtrlPgDn,
-       "Tab":            tcell.KeyTab,
-       "Esc":            tcell.KeyEsc,
-       "Escape":         tcell.KeyEscape,
-       "Enter":          tcell.KeyEnter,
-       "Backspace":      tcell.KeyBackspace2,
-       "OldBackspace":   tcell.KeyBackspace,
-
-       // I renamed these keys to PageUp and PageDown but I don't want to break someone's keybindings
-       "PgUp":   tcell.KeyPgUp,
-       "PgDown": tcell.KeyPgDn,
-}
-
-// The Key struct holds the data for a keypress (keycode + modifiers)
-type Key struct {
-       keyCode   tcell.Key
-       modifiers tcell.ModMask
-       buttons   tcell.ButtonMask
-       r         rune
-       escape    string
-}
-
-// InitBindings initializes the keybindings for micro
-func InitBindings() {
-       bindings = make(map[Key][]func(*View, bool) bool)
-       bindingsStr = make(map[string]string)
-       mouseBindings = make(map[Key][]func(*View, bool, *tcell.EventMouse) bool)
-
-       var parsed map[string]string
-       defaults := DefaultBindings()
-
-       filename := configDir + "/bindings.json"
-       if _, e := os.Stat(filename); e == nil {
-               input, err := ioutil.ReadFile(filename)
-               if err != nil {
-                       TermMessage("Error reading bindings.json file: " + err.Error())
-                       return
-               }
-
-               err = json5.Unmarshal(input, &parsed)
-               if err != nil {
-                       TermMessage("Error reading bindings.json:", err.Error())
-               }
-       }
-
-       parseBindings(defaults)
-       parseBindings(parsed)
-}
-
-func parseBindings(userBindings map[string]string) {
-       for k, v := range userBindings {
-               BindKey(k, v)
-       }
-}
-
-// findKey will find binding Key 'b' using string 'k'
-func findKey(k string) (b Key, ok bool) {
-       modifiers := tcell.ModNone
-
-       // First, we'll strip off all the modifiers in the name and add them to the
-       // ModMask
-modSearch:
-       for {
-               switch {
-               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
-                       k = k[4:]
-                       modifiers |= tcell.ModCtrl
-               case strings.HasPrefix(k, "Alt"):
-                       k = k[3:]
-                       modifiers |= tcell.ModAlt
-               case strings.HasPrefix(k, "Shift"):
-                       k = k[5:]
-                       modifiers |= tcell.ModShift
-               case strings.HasPrefix(k, "\x1b"):
-                       return Key{
-                               keyCode:   -1,
-                               modifiers: modifiers,
-                               buttons:   -1,
-                               r:         0,
-                               escape:    k,
-                       }, true
-               default:
-                       break modSearch
-               }
-       }
-
-       if len(k) == 0 {
-               return Key{buttons: -1}, false
-       }
-
-       // Control is handled specially, since some character codes in bindingKeys
-       // are different when Control is depressed. We should check for Control keys
-       // first.
-       if modifiers&tcell.ModCtrl != 0 {
-               // see if the key is in bindingKeys with the Ctrl prefix.
-               k = string(unicode.ToUpper(rune(k[0]))) + k[1:]
-               if code, ok := bindingKeys["Ctrl"+k]; ok {
-                       // It is, we're done.
-                       return Key{
-                               keyCode:   code,
-                               modifiers: modifiers,
-                               buttons:   -1,
-                               r:         0,
-                       }, true
-               }
-       }
-
-       // See if we can find the key in bindingKeys
-       if code, ok := bindingKeys[k]; ok {
-               return Key{
-                       keyCode:   code,
-                       modifiers: modifiers,
-                       buttons:   -1,
-                       r:         0,
-               }, true
-       }
-
-       // See if we can find the key in bindingMouse
-       if code, ok := bindingMouse[k]; ok {
-               return Key{
-                       modifiers: modifiers,
-                       buttons:   code,
-                       r:         0,
-               }, true
-       }
-
-       // If we were given one character, then we've got a rune.
-       if len(k) == 1 {
-               return Key{
-                       keyCode:   tcell.KeyRune,
-                       modifiers: modifiers,
-                       buttons:   -1,
-                       r:         rune(k[0]),
-               }, true
-       }
-
-       // We don't know what happened.
-       return Key{buttons: -1}, false
-}
-
-// findAction will find 'action' using string 'v'
-func findAction(v string) (action func(*View, bool) bool) {
-       action, ok := bindingActions[v]
-       if !ok {
-               // If the user seems to be binding a function that doesn't exist
-               // We hope that it's a lua function that exists and bind it to that
-               action = LuaFunctionBinding(v)
-       }
-       return action
-}
-
-func findMouseAction(v string) func(*View, bool, *tcell.EventMouse) bool {
-       action, ok := mouseBindingActions[v]
-       if !ok {
-               // If the user seems to be binding a function that doesn't exist
-               // We hope that it's a lua function that exists and bind it to that
-               action = LuaFunctionMouseBinding(v)
-       }
-       return action
-}
-
-// TryBindKey tries to bind a key by writing to configDir/bindings.json
-// This function is unused for now
-func TryBindKey(k, v string) {
-       filename := configDir + "/bindings.json"
-       if _, e := os.Stat(filename); e == nil {
-               input, err := ioutil.ReadFile(filename)
-               if err != nil {
-                       TermMessage("Error reading bindings.json file: " + err.Error())
-                       return
-               }
-
-               conflict := -1
-               lines := strings.Split(string(input), "\n")
-               for i, l := range lines {
-                       parts := strings.Split(l, ":")
-                       if len(parts) >= 2 {
-                               if strings.Contains(parts[0], k) {
-                                       conflict = i
-                                       TermMessage("Warning: Keybinding conflict:", k, " has been overwritten")
-                               }
-                       }
-               }
-
-               binding := fmt.Sprintf("    \"%s\": \"%s\",", k, v)
-               if conflict == -1 {
-                       lines = append([]string{lines[0], binding}, lines[conflict:]...)
-               } else {
-                       lines = append(append(lines[:conflict], binding), lines[conflict+1:]...)
-               }
-               txt := strings.Join(lines, "\n")
-               err = ioutil.WriteFile(filename, []byte(txt), 0644)
-               if err != nil {
-                       TermMessage("Error")
-               }
-       }
-}
-
-// BindKey takes a key and an action and binds the two together
-func BindKey(k, v string) {
-       key, ok := findKey(k)
-       if !ok {
-               TermMessage("Unknown keybinding: " + k)
-               return
-       }
-       if v == "ToggleHelp" {
-               helpBinding = k
-       }
-       if v == "ToggleKeyMenu" {
-               kmenuBinding = k
-       }
-       if helpBinding == k && v != "ToggleHelp" {
-               helpBinding = ""
-       }
-       if kmenuBinding == k && v != "ToggleKeyMenu" {
-               kmenuBinding = ""
-       }
-
-       actionNames := strings.Split(v, ",")
-       if actionNames[0] == "UnbindKey" {
-               delete(bindings, key)
-               delete(mouseBindings, key)
-               delete(bindingsStr, k)
-               if len(actionNames) == 1 {
-                       return
-               }
-               actionNames = append(actionNames[:0], actionNames[1:]...)
-       }
-       actions := make([]func(*View, bool) bool, 0, len(actionNames))
-       mouseActions := make([]func(*View, bool, *tcell.EventMouse) bool, 0, len(actionNames))
-       for _, actionName := range actionNames {
-               if strings.HasPrefix(actionName, "Mouse") {
-                       mouseActions = append(mouseActions, findMouseAction(actionName))
-               } else if strings.HasPrefix(actionName, "command:") {
-                       cmd := strings.SplitN(actionName, ":", 2)[1]
-                       actions = append(actions, CommandAction(cmd))
-               } else if strings.HasPrefix(actionName, "command-edit:") {
-                       cmd := strings.SplitN(actionName, ":", 2)[1]
-                       actions = append(actions, CommandEditAction(cmd))
-               } else {
-                       actions = append(actions, findAction(actionName))
-               }
-       }
-
-       if len(actions) > 0 {
-               // Can't have a binding be both mouse and normal
-               delete(mouseBindings, key)
-               bindings[key] = actions
-               bindingsStr[k] = v
-       } else if len(mouseActions) > 0 {
-               // Can't have a binding be both mouse and normal
-               delete(bindings, key)
-               mouseBindings[key] = mouseActions
-       }
-}
-
-// DefaultBindings returns a map containing micro's default keybindings
-func DefaultBindings() map[string]string {
-       return map[string]string{
-               "Up":             "CursorUp",
-               "Down":           "CursorDown",
-               "Right":          "CursorRight",
-               "Left":           "CursorLeft",
-               "ShiftUp":        "SelectUp",
-               "ShiftDown":      "SelectDown",
-               "ShiftLeft":      "SelectLeft",
-               "ShiftRight":     "SelectRight",
-               "AltLeft":        "WordLeft",
-               "AltRight":       "WordRight",
-               "AltUp":          "MoveLinesUp",
-               "AltDown":        "MoveLinesDown",
-               "AltShiftRight":  "SelectWordRight",
-               "AltShiftLeft":   "SelectWordLeft",
-               "CtrlLeft":       "StartOfLine",
-               "CtrlRight":      "EndOfLine",
-               "CtrlShiftLeft":  "SelectToStartOfLine",
-               "ShiftHome":      "SelectToStartOfLine",
-               "CtrlShiftRight": "SelectToEndOfLine",
-               "ShiftEnd":       "SelectToEndOfLine",
-               "CtrlUp":         "CursorStart",
-               "CtrlDown":       "CursorEnd",
-               "CtrlShiftUp":    "SelectToStart",
-               "CtrlShiftDown":  "SelectToEnd",
-               "Alt-{":          "ParagraphPrevious",
-               "Alt-}":          "ParagraphNext",
-               "Enter":          "InsertNewline",
-               "CtrlH":          "Backspace",
-               "Backspace":      "Backspace",
-               "Alt-CtrlH":      "DeleteWordLeft",
-               "Alt-Backspace":  "DeleteWordLeft",
-               "Tab":            "IndentSelection,InsertTab",
-               "Backtab":        "OutdentSelection,OutdentLine",
-               "CtrlO":          "OpenFile",
-               "CtrlS":          "Save",
-               "CtrlF":          "Find",
-               "CtrlN":          "FindNext",
-               "CtrlP":          "FindPrevious",
-               "CtrlZ":          "Undo",
-               "CtrlY":          "Redo",
-               "CtrlC":          "Copy",
-               "CtrlX":          "Cut",
-               "CtrlK":          "CutLine",
-               "CtrlD":          "DuplicateLine",
-               "CtrlV":          "Paste",
-               "CtrlA":          "SelectAll",
-               "CtrlT":          "AddTab",
-               "Alt,":           "PreviousTab",
-               "Alt.":           "NextTab",
-               "Home":           "StartOfLine",
-               "End":            "EndOfLine",
-               "CtrlHome":       "CursorStart",
-               "CtrlEnd":        "CursorEnd",
-               "PageUp":         "CursorPageUp",
-               "PageDown":       "CursorPageDown",
-               "CtrlPageUp":     "PreviousTab",
-               "CtrlPageDown":   "NextTab",
-               "CtrlG":          "ToggleHelp",
-               "Alt-g":          "ToggleKeyMenu",
-               "CtrlR":          "ToggleRuler",
-               "CtrlL":          "JumpLine",
-               "Delete":         "Delete",
-               "CtrlB":          "ShellMode",
-               "CtrlQ":          "Quit",
-               "CtrlE":          "CommandMode",
-               "CtrlW":          "NextSplit",
-               "CtrlU":          "ToggleMacro",
-               "CtrlJ":          "PlayMacro",
-               "Insert":         "ToggleOverwriteMode",
-
-               // Emacs-style keybindings
-               "Alt-f": "WordRight",
-               "Alt-b": "WordLeft",
-               "Alt-a": "StartOfLine",
-               "Alt-e": "EndOfLine",
-               // "Alt-p": "CursorUp",
-               // "Alt-n": "CursorDown",
-
-               // Integration with file managers
-               "F2":  "Save",
-               "F3":  "Find",
-               "F4":  "Quit",
-               "F7":  "Find",
-               "F10": "Quit",
-               "Esc": "Escape",
-
-               // Mouse bindings
-               "MouseWheelUp":   "ScrollUp",
-               "MouseWheelDown": "ScrollDown",
-               "MouseLeft":      "MousePress",
-               "MouseMiddle":    "PastePrimary",
-               "Ctrl-MouseLeft": "MouseMultiCursor",
-
-               "Alt-n": "SpawnMultiCursor",
-               "Alt-m": "SpawnMultiCursorSelect",
-               "Alt-p": "RemoveMultiCursor",
-               "Alt-c": "RemoveAllMultiCursors",
-               "Alt-x": "SkipMultiCursor",
-       }
-}
index c0161b342987e8a44a6bf9c3610704e688a0a8a9..747ace3d5051f599a361b30a7ff09459010f40ba 100644 (file)
@@ -4,44 +4,43 @@ import (
        "bufio"
        "bytes"
        "crypto/md5"
-       "encoding/gob"
        "errors"
        "io"
        "io/ioutil"
+       "log"
        "os"
        "os/exec"
        "os/signal"
        "path/filepath"
-       "strconv"
        "strings"
        "time"
-       "unicode"
        "unicode/utf8"
 
        "github.com/zyedidia/micro/cmd/micro/highlight"
 )
 
+// LargeFileThreshold is the number of bytes when fastdirty is forced
+// because hashing is too slow
 const LargeFileThreshold = 50000
 
+// The BufType defines what kind of buffer this is
+type BufType struct {
+       Kind     int
+       Readonly bool // The file cannot be edited
+       Scratch  bool // The file cannot be saved
+}
+
 var (
-       // 0 - no line type detected
-       // 1 - lf detected
-       // 2 - crlf detected
-       fileformat = 0
+       btDefault = BufType{0, false, false}
+       btHelp    = BufType{1, true, true}
+       btLog     = BufType{2, true, true}
+       btScratch = BufType{3, false, true}
+       btRaw     = BufType{4, true, true}
 )
 
-// Buffer stores the text for files that are loaded into the text editor
-// It uses a rope to efficiently store the string and contains some
-// simple functions for saving and wrapper functions for modifying the rope
 type Buffer struct {
-       // The eventhandler for undo/redo
-       *EventHandler
-       // This stores all the text in the buffer as an array of lines
        *LineArray
-
-       Cursor    Cursor
-       cursors   []*Cursor // for multiple cursors
-       curCursor int       // the current cursor
+       *EventHandler
 
        // Path to the file on disk
        Path string
@@ -51,30 +50,22 @@ type Buffer struct {
        name string
 
        // Whether or not the buffer has been modified since it was opened
-       IsModified bool
+       isModified bool
 
        // Stores the last modification time of the file the buffer is pointing to
        ModTime time.Time
 
-       // NumLines is the number of lines in the buffer
-       NumLines int
-
        syntaxDef   *highlight.Def
        highlighter *highlight.Highlighter
 
        // Hash of the original buffer -- empty if fastdirty is on
        origHash [md5.Size]byte
 
-       // Buffer local settings
+       // Settings customized by the user
        Settings map[string]interface{}
-}
 
-// The SerializedBuffer holds the types that get serialized when a buffer is saved
-// These are used for the savecursor and saveundo options
-type SerializedBuffer struct {
-       EventHandler *EventHandler
-       Cursor       Cursor
-       ModTime      time.Time
+       // Type of the buffer (e.g. help, raw, scratch etc..)
+       Type BufType
 }
 
 // NewBufferFromFile opens a new buffer using the given path
@@ -82,8 +73,13 @@ type SerializedBuffer struct {
 // It will return an empty buffer if the path does not exist
 // and an error if the file is a directory
 func NewBufferFromFile(path string) (*Buffer, error) {
+       var err error
        filename, cursorPosition := GetPathAndCursorPosition(path)
-       filename = ReplaceHome(filename)
+       filename, err = ReplaceHome(filename)
+       if err != nil {
+               return nil, err
+       }
+
        file, err := os.Open(filename)
        fileInfo, _ := os.Stat(filename)
 
@@ -110,20 +106,10 @@ func NewBufferFromString(text, path string) *Buffer {
 }
 
 // NewBuffer creates a new buffer from a given reader with a given path
+// Ensure that ReadSettings and InitGlobalSettings have been called before creating
+// a new buffer
 func NewBuffer(reader io.Reader, size int64, path string, cursorPosition []string) *Buffer {
-       // check if the file is already open in a tab. If it's open return the buffer to that tab
-       if path != "" {
-               for _, tab := range tabs {
-                       for _, view := range tab.Views {
-                               if view.Buf.Path == path {
-                                       return view.Buf
-                               }
-                       }
-               }
-       }
-
        b := new(Buffer)
-       b.LineArray = NewLineArray(size, reader)
 
        b.Settings = DefaultLocalSettings()
        for k, v := range globalSettings {
@@ -131,12 +117,9 @@ func NewBuffer(reader io.Reader, size int64, path string, cursorPosition []strin
                        b.Settings[k] = v
                }
        }
+       InitLocalSettings(b)
 
-       if fileformat == 1 {
-               b.Settings["fileformat"] = "unix"
-       } else if fileformat == 2 {
-               b.Settings["fileformat"] = "dos"
-       }
+       b.LineArray = NewLineArray(uint64(size), FFAuto, reader)
 
        absPath, _ := filepath.Abs(path)
 
@@ -148,110 +131,21 @@ func NewBuffer(reader io.Reader, size int64, path string, cursorPosition []strin
 
        b.EventHandler = NewEventHandler(b)
 
-       b.Update()
        b.UpdateRules()
-
-       if _, err := os.Stat(configDir + "/buffers/"); os.IsNotExist(err) {
-               os.Mkdir(configDir+"/buffers/", os.ModePerm)
-       }
-
-       cursorLocation, cursorLocationError := GetBufferCursorLocation(cursorPosition, b)
-       b.Cursor = Cursor{
-               Loc: cursorLocation,
-               buf: b,
-       }
-
-       InitLocalSettings(b)
-
-       if cursorLocationError != nil && len(*flagStartPos) == 0 && (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))
-               defer file.Close()
-               if err == nil {
-                       var buffer SerializedBuffer
-                       decoder := gob.NewDecoder(file)
-                       gob.Register(TextEvent{})
-                       err = decoder.Decode(&buffer)
-                       if err != nil {
-                               TermMessage(err.Error(), "\n", "You may want to remove the files in ~/.config/micro/buffers (these files store the information for the 'saveundo' and 'savecursor' options) if this problem persists.")
-                       }
-                       if b.Settings["savecursor"].(bool) {
-                               b.Cursor = buffer.Cursor
-                               b.Cursor.buf = b
-                               b.Cursor.Relocate()
-                       }
-
-                       if b.Settings["saveundo"].(bool) {
-                               // We should only use last time's eventhandler if the file wasn't modified by someone else in the meantime
-                               if b.ModTime == buffer.ModTime {
-                                       b.EventHandler = buffer.EventHandler
-                                       b.EventHandler.buf = b
-                               }
-                       }
-               }
-       }
+       log.Println("Filetype detected: ", b.Settings["filetype"])
 
        if !b.Settings["fastdirty"].(bool) {
                if size > LargeFileThreshold {
-                       // If the file is larger than a megabyte fastdirty needs to be on
+                       // If the file is larger than LargeFileThreshold fastdirty needs to be on
                        b.Settings["fastdirty"] = true
                } else {
                        calcHash(b, &b.origHash)
                }
        }
 
-       b.cursors = []*Cursor{&b.Cursor}
-
        return b
 }
 
-func GetBufferCursorLocation(cursorPosition []string, b *Buffer) (Loc, error) {
-       // parse the cursor position. The cursor location is ALWAYS initialised to 0, 0 even when
-       // an error occurs due to lack of arguments or because the arguments are not numbers
-       cursorLocation, cursorLocationError := ParseCursorLocation(cursorPosition)
-
-       // Put the cursor at the first spot. In the logic for cursor position the -startpos
-       // flag is processed first and will overwrite any line/col parameters with colons after the filename
-       if len(*flagStartPos) > 0 || cursorLocationError == nil {
-               var lineNum, colNum int
-               var errPos1, errPos2 error
-
-               positions := strings.Split(*flagStartPos, ",")
-
-               // if the -startpos flag contains enough args use them for the cursor location.
-               // In this case args passed at the end of the filename will be ignored
-               if len(positions) == 2 {
-                       lineNum, errPos1 = strconv.Atoi(positions[0])
-                       colNum, errPos2 = strconv.Atoi(positions[1])
-               }
-               // if -startpos has invalid arguments, use the arguments from filename.
-               // This will have a default value (0, 0) even when the filename arguments are invalid
-               if errPos1 != nil || errPos2 != nil || len(*flagStartPos) == 0 {
-                       // otherwise check if there are any arguments after the filename and use them
-                       lineNum = cursorLocation.Y
-                       colNum = cursorLocation.X
-               }
-
-               // if some arguments were found make sure they don't go outside the file and cause overflows
-               cursorLocation.Y = lineNum - 1
-               cursorLocation.X = colNum
-               // Check to avoid line overflow
-               if cursorLocation.Y > b.NumLines-1 {
-                       cursorLocation.Y = b.NumLines - 1
-               } else if cursorLocation.Y < 0 {
-                       cursorLocation.Y = 0
-               }
-               // Check to avoid column overflow
-               if cursorLocation.X > len(b.Line(cursorLocation.Y)) {
-                       cursorLocation.X = len(b.Line(cursorLocation.Y))
-               } else if cursorLocation.X < 0 {
-                       cursorLocation.X = 0
-               }
-       }
-       return cursorLocation, cursorLocationError
-}
-
 // GetName returns the name that should be displayed in the statusline
 // for this buffer
 func (b *Buffer) GetName() string {
@@ -264,209 +158,50 @@ func (b *Buffer) GetName() string {
        return b.name
 }
 
-// UpdateRules updates the syntax rules and filetype for this buffer
-// This is called when the colorscheme changes
-func (b *Buffer) UpdateRules() {
-       rehighlight := false
-       var files []*highlight.File
-       for _, f := range ListRuntimeFiles(RTSyntax) {
-               data, err := f.Data()
-               if err != nil {
-                       TermMessage("Error loading syntax file " + f.Name() + ": " + err.Error())
-               } else {
-                       file, err := highlight.ParseFile(data)
-                       if err != nil {
-                               TermMessage("Error loading syntax file " + f.Name() + ": " + err.Error())
-                               continue
-                       }
-                       ftdetect, err := highlight.ParseFtDetect(file)
-                       if err != nil {
-                               TermMessage("Error loading syntax file " + f.Name() + ": " + err.Error())
-                               continue
-                       }
-
-                       ft := b.Settings["filetype"].(string)
-                       if (ft == "Unknown" || ft == "") && !rehighlight {
-                               if highlight.MatchFiletype(ftdetect, b.Path, b.lines[0].data) {
-                                       header := new(highlight.Header)
-                                       header.FileType = file.FileType
-                                       header.FtDetect = ftdetect
-                                       b.syntaxDef, err = highlight.ParseDef(file, header)
-                                       if err != nil {
-                                               TermMessage("Error loading syntax file " + f.Name() + ": " + err.Error())
-                                               continue
-                                       }
-                                       rehighlight = true
-                               }
-                       } else {
-                               if file.FileType == ft && !rehighlight {
-                                       header := new(highlight.Header)
-                                       header.FileType = file.FileType
-                                       header.FtDetect = ftdetect
-                                       b.syntaxDef, err = highlight.ParseDef(file, header)
-                                       if err != nil {
-                                               TermMessage("Error loading syntax file " + f.Name() + ": " + err.Error())
-                                               continue
-                                       }
-                                       rehighlight = true
-                               }
-                       }
-                       files = append(files, file)
-               }
-       }
-
-       if b.syntaxDef != nil {
-               highlight.ResolveIncludes(b.syntaxDef, files)
-       }
-
-       if b.highlighter == nil || rehighlight {
-               if b.syntaxDef != nil {
-                       b.Settings["filetype"] = b.syntaxDef.FileType
-                       b.highlighter = highlight.NewHighlighter(b.syntaxDef)
-                       if b.Settings["syntax"].(bool) {
-                               b.highlighter.HighlightStates(b)
-                       }
-               }
-       }
-}
-
 // FileType returns the buffer's filetype
 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
-func (b *Buffer) CheckModTime() {
-       modTime, ok := GetModTime(b.Path)
-       if ok {
-               if modTime != b.ModTime {
-                       choice, canceled := messenger.YesNoPrompt("The file has changed since it was last read. Reload file? (y,n)")
-                       messenger.Reset()
-                       messenger.Clear()
-                       if !choice || canceled {
-                               // Don't load new changes -- do nothing
-                               b.ModTime, _ = GetModTime(b.Path)
-                       } else {
-                               // Load new changes
-                               b.ReOpen()
-                       }
-               }
-       }
-}
-
 // ReOpen reloads the current buffer from disk
-func (b *Buffer) ReOpen() {
+func (b *Buffer) ReOpen() error {
        data, err := ioutil.ReadFile(b.Path)
        txt := string(data)
 
        if err != nil {
-               messenger.Error(err.Error())
-               return
+               return err
        }
        b.EventHandler.ApplyDiff(txt)
 
-       b.ModTime, _ = GetModTime(b.Path)
-       b.IsModified = false
-       b.Update()
-       b.Cursor.Relocate()
-}
-
-// Update fetches the string from the rope and updates the `text` and `lines` in the buffer
-func (b *Buffer) Update() {
-       b.NumLines = len(b.lines)
+       b.ModTime, err = GetModTime(b.Path)
+       b.isModified = false
+       return err
+       // TODO: buffer cursor
+       // b.Cursor.Relocate()
 }
 
-// MergeCursors merges any cursors that are at the same position
-// into one cursor
-func (b *Buffer) MergeCursors() {
-       var cursors []*Cursor
-       for i := 0; i < len(b.cursors); i++ {
-               c1 := b.cursors[i]
-               if c1 != nil {
-                       for j := 0; j < len(b.cursors); j++ {
-                               c2 := b.cursors[j]
-                               if c2 != nil && i != j && c1.Loc == c2.Loc {
-                                       b.cursors[j] = nil
-                               }
-                       }
-                       cursors = append(cursors, c1)
-               }
-       }
-
-       b.cursors = cursors
-
-       for i := range b.cursors {
-               b.cursors[i].Num = i
-       }
-
-       if b.curCursor >= len(b.cursors) {
-               b.curCursor = len(b.cursors) - 1
-       }
-}
-
-// UpdateCursors updates all the cursors indicies
-func (b *Buffer) UpdateCursors() {
-       for i, c := range b.cursors {
-               c.Num = i
-       }
-}
+// Saving
 
 // Save saves the buffer to its default path
 func (b *Buffer) Save() error {
        return b.SaveAs(b.Path)
 }
 
-// SaveWithSudo saves the buffer to the default path with sudo
-func (b *Buffer) SaveWithSudo() error {
-       return b.SaveAsWithSudo(b.Path)
-}
-
-// Serialize serializes the buffer to configDir/buffers
-func (b *Buffer) Serialize() error {
-       if !b.Settings["savecursor"].(bool) && !b.Settings["saveundo"].(bool) {
-               return nil
-       }
-
-       name := configDir + "/buffers/" + EscapePath(b.AbsPath)
-
-       return overwriteFile(name, func(file io.Writer) error {
-               return gob.NewEncoder(file).Encode(SerializedBuffer{
-                       b.EventHandler,
-                       b.Cursor,
-                       b.ModTime,
-               })
-       })
-}
-
-func init() {
-       gob.Register(TextEvent{})
-       gob.Register(SerializedBuffer{})
-}
-
 // SaveAs saves the buffer to a specified path (filename), creating the file if it does not exist
 func (b *Buffer) SaveAs(filename string) error {
+       // TODO: rmtrailingws and updaterules
        b.UpdateRules()
-       if b.Settings["rmtrailingws"].(bool) {
-               for i, l := range b.lines {
-                       pos := len(bytes.TrimRightFunc(l.data, unicode.IsSpace))
-
-                       if pos < len(l.data) {
-                               b.deleteToEnd(Loc{pos, i})
-                       }
-               }
-
-               b.Cursor.Relocate()
-       }
+       // if b.Settings["rmtrailingws"].(bool) {
+       //      for i, l := range b.lines {
+       //              pos := len(bytes.TrimRightFunc(l.data, unicode.IsSpace))
+       //
+       //              if pos < len(l.data) {
+       //                      b.deleteToEnd(Loc{pos, i})
+       //              }
+       //      }
+       //
+       //      b.Cursor.Relocate()
+       // }
 
        if b.Settings["eofnewline"].(bool) {
                end := b.End()
@@ -475,30 +210,32 @@ func (b *Buffer) SaveAs(filename string) error {
                }
        }
 
+       // Update the last time this file was updated after saving
        defer func() {
                b.ModTime, _ = GetModTime(filename)
        }()
 
        // Removes any tilde and replaces with the absolute path to home
-       absFilename := ReplaceHome(filename)
-
-       // Get the leading path to the file | "." is returned if there's no leading path provided
-       if dirname := filepath.Dir(absFilename); dirname != "." {
-               // Check if the parent dirs don't exist
-               if _, statErr := os.Stat(dirname); os.IsNotExist(statErr) {
-                       // Prompt to make sure they want to create the dirs that are missing
-                       if yes, canceled := messenger.YesNoPrompt("Parent folders \"" + dirname + "\" do not exist. Create them? (y,n)"); yes && !canceled {
-                               // Create all leading dir(s) since they don't exist
-                               if mkdirallErr := os.MkdirAll(dirname, os.ModePerm); mkdirallErr != nil {
-                                       // If there was an error creating the dirs
-                                       return mkdirallErr
-                               }
-                       } else {
-                               // If they canceled the creation of leading dirs
-                               return errors.New("Save aborted")
-                       }
-               }
-       }
+       absFilename, _ := ReplaceHome(filename)
+
+       // TODO: save creates parent dirs
+       // // Get the leading path to the file | "." is returned if there's no leading path provided
+       // if dirname := filepath.Dir(absFilename); dirname != "." {
+       //      // Check if the parent dirs don't exist
+       //      if _, statErr := os.Stat(dirname); os.IsNotExist(statErr) {
+       //              // Prompt to make sure they want to create the dirs that are missing
+       //              if yes, canceled := messenger.YesNoPrompt("Parent folders \"" + dirname + "\" do not exist. Create them? (y,n)"); yes && !canceled {
+       //                      // Create all leading dir(s) since they don't exist
+       //                      if mkdirallErr := os.MkdirAll(dirname, os.ModePerm); mkdirallErr != nil {
+       //                              // If there was an error creating the dirs
+       //                              return mkdirallErr
+       //                      }
+       //              } else {
+       //                      // If they canceled the creation of leading dirs
+       //                      return errors.New("Save aborted")
+       //              }
+       //      }
+       // }
 
        var fileSize int
 
@@ -509,7 +246,6 @@ func (b *Buffer) SaveAs(filename string) error {
 
                // end of line
                var eol []byte
-
                if b.Settings["fileformat"] == "dos" {
                        eol = []byte{'\r', '\n'}
                } else {
@@ -525,14 +261,11 @@ func (b *Buffer) SaveAs(filename string) error {
                        if _, e = file.Write(eol); e != nil {
                                return
                        }
-
                        if _, e = file.Write(l.data); e != nil {
                                return
                        }
-
                        fileSize += len(eol) + len(l.data)
                }
-
                return
        })
 
@@ -550,8 +283,12 @@ func (b *Buffer) SaveAs(filename string) error {
        }
 
        b.Path = filename
-       b.IsModified = false
-       return b.Serialize()
+       absPath, _ := filepath.Abs(filename)
+       b.AbsPath = absPath
+       b.isModified = false
+       // TODO: serialize
+       // return b.Serialize()
+       return nil
 }
 
 // overwriteFile opens the given file for writing, truncating if one exists, and then calls
@@ -580,20 +317,9 @@ func overwriteFile(name string, fn func(io.Writer) error) (err error) {
        return
 }
 
-// calcHash calculates md5 hash of all lines in the buffer
-func calcHash(b *Buffer, out *[md5.Size]byte) {
-       h := md5.New()
-
-       if len(b.lines) > 0 {
-               h.Write(b.lines[0].data)
-
-               for _, l := range b.lines[1:] {
-                       h.Write([]byte{'\n'})
-                       h.Write(l.data)
-               }
-       }
-
-       h.Sum((*out)[:0])
+// SaveWithSudo saves the buffer to the default path with sudo
+func (b *Buffer) SaveWithSudo() error {
+       return b.SaveAsWithSudo(b.Path)
 }
 
 // SaveAsWithSudo is the same as SaveAs except it uses a neat trick
@@ -601,14 +327,12 @@ func calcHash(b *Buffer, out *[md5.Size]byte) {
 func (b *Buffer) SaveAsWithSudo(filename string) error {
        b.UpdateRules()
        b.Path = filename
-
-       // Shut down the screen because we're going to interact directly with the shell
-       screen.Fini()
-       screen = nil
+       absPath, _ := filepath.Abs(filename)
+       b.AbsPath = absPath
 
        // Set up everything for the command
        cmd := exec.Command(globalSettings["sucmd"].(string), "tee", filename)
-       cmd.Stdin = bytes.NewBufferString(b.SaveString(b.Settings["fileformat"] == "dos"))
+       cmd.Stdin = bytes.NewBuffer(b.Bytes())
 
        // This is a trap for Ctrl-C so that it doesn't kill micro
        // Instead we trap Ctrl-C to kill the program we're running
@@ -624,230 +348,160 @@ func (b *Buffer) SaveAsWithSudo(filename string) error {
        cmd.Start()
        err := cmd.Wait()
 
-       // Start the screen back up
-       InitScreen()
        if err == nil {
-               b.IsModified = false
+               b.isModified = false
                b.ModTime, _ = GetModTime(filename)
-               b.Serialize()
+               // TODO: serialize
        }
        return err
 }
 
-// Modified returns if this buffer has been modified since
-// being opened
-func (b *Buffer) Modified() bool {
-       if b.Settings["fastdirty"].(bool) {
-               return b.IsModified
-       }
-
-       var buff [md5.Size]byte
-
-       calcHash(b, &buff)
-       return buff != b.origHash
-}
-
-func (b *Buffer) insert(pos Loc, value []byte) {
-       b.IsModified = true
-       b.LineArray.insert(pos, value)
-       b.Update()
-}
-func (b *Buffer) remove(start, end Loc) string {
-       b.IsModified = true
-       sub := b.LineArray.remove(start, end)
-       b.Update()
-       return sub
-}
-func (b *Buffer) deleteToEnd(start Loc) {
-       b.IsModified = true
-       b.LineArray.DeleteToEnd(start)
-       b.Update()
+func (b *Buffer) GetActiveCursor() *Cursor {
+       return nil
 }
 
-// Start returns the location of the first character in the buffer
-func (b *Buffer) Start() Loc {
-       return Loc{0, 0}
+func (b *Buffer) GetCursor(n int) *Cursor {
+       return nil
 }
 
-// End returns the location of the last character in the buffer
-func (b *Buffer) End() Loc {
-       return Loc{utf8.RuneCount(b.lines[b.NumLines-1].data), b.NumLines - 1}
+func (b *Buffer) GetCursors() []*Cursor {
+       return nil
 }
 
-// RuneAt returns the rune at a given location in the buffer
-func (b *Buffer) RuneAt(loc Loc) rune {
-       line := b.LineRunes(loc.Y)
-       if len(line) > 0 {
-               return line[loc.X]
-       }
-       return '\n'
+func (b *Buffer) NumCursors() int {
+       return 0
 }
 
-// LineBytes returns a single line as an array of runes
 func (b *Buffer) LineBytes(n int) []byte {
-       if n >= len(b.lines) {
+       if n >= len(b.lines) || n < 0 {
                return []byte{}
        }
        return b.lines[n].data
 }
 
-// LineRunes returns a single line as an array of runes
-func (b *Buffer) LineRunes(n int) []rune {
-       if n >= len(b.lines) {
-               return []rune{}
-       }
-       return toRunes(b.lines[n].data)
+func (b *Buffer) LinesNum() int {
+       return len(b.lines)
 }
 
-// Line returns a single line
-func (b *Buffer) Line(n int) string {
-       if n >= len(b.lines) {
-               return ""
-       }
-       return string(b.lines[n].data)
+func (b *Buffer) Start() Loc {
+       return Loc{0, 0}
 }
 
-// LinesNum returns the number of lines in the buffer
-func (b *Buffer) LinesNum() int {
-       return len(b.lines)
+// End returns the location of the last character in the buffer
+func (b *Buffer) End() Loc {
+       numlines := len(b.lines)
+       return Loc{utf8.RuneCount(b.lines[numlines-1].data), numlines - 1}
 }
 
-// Lines returns an array of strings containing the lines from start to end
-func (b *Buffer) Lines(start, end int) []string {
-       lines := b.lines[start:end]
-       var slice []string
-       for _, line := range lines {
-               slice = append(slice, string(line.data))
+// RuneAt returns the rune at a given location in the buffer
+func (b *Buffer) RuneAt(loc Loc) rune {
+       line := b.LineBytes(loc.Y)
+       if len(line) > 0 {
+               i := 0
+               for len(line) > 0 {
+                       r, size := utf8.DecodeRune(line)
+                       line = line[size:]
+                       i++
+
+                       if i == loc.X {
+                               return r
+                       }
+               }
        }
-       return slice
+       return '\n'
 }
 
-// Len gives the length of the buffer
-func (b *Buffer) Len() (n int) {
-       for _, l := range b.lines {
-               n += utf8.RuneCount(l.data)
-       }
-
-       if len(b.lines) > 1 {
-               n += len(b.lines) - 1 // account for newlines
+// Modified returns if this buffer has been modified since
+// being opened
+func (b *Buffer) Modified() bool {
+       if b.Settings["fastdirty"].(bool) {
+               return b.isModified
        }
 
-       return
-}
+       var buff [md5.Size]byte
 
-// 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].data),
-                               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},
-       )
+       calcHash(b, &buff)
+       return buff != b.origHash
 }
 
-// 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},
-       )
-}
+// calcHash calculates md5 hash of all lines in the buffer
+func calcHash(b *Buffer, out *[md5.Size]byte) {
+       h := md5.New()
 
-// ClearMatches clears all of the syntax highlighting for this buffer
-func (b *Buffer) ClearMatches() {
-       for i := range b.lines {
-               b.SetMatch(i, nil)
-               b.SetState(i, nil)
-       }
-}
+       if len(b.lines) > 0 {
+               h.Write(b.lines[0].data)
 
-func (b *Buffer) clearCursors() {
-       for i := 1; i < len(b.cursors); i++ {
-               b.cursors[i] = nil
+               for _, l := range b.lines[1:] {
+                       h.Write([]byte{'\n'})
+                       h.Write(l.data)
+               }
        }
-       b.cursors = b.cursors[:1]
-       b.UpdateCursors()
-       b.Cursor.ResetSelection()
-}
 
-var bracePairs = [][2]rune{
-       {'(', ')'},
-       {'{', '}'},
-       {'[', ']'},
+       h.Sum((*out)[:0])
 }
 
-// FindMatchingBrace returns the location in the buffer of the matching bracket
-// It is given a brace type containing the open and closing character, (for example
-// '{' and '}') as well as the location to match from
-func (b *Buffer) FindMatchingBrace(braceType [2]rune, start Loc) Loc {
-       curLine := b.LineRunes(start.Y)
-       startChar := curLine[start.X]
-       var i int
-       if startChar == braceType[0] {
-               for y := start.Y; y < b.NumLines; y++ {
-                       l := b.LineRunes(y)
-                       xInit := 0
-                       if y == start.Y {
-                               xInit = start.X
+// UpdateRules updates the syntax rules and filetype for this buffer
+// This is called when the colorscheme changes
+func (b *Buffer) UpdateRules() {
+       rehighlight := false
+       var files []*highlight.File
+       for _, f := range ListRuntimeFiles(RTSyntax) {
+               data, err := f.Data()
+               if err != nil {
+                       TermMessage("Error loading syntax file " + f.Name() + ": " + err.Error())
+               } else {
+                       file, err := highlight.ParseFile(data)
+                       if err != nil {
+                               TermMessage("Error loading syntax file " + f.Name() + ": " + err.Error())
+                               continue
                        }
-                       for x := xInit; x < len(l); x++ {
-                               r := l[x]
-                               if r == braceType[0] {
-                                       i++
-                               } else if r == braceType[1] {
-                                       i--
-                                       if i == 0 {
-                                               return Loc{x, y}
+                       ftdetect, err := highlight.ParseFtDetect(file)
+                       if err != nil {
+                               TermMessage("Error loading syntax file " + f.Name() + ": " + err.Error())
+                               continue
+                       }
+
+                       ft := b.Settings["filetype"].(string)
+                       if (ft == "Unknown" || ft == "") && !rehighlight {
+                               if highlight.MatchFiletype(ftdetect, b.Path, b.lines[0].data) {
+                                       header := new(highlight.Header)
+                                       header.FileType = file.FileType
+                                       header.FtDetect = ftdetect
+                                       b.syntaxDef, err = highlight.ParseDef(file, header)
+                                       if err != nil {
+                                               TermMessage("Error loading syntax file " + f.Name() + ": " + err.Error())
+                                               continue
                                        }
+                                       rehighlight = true
                                }
-                       }
-               }
-       } else if startChar == braceType[1] {
-               for y := start.Y; y >= 0; y-- {
-                       l := []rune(string(b.lines[y].data))
-                       xInit := len(l) - 1
-                       if y == start.Y {
-                               xInit = start.X
-                       }
-                       for x := xInit; x >= 0; x-- {
-                               r := l[x]
-                               if r == braceType[0] {
-                                       i--
-                                       if i == 0 {
-                                               return Loc{x, y}
+                       } else {
+                               if file.FileType == ft && !rehighlight {
+                                       header := new(highlight.Header)
+                                       header.FileType = file.FileType
+                                       header.FtDetect = ftdetect
+                                       b.syntaxDef, err = highlight.ParseDef(file, header)
+                                       if err != nil {
+                                               TermMessage("Error loading syntax file " + f.Name() + ": " + err.Error())
+                                               continue
                                        }
-                               } else if r == braceType[1] {
-                                       i++
+                                       rehighlight = true
                                }
                        }
+                       files = append(files, file)
+               }
+       }
+
+       if b.syntaxDef != nil {
+               highlight.ResolveIncludes(b.syntaxDef, files)
+       }
+
+       if b.highlighter == nil || rehighlight {
+               if b.syntaxDef != nil {
+                       b.Settings["filetype"] = b.syntaxDef.FileType
+                       b.highlighter = highlight.NewHighlighter(b.syntaxDef)
+                       if b.Settings["syntax"].(bool) {
+                               b.highlighter.HighlightStates(b)
+                       }
                }
        }
-       return start
 }
diff --git a/cmd/micro/buffer_test.go b/cmd/micro/buffer_test.go
deleted file mode 100644 (file)
index 7369c78..0000000
+++ /dev/null
@@ -1,117 +0,0 @@
-package main
-
-import (
-       "testing"
-)
-
-func TestGetBufferCursorLocationEmptyArgs(t *testing.T) {
-       buf := NewBufferFromString("this is my\nbuffer\nfile\nhello", "")
-
-       location, err := GetBufferCursorLocation(nil, buf)
-
-       assertEqual(t, 0, location.Y)
-       assertEqual(t, 0, location.X)
-
-       // an error is present due to the cursorLocation being nil
-       assertTrue(t, err != nil)
-
-}
-
-func TestGetBufferCursorLocationStartposFlag(t *testing.T) {
-       buf := NewBufferFromString("this is my\nbuffer\nfile\nhello", "")
-
-       *flagStartPos = "1,2"
-
-       location, err := GetBufferCursorLocation(nil, buf)
-
-       // note: 1 is subtracted from the line to get the correct index in the buffer
-       assertTrue(t, 0 == location.Y)
-       assertTrue(t, 2 == location.X)
-
-       // an error is present due to the cursorLocation being nil
-       assertTrue(t, err != nil)
-}
-
-func TestGetBufferCursorLocationInvalidStartposFlag(t *testing.T) {
-       buf := NewBufferFromString("this is my\nbuffer\nfile\nhello", "")
-
-       *flagStartPos = "apples,2"
-
-       location, err := GetBufferCursorLocation(nil, buf)
-       // expect to default to the start of the file, which is 0,0
-       assertEqual(t, 0, location.Y)
-       assertEqual(t, 0, location.X)
-
-       // an error is present due to the cursorLocation being nil
-       assertTrue(t, err != nil)
-}
-func TestGetBufferCursorLocationStartposFlagAndCursorPosition(t *testing.T) {
-       text := "this is my\nbuffer\nfile\nhello"
-       cursorPosition := []string{"3", "1"}
-
-       buf := NewBufferFromString(text, "")
-
-       *flagStartPos = "1,2"
-
-       location, err := GetBufferCursorLocation(cursorPosition, buf)
-       // expect to have the flag positions, not the cursor position
-       // note: 1 is subtracted from the line to get the correct index in the buffer
-       assertEqual(t, 0, location.Y)
-       assertEqual(t, 2, location.X)
-
-       assertTrue(t, err == nil)
-}
-func TestGetBufferCursorLocationCursorPositionAndInvalidStartposFlag(t *testing.T) {
-       text := "this is my\nbuffer\nfile\nhello"
-       cursorPosition := []string{"3", "1"}
-
-       buf := NewBufferFromString(text, "")
-
-       *flagStartPos = "apples,2"
-
-       location, err := GetBufferCursorLocation(cursorPosition, buf)
-       // expect to have the flag positions, not the cursor position
-       // note: 1 is subtracted from the line to get the correct index in the buffer
-       assertEqual(t, 2, location.Y)
-       assertEqual(t, 1, location.X)
-
-       // no errors this time as cursorPosition is not nil
-       assertTrue(t, err == nil)
-}
-
-func TestGetBufferCursorLocationNoErrorWhenOverflowWithStartpos(t *testing.T) {
-       text := "this is my\nbuffer\nfile\nhello"
-
-       buf := NewBufferFromString(text, "")
-
-       *flagStartPos = "50,50"
-
-       location, err := GetBufferCursorLocation(nil, buf)
-       // expect to have the flag positions, not the cursor position
-       assertEqual(t, buf.NumLines-1, location.Y)
-       assertEqual(t, 5, location.X)
-
-       // error is expected as cursorPosition is nil
-       assertTrue(t, err != nil)
-}
-func TestGetBufferCursorLocationNoErrorWhenOverflowWithCursorPosition(t *testing.T) {
-       text := "this is my\nbuffer\nfile\nhello"
-       cursorPosition := []string{"50", "2"}
-
-       *flagStartPos = ""
-
-       buf := NewBufferFromString(text, "")
-
-       location, err := GetBufferCursorLocation(cursorPosition, buf)
-       // expect to have the flag positions, not the cursor position
-       assertEqual(t, buf.NumLines-1, location.Y)
-       assertEqual(t, 2, location.X)
-
-       // error is expected as cursorPosition is nil
-       assertTrue(t, err == nil)
-}
-
-//func TestGetBufferCursorLocationColonArgs(t *testing.T) {
-//     buf := new(Buffer)
-
-//}
diff --git a/cmd/micro/cellview.go b/cmd/micro/cellview.go
deleted file mode 100644 (file)
index b0cb652..0000000
+++ /dev/null
@@ -1,238 +0,0 @@
-package main
-
-import (
-       "github.com/mattn/go-runewidth"
-       "github.com/zyedidia/tcell"
-)
-
-func min(a, b int) int {
-       if a <= b {
-               return a
-       }
-       return b
-}
-
-func visualToCharPos(visualIndex int, lineN int, str string, buf *Buffer, tabsize int) (int, int, *tcell.Style) {
-       charPos := 0
-       var lineIdx int
-       var lastWidth int
-       var style *tcell.Style
-       var width int
-       var rw int
-       for i, c := range str {
-               // width := StringWidth(str[:i], tabsize)
-
-               if group, ok := buf.Match(lineN)[charPos]; ok {
-                       s := GetColor(group.String())
-                       style = &s
-               }
-
-               if width >= visualIndex {
-                       return charPos, visualIndex - lastWidth, style
-               }
-
-               if i != 0 {
-                       charPos++
-                       lineIdx += rw
-               }
-               lastWidth = width
-               rw = 0
-               if c == '\t' {
-                       rw = tabsize - (lineIdx % tabsize)
-                       width += rw
-               } else {
-                       rw = runewidth.RuneWidth(c)
-                       width += rw
-               }
-       }
-
-       return -1, -1, style
-}
-
-type Char struct {
-       visualLoc Loc
-       realLoc   Loc
-       char      rune
-       // The actual character that is drawn
-       // This is only different from char if it's for example hidden character
-       drawChar rune
-       style    tcell.Style
-       width    int
-}
-
-type CellView struct {
-       lines [][]*Char
-}
-
-func (c *CellView) Draw(buf *Buffer, top, height, left, width int) {
-       if width <= 0 {
-               return
-       }
-
-       matchingBrace := Loc{-1, -1}
-       // bracePairs is defined in buffer.go
-       if buf.Settings["matchbrace"].(bool) {
-               for _, bp := range bracePairs {
-                       curX := buf.Cursor.X
-                       curLoc := buf.Cursor.Loc
-                       if buf.Settings["matchbraceleft"].(bool) {
-                               if curX > 0 {
-                                       curX--
-                                       curLoc = curLoc.Move(-1, buf)
-                               }
-                       }
-
-                       r := buf.Cursor.RuneUnder(curX)
-                       if r == bp[0] || r == bp[1] {
-                               matchingBrace = buf.FindMatchingBrace(bp, curLoc)
-                       }
-               }
-       }
-
-       tabsize := int(buf.Settings["tabsize"].(float64))
-       softwrap := buf.Settings["softwrap"].(bool)
-       indentrunes := []rune(buf.Settings["indentchar"].(string))
-       // if empty indentchar settings, use space
-       if indentrunes == nil || len(indentrunes) == 0 {
-               indentrunes = []rune{' '}
-       }
-       indentchar := indentrunes[0]
-
-       start := buf.Cursor.Y
-       if buf.Settings["syntax"].(bool) && buf.syntaxDef != nil {
-               if start > 0 && buf.lines[start-1].rehighlight {
-                       buf.highlighter.ReHighlightLine(buf, start-1)
-                       buf.lines[start-1].rehighlight = false
-               }
-
-               buf.highlighter.ReHighlightStates(buf, start)
-
-               buf.highlighter.HighlightMatches(buf, top, top+height)
-       }
-
-       c.lines = make([][]*Char, 0)
-
-       viewLine := 0
-       lineN := top
-
-       curStyle := defStyle
-       for viewLine < height {
-               if lineN >= len(buf.lines) {
-                       break
-               }
-
-               lineStr := buf.Line(lineN)
-               line := []rune(lineStr)
-
-               colN, startOffset, startStyle := visualToCharPos(left, lineN, lineStr, buf, tabsize)
-               if colN < 0 {
-                       colN = len(line)
-               }
-               viewCol := -startOffset
-               if startStyle != nil {
-                       curStyle = *startStyle
-               }
-
-               // We'll either draw the length of the line, or the width of the screen
-               // whichever is smaller
-               lineLength := min(StringWidth(lineStr, tabsize), width)
-               c.lines = append(c.lines, make([]*Char, lineLength))
-
-               wrap := false
-               // We only need to wrap if the length of the line is greater than the width of the terminal screen
-               if softwrap && StringWidth(lineStr, tabsize) > width {
-                       wrap = true
-                       // We're going to draw the entire line now
-                       lineLength = StringWidth(lineStr, tabsize)
-               }
-
-               for viewCol < lineLength {
-                       if colN >= len(line) {
-                               break
-                       }
-                       if group, ok := buf.Match(lineN)[colN]; ok {
-                               curStyle = GetColor(group.String())
-                       }
-
-                       char := line[colN]
-
-                       if viewCol >= 0 {
-                               st := curStyle
-                               if colN == matchingBrace.X && lineN == matchingBrace.Y && !buf.Cursor.HasSelection() {
-                                       st = curStyle.Reverse(true)
-                               }
-                               if viewCol < len(c.lines[viewLine]) {
-                                       c.lines[viewLine][viewCol] = &Char{Loc{viewCol, viewLine}, Loc{colN, lineN}, char, char, st, 1}
-                               }
-                       }
-                       if char == '\t' {
-                               charWidth := tabsize - (viewCol+left)%tabsize
-                               if viewCol >= 0 {
-                                       c.lines[viewLine][viewCol].drawChar = indentchar
-                                       c.lines[viewLine][viewCol].width = charWidth
-
-                                       indentStyle := curStyle
-                                       ch := buf.Settings["indentchar"].(string)
-                                       if group, ok := colorscheme["indent-char"]; ok && !IsStrWhitespace(ch) && ch != "" {
-                                               indentStyle = group
-                                       }
-
-                                       c.lines[viewLine][viewCol].style = indentStyle
-                               }
-
-                               for i := 1; i < charWidth; i++ {
-                                       viewCol++
-                                       if viewCol >= 0 && viewCol < lineLength && viewCol < len(c.lines[viewLine]) {
-                                               c.lines[viewLine][viewCol] = &Char{Loc{viewCol, viewLine}, Loc{colN, lineN}, char, ' ', curStyle, 1}
-                                       }
-                               }
-                               viewCol++
-                       } else if runewidth.RuneWidth(char) > 1 {
-                               charWidth := runewidth.RuneWidth(char)
-                               if viewCol >= 0 {
-                                       c.lines[viewLine][viewCol].width = charWidth
-                               }
-                               for i := 1; i < charWidth; i++ {
-                                       viewCol++
-                                       if viewCol >= 0 && viewCol < lineLength && viewCol < len(c.lines[viewLine]) {
-                                               c.lines[viewLine][viewCol] = &Char{Loc{viewCol, viewLine}, Loc{colN, lineN}, char, ' ', curStyle, 1}
-                                       }
-                               }
-                               viewCol++
-                       } else {
-                               viewCol++
-                       }
-                       colN++
-
-                       if wrap && viewCol >= width {
-                               viewLine++
-
-                               // If we go too far soft wrapping we have to cut off
-                               if viewLine >= height {
-                                       break
-                               }
-
-                               nextLine := line[colN:]
-                               lineLength := min(StringWidth(string(nextLine), tabsize), width)
-                               c.lines = append(c.lines, make([]*Char, lineLength))
-
-                               viewCol = 0
-                       }
-
-               }
-               if group, ok := buf.Match(lineN)[len(line)]; ok {
-                       curStyle = GetColor(group.String())
-               }
-
-               // newline
-               viewLine++
-               lineN++
-       }
-
-       for i := top; i < top+height; i++ {
-               if i >= buf.NumLines {
-                       break
-               }
-               buf.SetMatch(i, nil)
-       }
-}
index 8a649b1785d606d4149acfae214b86d5bbb0b63c..b3c84a2d25a6eecd30dd8e8c215f6ff2a091d11d 100644 (file)
@@ -1,7 +1,7 @@
 package main
 
 import (
-       "fmt"
+       "errors"
        "regexp"
        "strconv"
        "strings"
@@ -9,6 +9,9 @@ import (
        "github.com/zyedidia/tcell"
 )
 
+// Micro's default style
+var defStyle tcell.Style = tcell.StyleDefault
+
 // Colorscheme is a map from string to style -- it represents a colorscheme
 type Colorscheme map[string]tcell.Style
 
@@ -48,42 +51,41 @@ func ColorschemeExists(colorschemeName string) bool {
 }
 
 // InitColorscheme picks and initializes the colorscheme when micro starts
-func InitColorscheme() {
+func InitColorscheme() error {
        colorscheme = make(Colorscheme)
-       defStyle = tcell.StyleDefault.
-               Foreground(tcell.ColorDefault).
-               Background(tcell.ColorDefault)
-       if screen != nil {
-               // screen.SetStyle(defStyle)
-       }
+       defStyle = tcell.StyleDefault
 
-       LoadDefaultColorscheme()
+       return LoadDefaultColorscheme()
 }
 
 // LoadDefaultColorscheme loads the default colorscheme from $(configDir)/colorschemes
-func LoadDefaultColorscheme() {
-       LoadColorscheme(globalSettings["colorscheme"].(string))
+func LoadDefaultColorscheme() error {
+       return LoadColorscheme(globalSettings["colorscheme"].(string))
 }
 
 // LoadColorscheme loads the given colorscheme from a directory
-func LoadColorscheme(colorschemeName string) {
+func LoadColorscheme(colorschemeName string) error {
        file := FindRuntimeFile(RTColorscheme, colorschemeName)
        if file == nil {
-               TermMessage(colorschemeName, "is not a valid colorscheme")
+               return errors.New(colorschemeName + " is not a valid colorscheme")
+       }
+       if data, err := file.Data(); err != nil {
+               return errors.New("Error loading colorscheme: " + err.Error())
        } else {
-               if data, err := file.Data(); err != nil {
-                       TermMessage("Error loading colorscheme:", err)
-               } else {
-                       colorscheme = ParseColorscheme(string(data))
+               colorscheme, err = ParseColorscheme(string(data))
+               if err != nil {
+                       return err
                }
        }
+       return nil
 }
 
 // ParseColorscheme parses the text definition for a colorscheme and returns the corresponding object
 // Colorschemes are made up of color-link statements linking a color group to a list of colors
 // For example, color-link keyword (blue,red) makes all keywords have a blue foreground and
 // red background
-func ParseColorscheme(text string) Colorscheme {
+func ParseColorscheme(text string) (Colorscheme, error) {
+       var err error
        parser := regexp.MustCompile(`color-link\s+(\S*)\s+"(.*)"`)
 
        lines := strings.Split(text, "\n")
@@ -108,15 +110,12 @@ func ParseColorscheme(text string) Colorscheme {
                        if link == "default" {
                                defStyle = style
                        }
-                       if screen != nil {
-                               // screen.SetStyle(defStyle)
-                       }
                } else {
-                       fmt.Println("Color-link statement is not valid:", line)
+                       err = errors.New("Color-link statement is not valid: " + line)
                }
        }
 
-       return c
+       return c, err
 }
 
 // StringToStyle returns a style from a string
diff --git a/cmd/micro/colorscheme_test.go b/cmd/micro/colorscheme_test.go
new file mode 100644 (file)
index 0000000..098af94
--- /dev/null
@@ -0,0 +1,62 @@
+package main
+
+import (
+       "testing"
+
+       "github.com/stretchr/testify/assert"
+       "github.com/zyedidia/tcell"
+)
+
+func TestSimpleStringToStyle(t *testing.T) {
+       s := StringToStyle("lightblue,magenta")
+
+       fg, bg, _ := s.Decompose()
+
+       assert.Equal(t, tcell.ColorBlue, fg)
+       assert.Equal(t, tcell.ColorPurple, bg)
+}
+
+func TestAttributeStringToStyle(t *testing.T) {
+       s := StringToStyle("bold cyan,brightcyan")
+
+       fg, bg, attr := s.Decompose()
+
+       assert.Equal(t, tcell.ColorTeal, fg)
+       assert.Equal(t, tcell.ColorAqua, bg)
+       assert.NotEqual(t, 0, attr&tcell.AttrBold)
+}
+
+func TestColor256StringToStyle(t *testing.T) {
+       s := StringToStyle("128,60")
+
+       fg, bg, _ := s.Decompose()
+
+       assert.Equal(t, tcell.Color128, fg)
+       assert.Equal(t, tcell.Color60, bg)
+}
+
+func TestColorHexStringToStyle(t *testing.T) {
+       s := StringToStyle("#deadbe,#ef1234")
+
+       fg, bg, _ := s.Decompose()
+
+       assert.Equal(t, tcell.NewRGBColor(222, 173, 190), fg)
+       assert.Equal(t, tcell.NewRGBColor(239, 18, 52), bg)
+}
+
+func TestColorschemeParser(t *testing.T) {
+       testColorscheme := `color-link default "#F8F8F2,#282828"
+color-link comment "#75715E,#282828"
+# comment
+color-link identifier "#66D9EF,#282828" #comment
+color-link constant "#AE81FF,#282828"
+color-link constant.string "#E6DB74,#282828"
+color-link constant.string.char "#BDE6AD,#282828"`
+
+       c, err := ParseColorscheme(testColorscheme)
+       assert.Nil(t, err)
+
+       fg, bg, _ := c["comment"].Decompose()
+       assert.Equal(t, tcell.NewRGBColor(117, 113, 94), fg)
+       assert.Equal(t, tcell.NewRGBColor(40, 40, 40), bg)
+}
diff --git a/cmd/micro/command.go b/cmd/micro/command.go
deleted file mode 100644 (file)
index f3a1b26..0000000
+++ /dev/null
@@ -1,694 +0,0 @@
-package main
-
-import (
-       "fmt"
-       "os"
-       "path/filepath"
-       "regexp"
-       "runtime"
-       "strconv"
-       "strings"
-       "unicode/utf8"
-
-       humanize "github.com/dustin/go-humanize"
-       "github.com/zyedidia/micro/cmd/micro/shellwords"
-)
-
-// A Command contains a action (a function to call) as well as information about how to autocomplete the command
-type Command struct {
-       action      func([]string)
-       completions []Completion
-}
-
-// A StrCommand is similar to a command but keeps the name of the action
-type StrCommand struct {
-       action      string
-       completions []Completion
-}
-
-var commands map[string]Command
-
-var commandActions map[string]func([]string)
-
-func init() {
-       commandActions = map[string]func([]string){
-               "Set":        Set,
-               "SetLocal":   SetLocal,
-               "Show":       Show,
-               "ShowKey":    ShowKey,
-               "Run":        Run,
-               "Bind":       Bind,
-               "Quit":       Quit,
-               "Save":       Save,
-               "Replace":    Replace,
-               "ReplaceAll": ReplaceAll,
-               "VSplit":     VSplit,
-               "HSplit":     HSplit,
-               "Tab":        NewTab,
-               "Help":       Help,
-               "Eval":       Eval,
-               "ToggleLog":  ToggleLog,
-               "Plugin":     PluginCmd,
-               "Reload":     Reload,
-               "Cd":         Cd,
-               "Pwd":        Pwd,
-               "Open":       Open,
-               "TabSwitch":  TabSwitch,
-               "Term":       Term,
-               "MemUsage":   MemUsage,
-               "Retab":      Retab,
-               "Raw":        Raw,
-               "TextFilter": TextFilter,
-       }
-}
-
-// InitCommands initializes the default commands
-func InitCommands() {
-       commands = make(map[string]Command)
-
-       defaults := DefaultCommands()
-       parseCommands(defaults)
-}
-
-func parseCommands(userCommands map[string]StrCommand) {
-       for k, v := range userCommands {
-               MakeCommand(k, v.action, v.completions...)
-       }
-}
-
-// MakeCommand is a function to easily create new commands
-// This can be called by plugins in Lua so that plugins can define their own commands
-func MakeCommand(name, function string, completions ...Completion) {
-       action := commandActions[function]
-       if _, ok := commandActions[function]; !ok {
-               // If the user seems to be binding a function that doesn't exist
-               // We hope that it's a lua function that exists and bind it to that
-               action = LuaFunctionCommand(function)
-       }
-
-       commands[name] = Command{action, completions}
-}
-
-// DefaultCommands returns a map containing micro's default commands
-func DefaultCommands() map[string]StrCommand {
-       return map[string]StrCommand{
-               "set":        {"Set", []Completion{OptionCompletion, OptionValueCompletion}},
-               "setlocal":   {"SetLocal", []Completion{OptionCompletion, OptionValueCompletion}},
-               "show":       {"Show", []Completion{OptionCompletion, NoCompletion}},
-               "showkey":    {"ShowKey", []Completion{NoCompletion}},
-               "bind":       {"Bind", []Completion{NoCompletion}},
-               "run":        {"Run", []Completion{NoCompletion}},
-               "quit":       {"Quit", []Completion{NoCompletion}},
-               "save":       {"Save", []Completion{NoCompletion}},
-               "replace":    {"Replace", []Completion{NoCompletion}},
-               "replaceall": {"ReplaceAll", []Completion{NoCompletion}},
-               "vsplit":     {"VSplit", []Completion{FileCompletion, NoCompletion}},
-               "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}},
-               "tabswitch":  {"TabSwitch", []Completion{NoCompletion}},
-               "term":       {"Term", []Completion{NoCompletion}},
-               "memusage":   {"MemUsage", []Completion{NoCompletion}},
-               "retab":      {"Retab", []Completion{NoCompletion}},
-               "raw":        {"Raw", []Completion{NoCompletion}},
-               "textfilter": {"TextFilter", []Completion{NoCompletion}},
-       }
-}
-
-// CommandEditAction returns a bindable function that opens a prompt with
-// the given string and executes the command when the user presses
-// enter
-func CommandEditAction(prompt string) func(*View, bool) bool {
-       return func(v *View, usePlugin bool) bool {
-               input, canceled := messenger.Prompt("> ", prompt, "Command", CommandCompletion)
-               if !canceled {
-                       HandleCommand(input)
-               }
-               return false
-       }
-}
-
-// CommandAction returns a bindable function which executes the
-// given command
-func CommandAction(cmd string) func(*View, bool) bool {
-       return func(v *View, usePlugin bool) bool {
-               HandleCommand(cmd)
-               return false
-       }
-}
-
-// 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([]byte(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")
-       }
-}
-
-// Retab changes all spaces to tabs or all tabs to spaces
-// depending on the user's settings
-func Retab(args []string) {
-       CurView().Retab(true)
-}
-
-// Raw opens a new raw view which displays the escape sequences micro
-// is receiving in real-time
-func Raw(args []string) {
-       buf := NewBufferFromString("", "Raw events")
-
-       view := NewView(buf)
-       view.Buf.Insert(view.Cursor.Loc, "Warning: Showing raw event escape codes\n")
-       view.Buf.Insert(view.Cursor.Loc, "Use CtrlQ to exit\n")
-       view.Type = vtRaw
-       tab := NewTabFromView(view)
-       tab.SetNum(len(tabs))
-       tabs = append(tabs, tab)
-       curTab = len(tabs) - 1
-       if len(tabs) == 2 {
-               for _, t := range tabs {
-                       for _, v := range t.Views {
-                               v.ToggleTabbar()
-                       }
-               }
-       }
-}
-
-// TabSwitch switches to a given tab either by name or by number
-func TabSwitch(args []string) {
-       if len(args) > 0 {
-               num, err := strconv.Atoi(args[0])
-               if err != nil {
-                       // Check for tab with this name
-
-                       found := false
-                       for _, t := range tabs {
-                               v := t.Views[t.CurView]
-                               if v.Buf.GetName() == args[0] {
-                                       curTab = v.TabNum
-                                       found = true
-                               }
-                       }
-                       if !found {
-                               messenger.Error("Could not find tab: ", err)
-                       }
-               } else {
-                       num--
-                       if num >= 0 && num < len(tabs) {
-                               curTab = num
-                       } else {
-                               messenger.Error("Invalid tab index")
-                       }
-               }
-       }
-}
-
-// Cd changes the current working directory
-func Cd(args []string) {
-       if len(args) > 0 {
-               path := ReplaceHome(args[0])
-               err := os.Chdir(path)
-               if err != nil {
-                       messenger.Error("Error with cd: ", err)
-                       return
-               }
-               wd, _ := os.Getwd()
-               for _, tab := range tabs {
-                       for _, view := range tab.Views {
-                               if len(view.Buf.name) == 0 {
-                                       continue
-                               }
-
-                               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
-                               }
-                       }
-               }
-       }
-}
-
-// MemUsage prints micro's memory usage
-// Alloc shows how many bytes are currently in use
-// Sys shows how many bytes have been requested from the operating system
-// NumGC shows how many times the GC has been run
-// Note that Go commonly reserves more memory from the OS than is currently in-use/required
-// Additionally, even if Go returns memory to the OS, the OS does not always claim it because
-// there may be plenty of memory to spare
-func MemUsage(args []string) {
-       var mem runtime.MemStats
-       runtime.ReadMemStats(&mem)
-
-       messenger.Message(fmt.Sprintf("Alloc: %v, Sys: %v, NumGC: %v", humanize.Bytes(mem.Alloc), humanize.Bytes(mem.Sys), mem.NumGC))
-}
-
-// Pwd prints the current working directory
-func Pwd(args []string) {
-       wd, err := os.Getwd()
-       if err != nil {
-               messenger.Message(err.Error())
-       } else {
-               messenger.Message(wd)
-       }
-}
-
-// Open opens a new buffer with a given filename
-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.
-               args, err := shellwords.Split(filename)
-               if err != nil {
-                       messenger.Error("Error parsing args ", err)
-                       return
-               }
-               filename = strings.Join(args, " ")
-
-               CurView().Open(filename)
-       } else {
-               messenger.Error("No filename")
-       }
-}
-
-// ToggleLog toggles the log view
-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)
-       }
-}
-
-// Reload reloads all files (syntax files, colorschemes...)
-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 {
-               // Open the default help if the user just typed "> help"
-               CurView().openHelp("help")
-       } else {
-               helpPage := args[0]
-               if FindRuntimeFile(RTHelp, helpPage) != nil {
-                       CurView().openHelp(helpPage)
-               } else {
-                       messenger.Error("Sorry, no help for ", helpPage)
-               }
-       }
-}
-
-// VSplit opens a vertical split with file given in the first argument
-// If no file is given, it opens an empty buffer in a new split
-func VSplit(args []string) {
-       if len(args) == 0 {
-               CurView().VSplit(NewBufferFromString("", ""))
-       } else {
-               buf, err := NewBufferFromFile(args[0])
-               if err != nil {
-                       messenger.Error(err)
-                       return
-               }
-               CurView().VSplit(buf)
-       }
-}
-
-// HSplit opens a horizontal split with file given in the first argument
-// If no file is given, it opens an empty buffer in a new split
-func HSplit(args []string) {
-       if len(args) == 0 {
-               CurView().HSplit(NewBufferFromString("", ""))
-       } else {
-               buf, err := NewBufferFromFile(args[0])
-               if err != nil {
-                       messenger.Error(err)
-                       return
-               }
-               CurView().HSplit(buf)
-       }
-}
-
-// 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 {
-               CurView().AddTab(true)
-       } else {
-               buf, err := NewBufferFromFile(args[0])
-               if err != nil {
-                       messenger.Error(err)
-                       return
-               }
-
-               tab := NewTabFromView(NewView(buf))
-               tab.SetNum(len(tabs))
-               tabs = append(tabs, tab)
-               curTab = len(tabs) - 1
-               if len(tabs) == 2 {
-                       for _, t := range tabs {
-                               for _, v := range t.Views {
-                                       v.ToggleTabbar()
-                               }
-                       }
-               }
-       }
-}
-
-// Set sets an option
-func Set(args []string) {
-       if len(args) < 2 {
-               messenger.Error("Not enough arguments")
-               return
-       }
-
-       option := args[0]
-       value := args[1]
-
-       SetOptionAndSettings(option, value)
-}
-
-// SetLocal sets an option local to the buffer
-func SetLocal(args []string) {
-       if len(args) < 2 {
-               messenger.Error("Not enough arguments")
-               return
-       }
-
-       option := args[0]
-       value := args[1]
-
-       err := SetLocalOption(option, value, CurView())
-       if err != nil {
-               messenger.Error(err.Error())
-       }
-}
-
-// Show shows the value of the given option
-func Show(args []string) {
-       if len(args) < 1 {
-               messenger.Error("Please provide an option to show")
-               return
-       }
-
-       option := GetOption(args[0])
-
-       if option == nil {
-               messenger.Error(args[0], " is not a valid option")
-               return
-       }
-
-       messenger.Message(option)
-}
-
-// ShowKey displays the action that a key is bound to
-func ShowKey(args []string) {
-       if len(args) < 1 {
-               messenger.Error("Please provide a key to show")
-               return
-       }
-
-       if action, ok := bindingsStr[args[0]]; ok {
-               messenger.Message(action)
-       } else {
-               messenger.Message(args[0], " has no binding")
-       }
-}
-
-// Bind creates a new keybinding
-func Bind(args []string) {
-       if len(args) < 2 {
-               messenger.Error("Not enough arguments")
-               return
-       }
-       BindKey(args[0], args[1])
-}
-
-// Run runs a shell command in the background
-func Run(args []string) {
-       // Run a shell command in the background (openTerm is false)
-       HandleShellCommand(shellwords.Join(args...), false, true)
-}
-
-// Quit closes the main view
-func Quit(args []string) {
-       // Close the main view
-       CurView().Quit(true)
-}
-
-// 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])
-       }
-}
-
-// Replace runs search and replace
-func Replace(args []string) {
-       if len(args) < 2 || len(args) > 4 {
-               // We need to find both a search and replace expression
-               messenger.Error("Invalid replace statement: " + strings.Join(args, " "))
-               return
-       }
-
-       all := false
-       noRegex := false
-
-       if len(args) > 2 {
-               for _, arg := range args[2:] {
-                       switch arg {
-                       case "-a":
-                               all = true
-                       case "-l":
-                               noRegex = true
-                       default:
-                               messenger.Error("Invalid flag: " + arg)
-                               return
-                       }
-               }
-       }
-
-       search := string(args[0])
-
-       if noRegex {
-               search = regexp.QuoteMeta(search)
-       }
-
-       replace := string(args[1])
-       replaceBytes := []byte(replace)
-
-       regex, err := regexp.Compile("(?m)" + search)
-       if err != nil {
-               // There was an error with the user's regex
-               messenger.Error(err.Error())
-               return
-       }
-
-       view := CurView()
-
-       found := 0
-       replaceAll := func() {
-               var deltas []Delta
-               for i := 0; i < view.Buf.LinesNum(); i++ {
-                       newText := regex.ReplaceAllFunc(view.Buf.lines[i].data, func(in []byte) []byte {
-                               found++
-                               return replaceBytes
-                       })
-
-                       from := Loc{0, i}
-                       to := Loc{utf8.RuneCount(view.Buf.lines[i].data), i}
-
-                       deltas = append(deltas, Delta{string(newText), from, to})
-               }
-               view.Buf.MultipleReplace(deltas)
-       }
-
-       if all {
-               replaceAll()
-       } else {
-               for {
-                       // The 'check' flag was used
-                       Search(search, view, true)
-                       if !view.Cursor.HasSelection() {
-                               break
-                       }
-                       view.Relocate()
-                       RedrawAll()
-                       choice, canceled := messenger.LetterPrompt("Perform replacement? (y,n,a)", 'y', 'n', 'a')
-                       if canceled {
-                               if view.Cursor.HasSelection() {
-                                       view.Cursor.Loc = view.Cursor.CurSelection[0]
-                                       view.Cursor.ResetSelection()
-                               }
-                               messenger.Reset()
-                               break
-                       } else if choice == 'a' {
-                               if view.Cursor.HasSelection() {
-                                       view.Cursor.Loc = view.Cursor.CurSelection[0]
-                                       view.Cursor.ResetSelection()
-                               }
-                               messenger.Reset()
-                               replaceAll()
-                               break
-                       } else if choice == 'y' {
-                               view.Cursor.DeleteSelection()
-                               view.Buf.Insert(view.Cursor.Loc, replace)
-                               view.Cursor.ResetSelection()
-                               messenger.Reset()
-                               found++
-                       }
-                       if view.Cursor.HasSelection() {
-                               searchStart = view.Cursor.CurSelection[1]
-                       } else {
-                               searchStart = view.Cursor.Loc
-                       }
-               }
-       }
-       view.Cursor.Relocate()
-
-       if found > 1 {
-               messenger.Message("Replaced ", found, " occurrences of ", search)
-       } else if found == 1 {
-               messenger.Message("Replaced ", found, " occurrence of ", search)
-       } else {
-               messenger.Message("Nothing matched ", search)
-       }
-}
-
-// ReplaceAll replaces search term all at once
-func ReplaceAll(args []string) {
-       // aliased to Replace command
-       Replace(append(args, "-a"))
-}
-
-// Term opens a terminal in the current view
-func Term(args []string) {
-       var err error
-       if len(args) == 0 {
-               err = CurView().StartTerminal([]string{os.Getenv("SHELL"), "-i"}, true, false, "")
-       } else {
-               err = CurView().StartTerminal(args, true, false, "")
-       }
-       if err != nil {
-               messenger.Error(err)
-       }
-}
-
-// HandleCommand handles input from the user
-func HandleCommand(input string) {
-       args, err := shellwords.Split(input)
-       if err != nil {
-               messenger.Error("Error parsing args ", err)
-               return
-       }
-
-       inputCmd := args[0]
-
-       if _, ok := commands[inputCmd]; !ok {
-               messenger.Error("Unknown command ", inputCmd)
-       } else {
-               commands[inputCmd].action(args[1:])
-       }
-}
index 15dbb1b360bdf3da7903c18a6b0edf1ab8c63e76..9e9f1d8192447f73979f52d870364a010eac196e 100644 (file)
@@ -1,15 +1,23 @@
 package main
 
 import (
+       "unicode/utf8"
+
+       runewidth "github.com/mattn/go-runewidth"
        "github.com/zyedidia/clipboard"
 )
 
-// The Cursor struct stores the location of the cursor in the view
-// The complicated part about the cursor is storing its location.
-// The cursor must be displayed at an x, y location, but since the buffer
-// uses a rope to store text, to insert text we must have an index. It
-// is also simpler to use character indicies for other tasks such as
-// selection.
+// 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 >= len(buf.lines) || pos.X < 0 || pos.X > utf8.RuneCount(buf.LineBytes(pos.Y)) {
+               return false
+       }
+
+       return true
+}
+
+// The Cursor struct stores the location of the cursor in the buffer
+// as well as the selection
 type Cursor struct {
        buf *Buffer
        Loc
@@ -42,12 +50,72 @@ func (c *Cursor) GotoLoc(l Loc) {
        c.LastVisualX = c.GetVisualX()
 }
 
+// GetVisualX returns the x value of the cursor in visual spaces
+func (c *Cursor) GetVisualX() int {
+       if c.X <= 0 {
+               c.X = 0
+               return 0
+       }
+
+       bytes := c.buf.LineBytes(c.Y)
+       tabsize := int(c.buf.Settings["tabsize"].(float64))
+       if c.X > utf8.RuneCount(bytes) {
+               c.X = utf8.RuneCount(bytes) - 1
+       }
+
+       return StringWidth(bytes, c.X, tabsize)
+}
+
+// GetCharPosInLine gets the char position of a visual x y
+// coordinate (this is necessary because tabs are 1 char but
+// 4 visual spaces)
+func (c *Cursor) GetCharPosInLine(b []byte, visualPos int) int {
+       tabsize := int(c.buf.Settings["tabsize"].(float64))
+
+       // Scan rune by rune until we exceed the visual width that we are
+       // looking for. Then we can return the character position we have found
+       i := 0     // char pos
+       width := 0 // string visual width
+       for len(b) > 0 {
+               r, size := utf8.DecodeRune(b)
+               b = b[size:]
+
+               switch r {
+               case '\t':
+                       ts := tabsize - (width % tabsize)
+                       width += ts
+               default:
+                       width += runewidth.RuneWidth(r)
+               }
+
+               i++
+
+               if width >= visualPos {
+                       break
+               }
+       }
+
+       return i
+}
+
+// Start moves the cursor to the start of the line it is on
+func (c *Cursor) Start() {
+       c.X = 0
+       c.LastVisualX = c.GetVisualX()
+}
+
+// End moves the cursor to the end of the line it is on
+func (c *Cursor) End() {
+       c.X = utf8.RuneCount(c.buf.LineBytes(c.Y))
+       c.LastVisualX = c.GetVisualX()
+}
+
 // 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)
+                       clipboard.WriteAll(string(c.GetSelection()), target)
                }
        }
 }
@@ -87,14 +155,14 @@ func (c *Cursor) DeleteSelection() {
 }
 
 // GetSelection returns the cursor's selection
-func (c *Cursor) GetSelection() string {
+func (c *Cursor) GetSelection() []byte {
        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])
        }
-       return ""
+       return []byte{}
 }
 
 // SelectLine selects the current line
@@ -102,7 +170,7 @@ func (c *Cursor) SelectLine() {
        c.Start()
        c.SetSelectionStart(c.Loc)
        c.End()
-       if c.buf.NumLines-1 > c.Y {
+       if len(c.buf.lines)-1 > c.Y {
                c.SetSelectionEnd(c.Loc.Move(1, c.buf))
        } else {
                c.SetSelectionEnd(c.Loc)
@@ -129,146 +197,20 @@ func (c *Cursor) AddLineToSelection() {
        }
 }
 
-// SelectWord selects the word the cursor is currently on
-func (c *Cursor) SelectWord() {
-       if len(c.buf.Line(c.Y)) == 0 {
-               return
-       }
-
-       if !IsWordChar(string(c.RuneUnder(c.X))) {
-               c.SetSelectionStart(c.Loc)
-               c.SetSelectionEnd(c.Loc.Move(1, c.buf))
-               c.OrigSelection = c.CurSelection
-               return
-       }
-
-       forward, backward := c.X, c.X
-
-       for backward > 0 && IsWordChar(string(c.RuneUnder(backward-1))) {
-               backward--
-       }
-
-       c.SetSelectionStart(Loc{backward, c.Y})
-       c.OrigSelection[0] = c.CurSelection[0]
-
-       for forward < Count(c.buf.Line(c.Y))-1 && IsWordChar(string(c.RuneUnder(forward+1))) {
-               forward++
-       }
-
-       c.SetSelectionEnd(Loc{forward, c.Y}.Move(1, c.buf))
-       c.OrigSelection[1] = c.CurSelection[1]
-       c.Loc = c.CurSelection[1]
-}
-
-// AddWordToSelection adds the word the cursor is currently on
-// to the selection
-func (c *Cursor) AddWordToSelection() {
-       if c.Loc.GreaterThan(c.OrigSelection[0]) && c.Loc.LessThan(c.OrigSelection[1]) {
-               c.CurSelection = c.OrigSelection
-               return
-       }
-
-       if c.Loc.LessThan(c.OrigSelection[0]) {
-               backward := c.X
-
-               for backward > 0 && IsWordChar(string(c.RuneUnder(backward-1))) {
-                       backward--
-               }
-
-               c.SetSelectionStart(Loc{backward, c.Y})
-               c.SetSelectionEnd(c.OrigSelection[1])
-       }
-
-       if c.Loc.GreaterThan(c.OrigSelection[1]) {
-               forward := c.X
-
-               for forward < Count(c.buf.Line(c.Y))-1 && IsWordChar(string(c.RuneUnder(forward+1))) {
-                       forward++
-               }
-
-               c.SetSelectionEnd(Loc{forward, c.Y}.Move(1, c.buf))
-               c.SetSelectionStart(c.OrigSelection[0])
-       }
-
-       c.Loc = c.CurSelection[1]
-}
-
-// SelectTo selects from the current cursor location to the given
-// location
-func (c *Cursor) SelectTo(loc Loc) {
-       if loc.GreaterThan(c.OrigSelection[0]) {
-               c.SetSelectionStart(c.OrigSelection[0])
-               c.SetSelectionEnd(loc)
-       } else {
-               c.SetSelectionStart(loc)
-               c.SetSelectionEnd(c.OrigSelection[0])
-       }
-}
-
-// WordRight moves the cursor one word to the right
-func (c *Cursor) WordRight() {
-       for IsWhitespace(c.RuneUnder(c.X)) {
-               if c.X == Count(c.buf.Line(c.Y)) {
-                       c.Right()
-                       return
-               }
-               c.Right()
-       }
-       c.Right()
-       for IsWordChar(string(c.RuneUnder(c.X))) {
-               if c.X == Count(c.buf.Line(c.Y)) {
-                       return
-               }
-               c.Right()
-       }
-}
-
-// WordLeft moves the cursor one word to the left
-func (c *Cursor) WordLeft() {
-       c.Left()
-       for IsWhitespace(c.RuneUnder(c.X)) {
-               if c.X == 0 {
-                       return
-               }
-               c.Left()
-       }
-       c.Left()
-       for IsWordChar(string(c.RuneUnder(c.X))) {
-               if c.X == 0 {
-                       return
-               }
-               c.Left()
-       }
-       c.Right()
-}
-
-// RuneUnder returns the rune under the given x position
-func (c *Cursor) RuneUnder(x int) rune {
-       line := []rune(c.buf.Line(c.Y))
-       if len(line) == 0 {
-               return '\n'
-       }
-       if x >= len(line) {
-               return '\n'
-       } else if x < 0 {
-               x = 0
-       }
-       return line[x]
-}
 // UpN moves the cursor up N lines (if possible)
 func (c *Cursor) UpN(amount int) {
        proposedY := c.Y - amount
        if proposedY < 0 {
                proposedY = 0
-               c.LastVisualX = 0
-       } else if proposedY >= c.buf.NumLines {
-               proposedY = c.buf.NumLines - 1
+       } else if proposedY >= len(c.buf.lines) {
+               proposedY = len(c.buf.lines) - 1
        }
 
-       runes := []rune(c.buf.Line(proposedY))
-       c.X = c.GetCharPosInLine(proposedY, c.LastVisualX)
-       if c.X > len(runes) || (amount < 0 && proposedY == c.Y) {
-               c.X = len(runes)
+       bytes := c.buf.LineBytes(proposedY)
+       c.X = c.GetCharPosInLine(bytes, c.LastVisualX)
+
+       if c.X > utf8.RuneCount(bytes) || (amount < 0 && proposedY == c.Y) {
+               c.X = utf8.RuneCount(bytes)
        }
 
        c.Y = proposedY
@@ -310,7 +252,7 @@ func (c *Cursor) Right() {
        if c.Loc == c.buf.End() {
                return
        }
-       if c.X < Count(c.buf.Line(c.Y)) {
+       if c.X < utf8.RuneCount(c.buf.LineBytes(c.Y)) {
                c.X++
        } else {
                c.Down()
@@ -319,80 +261,19 @@ func (c *Cursor) Right() {
        c.LastVisualX = c.GetVisualX()
 }
 
-// End moves the cursor to the end of the line it is on
-func (c *Cursor) End() {
-       c.X = Count(c.buf.Line(c.Y))
-       c.LastVisualX = c.GetVisualX()
-}
-
-// Start moves the cursor to the start of the line it is on
-func (c *Cursor) Start() {
-       c.X = 0
-       c.LastVisualX = c.GetVisualX()
-}
-
-// StartOfText moves the cursor to the first non-whitespace rune of
-// the line it is on
-func (c *Cursor) StartOfText() {
-       c.Start()
-       for IsWhitespace(c.RuneUnder(c.X)) {
-               if c.X == Count(c.buf.Line(c.Y)) {
-                       break
-               }
-               c.Right()
-       }
-}
-
-// GetCharPosInLine gets the char position of a visual x y
-// coordinate (this is necessary because tabs are 1 char but
-// 4 visual spaces)
-func (c *Cursor) GetCharPosInLine(lineNum, visualPos int) int {
-       // Get the tab size
-       tabSize := int(c.buf.Settings["tabsize"].(float64))
-       visualLineLen := StringWidth(c.buf.Line(lineNum), tabSize)
-       if visualPos > visualLineLen {
-               visualPos = visualLineLen
-       }
-       width := WidthOfLargeRunes(c.buf.Line(lineNum), tabSize)
-       if visualPos >= width {
-               return visualPos - width
-       }
-       return visualPos / tabSize
-}
-
-// GetVisualX returns the x value of the cursor in visual spaces
-func (c *Cursor) GetVisualX() int {
-       runes := []rune(c.buf.Line(c.Y))
-       tabSize := int(c.buf.Settings["tabsize"].(float64))
-       if c.X > len(runes) {
-               c.X = len(runes) - 1
-       }
-
-       if c.X < 0 {
-               c.X = 0
-       }
-
-       return StringWidth(string(runes[:c.X]), tabSize)
-}
-
-// StoreVisualX stores the current visual x value in the cursor
-func (c *Cursor) StoreVisualX() {
-       c.LastVisualX = c.GetVisualX()
-}
-
 // Relocate makes sure that the cursor is inside the bounds
 // of the buffer If it isn't, it moves it to be within the
 // buffer's lines
 func (c *Cursor) Relocate() {
        if c.Y < 0 {
                c.Y = 0
-       } else if c.Y >= c.buf.NumLines {
-               c.Y = c.buf.NumLines - 1
+       } else if c.Y >= len(c.buf.lines) {
+               c.Y = len(c.buf.lines) - 1
        }
 
        if c.X < 0 {
                c.X = 0
-       } else if c.X > Count(c.buf.Line(c.Y)) {
-               c.X = Count(c.buf.Line(c.Y))
+       } else if c.X > utf8.RuneCount(c.buf.LineBytes(c.Y)) {
+               c.X = utf8.RuneCount(c.buf.LineBytes(c.Y))
        }
 }
diff --git a/cmd/micro/cursor_test.go b/cmd/micro/cursor_test.go
new file mode 100644 (file)
index 0000000..06ab7d0
--- /dev/null
@@ -0,0 +1 @@
+package main
diff --git a/cmd/micro/debug.go b/cmd/micro/debug.go
new file mode 100644 (file)
index 0000000..494b0e9
--- /dev/null
@@ -0,0 +1,27 @@
+package main
+
+import (
+       "log"
+       "os"
+)
+
+type NullWriter struct{}
+
+func (NullWriter) Write(data []byte) (n int, err error) {
+       return 0, nil
+}
+
+func InitLog() {
+       if Debug == "ON" {
+               f, err := os.OpenFile("log.txt", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
+               if err != nil {
+                       log.Fatalf("error opening file: %v", err)
+               }
+
+               log.SetOutput(f)
+               log.Println("Micro started")
+       } else {
+               log.SetOutput(NullWriter{})
+               log.Println("Micro started")
+       }
+}
index adf7cba4a3cbfd4c9973d0adaa22b77093f2c504..5eefa2afa17191835e58927fff70a6ac1f14a5dd 100644 (file)
@@ -1,11 +1,10 @@
 package main
 
 import (
-       "strings"
        "time"
+       "unicode/utf8"
 
        dmp "github.com/sergi/go-diff/diffmatchpatch"
-       "github.com/yuin/gopher-lua"
 )
 
 const (
@@ -30,7 +29,7 @@ type TextEvent struct {
 
 // A Delta is a change to the buffer
 type Delta struct {
-       Text  string
+       Text  []byte
        Start Loc
        End   Loc
 }
@@ -39,7 +38,7 @@ type Delta struct {
 func ExecuteTextEvent(t *TextEvent, buf *Buffer) {
        if t.EventType == TextEventInsert {
                for _, d := range t.Deltas {
-                       buf.insert(d.Start, []byte(d.Text))
+                       buf.insert(d.Start, d.Text)
                }
        } else if t.EventType == TextEventRemove {
                for i, d := range t.Deltas {
@@ -48,9 +47,9 @@ func ExecuteTextEvent(t *TextEvent, buf *Buffer) {
        } else if t.EventType == TextEventReplace {
                for i, d := range t.Deltas {
                        t.Deltas[i].Text = buf.remove(d.Start, d.End)
-                       buf.insert(d.Start, []byte(d.Text))
+                       buf.insert(d.Start, d.Text)
                        t.Deltas[i].Start = d.Start
-                       t.Deltas[i].End = Loc{d.Start.X + Count(d.Text), d.Start.Y}
+                       t.Deltas[i].End = Loc{d.Start.X + utf8.RuneCount(d.Text), d.Start.Y}
                }
                for i, j := 0, len(t.Deltas)-1; i < j; i, j = i+1, j-1 {
                        t.Deltas[i], t.Deltas[j] = t.Deltas[j], t.Deltas[i]
@@ -67,15 +66,15 @@ func UndoTextEvent(t *TextEvent, buf *Buffer) {
 // EventHandler executes text manipulations and allows undoing and redoing
 type EventHandler struct {
        buf       *Buffer
-       UndoStack *Stack
-       RedoStack *Stack
+       UndoStack *TEStack
+       RedoStack *TEStack
 }
 
 // NewEventHandler returns a new EventHandler
 func NewEventHandler(buf *Buffer) *EventHandler {
        eh := new(EventHandler)
-       eh.UndoStack = new(Stack)
-       eh.RedoStack = new(Stack)
+       eh.UndoStack = new(TEStack)
+       eh.RedoStack = new(TEStack)
        eh.buf = buf
        return eh
 }
@@ -86,38 +85,39 @@ func NewEventHandler(buf *Buffer) *EventHandler {
 // through insert and delete events
 func (eh *EventHandler) ApplyDiff(new string) {
        differ := dmp.New()
-       diff := differ.DiffMain(eh.buf.String(), new, false)
+       diff := differ.DiffMain(string(eh.buf.Bytes()), new, false)
        loc := eh.buf.Start()
        for _, d := range diff {
                if d.Type == dmp.DiffDelete {
-                       eh.Remove(loc, loc.Move(Count(d.Text), eh.buf))
+                       eh.Remove(loc, loc.Move(utf8.RuneCountInString(d.Text), eh.buf))
                } else {
                        if d.Type == dmp.DiffInsert {
                                eh.Insert(loc, d.Text)
                        }
-                       loc = loc.Move(Count(d.Text), eh.buf)
+                       loc = loc.Move(utf8.RuneCountInString(d.Text), eh.buf)
                }
        }
 }
 
 // Insert creates an insert text event and executes it
-func (eh *EventHandler) Insert(start Loc, text string) {
+func (eh *EventHandler) Insert(start Loc, textStr string) {
+       text := []byte(textStr)
        e := &TextEvent{
-               C:         *eh.buf.cursors[eh.buf.curCursor],
+               C:         *eh.buf.GetActiveCursor(),
                EventType: TextEventInsert,
                Deltas:    []Delta{{text, start, Loc{0, 0}}},
                Time:      time.Now(),
        }
        eh.Execute(e)
-       e.Deltas[0].End = start.Move(Count(text), eh.buf)
+       e.Deltas[0].End = start.Move(utf8.RuneCount(text), eh.buf)
        end := e.Deltas[0].End
 
-       for _, c := range eh.buf.cursors {
+       for _, c := range eh.buf.GetCursors() {
                move := func(loc Loc) Loc {
                        if start.Y != end.Y && loc.GreaterThan(start) {
                                loc.Y += end.Y - start.Y
                        } else if loc.Y == start.Y && loc.GreaterEqual(start) {
-                               loc = loc.Move(Count(text), eh.buf)
+                               loc = loc.Move(utf8.RuneCount(text), eh.buf)
                        }
                        return loc
                }
@@ -133,14 +133,14 @@ func (eh *EventHandler) Insert(start Loc, text string) {
 // Remove creates a remove text event and executes it
 func (eh *EventHandler) Remove(start, end Loc) {
        e := &TextEvent{
-               C:         *eh.buf.cursors[eh.buf.curCursor],
+               C:         *eh.buf.GetActiveCursor(),
                EventType: TextEventRemove,
-               Deltas:    []Delta{{"", start, end}},
+               Deltas:    []Delta{{[]byte{}, start, end}},
                Time:      time.Now(),
        }
        eh.Execute(e)
 
-       for _, c := range eh.buf.cursors {
+       for _, c := range eh.buf.GetCursors() {
                move := func(loc Loc) Loc {
                        if start.Y != end.Y && loc.GreaterThan(end) {
                                loc.Y -= end.Y - start.Y
@@ -161,7 +161,7 @@ func (eh *EventHandler) Remove(start, end Loc) {
 // MultipleReplace creates an multiple insertions executes them
 func (eh *EventHandler) MultipleReplace(deltas []Delta) {
        e := &TextEvent{
-               C:         *eh.buf.cursors[eh.buf.curCursor],
+               C:         *eh.buf.GetActiveCursor(),
                EventType: TextEventReplace,
                Deltas:    deltas,
                Time:      time.Now(),
@@ -178,19 +178,20 @@ func (eh *EventHandler) Replace(start, end Loc, replace string) {
 // Execute a textevent and add it to the undo stack
 func (eh *EventHandler) Execute(t *TextEvent) {
        if eh.RedoStack.Len() > 0 {
-               eh.RedoStack = new(Stack)
+               eh.RedoStack = new(TEStack)
        }
        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
-               }
-       }
+       // TODO: Call plugins on text events
+       // for pl := range loadedPlugins {
+       //      ret, err := Call(pl+".onBeforeTextEvent", t)
+       //      if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") {
+       //              TermMessage(err)
+       //      }
+       //      if val, ok := ret.(lua.LBool); ok && val == lua.LFalse {
+       //              return
+       //      }
+       // }
 
        ExecuteTextEvent(t, eh.buf)
 }
@@ -236,9 +237,9 @@ func (eh *EventHandler) UndoOneEvent() {
 
        // Set the cursor in the right place
        teCursor := t.C
-       if teCursor.Num >= 0 && teCursor.Num < len(eh.buf.cursors) {
-               t.C = *eh.buf.cursors[teCursor.Num]
-               eh.buf.cursors[teCursor.Num].Goto(teCursor)
+       if teCursor.Num >= 0 && teCursor.Num < eh.buf.NumCursors() {
+               t.C = *eh.buf.GetCursor(teCursor.Num)
+               eh.buf.GetCursor(teCursor.Num).Goto(teCursor)
        } else {
                teCursor.Num = -1
        }
@@ -283,9 +284,9 @@ func (eh *EventHandler) RedoOneEvent() {
        UndoTextEvent(t, eh.buf)
 
        teCursor := t.C
-       if teCursor.Num >= 0 && teCursor.Num < len(eh.buf.cursors) {
-               t.C = *eh.buf.cursors[teCursor.Num]
-               eh.buf.cursors[teCursor.Num].Goto(teCursor)
+       if teCursor.Num >= 0 && teCursor.Num < eh.buf.NumCursors() {
+               t.C = *eh.buf.GetCursor(teCursor.Num)
+               eh.buf.GetCursor(teCursor.Num).Goto(teCursor)
        } else {
                teCursor.Num = -1
        }
diff --git a/cmd/micro/highlighter.go b/cmd/micro/highlighter.go
deleted file mode 100644 (file)
index 53a4655..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
-package main
-
-import "github.com/zyedidia/micro/cmd/micro/highlight"
-
-var syntaxFiles []*highlight.File
-
-func LoadSyntaxFiles() {
-       InitColorscheme()
-       for _, f := range ListRuntimeFiles(RTSyntax) {
-               data, err := f.Data()
-               if err != nil {
-                       TermMessage("Error loading syntax file " + f.Name() + ": " + err.Error())
-               } else {
-                       LoadSyntaxFile(data, f.Name())
-               }
-       }
-}
-
-func LoadSyntaxFile(text []byte, filename string) {
-       f, err := highlight.ParseFile(text)
-
-       if err != nil {
-               TermMessage("Syntax file error: " + filename + ": " + err.Error())
-               return
-       }
-
-       syntaxFiles = append(syntaxFiles, f)
-}
diff --git a/cmd/micro/job.go b/cmd/micro/job.go
deleted file mode 100644 (file)
index df9151b..0000000
+++ /dev/null
@@ -1,88 +0,0 @@
-package main
-
-import (
-       "bytes"
-       "io"
-       "os/exec"
-)
-
-// Jobs are the way plugins can run processes in the background
-// A job is simply a process that gets executed asynchronously
-// There are callbacks for when the job exits, when the job creates stdout
-// and when the job creates stderr
-
-// These jobs run in a separate goroutine but the lua callbacks need to be
-// executed in the main thread (where the Lua VM is running) so they are
-// put into the jobs channel which gets read by the main loop
-
-// JobFunction is a representation of a job (this data structure is what is loaded
-// into the jobs channel)
-type JobFunction struct {
-       function func(string, ...string)
-       output   string
-       args     []string
-}
-
-// A CallbackFile is the data structure that makes it possible to catch stderr and stdout write events
-type CallbackFile struct {
-       io.Writer
-
-       callback func(string, ...string)
-       args     []string
-}
-
-func (f *CallbackFile) Write(data []byte) (int, error) {
-       // This is either stderr or stdout
-       // In either case we create a new job function callback and put it in the jobs channel
-       jobFunc := JobFunction{f.callback, string(data), f.args}
-       jobs <- jobFunc
-       return f.Writer.Write(data)
-}
-
-// JobStart starts a shell command in the background with the given callbacks
-// It returns an *exec.Cmd as the job id
-func JobStart(cmd string, onStdout, onStderr, onExit string, userargs ...string) *exec.Cmd {
-       return JobSpawn("sh", []string{"-c", cmd}, onStdout, onStderr, onExit, userargs...)
-}
-
-// JobSpawn starts a process with args in the background with the given callbacks
-// It returns an *exec.Cmd as the job id
-func JobSpawn(cmdName string, cmdArgs []string, onStdout, onStderr, onExit string, userargs ...string) *exec.Cmd {
-       // Set up everything correctly if the functions have been provided
-       proc := exec.Command(cmdName, cmdArgs...)
-       var outbuf bytes.Buffer
-       if onStdout != "" {
-               proc.Stdout = &CallbackFile{&outbuf, LuaFunctionJob(onStdout), userargs}
-       } else {
-               proc.Stdout = &outbuf
-       }
-       if onStderr != "" {
-               proc.Stderr = &CallbackFile{&outbuf, LuaFunctionJob(onStderr), userargs}
-       } else {
-               proc.Stderr = &outbuf
-       }
-
-       go func() {
-               // Run the process in the background and create the onExit callback
-               proc.Run()
-               jobFunc := JobFunction{LuaFunctionJob(onExit), string(outbuf.Bytes()), userargs}
-               jobs <- jobFunc
-       }()
-
-       return proc
-}
-
-// JobStop kills a job
-func JobStop(cmd *exec.Cmd) {
-       cmd.Process.Kill()
-}
-
-// JobSend sends the given data into the job's stdin stream
-func JobSend(cmd *exec.Cmd, data string) {
-       stdin, err := cmd.StdinPipe()
-       if err != nil {
-               return
-       }
-
-       stdin.Write([]byte(data))
-}
diff --git a/cmd/micro/keymenu.go b/cmd/micro/keymenu.go
deleted file mode 100644 (file)
index 737cc36..0000000
+++ /dev/null
@@ -1,20 +0,0 @@
-package main
-
-// DisplayKeyMenu displays the nano-style key menu at the bottom of the screen
-func DisplayKeyMenu() {
-       w, h := screen.Size()
-
-       bot := h - 3
-
-       display := []string{"^Q Quit, ^S Save, ^O Open, ^G Help, ^E Command Bar, ^K Cut Line", "^F Find, ^Z Undo, ^Y Redo, ^A Select All, ^D Duplicate Line, ^T New Tab"}
-
-       for y := 0; y < len(display); y++ {
-               for x := 0; x < w; x++ {
-                       if x < len(display[y]) {
-                               screen.SetContent(x, bot+y, rune(display[y][x]), nil, defStyle)
-                       } else {
-                               screen.SetContent(x, bot+y, ' ', nil, defStyle)
-                       }
-               }
-       }
-}
diff --git a/cmd/micro/lineArray.go b/cmd/micro/lineArray.go
deleted file mode 100644 (file)
index 2486037..0000000
+++ /dev/null
@@ -1,267 +0,0 @@
-package main
-
-import (
-       "bufio"
-       "io"
-       "unicode/utf8"
-
-       "github.com/zyedidia/micro/cmd/micro/highlight"
-)
-
-func runeToByteIndex(n int, txt []byte) int {
-       if n == 0 {
-               return 0
-       }
-
-       count := 0
-       i := 0
-       for len(txt) > 0 {
-               _, size := utf8.DecodeRune(txt)
-
-               txt = txt[size:]
-               count += size
-               i++
-
-               if i == n {
-                       break
-               }
-       }
-       return count
-}
-
-// A Line contains the data in bytes as well as a highlight state, match
-// and a flag for whether the highlighting needs to be updated
-type Line struct {
-       data []byte
-
-       state       highlight.State
-       match       highlight.LineMatch
-       rehighlight bool
-}
-
-// A LineArray simply stores and array of lines and makes it easy to insert
-// and delete in it
-type LineArray struct {
-       lines []Line
-}
-
-// Append efficiently appends lines together
-// It allocates an additional 10000 lines if the original estimate
-// is incorrect
-func Append(slice []Line, data ...Line) []Line {
-       l := len(slice)
-       if l+len(data) > cap(slice) { // reallocate
-               newSlice := make([]Line, (l+len(data))+10000)
-               // The copy function is predeclared and works for any slice type.
-               copy(newSlice, slice)
-               slice = newSlice
-       }
-       slice = slice[0 : l+len(data)]
-       for i, c := range data {
-               slice[l+i] = c
-       }
-       return slice
-}
-
-// NewLineArray returns a new line array from an array of bytes
-func NewLineArray(size int64, reader io.Reader) *LineArray {
-       la := new(LineArray)
-
-       la.lines = make([]Line, 0, 1000)
-
-       br := bufio.NewReader(reader)
-       var loaded int
-
-       n := 0
-       for {
-               data, err := br.ReadBytes('\n')
-               if len(data) > 1 && data[len(data)-2] == '\r' {
-                       data = append(data[:len(data)-2], '\n')
-                       if fileformat == 0 {
-                               fileformat = 2
-                       }
-               } else if len(data) > 0 {
-                       if fileformat == 0 {
-                               fileformat = 1
-                       }
-               }
-
-               if n >= 1000 && loaded >= 0 {
-                       totalLinesNum := int(float64(size) * (float64(n) / float64(loaded)))
-                       newSlice := make([]Line, len(la.lines), totalLinesNum+10000)
-                       copy(newSlice, la.lines)
-                       la.lines = newSlice
-                       loaded = -1
-               }
-
-               if loaded >= 0 {
-                       loaded += len(data)
-               }
-
-               if err != nil {
-                       if err == io.EOF {
-                               la.lines = Append(la.lines, Line{data[:], nil, nil, false})
-                               // la.lines = Append(la.lines, Line{data[:len(data)]})
-                       }
-                       // Last line was read
-                       break
-               } else {
-                       // la.lines = Append(la.lines, Line{data[:len(data)-1]})
-                       la.lines = Append(la.lines, Line{data[:len(data)-1], nil, nil, false})
-               }
-               n++
-       }
-
-       return la
-}
-
-// Returns the String representation of the LineArray
-func (la *LineArray) String() string {
-       str := ""
-       for i, l := range la.lines {
-               str += string(l.data)
-               if i != len(la.lines)-1 {
-                       str += "\n"
-               }
-       }
-       return str
-}
-
-// SaveString returns the string that should be written to disk when
-// the line array is saved
-// It is the same as string but uses crlf or lf line endings depending
-func (la *LineArray) SaveString(useCrlf bool) string {
-       str := ""
-       for i, l := range la.lines {
-               str += string(l.data)
-               if i != len(la.lines)-1 {
-                       if useCrlf {
-                               str += "\r"
-                       }
-                       str += "\n"
-               }
-       }
-       return str
-}
-
-// NewlineBelow adds a newline below the given line number
-func (la *LineArray) NewlineBelow(y int) {
-       la.lines = append(la.lines, Line{[]byte{' '}, nil, nil, false})
-       copy(la.lines[y+2:], la.lines[y+1:])
-       la.lines[y+1] = Line{[]byte{}, la.lines[y].state, nil, false}
-}
-
-// inserts a byte array at a given location
-func (la *LineArray) insert(pos Loc, value []byte) {
-       x, y := runeToByteIndex(pos.X, la.lines[pos.Y].data), pos.Y
-       // x, y := pos.x, pos.y
-       for i := 0; i < len(value); i++ {
-               if value[i] == '\n' {
-                       la.Split(Loc{x, y})
-                       x = 0
-                       y++
-                       continue
-               }
-               la.insertByte(Loc{x, y}, value[i])
-               x++
-       }
-}
-
-// inserts a byte at a given location
-func (la *LineArray) insertByte(pos Loc, value byte) {
-       la.lines[pos.Y].data = append(la.lines[pos.Y].data, 0)
-       copy(la.lines[pos.Y].data[pos.X+1:], la.lines[pos.Y].data[pos.X:])
-       la.lines[pos.Y].data[pos.X] = value
-}
-
-// JoinLines joins the two lines a and b
-func (la *LineArray) JoinLines(a, b int) {
-       la.insert(Loc{len(la.lines[a].data), a}, la.lines[b].data)
-       la.DeleteLine(b)
-}
-
-// Split splits a line at a given position
-func (la *LineArray) Split(pos Loc) {
-       la.NewlineBelow(pos.Y)
-       la.insert(Loc{0, pos.Y + 1}, la.lines[pos.Y].data[pos.X:])
-       la.lines[pos.Y+1].state = la.lines[pos.Y].state
-       la.lines[pos.Y].state = nil
-       la.lines[pos.Y].match = nil
-       la.lines[pos.Y+1].match = nil
-       la.lines[pos.Y].rehighlight = true
-       la.DeleteToEnd(Loc{pos.X, pos.Y})
-}
-
-// removes from start to end
-func (la *LineArray) remove(start, end Loc) string {
-       sub := la.Substr(start, end)
-       startX := runeToByteIndex(start.X, la.lines[start.Y].data)
-       endX := runeToByteIndex(end.X, la.lines[end.Y].data)
-       if start.Y == end.Y {
-               la.lines[start.Y].data = append(la.lines[start.Y].data[:startX], la.lines[start.Y].data[endX:]...)
-       } else {
-               for i := start.Y + 1; i <= end.Y-1; i++ {
-                       la.DeleteLine(start.Y + 1)
-               }
-               la.DeleteToEnd(Loc{startX, start.Y})
-               la.DeleteFromStart(Loc{endX - 1, start.Y + 1})
-               la.JoinLines(start.Y, start.Y+1)
-       }
-       return sub
-}
-
-// DeleteToEnd deletes from the end of a line to the position
-func (la *LineArray) DeleteToEnd(pos Loc) {
-       la.lines[pos.Y].data = la.lines[pos.Y].data[:pos.X]
-}
-
-// DeleteFromStart deletes from the start of a line to the position
-func (la *LineArray) DeleteFromStart(pos Loc) {
-       la.lines[pos.Y].data = la.lines[pos.Y].data[pos.X+1:]
-}
-
-// DeleteLine deletes the line number
-func (la *LineArray) DeleteLine(y int) {
-       la.lines = la.lines[:y+copy(la.lines[y:], la.lines[y+1:])]
-}
-
-// DeleteByte deletes the byte at a position
-func (la *LineArray) DeleteByte(pos Loc) {
-       la.lines[pos.Y].data = la.lines[pos.Y].data[:pos.X+copy(la.lines[pos.Y].data[pos.X:], la.lines[pos.Y].data[pos.X+1:])]
-}
-
-// Substr returns the string representation between two locations
-func (la *LineArray) Substr(start, end Loc) string {
-       startX := runeToByteIndex(start.X, la.lines[start.Y].data)
-       endX := runeToByteIndex(end.X, la.lines[end.Y].data)
-       if start.Y == end.Y {
-               return string(la.lines[start.Y].data[startX:endX])
-       }
-       var str string
-       str += string(la.lines[start.Y].data[startX:]) + "\n"
-       for i := start.Y + 1; i <= end.Y-1; i++ {
-               str += string(la.lines[i].data) + "\n"
-       }
-       str += string(la.lines[end.Y].data[:endX])
-       return str
-}
-
-// State gets the highlight state for the given line number
-func (la *LineArray) State(lineN int) highlight.State {
-       return la.lines[lineN].state
-}
-
-// SetState sets the highlight state at the given line number
-func (la *LineArray) SetState(lineN int, s highlight.State) {
-       la.lines[lineN].state = s
-}
-
-// SetMatch sets the match at the given line number
-func (la *LineArray) SetMatch(lineN int, m highlight.LineMatch) {
-       la.lines[lineN].match = m
-}
-
-// Match retrieves the match for the given line number
-func (la *LineArray) Match(lineN int) highlight.LineMatch {
-       return la.lines[lineN].match
-}
diff --git a/cmd/micro/line_array.go b/cmd/micro/line_array.go
new file mode 100644 (file)
index 0000000..44907f2
--- /dev/null
@@ -0,0 +1,276 @@
+package main
+
+import (
+       "bufio"
+       "io"
+       "unicode/utf8"
+
+       "github.com/zyedidia/micro/cmd/micro/highlight"
+)
+
+// Finds the byte index of the nth rune in a byte slice
+func runeToByteIndex(n int, txt []byte) int {
+       if n == 0 {
+               return 0
+       }
+
+       count := 0
+       i := 0
+       for len(txt) > 0 {
+               _, size := utf8.DecodeRune(txt)
+
+               txt = txt[size:]
+               count += size
+               i++
+
+               if i == n {
+                       break
+               }
+       }
+       return count
+}
+
+// A Line contains the data in bytes as well as a highlight state, match
+// and a flag for whether the highlighting needs to be updated
+type Line struct {
+       data []byte
+
+       state       highlight.State
+       match       highlight.LineMatch
+       rehighlight bool
+}
+
+const (
+       // Line ending file formats
+       FFAuto = 0 // Autodetect format
+       FFUnix = 1 // LF line endings (unix style '\n')
+       FFDos  = 2 // CRLF line endings (dos style '\r\n')
+)
+
+type FileFormat byte
+
+// A LineArray simply stores and array of lines and makes it easy to insert
+// and delete in it
+type LineArray struct {
+       lines    []Line
+       endings  FileFormat
+       initsize uint64
+}
+
+// Append efficiently appends lines together
+// It allocates an additional 10000 lines if the original estimate
+// is incorrect
+func Append(slice []Line, data ...Line) []Line {
+       l := len(slice)
+       if l+len(data) > cap(slice) { // reallocate
+               newSlice := make([]Line, (l+len(data))+10000)
+               copy(newSlice, slice)
+               slice = newSlice
+       }
+       slice = slice[0 : l+len(data)]
+       for i, c := range data {
+               slice[l+i] = c
+       }
+       return slice
+}
+
+// NewLineArray returns a new line array from an array of bytes
+func NewLineArray(size uint64, endings FileFormat, reader io.Reader) *LineArray {
+       la := new(LineArray)
+
+       la.lines = make([]Line, 0, 1000)
+       la.initsize = size
+
+       br := bufio.NewReader(reader)
+       var loaded int
+
+       n := 0
+       for {
+               data, err := br.ReadBytes('\n')
+               // Detect the line ending by checking to see if there is a '\r' char
+               // before the '\n'
+               // Even if the file format is set to DOS, the '\r' is removed so
+               // that all lines end with '\n'
+               dlen := len(data)
+               if dlen > 1 && data[dlen-2] == '\r' {
+                       data = append(data[:dlen-2], '\n')
+                       if endings == FFAuto {
+                               la.endings = FFDos
+                       }
+               } else if dlen > 0 {
+                       if endings == FFAuto {
+                               la.endings = FFUnix
+                       }
+               }
+
+               // If we are loading a large file (greater than 1000) we use the file
+               // size and the length of the first 1000 lines to try to estimate
+               // how many lines will need to be allocated for the rest of the file
+               // We add an extra 10000 to the original estimate to be safe and give
+               // plenty of room for expansion
+               if n >= 1000 && loaded >= 0 {
+                       totalLinesNum := int(float64(size) * (float64(n) / float64(loaded)))
+                       newSlice := make([]Line, len(la.lines), totalLinesNum+10000)
+                       copy(newSlice, la.lines)
+                       la.lines = newSlice
+                       loaded = -1
+               }
+
+               // Counter for the number of bytes in the first 1000 lines
+               if loaded >= 0 {
+                       loaded += dlen
+               }
+
+               if err != nil {
+                       if err == io.EOF {
+                               la.lines = Append(la.lines, Line{data[:], nil, nil, false})
+                       }
+                       // Last line was read
+                       break
+               } else {
+                       la.lines = Append(la.lines, Line{data[:dlen-1], nil, nil, false})
+               }
+               n++
+       }
+
+       return la
+}
+
+// Bytes returns the string that should be written to disk when
+// the line array is saved
+func (la *LineArray) Bytes() []byte {
+       str := make([]byte, 0, la.initsize+1000) // initsize should provide a good estimate
+       for i, l := range la.lines {
+               str = append(str, l.data...)
+               if i != len(la.lines)-1 {
+                       if la.endings == FFDos {
+                               str = append(str, '\r')
+                       }
+                       str = append(str, '\n')
+               }
+       }
+       return str
+}
+
+// newlineBelow adds a newline below the given line number
+func (la *LineArray) newlineBelow(y int) {
+       la.lines = append(la.lines, Line{[]byte{' '}, nil, nil, false})
+       copy(la.lines[y+2:], la.lines[y+1:])
+       la.lines[y+1] = Line{[]byte{}, la.lines[y].state, nil, false}
+}
+
+// Inserts a byte array at a given location
+func (la *LineArray) insert(pos Loc, value []byte) {
+       x, y := runeToByteIndex(pos.X, la.lines[pos.Y].data), pos.Y
+       for i := 0; i < len(value); i++ {
+               if value[i] == '\n' {
+                       la.split(Loc{x, y})
+                       x = 0
+                       y++
+                       continue
+               }
+               la.insertByte(Loc{x, y}, value[i])
+               x++
+       }
+}
+
+// InsertByte inserts a byte at a given location
+func (la *LineArray) insertByte(pos Loc, value byte) {
+       la.lines[pos.Y].data = append(la.lines[pos.Y].data, 0)
+       copy(la.lines[pos.Y].data[pos.X+1:], la.lines[pos.Y].data[pos.X:])
+       la.lines[pos.Y].data[pos.X] = value
+}
+
+// joinLines joins the two lines a and b
+func (la *LineArray) joinLines(a, b int) {
+       la.insert(Loc{len(la.lines[a].data), a}, la.lines[b].data)
+       la.deleteLine(b)
+}
+
+// split splits a line at a given position
+func (la *LineArray) split(pos Loc) {
+       la.newlineBelow(pos.Y)
+       la.insert(Loc{0, pos.Y + 1}, la.lines[pos.Y].data[pos.X:])
+       la.lines[pos.Y+1].state = la.lines[pos.Y].state
+       la.lines[pos.Y].state = nil
+       la.lines[pos.Y].match = nil
+       la.lines[pos.Y+1].match = nil
+       la.lines[pos.Y].rehighlight = true
+       la.deleteToEnd(Loc{pos.X, pos.Y})
+}
+
+// removes from start to end
+func (la *LineArray) remove(start, end Loc) []byte {
+       sub := la.Substr(start, end)
+       startX := runeToByteIndex(start.X, la.lines[start.Y].data)
+       endX := runeToByteIndex(end.X, la.lines[end.Y].data)
+       if start.Y == end.Y {
+               la.lines[start.Y].data = append(la.lines[start.Y].data[:startX], la.lines[start.Y].data[endX:]...)
+       } else {
+               for i := start.Y + 1; i <= end.Y-1; i++ {
+                       la.deleteLine(start.Y + 1)
+               }
+               la.deleteToEnd(Loc{startX, start.Y})
+               la.deleteFromStart(Loc{endX - 1, start.Y + 1})
+               la.joinLines(start.Y, start.Y+1)
+       }
+       return sub
+}
+
+// deleteToEnd deletes from the end of a line to the position
+func (la *LineArray) deleteToEnd(pos Loc) {
+       la.lines[pos.Y].data = la.lines[pos.Y].data[:pos.X]
+}
+
+// deleteFromStart deletes from the start of a line to the position
+func (la *LineArray) deleteFromStart(pos Loc) {
+       la.lines[pos.Y].data = la.lines[pos.Y].data[pos.X+1:]
+}
+
+// deleteLine deletes the line number
+func (la *LineArray) deleteLine(y int) {
+       la.lines = la.lines[:y+copy(la.lines[y:], la.lines[y+1:])]
+}
+
+// DeleteByte deletes the byte at a position
+func (la *LineArray) deleteByte(pos Loc) {
+       la.lines[pos.Y].data = la.lines[pos.Y].data[:pos.X+copy(la.lines[pos.Y].data[pos.X:], la.lines[pos.Y].data[pos.X+1:])]
+}
+
+// Substr returns the string representation between two locations
+func (la *LineArray) Substr(start, end Loc) []byte {
+       startX := runeToByteIndex(start.X, la.lines[start.Y].data)
+       endX := runeToByteIndex(end.X, la.lines[end.Y].data)
+       if start.Y == end.Y {
+               return la.lines[start.Y].data[startX:endX]
+       }
+       str := make([]byte, 0, len(la.lines[start.Y+1].data)*(end.Y-start.Y))
+       str = append(str, la.lines[start.Y].data[startX:]...)
+       str = append(str, '\n')
+       for i := start.Y + 1; i <= end.Y-1; i++ {
+               str = append(str, la.lines[i].data...)
+               str = append(str, '\n')
+       }
+       str = append(str, la.lines[end.Y].data[:endX]...)
+       return str
+}
+
+// State gets the highlight state for the given line number
+func (la *LineArray) State(lineN int) highlight.State {
+       return la.lines[lineN].state
+}
+
+// SetState sets the highlight state at the given line number
+func (la *LineArray) SetState(lineN int, s highlight.State) {
+       la.lines[lineN].state = s
+}
+
+// SetMatch sets the match at the given line number
+func (la *LineArray) SetMatch(lineN int, m highlight.LineMatch) {
+       la.lines[lineN].match = m
+}
+
+// Match retrieves the match for the given line number
+func (la *LineArray) Match(lineN int) highlight.LineMatch {
+       return la.lines[lineN].match
+}
diff --git a/cmd/micro/line_array_test.go b/cmd/micro/line_array_test.go
new file mode 100644 (file)
index 0000000..72e370c
--- /dev/null
@@ -0,0 +1,60 @@
+package main
+
+import (
+       "strings"
+       "testing"
+
+       "github.com/stretchr/testify/assert"
+)
+
+var unicode_txt = `An preost wes on leoden, Laȝamon was ihoten
+He wes Leovenaðes sone -- liðe him be Drihten.
+He wonede at Ernleȝe at æðelen are chirechen,
+Uppen Sevarne staþe, sel þar him þuhte,
+Onfest Radestone, þer he bock radde.`
+
+var la *LineArray
+
+func init() {
+       reader := strings.NewReader(unicode_txt)
+       la = NewLineArray(uint64(len(unicode_txt)), FFAuto, reader)
+}
+
+func TestSplit(t *testing.T) {
+       la.insert(Loc{17, 1}, []byte{'\n'})
+       assert.Equal(t, len(la.lines), 6)
+       sub1 := la.Substr(Loc{0, 1}, Loc{17, 1})
+       sub2 := la.Substr(Loc{0, 2}, Loc{30, 2})
+
+       assert.Equal(t, []byte("He wes Leovenaðes"), sub1)
+       assert.Equal(t, []byte(" sone -- liðe him be Drihten."), sub2)
+}
+
+func TestJoin(t *testing.T) {
+       la.remove(Loc{47, 1}, Loc{0, 2})
+       assert.Equal(t, len(la.lines), 5)
+       sub := la.Substr(Loc{0, 1}, Loc{47, 1})
+       bytes := la.Bytes()
+
+       assert.Equal(t, []byte("He wes Leovenaðes sone -- liðe him be Drihten."), sub)
+       assert.Equal(t, unicode_txt, string(bytes))
+}
+
+func TestInsert(t *testing.T) {
+       la.insert(Loc{20, 3}, []byte(" foobar"))
+       sub1 := la.Substr(Loc{0, 3}, Loc{50, 3})
+
+       assert.Equal(t, []byte("Uppen Sevarne staþe, foobar sel þar him þuhte,"), sub1)
+
+       la.insert(Loc{25, 2}, []byte("ಮಣ್ಣಾಗಿ"))
+       sub2 := la.Substr(Loc{0, 2}, Loc{60, 2})
+       assert.Equal(t, []byte("He wonede at Ernleȝe at æಮಣ್ಣಾಗಿðelen are chirechen,"), sub2)
+}
+
+func TestRemove(t *testing.T) {
+       la.remove(Loc{20, 3}, Loc{27, 3})
+       la.remove(Loc{25, 2}, Loc{32, 2})
+
+       bytes := la.Bytes()
+       assert.Equal(t, unicode_txt, string(bytes))
+}
index a3806e548d24e179b8ac07393b6e772ecebc3a5f..d7ce8a1a588ec33d366675d97f1f65226305a792 100644 (file)
@@ -1,91 +1,18 @@
 package main
 
-// FromCharPos converts from a character position to an x, y position
-func FromCharPos(loc int, buf *Buffer) Loc {
-       charNum := 0
-       x, y := 0, 0
-
-       lineLen := Count(buf.Line(y)) + 1
-       for charNum+lineLen <= loc {
-               charNum += lineLen
-               y++
-               lineLen = Count(buf.Line(y)) + 1
-       }
-       x = loc - charNum
-
-       return Loc{x, y}
-}
-
-// ToCharPos converts from an x, y position to a character position
-func ToCharPos(start Loc, buf *Buffer) int {
-       x, y := start.X, start.Y
-       loc := 0
-       for i := 0; i < y; i++ {
-               // + 1 for the newline
-               loc += Count(buf.Line(i)) + 1
-       }
-       loc += x
-       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
-}
+import "unicode/utf8"
 
 // Loc stores a location
 type Loc struct {
        X, Y int
 }
 
-// Diff returns the distance between two locations
-func Diff(a, b Loc, buf *Buffer) int {
-       if a.Y == b.Y {
-               if a.X > b.X {
-                       return a.X - b.X
-               }
-               return b.X - a.X
-       }
-
-       // Make sure a is guaranteed to be less than b
-       if b.LessThan(a) {
-               a, b = b, a
-       }
-
-       loc := 0
-       for i := a.Y + 1; i < b.Y; i++ {
-               // + 1 for the newline
-               loc += Count(buf.Line(i)) + 1
-       }
-       loc += Count(buf.Line(a.Y)) - a.X + b.X + 1
-       return loc
-}
-
 // LessThan returns true if b is smaller
 func (l Loc) LessThan(b Loc) bool {
        if l.Y < b.Y {
                return true
        }
-       if l.Y == b.Y && l.X < b.X {
-               return true
-       }
-       return false
+       return l.Y == b.Y && l.X < b.X
 }
 
 // GreaterThan returns true if b is bigger
@@ -93,10 +20,7 @@ func (l Loc) GreaterThan(b Loc) bool {
        if l.Y > b.Y {
                return true
        }
-       if l.Y == b.Y && l.X > b.X {
-               return true
-       }
-       return false
+       return l.Y == b.Y && l.X > b.X
 }
 
 // GreaterEqual returns true if b is greater than or equal to b
@@ -107,10 +31,7 @@ func (l Loc) GreaterEqual(b Loc) bool {
        if l.Y == b.Y && l.X > b.X {
                return true
        }
-       if l == b {
-               return true
-       }
-       return false
+       return l == b
 }
 
 // LessEqual returns true if b is less than or equal to b
@@ -121,10 +42,32 @@ func (l Loc) LessEqual(b Loc) bool {
        if l.Y == b.Y && l.X < b.X {
                return true
        }
-       if l == b {
-               return true
+       return l == b
+}
+
+// The following functions require a buffer to know where newlines are
+
+// Diff returns the distance between two locations
+func Diff(a, b Loc, buf *Buffer) int {
+       if a.Y == b.Y {
+               if a.X > b.X {
+                       return a.X - b.X
+               }
+               return b.X - a.X
        }
-       return false
+
+       // Make sure a is guaranteed to be less than b
+       if b.LessThan(a) {
+               a, b = b, a
+       }
+
+       loc := 0
+       for i := a.Y + 1; i < b.Y; i++ {
+               // + 1 for the newline
+               loc += utf8.RuneCount(buf.LineBytes(i)) + 1
+       }
+       loc += utf8.RuneCount(buf.LineBytes(a.Y)) - a.X + b.X + 1
+       return loc
 }
 
 // This moves the location one character to the right
@@ -133,7 +76,7 @@ func (l Loc) right(buf *Buffer) Loc {
                return Loc{l.X + 1, l.Y}
        }
        var res Loc
-       if l.X < Count(buf.Line(l.Y)) {
+       if l.X < utf8.RuneCount(buf.LineBytes(l.Y)) {
                res = Loc{l.X + 1, l.Y}
        } else {
                res = Loc{0, l.Y + 1}
@@ -150,7 +93,7 @@ func (l Loc) left(buf *Buffer) Loc {
        if l.X > 0 {
                res = Loc{l.X - 1, l.Y}
        } else {
-               res = Loc{Count(buf.Line(l.Y - 1)), l.Y - 1}
+               res = Loc{utf8.RuneCount(buf.LineBytes(l.Y - 1)), l.Y - 1}
        }
        return res
 }
index d97a1191eb6d6a903973b318c3bfb0e82609bbfd..0425efa2adca620c08633764543545435ac4a331 100644 (file)
@@ -16,9 +16,8 @@ import (
        "strings"
        "time"
 
-       luar "layeh.com/gopher-luar"
-
        lua "github.com/yuin/gopher-lua"
+       luar "layeh.com/gopher-luar"
 )
 
 var L *lua.LState
diff --git a/cmd/micro/message.go b/cmd/micro/message.go
new file mode 100644 (file)
index 0000000..1c660fc
--- /dev/null
@@ -0,0 +1,38 @@
+package main
+
+import (
+       "bufio"
+       "fmt"
+       "os"
+       "strconv"
+)
+
+// TermMessage sends a message to the user in the terminal. This usually occurs before
+// micro has been fully initialized -- ie if there is an error in the syntax highlighting
+// regular expressions
+// The function must be called when the screen is not initialized
+// This will write the message, and wait for the user
+// to press and key to continue
+func TermMessage(msg ...interface{}) {
+       screenWasNil := screen == nil
+       if !screenWasNil {
+               screen.Fini()
+               screen = nil
+       }
+
+       fmt.Println(msg...)
+       fmt.Print("\nPress enter to continue")
+
+       reader := bufio.NewReader(os.Stdin)
+       reader.ReadString('\n')
+
+       if !screenWasNil {
+               InitScreen()
+       }
+}
+
+// TermError sends an error to the user in the terminal. Like TermMessage except formatted
+// as an error
+func TermError(filename string, lineNum int, err string) {
+       TermMessage(filename + ", " + strconv.Itoa(lineNum) + ": " + err)
+}
diff --git a/cmd/micro/messenger.go b/cmd/micro/messenger.go
deleted file mode 100644 (file)
index b55557a..0000000
+++ /dev/null
@@ -1,669 +0,0 @@
-package main
-
-import (
-       "bufio"
-       "bytes"
-       "encoding/gob"
-       "fmt"
-       "os"
-       "strconv"
-
-       "github.com/mattn/go-runewidth"
-       "github.com/zyedidia/clipboard"
-       "github.com/zyedidia/micro/cmd/micro/shellwords"
-       "github.com/zyedidia/tcell"
-)
-
-// TermMessage sends a message to the user in the terminal. This usually occurs before
-// micro has been fully initialized -- ie if there is an error in the syntax highlighting
-// regular expressions
-// The function must be called when the screen is not initialized
-// This will write the message, and wait for the user
-// to press and key to continue
-func TermMessage(msg ...interface{}) {
-       screenWasNil := screen == nil
-       if !screenWasNil {
-               screen.Fini()
-               screen = nil
-       }
-
-       fmt.Println(msg...)
-       fmt.Print("\nPress enter to continue")
-
-       reader := bufio.NewReader(os.Stdin)
-       reader.ReadString('\n')
-
-       if !screenWasNil {
-               InitScreen()
-       }
-}
-
-// TermError sends an error to the user in the terminal. Like TermMessage except formatted
-// as an error
-func TermError(filename string, lineNum int, err string) {
-       TermMessage(filename + ", " + strconv.Itoa(lineNum) + ": " + err)
-}
-
-// 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
-       hasMessage bool
-
-       // Message to print
-       message string
-       // The user's response to a prompt
-       response string
-       // style to use when drawing the message
-       style tcell.Style
-
-       // We have to keep track of the cursor for prompting
-       cursorx int
-
-       // This map stores the history for all the different kinds of uses Prompt has
-       // It's a map of history type -> history array
-       history    map[string][]string
-       historyNum int
-
-       // Is the current message a message from the gutter
-       gutterMessage bool
-}
-
-// AddLog sends a message to the log view
-func (m *Messenger) AddLog(msg ...interface{}) {
-       logMessage := fmt.Sprint(msg...)
-       buffer := m.getBuffer()
-       buffer.insert(buffer.End(), []byte(logMessage+"\n"))
-       buffer.Cursor.Loc = buffer.End()
-       buffer.Cursor.Relocate()
-}
-
-func (m *Messenger) getBuffer() *Buffer {
-       if m.log == nil {
-               m.log = NewBufferFromString("", "")
-               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
-
-               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)
-}
-
-// Error sends an error message to the user
-func (m *Messenger) Error(msg ...interface{}) {
-       buf := new(bytes.Buffer)
-       fmt.Fprint(buf, 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 = 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
-       }
-       // 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)
-
-       _, h := screen.Size()
-       for {
-               m.Clear()
-               m.Display()
-               screen.ShowCursor(Count(m.message), h-1)
-               screen.Show()
-               event := <-events
-
-               switch e := event.(type) {
-               case *tcell.EventKey:
-                       switch e.Key() {
-                       case tcell.KeyRune:
-                               if e.Rune() == 'y' || e.Rune() == 'Y' {
-                                       m.AddLog("\t--> y")
-                                       m.hasPrompt = false
-                                       return true, false
-                               } else if e.Rune() == 'n' || 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.Clear()
-                               m.Reset()
-                               m.hasPrompt = false
-                               return false, true
-                       }
-               }
-       }
-}
-
-// 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)
-
-       _, h := screen.Size()
-       for {
-               m.Clear()
-               m.Display()
-               screen.ShowCursor(Count(m.message), h-1)
-               screen.Show()
-               event := <-events
-
-               switch e := event.(type) {
-               case *tcell.EventKey:
-                       switch e.Key() {
-                       case tcell.KeyRune:
-                               for _, r := range responses {
-                                       if e.Rune() == r {
-                                               m.AddLog("\t--> " + string(r))
-                                               m.Clear()
-                                               m.Reset()
-                                               m.hasPrompt = false
-                                               return r, false
-                                       }
-                               }
-                       case tcell.KeyCtrlC, tcell.KeyCtrlQ, tcell.KeyEscape:
-                               m.AddLog("\t--> (cancel)")
-                               m.Clear()
-                               m.Reset()
-                               m.hasPrompt = false
-                               return ' ', true
-                       }
-               }
-       }
-}
-
-// Completion represents a type of completion
-type Completion int
-
-const (
-       NoCompletion Completion = iota
-       FileCompletion
-       CommandCompletion
-       HelpCompletion
-       OptionCompletion
-       PluginCmdCompletion
-       PluginNameCompletion
-       OptionValueCompletion
-)
-
-// 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) {
-       m.hasPrompt = true
-       m.PromptText(prompt)
-       if _, ok := m.history[historyType]; !ok {
-               m.history[historyType] = []string{""}
-       } else {
-               m.history[historyType] = append(m.history[historyType], "")
-       }
-       m.historyNum = len(m.history[historyType]) - 1
-
-       response, canceled := placeholder, true
-       m.response = response
-       m.cursorx = Count(placeholder)
-
-       RedrawAll()
-       for m.hasPrompt {
-               var suggestions []string
-               m.Clear()
-
-               event := <-events
-
-               switch e := event.(type) {
-               case *tcell.EventResize:
-                       for _, t := range tabs {
-                               t.Resize()
-                       }
-                       RedrawAll()
-               case *tcell.EventKey:
-                       switch e.Key() {
-                       case tcell.KeyCtrlQ, tcell.KeyCtrlC, tcell.KeyEscape:
-                               // Cancel
-                               m.AddLog("\t--> (cancel)")
-                               m.hasPrompt = false
-                       case tcell.KeyEnter:
-                               // User is done entering their response
-                               m.AddLog("\t--> " + m.response)
-                               m.hasPrompt = false
-                               response, canceled = m.response, false
-                               m.history[historyType][len(m.history[historyType])-1] = response
-                       case tcell.KeyTab:
-                               args, err := shellwords.Split(m.response)
-                               if err != nil {
-                                       break
-                               }
-                               currentArg := ""
-                               currentArgNum := 0
-                               if len(args) > 0 {
-                                       currentArgNum = len(args) - 1
-                                       currentArg = args[currentArgNum]
-                               }
-                               var completionType Completion
-
-                               if completionTypes[0] == CommandCompletion && currentArgNum > 0 {
-                                       if command, ok := commands[args[0]]; ok {
-                                               completionTypes = append([]Completion{CommandCompletion}, command.completions...)
-                                       }
-                               }
-
-                               if currentArgNum >= len(completionTypes) {
-                                       completionType = completionTypes[len(completionTypes)-1]
-                               } else {
-                                       completionType = completionTypes[currentArgNum]
-                               }
-
-                               var chosen string
-                               if completionType == FileCompletion {
-                                       chosen, suggestions = FileComplete(currentArg)
-                               } else if completionType == CommandCompletion {
-                                       chosen, suggestions = CommandComplete(currentArg)
-                               } else if completionType == HelpCompletion {
-                                       chosen, suggestions = HelpComplete(currentArg)
-                               } else if completionType == OptionCompletion {
-                                       chosen, suggestions = OptionComplete(currentArg)
-                               } else if completionType == OptionValueCompletion {
-                                       if currentArgNum-1 > 0 {
-                                               chosen, suggestions = OptionValueComplete(args[currentArgNum-1], currentArg)
-                                       }
-                               } else if completionType == PluginCmdCompletion {
-                                       chosen, suggestions = PluginCmdComplete(currentArg)
-                               } else if completionType == PluginNameCompletion {
-                                       chosen, suggestions = PluginNameComplete(currentArg)
-                               } else if completionType < NoCompletion {
-                                       chosen, suggestions = PluginComplete(completionType, currentArg)
-                               }
-
-                               if len(suggestions) > 1 {
-                                       chosen = chosen + CommonSubstring(suggestions...)
-                               }
-
-                               if len(suggestions) != 0 && chosen != "" {
-                                       m.response = shellwords.Join(append(args[:len(args)-1], chosen)...)
-                                       m.cursorx = Count(m.response)
-                               }
-                       }
-               }
-
-               m.HandleEvent(event, m.history[historyType])
-
-               m.Clear()
-               for _, v := range tabs[curTab].Views {
-                       v.Display()
-               }
-               DisplayTabs()
-               m.Display()
-               if len(suggestions) > 1 {
-                       m.DisplaySuggestions(suggestions)
-               }
-               screen.Show()
-       }
-
-       m.Clear()
-       m.Reset()
-       return response, canceled
-}
-
-// UpHistory fetches the previous item in the history
-func (m *Messenger) UpHistory(history []string) {
-       if m.historyNum > 0 {
-               m.historyNum--
-               m.response = history[m.historyNum]
-               m.cursorx = Count(m.response)
-       }
-}
-
-// DownHistory fetches the next item in the history
-func (m *Messenger) DownHistory(history []string) {
-       if m.historyNum < len(history)-1 {
-               m.historyNum++
-               m.response = history[m.historyNum]
-               m.cursorx = Count(m.response)
-       }
-}
-
-// CursorLeft moves the cursor one character left
-func (m *Messenger) CursorLeft() {
-       if m.cursorx > 0 {
-               m.cursorx--
-       }
-}
-
-// CursorRight moves the cursor one character right
-func (m *Messenger) CursorRight() {
-       if m.cursorx < Count(m.response) {
-               m.cursorx++
-       }
-}
-
-// Start moves the cursor to the start of the line
-func (m *Messenger) Start() {
-       m.cursorx = 0
-}
-
-// End moves the cursor to the end of the line
-func (m *Messenger) End() {
-       m.cursorx = Count(m.response)
-}
-
-// Backspace deletes one character
-func (m *Messenger) Backspace() {
-       if m.cursorx > 0 {
-               m.response = string([]rune(m.response)[:m.cursorx-1]) + string([]rune(m.response)[m.cursorx:])
-               m.cursorx--
-       }
-}
-
-// Paste pastes the clipboard
-func (m *Messenger) Paste() {
-       clip, _ := clipboard.ReadAll("clipboard")
-       m.response = Insert(m.response, m.cursorx, clip)
-       m.cursorx += Count(clip)
-}
-
-// WordLeft moves the cursor one word to the left
-func (m *Messenger) WordLeft() {
-       response := []rune(m.response)
-       m.CursorLeft()
-       if m.cursorx <= 0 {
-               return
-       }
-       for IsWhitespace(response[m.cursorx]) {
-               if m.cursorx <= 0 {
-                       return
-               }
-               m.CursorLeft()
-       }
-       m.CursorLeft()
-       for IsWordChar(string(response[m.cursorx])) {
-               if m.cursorx <= 0 {
-                       return
-               }
-               m.CursorLeft()
-       }
-       m.CursorRight()
-}
-
-// WordRight moves the cursor one word to the right
-func (m *Messenger) WordRight() {
-       response := []rune(m.response)
-       if m.cursorx >= len(response) {
-               return
-       }
-       for IsWhitespace(response[m.cursorx]) {
-               m.CursorRight()
-               if m.cursorx >= len(response) {
-                       m.CursorRight()
-                       return
-               }
-       }
-       m.CursorRight()
-       if m.cursorx >= len(response) {
-               return
-       }
-       for IsWordChar(string(response[m.cursorx])) {
-               m.CursorRight()
-               if m.cursorx >= len(response) {
-                       return
-               }
-       }
-}
-
-// DeleteWordLeft deletes one word to the left
-func (m *Messenger) DeleteWordLeft() {
-       m.WordLeft()
-       m.response = string([]rune(m.response)[:m.cursorx])
-}
-
-// HandleEvent handles an event for the prompter
-func (m *Messenger) HandleEvent(event tcell.Event, history []string) {
-       switch e := event.(type) {
-       case *tcell.EventKey:
-               switch e.Key() {
-               case tcell.KeyCtrlA:
-                       m.Start()
-               case tcell.KeyCtrlE:
-                       m.End()
-               case tcell.KeyUp:
-                       m.UpHistory(history)
-               case tcell.KeyDown:
-                       m.DownHistory(history)
-               case tcell.KeyLeft:
-                       if e.Modifiers() == tcell.ModCtrl {
-                               m.Start()
-                       } else if e.Modifiers() == tcell.ModAlt || e.Modifiers() == tcell.ModMeta {
-                               m.WordLeft()
-                       } else {
-                               m.CursorLeft()
-                       }
-               case tcell.KeyRight:
-                       if e.Modifiers() == tcell.ModCtrl {
-                               m.End()
-                       } else if e.Modifiers() == tcell.ModAlt || e.Modifiers() == tcell.ModMeta {
-                               m.WordRight()
-                       } else {
-                               m.CursorRight()
-                       }
-               case tcell.KeyBackspace2, tcell.KeyBackspace:
-                       if e.Modifiers() == tcell.ModCtrl || e.Modifiers() == tcell.ModAlt || e.Modifiers() == tcell.ModMeta {
-                               m.DeleteWordLeft()
-                       } else {
-                               m.Backspace()
-                       }
-               case tcell.KeyCtrlW:
-                       m.DeleteWordLeft()
-               case tcell.KeyCtrlV:
-                       m.Paste()
-               case tcell.KeyCtrlF:
-                       m.WordRight()
-               case tcell.KeyCtrlB:
-                       m.WordLeft()
-               case tcell.KeyRune:
-                       m.response = Insert(m.response, m.cursorx, string(e.Rune()))
-                       m.cursorx++
-               }
-               history[m.historyNum] = m.response
-
-       case *tcell.EventPaste:
-               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)
-                               }
-                       }
-               }
-       }
-}
-
-// Reset resets the messenger's cursor, message and response
-func (m *Messenger) Reset() {
-       m.cursorx = 0
-       m.message = ""
-       m.response = ""
-}
-
-// Clear clears the line at the bottom of the editor
-func (m *Messenger) Clear() {
-       w, h := screen.Size()
-       for x := 0; x < w; x++ {
-               screen.SetContent(x, h-1, ' ', nil, defStyle)
-       }
-}
-
-func (m *Messenger) DisplaySuggestions(suggestions []string) {
-       w, screenH := screen.Size()
-
-       y := screenH - 2
-
-       statusLineStyle := defStyle.Reverse(true)
-       if style, ok := colorscheme["statusline"]; ok {
-               statusLineStyle = style
-       }
-
-       for x := 0; x < w; x++ {
-               screen.SetContent(x, y, ' ', nil, statusLineStyle)
-       }
-
-       x := 0
-       for _, suggestion := range suggestions {
-               for _, c := range suggestion {
-                       screen.SetContent(x, y, c, nil, statusLineStyle)
-                       x++
-               }
-               screen.SetContent(x, y, ' ', nil, statusLineStyle)
-               x++
-       }
-}
-
-// Display displays messages or prompts
-func (m *Messenger) Display() {
-       _, h := screen.Size()
-       if m.hasMessage {
-               if m.hasPrompt || globalSettings["infobar"].(bool) {
-                       runes := []rune(m.message + m.response)
-                       posx := 0
-                       for x := 0; x < len(runes); x++ {
-                               screen.SetContent(posx, h-1, runes[x], nil, m.style)
-                               posx += runewidth.RuneWidth(runes[x])
-                       }
-               }
-       }
-
-       if m.hasPrompt {
-               screen.ShowCursor(Count(m.message)+m.cursorx, h-1)
-               screen.Show()
-       }
-}
-
-// LoadHistory attempts to load user history from configDir/buffers/history
-// into the history map
-// The savehistory option must be on
-func (m *Messenger) LoadHistory() {
-       if GetGlobalOption("savehistory").(bool) {
-               file, err := os.Open(configDir + "/buffers/history")
-               defer file.Close()
-               var decodedMap map[string][]string
-               if err == nil {
-                       decoder := gob.NewDecoder(file)
-                       err = decoder.Decode(&decodedMap)
-
-                       if err != nil {
-                               m.Error("Error loading history:", err)
-                               return
-                       }
-               }
-
-               if decodedMap != nil {
-                       m.history = decodedMap
-               } else {
-                       m.history = make(map[string][]string)
-               }
-       } else {
-               m.history = make(map[string][]string)
-       }
-}
-
-// SaveHistory saves the user's command history to configDir/buffers/history
-// only if the savehistory option is on
-func (m *Messenger) SaveHistory() {
-       if GetGlobalOption("savehistory").(bool) {
-               // Don't save history past 100
-               for k, v := range m.history {
-                       if len(v) > 100 {
-                               m.history[k] = v[len(m.history[k])-100:]
-                       }
-               }
-
-               file, err := os.Create(configDir + "/buffers/history")
-               defer file.Close()
-               if err == nil {
-                       encoder := gob.NewEncoder(file)
-
-                       err = encoder.Encode(m.history)
-                       if err != nil {
-                               m.Error("Error saving history:", err)
-                               return
-                       }
-               }
-       }
-}
-
-// A GutterMessage is a message displayed on the side of the editor
-type GutterMessage struct {
-       lineNum int
-       msg     string
-       kind    int
-}
-
-// These are the different types of messages
-const (
-       // GutterInfo represents a simple info message
-       GutterInfo = iota
-       // GutterWarning represents a compiler warning
-       GutterWarning
-       // GutterError represents a compiler error
-       GutterError
-)
index e1787c77d6e494522f979e016eb4ff67317d63e9..bd01dda1632fb044ce33c1816d28549035536141 100644 (file)
@@ -3,22 +3,13 @@ package main
 import (
        "flag"
        "fmt"
-       "io/ioutil"
        "os"
-       "path/filepath"
-       "runtime"
-       "strings"
        "time"
 
        "github.com/go-errors/errors"
-       "github.com/mattn/go-isatty"
-       "github.com/mitchellh/go-homedir"
-       "github.com/yuin/gopher-lua"
-       "github.com/zyedidia/clipboard"
+       homedir "github.com/mitchellh/go-homedir"
        "github.com/zyedidia/micro/cmd/micro/terminfo"
        "github.com/zyedidia/tcell"
-       "github.com/zyedidia/tcell/encoding"
-       "layeh.com/gopher-luar"
 )
 
 const (
@@ -31,13 +22,6 @@ var (
        // The main screen
        screen tcell.Screen
 
-       // Object to send messages and prompts to the user
-       messenger *Messenger
-
-       // The default highlighting style
-       // This simply defines the default foreground and background colors
-       defStyle tcell.Style
-
        // Where the user's configuration is
        // This should be $XDG_CONFIG_HOME/micro
        // If $XDG_CONFIG_HOME is not set, it is ~/.config/micro
@@ -50,87 +34,22 @@ var (
        CommitHash = "Unknown"
        // CompileDate is the date this binary was compiled on
        CompileDate = "Unknown"
-
-       // The list of views
-       tabs []*Tab
-       // This is the currently open tab
-       // It's just an index to the tab in the tabs array
-       curTab int
-
-       // Channel of jobs running in the background
-       jobs chan JobFunction
+       // Debug logging
+       Debug = "ON"
 
        // Event channel
        events   chan tcell.Event
        autosave chan bool
 
-       // Channels for the terminal emulator
-       updateterm chan bool
-       closeterm  chan int
-
        // How many redraws have happened
        numRedraw uint
-)
 
-// LoadInput determines which files should be loaded into buffers
-// based on the input stored in flag.Args()
-func LoadInput() []*Buffer {
-       // There are a number of ways micro should start given its input
-
-       // 1. If it is given a files in flag.Args(), it should open those
-
-       // 2. If there is no input file and the input is not a terminal, that means
-       // something is being piped in and the stdin should be opened in an
-       // empty buffer
-
-       // 3. If there is no input file and the input is a terminal, an empty buffer
-       // should be opened
-
-       var filename string
-       var input []byte
-       var err error
-       args := flag.Args()
-       buffers := make([]*Buffer, 0, len(args))
-
-       if len(args) > 0 {
-               // Option 1
-               // We go through each file and load it
-               for i := 0; i < len(args); i++ {
-                       if strings.HasPrefix(args[i], "+") {
-                               if strings.Contains(args[i], ":") {
-                                       split := strings.Split(args[i], ":")
-                                       *flagStartPos = split[0][1:] + "," + split[1]
-                               } else {
-                                       *flagStartPos = args[i][1:] + ",0"
-                               }
-                               continue
-                       }
-
-                       buf, err := NewBufferFromFile(args[i])
-                       if err != nil {
-                               TermMessage(err)
-                               continue
-                       }
-                       // If the file didn't exist, input will be empty, and we'll open an empty buffer
-                       buffers = append(buffers, buf)
-               }
-       } else if !isatty.IsTerminal(os.Stdin.Fd()) {
-               // Option 2
-               // The input is not a terminal, so something is being piped in
-               // and we should read from stdin
-               input, err = ioutil.ReadAll(os.Stdin)
-               if err != nil {
-                       TermMessage("Error reading from stdin: ", err)
-                       input = []byte{}
-               }
-               buffers = append(buffers, NewBufferFromString(string(input), filename))
-       } else {
-               // Option 3, just open an empty buffer
-               buffers = append(buffers, NewBufferFromString(string(input), filename))
-       }
-
-       return buffers
-}
+       // Command line flags
+       flagVersion   = flag.Bool("version", false, "Show the version number and information")
+       flagStartPos  = flag.String("startpos", "", "LINE,COL to start the cursor at when opening a buffer.")
+       flagConfigDir = flag.String("config-dir", "", "Specify a custom location for the configuration directory")
+       flagOptions   = flag.Bool("options", false, "Show all option help")
+)
 
 // InitConfigDir finds the configuration directory for micro according to the XDG spec.
 // If no directory is found, it creates one.
@@ -230,63 +149,7 @@ func InitScreen() {
        // screen.SetStyle(defStyle)
 }
 
-// RedrawAll redraws everything -- all the views and the messenger
-func RedrawAll() {
-       messenger.Clear()
-
-       w, h := screen.Size()
-       for x := 0; x < w; x++ {
-               for y := 0; y < h; y++ {
-                       screen.SetContent(x, y, ' ', nil, defStyle)
-               }
-       }
-
-       for _, v := range tabs[curTab].Views {
-               v.Display()
-       }
-       DisplayTabs()
-       messenger.Display()
-       if globalSettings["keymenu"].(bool) {
-               DisplayKeyMenu()
-       }
-       screen.Show()
-
-       if numRedraw%50 == 0 {
-               runtime.GC()
-       }
-       numRedraw++
-}
-
-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()
-
-       InitColorscheme()
-       LoadPlugins()
-
-       for _, tab := range tabs {
-               for _, v := range tab.Views {
-                       v.Buf.UpdateRules()
-               }
-       }
-}
-
-// Command line flags
-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.")
-var flagConfigDir = flag.String("config-dir", "", "Specify a custom location for the configuration directory")
-var flagOptions = flag.Bool("options", false, "Show all option help")
-
-func main() {
+func InitFlags() {
        flag.Usage = func() {
                fmt.Println("Usage: micro [OPTIONS] [FILE]...")
                fmt.Println("-config-dir dir")
@@ -327,35 +190,32 @@ func main() {
                // If -options was passed
                for k, v := range DefaultGlobalSettings() {
                        fmt.Printf("-%s value\n", k)
-                       fmt.Printf("    \tThe %s option. Default value: '%v'\n", k, v)
+                       fmt.Printf("    \tDefault value: '%v'\n", v)
                }
                os.Exit(0)
        }
+}
 
-       // Start the Lua VM for running plugins
-       L = lua.NewState()
-       defer L.Close()
-
-       // Some encoding stuff in case the user isn't using UTF-8
-       encoding.Register()
-       tcell.SetEncodingFallback(tcell.EncodingFallbackASCII)
+func main() {
+       var err error
 
-       // Find the user's configuration directory (probably $XDG_CONFIG_HOME/micro)
+       InitLog()
+       InitFlags()
        InitConfigDir()
-
-       // Build a list of available Extensions (Syntax, Colorscheme etc.)
        InitRuntimeFiles()
-
-       // Load the user's settings
+       err = ReadSettings()
+       if err != nil {
+               TermMessage(err)
+       }
        InitGlobalSettings()
+       err = InitColorscheme()
+       if err != nil {
+               TermMessage(err)
+       }
 
-       InitCommands()
-       InitBindings()
-
-       // Start the screen
        InitScreen()
 
-       // This is just so if we have an error, we can exit cleanly and not completely
+       // If we have an error, we can exit cleanly and not completely
        // mess up the terminal being worked in
        // In other words we need to shut down tcell before the program crashes
        defer func() {
@@ -368,227 +228,26 @@ func main() {
                }
        }()
 
-       // Create a new messenger
-       // This is used for sending the user messages in the bottom of the editor
-       messenger = new(Messenger)
-       messenger.LoadHistory()
+       b, err := NewBufferFromFile(os.Args[1])
 
-       // Now we load the input
-       buffers := LoadInput()
-       if len(buffers) == 0 {
-               screen.Fini()
-               os.Exit(1)
+       if err != nil {
+               TermMessage(err)
        }
 
-       for _, buf := range buffers {
-               // For each buffer we create a new tab and place the view in that tab
-               tab := NewTabFromView(NewView(buf))
-               tab.SetNum(len(tabs))
-               tabs = append(tabs, tab)
-               for _, t := range tabs {
-                       for _, v := range t.Views {
-                               v.Center(false)
-                       }
-
-                       t.Resize()
-               }
-       }
+       width, height := screen.Size()
 
-       for k, v := range optionFlags {
-               if *v != "" {
-                       SetOption(k, *v)
-               }
-       }
+       w := NewWindow(0, 0, width/2, height/2, b)
 
-       // Load all the plugin stuff
-       // We give plugins access to a bunch of variables here which could be useful to them
-       L.SetGlobal("OS", luar.New(L, runtime.GOOS))
-       L.SetGlobal("tabs", luar.New(L, tabs))
-       L.SetGlobal("GetTabs", luar.New(L, func() []*Tab {
-               return tabs
-       }))
-       L.SetGlobal("curTab", luar.New(L, curTab))
-       L.SetGlobal("messenger", luar.New(L, messenger))
-       L.SetGlobal("GetOption", luar.New(L, GetOption))
-       L.SetGlobal("AddOption", luar.New(L, AddOption))
-       L.SetGlobal("SetOption", luar.New(L, SetOption))
-       L.SetGlobal("SetLocalOption", luar.New(L, SetLocalOption))
-       L.SetGlobal("BindKey", luar.New(L, BindKey))
-       L.SetGlobal("MakeCommand", luar.New(L, MakeCommand))
-       L.SetGlobal("CurView", luar.New(L, CurView))
-       L.SetGlobal("IsWordChar", luar.New(L, IsWordChar))
-       L.SetGlobal("HandleCommand", luar.New(L, HandleCommand))
-       L.SetGlobal("HandleShellCommand", luar.New(L, HandleShellCommand))
-       L.SetGlobal("ExecCommand", luar.New(L, ExecCommand))
-       L.SetGlobal("RunShellCommand", luar.New(L, RunShellCommand))
-       L.SetGlobal("RunBackgroundShell", luar.New(L, RunBackgroundShell))
-       L.SetGlobal("RunInteractiveShell", luar.New(L, RunInteractiveShell))
-       L.SetGlobal("TermEmuSupported", luar.New(L, TermEmuSupported))
-       L.SetGlobal("RunTermEmulator", luar.New(L, RunTermEmulator))
-       L.SetGlobal("GetLeadingWhitespace", luar.New(L, GetLeadingWhitespace))
-       L.SetGlobal("MakeCompletion", luar.New(L, MakeCompletion))
-       L.SetGlobal("NewBuffer", luar.New(L, NewBufferFromString))
-       L.SetGlobal("NewBufferFromFile", luar.New(L, NewBufferFromFile))
-       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("WorkingDirectory", luar.New(L, os.Getwd))
-       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))
-       L.SetGlobal("AddRuntimeFileFromMemory", luar.New(L, PluginAddRuntimeFileFromMemory))
-
-       // Access to Go stdlib
-       L.SetGlobal("import", luar.New(L, Import))
-
-       jobs = make(chan JobFunction, 100)
-       events = make(chan tcell.Event, 100)
-       autosave = make(chan bool)
-       updateterm = make(chan bool)
-       closeterm = make(chan int)
-
-       LoadPlugins()
-
-       for _, t := range tabs {
-               for _, v := range t.Views {
-                       GlobalPluginCall("onViewOpen", v)
-                       GlobalPluginCall("onBufferOpen", v.Buf)
-               }
+       for i := 0; i < 5; i++ {
+               screen.Clear()
+               w.DisplayBuffer()
+               w.DisplayStatusLine()
+               screen.Show()
+               time.Sleep(200 * time.Millisecond)
+               w.StartLine++
        }
 
-       InitColorscheme()
-       messenger.style = defStyle
-
-       // Here is the event loop which runs in a separate thread
-       go func() {
-               for {
-                       if screen != nil {
-                               events <- screen.PollEvent()
-                       }
-               }
-       }()
-
-       go func() {
-               for {
-                       time.Sleep(autosaveTime * time.Second)
-                       if globalSettings["autosave"].(bool) {
-                               autosave <- true
-                       }
-               }
-       }()
+       // time.Sleep(2 * time.Second)
 
-       for {
-               // Display everything
-               RedrawAll()
-
-               var event tcell.Event
-
-               // Check for new events
-               select {
-               case f := <-jobs:
-                       // 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:
-                       if CurView().Buf.Path != "" {
-                               CurView().Save(true)
-                       }
-               case <-updateterm:
-                       continue
-               case vnum := <-closeterm:
-                       tabs[curTab].Views[vnum].CloseTerminal()
-               case event = <-events:
-               }
-
-               for event != nil {
-                       didAction := false
-
-                       switch e := event.(type) {
-                       case *tcell.EventResize:
-                               for _, t := range tabs {
-                                       t.Resize()
-                               }
-                       case *tcell.EventMouse:
-                               if !searching {
-                                       if e.Buttons() == tcell.Button1 {
-                                               // If the user left clicked we check a couple things
-                                               _, h := screen.Size()
-                                               x, y := e.Position()
-                                               if y == h-1 && messenger.message != "" && globalSettings["infobar"].(bool) {
-                                                       // If the user clicked in the bottom bar, and there is a message down there
-                                                       // we copy it to the clipboard.
-                                                       // Often error messages are displayed down there so it can be useful to easily
-                                                       // copy the message
-                                                       clipboard.WriteAll(messenger.message, "primary")
-                                                       break
-                                               }
-
-                                               if CurView().mouseReleased {
-                                                       // We loop through each view in the current tab and make sure the current view
-                                                       // is the one being clicked in
-                                                       for _, v := range tabs[curTab].Views {
-                                                               if x >= v.x && x < v.x+v.Width && y >= v.y && y < v.y+v.Height {
-                                                                       tabs[curTab].CurView = v.Num
-                                                               }
-                                                       }
-                                               }
-                                       } else if e.Buttons() == tcell.WheelUp || e.Buttons() == tcell.WheelDown {
-                                               var view *View
-                                               x, y := e.Position()
-                                               for _, v := range tabs[curTab].Views {
-                                                       if x >= v.x && x < v.x+v.Width && y >= v.y && y < v.y+v.Height {
-                                                               view = tabs[curTab].Views[v.Num]
-                                                       }
-                                               }
-                                               if view != nil {
-                                                       view.HandleEvent(e)
-                                                       didAction = true
-                                               }
-                                       }
-                               }
-                       }
-
-                       if !didAction {
-                               // 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
-                       }
-               }
-       }
+       screen.Fini()
 }
diff --git a/cmd/micro/plugin.go b/cmd/micro/plugin.go
deleted file mode 100644 (file)
index b335078..0000000
+++ /dev/null
@@ -1,184 +0,0 @@
-package main
-
-import (
-       "errors"
-       "io/ioutil"
-       "os"
-       "strings"
-
-       "github.com/yuin/gopher-lua"
-       "github.com/zyedidia/tcell"
-       "layeh.com/gopher-luar"
-)
-
-var loadedPlugins map[string]string
-
-// Call calls the lua function 'function'
-// If it does not exist nothing happens, if there is an error,
-// the error is returned
-func Call(function string, args ...interface{}) (lua.LValue, error) {
-       var luaFunc lua.LValue
-       if strings.Contains(function, ".") {
-               plugin := L.GetGlobal(strings.Split(function, ".")[0])
-               if plugin.String() == "nil" {
-                       return nil, errors.New("function does not exist: " + function)
-               }
-               luaFunc = L.GetField(plugin, strings.Split(function, ".")[1])
-       } else {
-               luaFunc = L.GetGlobal(function)
-       }
-
-       if luaFunc.String() == "nil" {
-               return nil, errors.New("function does not exist: " + function)
-       }
-       var luaArgs []lua.LValue
-       for _, v := range args {
-               luaArgs = append(luaArgs, luar.New(L, v))
-       }
-       err := L.CallByParam(lua.P{
-               Fn:      luaFunc,
-               NRet:    1,
-               Protect: true,
-       }, luaArgs...)
-       ret := L.Get(-1) // returned value
-       if ret.String() != "nil" {
-               L.Pop(1) // remove received value
-       }
-       return ret, err
-}
-
-// LuaFunctionBinding is a function generator which takes the name of a lua function
-// and creates a function that will call that lua function
-// Specifically it creates a function that can be called as a binding because this is used
-// to bind keys to lua functions
-func LuaFunctionBinding(function string) func(*View, bool) bool {
-       return func(v *View, _ bool) bool {
-               _, err := Call(function, nil)
-               if err != nil {
-                       TermMessage(err)
-               }
-               return false
-       }
-}
-
-// LuaFunctionMouseBinding is a function generator which takes the name of a lua function
-// and creates a function that will call that lua function
-// Specifically it creates a function that can be called as a mouse binding because this is used
-// to bind mouse actions to lua functions
-func LuaFunctionMouseBinding(function string) func(*View, bool, *tcell.EventMouse) bool {
-       return func(v *View, _ bool, e *tcell.EventMouse) bool {
-               _, err := Call(function, e)
-               if err != nil {
-                       TermMessage(err)
-               }
-               return false
-       }
-}
-
-func unpack(old []string) []interface{} {
-       new := make([]interface{}, len(old))
-       for i, v := range old {
-               new[i] = v
-       }
-       return new
-}
-
-// LuaFunctionCommand is the same as LuaFunctionBinding except it returns a normal function
-// so that a command can be bound to a lua function
-func LuaFunctionCommand(function string) func([]string) {
-       return func(args []string) {
-               _, err := Call(function, unpack(args)...)
-               if err != nil {
-                       TermMessage(err)
-               }
-       }
-}
-
-// LuaFunctionComplete returns a function which can be used for autocomplete in plugins
-func LuaFunctionComplete(function string) func(string) []string {
-       return func(input string) (result []string) {
-
-               res, err := Call(function, input)
-               if err != nil {
-                       TermMessage(err)
-               }
-               if tbl, ok := res.(*lua.LTable); !ok {
-                       TermMessage(function, "should return a table of strings")
-               } else {
-                       for i := 1; i <= tbl.Len(); i++ {
-                               val := tbl.RawGetInt(i)
-                               if v, ok := val.(lua.LString); !ok {
-                                       TermMessage(function, "should return a table of strings")
-                               } else {
-                                       result = append(result, string(v))
-                               }
-                       }
-               }
-               return result
-       }
-}
-
-// LuaFunctionJob returns a function that will call the given lua function
-// structured as a job call i.e. the job output and arguments are provided
-// to the lua function
-func LuaFunctionJob(function string) func(string, ...string) {
-       return func(output string, args ...string) {
-               _, err := Call(function, unpack(append([]string{output}, args...))...)
-               if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") {
-                       TermMessage(err)
-               }
-       }
-}
-
-// 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() {
-       loadedPlugins = make(map[string]string)
-
-       for _, plugin := range ListRuntimeFiles(RTPlugin) {
-               pluginName := plugin.Name()
-               if _, ok := loadedPlugins[pluginName]; ok {
-                       continue
-               }
-
-               data, err := plugin.Data()
-               if err != nil {
-                       TermMessage("Error loading plugin: " + pluginName)
-                       continue
-               }
-
-               pluginLuaName := luaPluginName(pluginName)
-
-               if err := LoadFile(pluginLuaName, pluginLuaName, string(data)); err != nil {
-                       TermMessage(err)
-                       continue
-               }
-
-               loadedPlugins[pluginName] = pluginLuaName
-
-       }
-
-       if _, err := os.Stat(configDir + "/init.lua"); err == nil {
-               data, _ := ioutil.ReadFile(configDir + "/init.lua")
-               if err := LoadFile("init", configDir+"init.lua", string(data)); err != nil {
-                       TermMessage(err)
-               }
-               loadedPlugins["init"] = "init"
-       }
-}
-
-// GlobalCall makes a call to a function in every plugin that is currently
-// loaded
-func GlobalPluginCall(function string, args ...interface{}) {
-       for pl := range loadedPlugins {
-               _, err := Call(pl+"."+function, args...)
-               if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") {
-                       TermMessage(err)
-                       continue
-               }
-       }
-}
diff --git a/cmd/micro/pluginmanager.go b/cmd/micro/pluginmanager.go
deleted file mode 100644 (file)
index ad7843e..0000000
+++ /dev/null
@@ -1,622 +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/flynn/json5"
-       "github.com/yuin/gopher-lua"
-)
-
-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
-               }
-       }
-
-       // Install files and directory's
-       for _, f := range z.File {
-               parts := strings.Split(f.Name, "/")
-               if allPrefixed {
-                       parts = parts[1:]
-               }
-
-               targetName := filepath.Join(targetDir, filepath.Join(parts...))
-               if f.FileInfo().IsDir() {
-                       if err := os.MkdirAll(targetName, dirPerm); err != nil {
-                               return err
-                       }
-               } else {
-                       basepath := filepath.Dir(targetName)
-
-                       if err := os.MkdirAll(basepath, dirPerm); err != nil {
-                               return err
-                       }
-
-                       content, err := f.Open()
-                       if err != nil {
-                               return err
-                       }
-                       defer content.Close()
-                       target, err := os.Create(targetName)
-                       if err != nil {
-                               return err
-                       }
-                       defer target.Close()
-                       if _, err = io.Copy(target, content); err != nil {
-                               return err
-                       }
-               }
-       }
-       return nil
-}
-
-func (pl PluginPackages) Get(name string) *PluginPackage {
-       for _, p := range pl {
-               if p.Name == name {
-                       return p
-               }
-       }
-       return nil
-}
-
-func (pl PluginPackages) GetAllVersions(name string) PluginVersions {
-       result := make(PluginVersions, 0)
-       p := pl.Get(name)
-       if p != nil {
-               for _, v := range p.Versions {
-                       result = append(result, v)
-               }
-       }
-       return result
-}
-
-func (req PluginDependencies) Join(other PluginDependencies) PluginDependencies {
-       m := make(map[string]*PluginDependency)
-       for _, r := range req {
-               m[r.Name] = r
-       }
-       for _, o := range other {
-               cur, ok := m[o.Name]
-               if ok {
-                       m[o.Name] = &PluginDependency{
-                               o.Name,
-                               o.Range.AND(cur.Range),
-                       }
-               } else {
-                       m[o.Name] = o
-               }
-       }
-       result := make(PluginDependencies, 0, len(m))
-       for _, v := range m {
-               result = append(result, v)
-       }
-       return result
-}
-
-// Resolve resolves dependencies between different plugins
-func (all PluginPackages) Resolve(selectedVersions PluginVersions, open PluginDependencies) (PluginVersions, error) {
-       if len(open) == 0 {
-               return selectedVersions, nil
-       }
-       currentRequirement, stillOpen := open[0], open[1:]
-       if currentRequirement != nil {
-               if selVersion := selectedVersions.find(currentRequirement.Name); selVersion != nil {
-                       if currentRequirement.Range(selVersion.Version) {
-                               return all.Resolve(selectedVersions, stillOpen)
-                       }
-                       return nil, fmt.Errorf("unable to find a matching version for \"%s\"", currentRequirement.Name)
-               }
-               availableVersions := all.GetAllVersions(currentRequirement.Name)
-               sort.Sort(availableVersions)
-
-               for _, version := range availableVersions {
-                       if currentRequirement.Range(version.Version) {
-                               resolved, err := all.Resolve(append(selectedVersions, version), stillOpen.Join(version.Require))
-
-                               if err == nil {
-                                       return resolved, nil
-                               }
-                       }
-               }
-               return nil, fmt.Errorf("unable to find a matching version for \"%s\"", currentRequirement.Name)
-       }
-       return selectedVersions, nil
-}
-
-func (pv PluginVersions) install() {
-       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("Uninstalling", 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()
-}
diff --git a/cmd/micro/pluginmanager_test.go b/cmd/micro/pluginmanager_test.go
deleted file mode 100644 (file)
index e7ef5e9..0000000
+++ /dev/null
@@ -1,56 +0,0 @@
-package main
-
-import (
-       "testing"
-
-       "github.com/blang/semver"
-
-       "github.com/flynn/json5"
-)
-
-func TestDependencyResolving(t *testing.T) {
-       js := `
-[{
-  "Name": "Foo",
-  "Versions": [{ "Version": "1.0.0" }, { "Version": "1.5.0" },{ "Version": "2.0.0" }]
-}, {
-  "Name": "Bar",
-  "Versions": [{ "Version": "1.0.0", "Require": {"Foo": ">1.0.0 <2.0.0"} }]
-}, {
-  "Name": "Unresolvable",
-  "Versions": [{ "Version": "1.0.0", "Require": {"Foo": "<=1.0.0", "Bar": ">0.0.0"} }]
-       }]
-`
-       var all PluginPackages
-       err := json5.Unmarshal([]byte(js), &all)
-       if err != nil {
-               t.Error(err)
-       }
-       selected, err := all.Resolve(PluginVersions{}, PluginDependencies{
-               &PluginDependency{"Bar", semver.MustParseRange(">=1.0.0")},
-       })
-
-       check := func(name, version string) {
-               v := selected.find(name)
-               expected := semver.MustParse(version)
-               if v == nil {
-                       t.Errorf("Failed to resolve %s", name)
-               } else if expected.NE(v.Version) {
-                       t.Errorf("%s resolved in wrong version %v", name, v)
-               }
-       }
-
-       if err != nil {
-               t.Error(err)
-       } else {
-               check("Foo", "1.5.0")
-               check("Bar", "1.0.0")
-       }
-
-       selected, err = all.Resolve(PluginVersions{}, PluginDependencies{
-               &PluginDependency{"Unresolvable", semver.MustParseRange(">0.0.0")},
-       })
-       if err == nil {
-               t.Error("Unresolvable package resolved:", selected)
-       }
-}
diff --git a/cmd/micro/profile.go b/cmd/micro/profile.go
new file mode 100644 (file)
index 0000000..86ac558
--- /dev/null
@@ -0,0 +1,14 @@
+package main
+
+import (
+       "fmt"
+       "runtime"
+
+       humanize "github.com/dustin/go-humanize"
+)
+
+func GetMemStats() string {
+       var memstats runtime.MemStats
+       runtime.ReadMemStats(&memstats)
+       return fmt.Sprintf("Alloc: %s, Sys: %s, GC: %d, PauseTotalNs: %dns", humanize.Bytes(memstats.Alloc), humanize.Bytes(memstats.Sys), memstats.NumGC, memstats.PauseTotalNs)
+}
index a72b6ee35b1299c697a3867e9693cd5f7fac2193..947c4e13dee20766272027c6a36d1df2e7183933 100644 (file)
@@ -8,12 +8,15 @@ import (
 )
 
 const (
-       RTColorscheme = "colorscheme"
-       RTSyntax      = "syntax"
-       RTHelp        = "help"
-       RTPlugin      = "plugin"
+       RTColorscheme = 0
+       RTSyntax      = 1
+       RTHelp        = 2
+       RTPlugin      = 3
+       NumTypes      = 4 // How many filetypes are there
 )
 
+type RTFiletype byte
+
 // 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
@@ -23,7 +26,7 @@ type RuntimeFile interface {
 }
 
 // allFiles contains all available files, mapped by filetype
-var allFiles map[string][]RuntimeFile
+var allFiles [NumTypes][]RuntimeFile
 
 // some file on filesystem
 type realFile string
@@ -73,16 +76,13 @@ func (nf namedFile) Name() string {
 }
 
 // AddRuntimeFile registers a file for the given filetype
-func AddRuntimeFile(fileType string, file RuntimeFile) {
-       if allFiles == nil {
-               allFiles = make(map[string][]RuntimeFile)
-       }
+func AddRuntimeFile(fileType RTFiletype, file 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) {
+func AddRuntimeFilesFromDirectory(fileType RTFiletype, directory, pattern string) {
        files, _ := ioutil.ReadDir(directory)
        for _, f := range files {
                if ok, _ := filepath.Match(pattern, f.Name()); !f.IsDir() && ok {
@@ -94,7 +94,7 @@ func AddRuntimeFilesFromDirectory(fileType, directory, pattern string) {
 
 // AddRuntimeFilesFromAssets registers each file from the given asset-directory for
 // the filetype which matches the file-pattern
-func AddRuntimeFilesFromAssets(fileType, directory, pattern string) {
+func AddRuntimeFilesFromAssets(fileType RTFiletype, directory, pattern string) {
        files, err := AssetDir(directory)
        if err != nil {
                return
@@ -108,7 +108,7 @@ func AddRuntimeFilesFromAssets(fileType, directory, pattern string) {
 
 // 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 {
+func FindRuntimeFile(fileType RTFiletype, name string) RuntimeFile {
        for _, f := range ListRuntimeFiles(fileType) {
                if f.Name() == name {
                        return f
@@ -118,16 +118,13 @@ func FindRuntimeFile(fileType, name string) RuntimeFile {
 }
 
 // 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{}
+func ListRuntimeFiles(fileType RTFiletype) []RuntimeFile {
+       return allFiles[fileType]
 }
 
 // InitRuntimeFiles initializes all assets file and the config directory
 func InitRuntimeFiles() {
-       add := func(fileType, dir, pattern string) {
+       add := func(fileType RTFiletype, dir, pattern string) {
                AddRuntimeFilesFromDirectory(fileType, filepath.Join(configDir, dir), pattern)
                AddRuntimeFilesFromAssets(fileType, path.Join("runtime", dir), pattern)
        }
@@ -160,7 +157,7 @@ func InitRuntimeFiles() {
 }
 
 // PluginReadRuntimeFile allows plugin scripts to read the content of a runtime file
-func PluginReadRuntimeFile(fileType, name string) string {
+func PluginReadRuntimeFile(fileType RTFiletype, name string) string {
        if file := FindRuntimeFile(fileType, name); file != nil {
                if data, err := file.Data(); err == nil {
                        return string(data)
@@ -170,7 +167,7 @@ func PluginReadRuntimeFile(fileType, name string) string {
 }
 
 // PluginListRuntimeFiles allows plugins to lists all runtime files of the given type
-func PluginListRuntimeFiles(fileType string) []string {
+func PluginListRuntimeFiles(fileType RTFiletype) []string {
        files := ListRuntimeFiles(fileType)
        result := make([]string, len(files))
        for i, f := range files {
@@ -180,7 +177,7 @@ func PluginListRuntimeFiles(fileType string) []string {
 }
 
 // PluginAddRuntimeFile adds a file to the runtime files for a plugin
-func PluginAddRuntimeFile(plugin, filetype, filePath string) {
+func PluginAddRuntimeFile(plugin string, filetype RTFiletype, filePath string) {
        fullpath := filepath.Join(configDir, "plugins", plugin, filePath)
        if _, err := os.Stat(fullpath); err == nil {
                AddRuntimeFile(filetype, realFile(fullpath))
@@ -191,7 +188,7 @@ func PluginAddRuntimeFile(plugin, filetype, filePath string) {
 }
 
 // PluginAddRuntimeFilesFromDirectory adds files from a directory to the runtime files for a plugin
-func PluginAddRuntimeFilesFromDirectory(plugin, filetype, directory, pattern string) {
+func PluginAddRuntimeFilesFromDirectory(plugin string, filetype RTFiletype, directory, pattern string) {
        fullpath := filepath.Join(configDir, "plugins", plugin, directory)
        if _, err := os.Stat(fullpath); err == nil {
                AddRuntimeFilesFromDirectory(filetype, fullpath, pattern)
@@ -202,6 +199,6 @@ func PluginAddRuntimeFilesFromDirectory(plugin, filetype, directory, pattern str
 }
 
 // PluginAddRuntimeFileFromMemory adds a file to the runtime files for a plugin from a given string
-func PluginAddRuntimeFileFromMemory(plugin, filetype, filename, data string) {
+func PluginAddRuntimeFileFromMemory(plugin string, filetype RTFiletype, filename, data string) {
        AddRuntimeFile(filetype, memoryFile{filename, []byte(data)})
 }
diff --git a/cmd/micro/rtfiles_test.go b/cmd/micro/rtfiles_test.go
new file mode 100644 (file)
index 0000000..f3a8b0d
--- /dev/null
@@ -0,0 +1,42 @@
+package main
+
+import (
+       "testing"
+
+       "github.com/stretchr/testify/assert"
+)
+
+func init() {
+       InitRuntimeFiles()
+}
+
+func TestAddFile(t *testing.T) {
+       AddRuntimeFile(RTPlugin, memoryFile{"foo.lua", []byte("hello world\n")})
+       AddRuntimeFile(RTSyntax, memoryFile{"bar", []byte("some syntax file\n")})
+
+       f1 := FindRuntimeFile(RTPlugin, "foo.lua")
+       assert.NotNil(t, f1)
+       assert.Equal(t, "foo.lua", f1.Name())
+       data, err := f1.Data()
+       assert.Nil(t, err)
+       assert.Equal(t, []byte("hello world\n"), data)
+
+       f2 := FindRuntimeFile(RTSyntax, "bar")
+       assert.NotNil(t, f2)
+       assert.Equal(t, "bar", f2.Name())
+       data, err = f2.Data()
+       assert.Nil(t, err)
+       assert.Equal(t, []byte("some syntax file\n"), data)
+}
+
+func TestFindFile(t *testing.T) {
+       f := FindRuntimeFile(RTSyntax, "go")
+       assert.NotNil(t, f)
+       assert.Equal(t, "go", f.Name())
+       data, err := f.Data()
+       assert.Nil(t, err)
+       assert.Equal(t, []byte("filetype: go"), data[:12])
+
+       e := FindRuntimeFile(RTSyntax, "foobar")
+       assert.Nil(t, e)
+}
index a85f317d4563eecaf7a8a8ea4a13554f70c608eb..94741c4e2f12c7d53b1df7c877b2aef902cf987c 100644 (file)
@@ -3176,7 +3176,7 @@ func runtimeSyntaxSedYaml() (*asset, error) {
        return a, nil
 }
 
-var _runtimeSyntaxShYaml = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x94\x54\x7f\x73\x1b\x45\x0c\xfd\x3f\x9f\xe2\xea\x64\xa8\xdd\x62\xd3\x96\xb6\x03\xe6\x87\x29\x2d\x30\x9d\x02\xed\x0c\x30\x93\x21\x9b\x96\xf5\xae\xce\xb7\xdc\xfe\xb8\xee\xea\xe2\x84\xbe\x7c\x77\x46\x67\x3b\x69\x4d\xa7\x40\x26\xb7\x3a\x49\x96\xf4\xa4\x7d\xa7\xda\x79\xe2\x8b\x8e\xe6\x55\x69\xc8\xfb\x83\x03\x4b\x4c\x86\xe7\x07\x55\x55\x55\xe2\x8c\x3a\xd0\xbc\x1a\x8d\x95\x9a\x95\xe6\x08\x4a\xcd\x96\xba\x34\x22\xb7\x42\xd4\x6c\xb0\x15\x5b\xc3\x2b\xed\x9d\x2e\x54\xf0\x8e\xb2\x73\xd6\x7d\x34\xec\x52\xdc\xba\xaf\xd5\xdd\x0f\xba\x9c\xa4\x36\xde\x51\x94\x9a\xed\x5e\x77\xf2\x45\xbb\xda\xe8\xed\x2a\xb4\x33\x93\x62\x7d\xe5\xca\x66\xa3\xbf\x78\xf6\xc3\xb7\xbf\x3d\xfd\xf1\x09\x66\xb4\xec\x9d\xb7\x4a\x1d\xe1\xd1\x8b\x67\x83\x6d\x32\x1a\xda\x6c\x48\x5b\xca\xf3\x6a\xf4\xf2\xf0\xc6\xec\xd6\x27\x63\x8a\x67\xd5\xed\xc9\x62\xbc\xd4\x93\xc5\x58\x4f\x16\xa5\x19\x57\x38\x9a\x8c\x0e\x0e\x72\xef\xa9\x6c\x66\x73\x58\xfd\xdc\x87\x25\xe5\x32\x68\xd3\xca\xa4\x58\x58\x47\x9e\xc5\xc1\x3c\xaf\x46\x4a\x2d\x4f\xee\x4c\x3f\x3f\xbd\xad\xd4\x72\x53\x48\xa2\x1e\xa7\x68\x9d\x74\xab\x7d\xa9\x74\xb4\x12\xc8\x39\xf9\xaa\xf6\x69\xbd\xcd\x55\x58\x33\x05\x8a\xbc\xc9\x32\x36\xba\x10\x6c\x82\x4d\x91\x40\xde\xd5\x20\x5f\x08\x54\xb4\x01\x9d\x3b\x46\xed\x50\xa7\x8c\xdd\x20\xe1\x6a\xb8\x08\x9f\x8c\xf6\xc8\xa4\x2d\x32\x71\x9f\x23\x0a\x79\x32\x8c\xd2\xb8\x9a\xc1\x0d\x45\xb0\x0b\x84\x3e\xb2\xf3\x58\x37\xce\xd3\xe4\x0a\xec\xb4\x2a\x1d\x19\xa7\xfd\xe6\xf6\xdf\x40\xa9\x4b\x28\x35\x86\x52\x13\x28\xf5\x05\x94\x3a\x85\x52\x27\xf8\x03\x4a\x29\x05\x19\xec\x97\xf8\x1a\x37\xf0\x15\x3e\x82\x52\x98\x5c\x37\xfd\x8b\x70\xab\x32\x29\x04\x1d\xed\x6e\x62\x1b\xda\x6d\x1a\xb4\x20\xd3\x24\xd0\x79\x97\x32\xc3\x13\xa3\x10\xa3\x0f\xba\xb4\xe8\x63\x21\x9e\xec\x0d\x31\x84\x14\x2b\xef\x62\x7f\xfe\x81\xb4\xe3\x15\xdc\x6a\xb2\xd0\xeb\x76\x60\x12\xac\x1c\xb5\x8b\x16\x4a\xad\xdf\xdc\xf9\xf8\xfe\xe5\x2a\x53\x87\xd6\x79\x3f\x1c\xda\xfb\x2b\x8f\xa7\x52\x10\x74\x2b\xe4\x12\x7f\x21\x8b\xd2\x80\x75\xde\x87\x92\xa9\x67\xe7\xcb\x07\x70\x2c\x75\xa1\x87\xf7\x05\xc3\xf0\x39\xc1\x68\x86\x69\x4c\x8a\x30\xcd\x2a\x77\x30\x4d\x48\x16\xa6\x49\x6b\xb1\xe4\x94\x18\xa6\x2d\x7d\x80\xa4\x84\xe9\x60\x4a\xe7\x1d\xc3\xf4\x0c\xab\x99\x60\x2d\x6c\x0d\xeb\xb2\x3c\x26\xf9\x94\x8b\xbc\x0d\xc9\x6d\x0f\x8a\x67\x32\x4a\x1d\xad\x88\x8c\x5a\x1b\x16\x7a\x68\x61\x4d\x1d\x18\x75\xf2\x16\x42\x7b\x34\xa9\xb0\xb3\x90\x7f\xe1\xaf\xf7\xf8\x33\x09\x75\x5c\x6c\xe1\x85\x42\xab\x21\xa9\x2f\x08\xf6\x81\x40\x0a\xad\x94\x0d\x6d\xed\xea\x84\xd0\xc6\x64\x11\x5a\xa6\xd0\x21\x9c\x21\x3a\x43\x88\x1e\x31\x35\x7d\x87\xd8\xe5\x64\x10\xfb\x20\x25\x93\x45\xa7\x0b\x13\x3a\xcd\x8d\x69\x5a\x74\x2e\xb6\x17\xe8\x32\xba\xec\x22\x0b\xe4\xe1\xa5\x46\xc7\xe7\xe8\xd6\x76\x20\xee\x80\x23\x93\xf6\x12\x85\x1c\x90\x83\x94\xcf\x7d\x94\xe9\x15\x7a\x8d\x71\x69\xf4\x5d\x94\x46\xdf\xbb\x77\x7f\x10\x0f\x1e\x8a\xf8\xf4\xb3\x41\x7b\x70\xf7\xde\x44\x50\x97\x26\x0f\xf7\xd7\xd7\x28\x9e\xa8\x43\x11\x9e\x6d\xa6\x2a\x5f\x1b\x0a\xdb\xa5\x38\x99\x2f\x30\x04\x5c\x44\x03\xd6\xf2\x38\x0f\x26\x02\x53\xe1\xcd\xe7\x22\x47\xea\x19\x9c\x7a\xd3\x80\x33\x38\xf7\x24\x47\x34\x72\x39\x3c\xe4\x96\x44\xfd\x30\xbb\x3e\x6e\xef\xa2\x8f\xee\x35\xfa\x38\xf4\xd4\x17\xca\x05\x67\xd2\xcc\xda\x60\xdd\x24\x79\x74\x70\xb8\xa0\xb2\x4f\xb0\xab\x85\x51\xd5\x5e\xaf\xca\x7b\x96\xc4\x74\x7a\xa2\xa7\x7f\x4d\x4f\x6f\x8f\xde\xbb\x41\xaa\xc1\x2d\xde\xad\xdb\x59\x8a\xec\x6a\xb7\xdd\x53\x47\x4a\xbd\x59\xc8\xae\x7a\x34\xfd\xfd\xd5\x8d\x6f\x0e\x8f\x6e\x2d\xa6\xb2\xb6\x2e\x17\xa3\xff\x1f\xb0\xbf\x0f\x0b\x67\x17\x57\xf3\x6d\x3f\x95\x40\xcb\x03\xac\xd1\xe8\xca\x46\xd1\xee\x59\x4a\xeb\xba\xa1\x94\x52\xb3\x6b\xeb\x5b\xfb\x77\xf7\xf7\x76\xa5\xcd\xbe\x7a\xdc\xe8\x7c\x1d\xfa\x5f\xe1\xdc\xdc\x47\x73\x73\xbf\x6c\x75\x72\x7a\x9d\x2d\x0c\xc3\xfd\x47\x96\xf1\x4b\x28\x55\x26\x87\xfb\xc9\x8e\xfe\xa5\x07\x4e\x36\x49\xf8\xaf\xcf\x9f\x3c\xc7\xf1\xf1\x31\xbe\x7f\x7a\xfc\xd3\x77\x93\xb9\xcc\xf3\xef\x00\x00\x00\xff\xff\xef\x45\x76\x90\xa3\x07\x00\x00")
+var _runtimeSyntaxShYaml = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x94\x54\x7f\x73\x1b\x45\x0c\xfd\x3f\x9f\xe2\xea\x64\xa8\xdd\x62\xd3\x96\xb6\x03\xe6\x87\x29\x2d\x30\x9d\x02\xed\x0c\x30\x93\x21\x9b\x96\xf5\xae\xce\xb7\xdc\xfe\xb8\xee\xea\xe2\x84\xbe\x7c\x77\x46\x67\x3b\x69\x4d\xa7\x40\x26\xb7\x3a\x49\x96\xf4\xa4\x7d\xa7\xda\x79\xe2\x8b\x8e\xe6\x55\x69\xc8\xfb\x83\x03\x4b\x4c\x86\xe7\x07\x55\x55\x55\xe2\x8c\x3a\xd0\xbc\x1a\x8d\x95\x9a\x95\xe6\x08\x4a\xcd\x96\xba\x34\x3b\x99\x0d\xb6\x62\x6b\x78\xa5\xbd\xd3\x85\x0a\xde\x51\x76\xce\xba\x8f\x86\x5d\x8a\x5b\xf7\xb5\xba\xfb\x41\x97\x93\x14\xc5\x3b\xca\x8b\x76\x35\xc8\xae\x5d\x85\x76\x66\x52\xac\xb1\x73\x65\xb3\xd1\x5f\x3c\xfb\xe1\xdb\xdf\x9e\xfe\xf8\x04\x33\x5a\xf6\xce\x5b\xa5\x8e\xf0\xe8\xc5\xb3\xc1\x36\x19\x0d\xcd\x34\xa4\x2d\xe5\x79\x35\x7a\x79\x78\x63\x76\xeb\x93\x31\xc5\xb3\xea\xf6\x64\x31\x5e\xea\xc9\xa2\x34\xe3\x0a\x47\x93\xd1\xc1\x41\xee\x3d\x95\x4d\xf7\x87\xd5\xcf\x7d\x58\x52\x2e\x83\x36\xad\x4c\x8a\x85\x75\xe4\x59\x1c\xcc\xf3\x6a\xa4\xd4\xf2\xe4\xce\xf4\xf3\xd3\xdb\x4a\x2d\x37\x45\x24\xea\x71\x8a\xd6\x49\x5b\xda\x97\x4a\x47\x2b\x81\x9c\x93\xaf\x6a\x9f\xd6\xdb\x5c\x85\x35\x53\xa0\xc8\x9b\x2c\x63\xa3\x0b\xc1\x26\xd8\x14\x09\xe4\x5d\x0d\xf2\x85\x40\x45\x1b\xd0\xb9\x63\xd4\x0e\x75\xca\xd8\x4d\x0c\xae\x86\x8b\xf0\xc9\x68\x8f\x4c\xda\x22\x13\xf7\x39\xa2\x90\x27\xc3\x28\x8d\xab\x19\xdc\x50\x04\xbb\x40\xe8\x23\x3b\x8f\x75\xe3\x3c\x4d\xae\xc0\x4e\xab\xd2\x91\x71\xda\x6f\xee\xf7\x0d\x94\xba\x84\x52\x63\x28\x35\x81\x52\x5f\x40\xa9\x53\x28\x75\x82\x3f\xa0\x94\x52\x90\xa1\x7e\x89\xaf\x71\x03\x5f\xe1\x23\x28\x85\xc9\x75\xd3\xbf\x08\x7b\x2a\x93\x42\xd0\xd1\xee\x26\xb6\x21\xd6\xa6\x41\x0b\x32\x4d\x02\x9d\x77\x29\x33\x3c\x31\x0a\x31\xfa\xa0\x4b\x8b\x3e\x16\xe2\xc9\xde\x10\x43\x48\xb1\xf2\x2e\xf6\xe7\x1f\x48\x3b\x5e\xc1\xad\x26\x0b\xbd\x6e\x07\xca\xc0\xca\x51\xbb\x68\xa1\xd4\xfa\xcd\x9d\x8f\xef\x5f\xae\x32\x75\x68\x9d\xf7\xc3\xa1\xbd\xbf\xf2\x78\x2a\x05\x41\xb7\x42\x2c\xf1\x17\xb2\x28\x0d\x58\xe7\x7d\x28\x99\x7a\x76\xbe\x7c\x00\xc7\x52\x17\x7a\x78\x5f\x30\x0c\x1f\x0c\x8c\x66\x98\xc6\xa4\x08\xd3\xac\x72\x07\xd3\x84\x64\x61\x9a\xb4\x16\x4b\x4e\x89\x61\xda\xd2\x07\x48\x4a\x98\x0e\xa6\x74\xde\x31\x4c\xcf\xb0\x9a\x09\xd6\xc2\xd6\xb0\x2e\xcb\x63\x92\x4f\xb9\xc8\xdb\x90\xdc\xf6\xa0\x78\x26\xa3\xd4\xd1\x8a\xc8\xa8\xb5\x61\xa1\x87\x16\xd6\xd4\x81\x51\x27\x6f\x21\x94\x47\x93\x0a\x3b\x0b\xf9\x17\xfe\x7a\x8f\x3f\x93\x50\xc7\xc5\x16\x5e\x28\xb4\x1a\x92\xfa\x82\x60\x1f\x08\xa4\xd0\x4a\xd9\xd0\xd6\xae\x4e\x08\x6d\x4c\x16\xa1\x65\x0a\x1d\xc2\x19\xa2\x33\x84\xe8\x11\x53\xd3\x77\x88\x5d\x4e\x06\xb1\x0f\x52\x32\x59\x74\xba\x30\xa1\xd3\xdc\x98\xa6\x45\xe7\x62\x7b\x81\x2e\xa3\xcb\x2e\xb2\x40\x1e\x5e\x6a\x74\x7c\x8e\x6e\x6d\x07\xe2\x0e\x38\x32\x69\x2f\x51\xc8\x01\x39\x48\xf9\xdc\x47\x99\x5e\xa1\xd7\x18\x97\x46\xdf\x45\x69\xf4\xbd\x7b\xf7\x07\xf1\xe0\xa1\x88\x4f\x3f\x1b\xb4\x07\x77\xef\x4d\x04\x75\x69\xf2\x70\x7f\x7d\x8d\xe2\x89\x3a\x14\xe1\xd9\x66\xaa\xf2\xb5\xa1\xb0\x5d\x8a\x93\xf9\x02\x43\xc0\x45\x34\x60\x2d\x8f\xf3\x60\x22\x30\x15\xde\x7c\x2e\x72\xa4\x9e\xc1\xa9\x37\x0d\x38\x83\x73\x4f\x72\x44\x23\x97\xc3\x43\x6e\x49\xd4\x0f\xb3\xeb\xe3\xf6\x2e\xfa\xe8\x5e\xa3\x8f\x43\x4f\x7d\xa1\x5c\x70\x26\xcd\xac\x0d\xd6\x4d\x92\x47\x07\x87\x0b\x2a\xfb\x04\xbb\x5a\x18\x55\xed\xf5\xaa\xbc\x67\x49\x4c\xa7\x27\x7a\xfa\xd7\xf4\xf4\xf6\xe8\xbd\x1b\xa4\x1a\xdc\xe2\xdd\xba\x9d\xa5\xc8\xae\x76\xdb\x3d\x75\xa4\xd4\x9b\x85\xec\xaa\x47\xd3\xdf\x5f\xdd\xf8\xe6\xf0\xe8\xd6\x62\x2a\x6b\xeb\x72\x31\xfa\xff\x01\xfb\xfb\xb0\x70\x76\x71\x35\xdf\xf6\x53\x09\xb4\x3c\xc0\x1a\x8d\xae\x6c\x14\xed\x9e\xa5\xb4\xae\x1b\x4a\x29\x35\xbb\xb6\xbe\xb5\x7f\x77\x7f\x6f\x57\xda\xec\xab\xc7\x8d\xce\xd7\xa1\xff\x15\xce\xcd\x7d\x34\x37\xf7\xcb\x56\x27\xa7\xd7\xd9\xc2\x30\xdc\x7f\x64\x19\xbf\x84\x52\x65\x72\xb8\x9f\xec\xe8\x5f\x7a\xe0\x64\x93\x84\xff\xfa\xfc\xc9\x73\x1c\x1f\x1f\xe3\xfb\xa7\xc7\x3f\x7d\x37\x99\xcb\x3c\xff\x0e\x00\x00\xff\xff\x42\xc3\x8d\xb7\x85\x07\x00\x00")
 
 func runtimeSyntaxShYamlBytes() ([]byte, error) {
        return bindataRead(
diff --git a/cmd/micro/scrollbar.go b/cmd/micro/scrollbar.go
deleted file mode 100644 (file)
index f4a9a07..0000000
+++ /dev/null
@@ -1,20 +0,0 @@
-package main
-
-// ScrollBar represents an optional scrollbar that can be used
-type ScrollBar struct {
-       view *View
-}
-
-// Display shows the scrollbar
-func (sb *ScrollBar) Display() {
-       style := defStyle.Reverse(true)
-       screen.SetContent(sb.view.x+sb.view.Width-1, sb.view.y+sb.pos(), ' ', nil, style)
-}
-
-func (sb *ScrollBar) pos() int {
-       numlines := sb.view.Buf.NumLines
-       h := sb.view.Height
-       filepercent := float32(sb.view.Topline) / float32(numlines)
-
-       return int(filepercent * float32(h))
-}
diff --git a/cmd/micro/search.go b/cmd/micro/search.go
deleted file mode 100644 (file)
index 7139f84..0000000
+++ /dev/null
@@ -1,214 +0,0 @@
-package main
-
-import (
-       "regexp"
-       "strings"
-
-       "github.com/zyedidia/tcell"
-)
-
-var (
-       // What was the last search
-       lastSearch string
-
-       // Where should we start the search down from (or up from)
-       searchStart Loc
-
-       // Is there currently a search in progress
-       searching bool
-
-       // Stores the history for searching
-       searchHistory []string
-)
-
-// BeginSearch starts a search
-func BeginSearch(searchStr string) {
-       searchHistory = append(searchHistory, "")
-       messenger.historyNum = len(searchHistory) - 1
-       searching = true
-       messenger.response = searchStr
-       messenger.cursorx = Count(searchStr)
-       messenger.Message("Find: ")
-       messenger.hasPrompt = true
-}
-
-// EndSearch stops the current search
-func EndSearch() {
-       searchHistory[len(searchHistory)-1] = messenger.response
-       searching = false
-       messenger.hasPrompt = false
-       messenger.Clear()
-       messenger.Reset()
-       if lastSearch != "" {
-               messenger.Message("^P Previous ^N Next")
-       }
-}
-
-// ExitSearch exits 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.KeyEnter:
-                       // If the user has pressed Enter, they want this to be the lastSearch
-                       lastSearch = messenger.response
-                       EndSearch()
-                       return
-               case tcell.KeyCtrlQ, tcell.KeyCtrlC:
-                       // Done
-                       EndSearch()
-                       return
-               }
-       }
-
-       messenger.HandleEvent(event, searchHistory)
-
-       if messenger.cursorx < 0 {
-               // Done
-               EndSearch()
-               return
-       }
-
-       if messenger.response == "" {
-               v.Cursor.ResetSelection()
-               // We don't end the search though
-               return
-       }
-
-       Search(messenger.response, v, true)
-
-       v.Relocate()
-
-       return
-}
-
-func searchDown(r *regexp.Regexp, v *View, start, end Loc) bool {
-       if start.Y >= v.Buf.NumLines {
-               start.Y = v.Buf.NumLines - 1
-       }
-       if start.Y < 0 {
-               start.Y = 0
-       }
-       for i := start.Y; i <= end.Y; i++ {
-               var l []byte
-               var charPos int
-               if i == start.Y {
-                       runes := []rune(string(v.Buf.lines[i].data))
-                       if start.X >= len(runes) {
-                               start.X = len(runes) - 1
-                       }
-                       if start.X < 0 {
-                               start.X = 0
-                       }
-                       l = []byte(string(runes[start.X:]))
-                       charPos = start.X
-
-                       if strings.Contains(r.String(), "^") && start.X != 0 {
-                               continue
-                       }
-               } else {
-                       l = v.Buf.lines[i].data
-               }
-
-               match := r.FindIndex(l)
-
-               if match != nil {
-                       v.Cursor.SetSelectionStart(Loc{charPos + runePos(match[0], string(l)), i})
-                       v.Cursor.SetSelectionEnd(Loc{charPos + runePos(match[1], string(l)), i})
-                       v.Cursor.OrigSelection[0] = v.Cursor.CurSelection[0]
-                       v.Cursor.OrigSelection[1] = v.Cursor.CurSelection[1]
-                       v.Cursor.Loc = v.Cursor.CurSelection[1]
-
-                       return true
-               }
-       }
-       return false
-}
-
-func searchUp(r *regexp.Regexp, v *View, start, end Loc) bool {
-       if start.Y >= v.Buf.NumLines {
-               start.Y = v.Buf.NumLines - 1
-       }
-       if start.Y < 0 {
-               start.Y = 0
-       }
-       for i := start.Y; i >= end.Y; i-- {
-               var l []byte
-               if i == start.Y {
-                       runes := []rune(string(v.Buf.lines[i].data))
-                       if start.X >= len(runes) {
-                               start.X = len(runes) - 1
-                       }
-                       if start.X < 0 {
-                               start.X = 0
-                       }
-                       l = []byte(string(runes[:start.X]))
-
-                       if strings.Contains(r.String(), "$") && start.X != Count(string(l)) {
-                               continue
-                       }
-               } else {
-                       l = v.Buf.lines[i].data
-               }
-
-               match := r.FindIndex(l)
-
-               if match != nil {
-                       v.Cursor.SetSelectionStart(Loc{runePos(match[0], string(l)), i})
-                       v.Cursor.SetSelectionEnd(Loc{runePos(match[1], string(l)), i})
-                       v.Cursor.OrigSelection[0] = v.Cursor.CurSelection[0]
-                       v.Cursor.OrigSelection[1] = v.Cursor.CurSelection[1]
-                       v.Cursor.Loc = v.Cursor.CurSelection[1]
-
-                       return true
-               }
-       }
-       return false
-}
-
-// Search searches in the view for the given regex. The down bool
-// specifies whether it should search down from the searchStart position
-// or up from there
-func Search(searchStr string, v *View, down bool) {
-       if searchStr == "" {
-               return
-       }
-       r, err := regexp.Compile(searchStr)
-       if v.Buf.Settings["ignorecase"].(bool) {
-               r, err = regexp.Compile("(?i)" + searchStr)
-       }
-       if err != nil {
-               return
-       }
-
-       var found bool
-       if down {
-               found = searchDown(r, v, searchStart, v.Buf.End())
-               if !found {
-                       found = searchDown(r, v, v.Buf.Start(), searchStart)
-               }
-       } else {
-               found = searchUp(r, v, searchStart, v.Buf.Start())
-               if !found {
-                       found = searchUp(r, v, v.Buf.End(), searchStart)
-               }
-       }
-       if !found {
-               v.Cursor.ResetSelection()
-       }
-}
index c8e50e126094dfc4a51af23e552969913d70c5cc..e4ac634462e28c62231572d5d3e118b77699e11d 100644 (file)
@@ -1,16 +1,12 @@
 package main
 
 import (
-       "crypto/md5"
        "encoding/json"
        "errors"
-       "io"
        "io/ioutil"
        "os"
        "reflect"
-       "strconv"
        "strings"
-       "sync"
 
        "github.com/flynn/json5"
        "github.com/zyedidia/glob"
@@ -21,7 +17,8 @@ type optionValidator func(string, interface{}) error
 // The options that the user can set
 var globalSettings map[string]interface{}
 
-var invalidSettings bool
+// This is the raw parsed json
+var parsedSettings map[string]interface{}
 
 // Options with validators
 var optionValidators = map[string]optionValidator{
@@ -33,74 +30,42 @@ var optionValidators = map[string]optionValidator{
        "fileformat":   validateLineEnding,
 }
 
-// InitGlobalSettings initializes the options map and sets all options to their default values
-func InitGlobalSettings() {
-       invalidSettings = false
-       defaults := DefaultGlobalSettings()
-       var parsed map[string]interface{}
-
+func ReadSettings() error {
        filename := configDir + "/settings.json"
-       writeSettings := false
        if _, e := os.Stat(filename); e == nil {
                input, err := ioutil.ReadFile(filename)
+               if err != nil {
+                       return errors.New("Error reading settings.json file: " + err.Error())
+               }
                if !strings.HasPrefix(string(input), "null") {
+                       // Unmarshal the input into the parsed map
+                       err = json5.Unmarshal(input, &parsedSettings)
                        if err != nil {
-                               TermMessage("Error reading settings.json file: " + err.Error())
-                               invalidSettings = true
-                               return
+                               return errors.New("Error reading settings.json: " + err.Error())
                        }
-
-                       err = json5.Unmarshal(input, &parsed)
-                       if err != nil {
-                               TermMessage("Error reading settings.json:", err.Error())
-                               invalidSettings = true
-                       }
-               } else {
-                       writeSettings = true
                }
        }
+       return nil
+}
 
-       globalSettings = make(map[string]interface{})
-       for k, v := range defaults {
-               globalSettings[k] = v
-       }
-       for k, v := range parsed {
+// InitGlobalSettings initializes the options map and sets all options to their default values
+// Must be called after ReadSettings
+func InitGlobalSettings() {
+       globalSettings = DefaultGlobalSettings()
+
+       for k, v := range parsedSettings {
                if !strings.HasPrefix(reflect.TypeOf(v).String(), "map") {
                        globalSettings[k] = v
                }
        }
-
-       if _, err := os.Stat(filename); os.IsNotExist(err) || writeSettings {
-               err := WriteSettings(filename)
-               if err != nil {
-                       TermMessage("Error writing settings.json file: " + err.Error())
-               }
-       }
 }
 
 // InitLocalSettings scans the json in settings.json and sets the options locally based
 // on whether the buffer matches the glob
-func InitLocalSettings(buf *Buffer) {
-       invalidSettings = false
-       var parsed map[string]interface{}
-
-       filename := configDir + "/settings.json"
-       if _, e := os.Stat(filename); e == nil {
-               input, err := ioutil.ReadFile(filename)
-               if err != nil {
-                       TermMessage("Error reading settings.json file: " + err.Error())
-                       invalidSettings = true
-                       return
-               }
-
-               err = json5.Unmarshal(input, &parsed)
-               if err != nil {
-                       TermMessage("Error reading settings.json:", err.Error())
-                       invalidSettings = true
-               }
-       }
-
-       for k, v := range parsed {
+// Must be called after ReadSettings
+func InitLocalSettings(buf *Buffer) error {
+       var parseError error
+       for k, v := range parsedSettings {
                if strings.HasPrefix(reflect.TypeOf(v).String(), "map") {
                        if strings.HasPrefix(k, "ft:") {
                                if buf.Settings["filetype"].(string) == k[3:] {
@@ -111,7 +76,7 @@ func InitLocalSettings(buf *Buffer) {
                        } else {
                                g, err := glob.Compile(k)
                                if err != nil {
-                                       TermMessage("Error with glob setting ", k, ": ", err)
+                                       parseError = errors.New("Error with glob setting " + k + ": " + err.Error())
                                        continue
                                }
 
@@ -123,59 +88,31 @@ func InitLocalSettings(buf *Buffer) {
                        }
                }
        }
+       return parseError
 }
 
 // WriteSettings writes the settings to the specified filename as JSON
 func WriteSettings(filename string) error {
-       if invalidSettings {
-               // Do not write the settings if there was an error when reading them
-               return nil
-       }
-
        var err error
        if _, e := os.Stat(configDir); e == nil {
-               parsed := make(map[string]interface{})
-
-               filename := configDir + "/settings.json"
                for k, v := range globalSettings {
-                       parsed[k] = v
-               }
-               if _, e := os.Stat(filename); e == nil {
-                       input, err := ioutil.ReadFile(filename)
-                       if string(input) != "null" {
-                               if err != nil {
-                                       return err
-                               }
-
-                               err = json5.Unmarshal(input, &parsed)
-                               if err != nil {
-                                       TermMessage("Error reading settings.json:", err.Error())
-                                       invalidSettings = true
-                               }
-
-                               for k, v := range parsed {
-                                       if !strings.HasPrefix(reflect.TypeOf(v).String(), "map") {
-                                               if _, ok := globalSettings[k]; ok {
-                                                       parsed[k] = globalSettings[k]
-                                               }
-                                       }
-                               }
-                       }
+                       parsedSettings[k] = v
                }
 
-               txt, _ := json.MarshalIndent(parsed, "", "    ")
+               txt, _ := json.MarshalIndent(parsedSettings, "", "    ")
                err = ioutil.WriteFile(filename, append(txt, '\n'), 0644)
        }
        return err
 }
 
 // AddOption creates a new option. This is meant to be called by plugins to add options.
-func AddOption(name string, value interface{}) {
+func AddOption(name string, value interface{}) error {
        globalSettings[name] = value
        err := WriteSettings(configDir + "/settings.json")
        if err != nil {
-               TermMessage("Error writing settings.json file: " + err.Error())
+               return errors.New("Error writing settings.json file: " + err.Error())
        }
+       return nil
 }
 
 // GetGlobalOption returns the global value of the given option
@@ -188,25 +125,23 @@ func GetLocalOption(name string, buf *Buffer) interface{} {
        return buf.Settings[name]
 }
 
+// TODO: get option for current buffer
 // GetOption returns the value of the given option
 // If there is a local version of the option, it returns that
 // otherwise it will return the global version
-func GetOption(name string) interface{} {
-       if GetLocalOption(name, CurView().Buf) != nil {
-               return GetLocalOption(name, CurView().Buf)
-       }
-       return GetGlobalOption(name)
-}
-
-// DefaultGlobalSettings returns the default global settings for micro
-// Note that colorscheme is a global only option
-func DefaultGlobalSettings() map[string]interface{} {
+// func GetOption(name string) interface{} {
+//     if GetLocalOption(name, CurView().Buf) != nil {
+//             return GetLocalOption(name, CurView().Buf)
+//     }
+//     return GetGlobalOption(name)
+// }
+
+func DefaultCommonSettings() map[string]interface{} {
        return map[string]interface{}{
                "autoindent":     true,
                "autosave":       false,
                "basename":       false,
                "colorcolumn":    float64(0),
-               "colorscheme":    "default",
                "cursorline":     true,
                "eofnewline":     false,
                "fastdirty":      true,
@@ -214,18 +149,12 @@ func DefaultGlobalSettings() map[string]interface{} {
                "hidehelp":       false,
                "ignorecase":     false,
                "indentchar":     " ",
-               "infobar":        true,
                "keepautoindent": false,
-               "keymenu":        false,
                "matchbrace":     false,
                "matchbraceleft": false,
-               "mouse":          true,
-               "pluginchannels": []string{"https://raw.githubusercontent.com/micro-editor/plugin-channel/master/channel.json"},
-               "pluginrepos":    []string{},
                "rmtrailingws":   false,
                "ruler":          true,
                "savecursor":     false,
-               "savehistory":    true,
                "saveundo":       false,
                "scrollbar":      false,
                "scrollmargin":   float64(3),
@@ -235,244 +164,229 @@ func DefaultGlobalSettings() map[string]interface{} {
                "splitbottom":    true,
                "splitright":     true,
                "statusline":     true,
-               "sucmd":          "sudo",
                "syntax":         true,
                "tabmovement":    false,
                "tabsize":        float64(4),
                "tabstospaces":   false,
-               "termtitle":      false,
                "useprimary":     true,
        }
 }
 
+// DefaultGlobalSettings returns the default global settings for micro
+// Note that colorscheme is a global only option
+func DefaultGlobalSettings() map[string]interface{} {
+       common := DefaultCommonSettings()
+       common["colorscheme"] = "default"
+       common["infobar"] = true
+       common["keymenu"] = false
+       common["mouse"] = true
+       common["pluginchannels"] = []string{"https://raw.githubusercontent.com/micro-editor/plugin-channel/master/channel.json"}
+       common["pluginrepos"] = []string{}
+       common["savehistory"] = true
+       common["sucmd"] = "sudo"
+       common["termtitle"] = false
+       return common
+}
+
 // DefaultLocalSettings returns the default local settings
 // Note that filetype is a local only option
 func DefaultLocalSettings() map[string]interface{} {
-       return map[string]interface{}{
-               "autoindent":     true,
-               "autosave":       false,
-               "basename":       false,
-               "colorcolumn":    float64(0),
-               "cursorline":     true,
-               "eofnewline":     false,
-               "fastdirty":      true,
-               "fileformat":     "unix",
-               "filetype":       "Unknown",
-               "hidehelp":       false,
-               "ignorecase":     false,
-               "indentchar":     " ",
-               "keepautoindent": false,
-               "matchbrace":     false,
-               "matchbraceleft": false,
-               "rmtrailingws":   false,
-               "ruler":          true,
-               "savecursor":     false,
-               "saveundo":       false,
-               "scrollbar":      false,
-               "scrollmargin":   float64(3),
-               "scrollspeed":    float64(2),
-               "softwrap":       false,
-               "smartpaste":     true,
-               "splitbottom":    true,
-               "splitright":     true,
-               "statusline":     true,
-               "syntax":         true,
-               "tabmovement":    false,
-               "tabsize":        float64(4),
-               "tabstospaces":   false,
-               "useprimary":     true,
-       }
+       common := DefaultCommonSettings()
+       common["filetype"] = "Unknown"
+       return common
 }
 
+// TODO: everything else
+
 // SetOption attempts to set the given option to the value
 // By default it will set the option as global, but if the option
 // 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 _, ok := globalSettings[option]; !ok {
-               if _, ok := CurView().Buf.Settings[option]; !ok {
-                       return errors.New("Invalid option")
-               }
-               SetLocalOption(option, value, CurView())
-               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
-       } else if kind == reflect.String {
-               nativeValue = 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")
-       }
-
-       if err := optionIsValid(option, nativeValue); err != nil {
-               return err
-       }
-
-       globalSettings[option] = nativeValue
-
-       if option == "colorscheme" {
-               // LoadSyntaxFiles()
-               InitColorscheme()
-               for _, tab := range tabs {
-                       for _, view := range tab.Views {
-                               view.Buf.UpdateRules()
-                       }
-               }
-       }
-
-       if option == "infobar" || option == "keymenu" {
-               for _, tab := range tabs {
-                       tab.Resize()
-               }
-       }
-
-       if option == "mouse" {
-               if !nativeValue.(bool) {
-                       screen.DisableMouse()
-               } else {
-                       screen.EnableMouse()
-               }
-       }
-
-       if len(tabs) != 0 {
-               if _, ok := CurView().Buf.Settings[option]; ok {
-                       for _, tab := range tabs {
-                               for _, view := range tab.Views {
-                                       SetLocalOption(option, value, view)
-                               }
-                       }
-               }
-       }
-
-       return nil
-}
-
-// SetLocalOption sets the local version of this option
-func SetLocalOption(option, value string, view *View) error {
-       buf := view.Buf
-       if _, ok := buf.Settings[option]; !ok {
-               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
-       } else if kind == reflect.String {
-               nativeValue = 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")
-       }
-
-       if err := optionIsValid(option, nativeValue); err != nil {
-               return err
-       }
-
-       if option == "fastdirty" {
-               // If it is being turned off, we have to hash every open buffer
-               var empty [md5.Size]byte
-               var wg sync.WaitGroup
-
-               for _, tab := range tabs {
-                       for _, v := range tab.Views {
-                               if !nativeValue.(bool) {
-                                       if v.Buf.origHash == empty {
-                                               wg.Add(1)
-
-                                               go func(b *Buffer) { // calculate md5 hash of the file
-                                                       defer wg.Done()
-
-                                                       if file, e := os.Open(b.AbsPath); e == nil {
-                                                               defer file.Close()
-
-                                                               h := md5.New()
-
-                                                               if _, e = io.Copy(h, file); e == nil {
-                                                                       h.Sum(b.origHash[:0])
-                                                               }
-                                                       }
-                                               }(v.Buf)
-                                       }
-                               } else {
-                                       v.Buf.IsModified = v.Buf.Modified()
-                               }
-                       }
-               }
-
-               wg.Wait()
-       }
-
-       buf.Settings[option] = nativeValue
-
-       if option == "statusline" {
-               view.ToggleStatusLine()
-       }
-
-       if option == "filetype" {
-               // LoadSyntaxFiles()
-               InitColorscheme()
-               buf.UpdateRules()
-       }
-
-       if option == "fileformat" {
-               buf.IsModified = true
-       }
-
-       if option == "syntax" {
-               if !nativeValue.(bool) {
-                       buf.ClearMatches()
-               } else {
-                       if buf.highlighter != nil {
-                               buf.highlighter.HighlightStates(buf)
-                       }
-               }
-       }
-
-       return nil
-}
-
-// SetOptionAndSettings sets the given option and saves the option setting to the settings config file
-func SetOptionAndSettings(option, value string) {
-       filename := configDir + "/settings.json"
-
-       err := SetOption(option, value)
-
-       if err != nil {
-               messenger.Error(err.Error())
-               return
-       }
-
-       err = WriteSettings(filename)
-       if err != nil {
-               messenger.Error("Error writing to settings.json: " + err.Error())
-               return
-       }
-}
+// func SetOption(option, value string) error {
+//     if _, ok := globalSettings[option]; !ok {
+//             if _, ok := CurView().Buf.Settings[option]; !ok {
+//                     return errors.New("Invalid option")
+//             }
+//             SetLocalOption(option, value, CurView())
+//             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
+//     } else if kind == reflect.String {
+//             nativeValue = 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")
+//     }
+//
+//     if err := optionIsValid(option, nativeValue); err != nil {
+//             return err
+//     }
+//
+//     globalSettings[option] = nativeValue
+//
+//     if option == "colorscheme" {
+//             // LoadSyntaxFiles()
+//             InitColorscheme()
+//             for _, tab := range tabs {
+//                     for _, view := range tab.Views {
+//                             view.Buf.UpdateRules()
+//                     }
+//             }
+//     }
+//
+//     if option == "infobar" || option == "keymenu" {
+//             for _, tab := range tabs {
+//                     tab.Resize()
+//             }
+//     }
+//
+//     if option == "mouse" {
+//             if !nativeValue.(bool) {
+//                     screen.DisableMouse()
+//             } else {
+//                     screen.EnableMouse()
+//             }
+//     }
+//
+//     if len(tabs) != 0 {
+//             if _, ok := CurView().Buf.Settings[option]; ok {
+//                     for _, tab := range tabs {
+//                             for _, view := range tab.Views {
+//                                     SetLocalOption(option, value, view)
+//                             }
+//                     }
+//             }
+//     }
+//
+//     return nil
+// }
+//
+// // SetLocalOption sets the local version of this option
+// func SetLocalOption(option, value string, view *View) error {
+//     buf := view.Buf
+//     if _, ok := buf.Settings[option]; !ok {
+//             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
+//     } else if kind == reflect.String {
+//             nativeValue = 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")
+//     }
+//
+//     if err := optionIsValid(option, nativeValue); err != nil {
+//             return err
+//     }
+//
+//     if option == "fastdirty" {
+//             // If it is being turned off, we have to hash every open buffer
+//             var empty [md5.Size]byte
+//             var wg sync.WaitGroup
+//
+//             for _, tab := range tabs {
+//                     for _, v := range tab.Views {
+//                             if !nativeValue.(bool) {
+//                                     if v.Buf.origHash == empty {
+//                                             wg.Add(1)
+//
+//                                             go func(b *Buffer) { // calculate md5 hash of the file
+//                                                     defer wg.Done()
+//
+//                                                     if file, e := os.Open(b.AbsPath); e == nil {
+//                                                             defer file.Close()
+//
+//                                                             h := md5.New()
+//
+//                                                             if _, e = io.Copy(h, file); e == nil {
+//                                                                     h.Sum(b.origHash[:0])
+//                                                             }
+//                                                     }
+//                                             }(v.Buf)
+//                                     }
+//                             } else {
+//                                     v.Buf.IsModified = v.Buf.Modified()
+//                             }
+//                     }
+//             }
+//
+//             wg.Wait()
+//     }
+//
+//     buf.Settings[option] = nativeValue
+//
+//     if option == "statusline" {
+//             view.ToggleStatusLine()
+//     }
+//
+//     if option == "filetype" {
+//             // LoadSyntaxFiles()
+//             InitColorscheme()
+//             buf.UpdateRules()
+//     }
+//
+//     if option == "fileformat" {
+//             buf.IsModified = true
+//     }
+//
+//     if option == "syntax" {
+//             if !nativeValue.(bool) {
+//                     buf.ClearMatches()
+//             } else {
+//                     if buf.highlighter != nil {
+//                             buf.highlighter.HighlightStates(buf)
+//                     }
+//             }
+//     }
+//
+//     return nil
+// }
+//
+// // SetOptionAndSettings sets the given option and saves the option setting to the settings config file
+// func SetOptionAndSettings(option, value string) {
+//     filename := configDir + "/settings.json"
+//
+//     err := SetOption(option, value)
+//
+//     if err != nil {
+//             messenger.Error(err.Error())
+//             return
+//     }
+//
+//     err = WriteSettings(filename)
+//     if err != nil {
+//             messenger.Error("Error writing to settings.json: " + err.Error())
+//             return
+//     }
+// }
 
 func optionIsValid(option string, value interface{}) error {
        if validator, ok := optionValidators[option]; ok {
diff --git a/cmd/micro/shell.go b/cmd/micro/shell.go
deleted file mode 100644 (file)
index 3e9e4ac..0000000
+++ /dev/null
@@ -1,129 +0,0 @@
-package main
-
-import (
-       "bytes"
-       "io"
-       "os"
-       "os/exec"
-       "os/signal"
-       "strings"
-
-       "github.com/zyedidia/micro/cmd/micro/shellwords"
-)
-
-// ExecCommand executes a command using exec
-// It returns any output/errors
-func ExecCommand(name string, arg ...string) (string, error) {
-       var err error
-       cmd := exec.Command(name, arg...)
-       outputBytes := &bytes.Buffer{}
-       cmd.Stdout = outputBytes
-       cmd.Stderr = outputBytes
-       err = cmd.Start()
-       if err != nil {
-               return "", err
-       }
-       err = cmd.Wait() // wait for command to finish
-       outstring := outputBytes.String()
-       return outstring, err
-}
-
-// RunShellCommand executes a shell command and returns the output/error
-func RunShellCommand(input string) (string, error) {
-       args, err := shellwords.Split(input)
-       if err != nil {
-               return "", err
-       }
-       inputCmd := args[0]
-
-       return ExecCommand(inputCmd, args[1:]...)
-}
-
-func RunBackgroundShell(input string) {
-       args, err := shellwords.Split(input)
-       if err != nil {
-               messenger.Error(err)
-               return
-       }
-       inputCmd := args[0]
-       messenger.Message("Running...")
-       go func() {
-               output, err := RunShellCommand(input)
-               totalLines := strings.Split(output, "\n")
-
-               if len(totalLines) < 3 {
-                       if err == nil {
-                               messenger.Message(inputCmd, " exited without error")
-                       } else {
-                               messenger.Message(inputCmd, " exited with error: ", err, ": ", output)
-                       }
-               } else {
-                       messenger.Message(output)
-               }
-               // We have to make sure to redraw
-               RedrawAll()
-       }()
-}
-
-func RunInteractiveShell(input string, wait bool, getOutput bool) (string, error) {
-       args, err := shellwords.Split(input)
-       if err != nil {
-               return "", err
-       }
-       inputCmd := args[0]
-
-       // Shut down the screen because we're going to interact directly with the shell
-       screen.Fini()
-       screen = nil
-
-       args = args[1:]
-
-       // Set up everything for the command
-       outputBytes := &bytes.Buffer{}
-       cmd := exec.Command(inputCmd, args...)
-       cmd.Stdin = os.Stdin
-       if getOutput {
-               cmd.Stdout = io.MultiWriter(os.Stdout, outputBytes)
-       } else {
-               cmd.Stdout = os.Stdout
-       }
-       cmd.Stderr = os.Stderr
-
-       // This is a trap for Ctrl-C so that it doesn't kill micro
-       // Instead we trap Ctrl-C to kill the program we're running
-       c := make(chan os.Signal, 1)
-       signal.Notify(c, os.Interrupt)
-       go func() {
-               for range c {
-                       cmd.Process.Kill()
-               }
-       }()
-
-       cmd.Start()
-       err = cmd.Wait()
-
-       output := outputBytes.String()
-
-       if wait {
-               // 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, err
-}
-
-// 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 {
-       if !openTerm {
-               RunBackgroundShell(input)
-               return ""
-       } else {
-               output, _ := RunInteractiveShell(input, waitToFinish, false)
-               return output
-       }
-}
diff --git a/cmd/micro/shell_supported.go b/cmd/micro/shell_supported.go
deleted file mode 100644 (file)
index 215e199..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-// +build linux darwin dragonfly openbsd_amd64 freebsd
-
-package main
-
-import (
-       "github.com/zyedidia/micro/cmd/micro/shellwords"
-)
-
-const TermEmuSupported = true
-
-func RunTermEmulator(input string, wait bool, getOutput bool, callback string) error {
-       args, err := shellwords.Split(input)
-       if err != nil {
-               return err
-       }
-       err = CurView().StartTerminal(args, wait, getOutput, callback)
-       return err
-}
diff --git a/cmd/micro/shell_unsupported.go b/cmd/micro/shell_unsupported.go
deleted file mode 100644 (file)
index 4d07845..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-// +build !linux,!darwin,!freebsd,!dragonfly,!openbsd_amd64
-
-package main
-
-import "errors"
-
-const TermEmuSupported = false
-
-func RunTermEmulator(input string, wait bool, getOutput bool) error {
-       return errors.New("Unsupported operating system")
-}
diff --git a/cmd/micro/split_tree.go b/cmd/micro/split_tree.go
deleted file mode 100644 (file)
index e34c37f..0000000
+++ /dev/null
@@ -1,317 +0,0 @@
-package main
-
-// SplitType specifies whether a split is horizontal or vertical
-type SplitType bool
-
-const (
-       // VerticalSplit type
-       VerticalSplit = false
-       // HorizontalSplit type
-       HorizontalSplit = true
-)
-
-// A Node on the split tree
-type Node interface {
-       VSplit(buf *Buffer, splitIndex int)
-       HSplit(buf *Buffer, splitIndex int)
-       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
-       n.view.splitNode = n
-       n.parent = parent
-       return n
-}
-
-// A SplitTree is a Node itself and it contains other nodes
-type SplitTree struct {
-       kind SplitType
-
-       parent   *SplitTree
-       children []Node
-
-       x int
-       y int
-
-       width      int
-       height     int
-       lockWidth  bool
-       lockHeight bool
-
-       tabNum int
-}
-
-// VSplit creates a vertical split
-func (l *LeafNode) VSplit(buf *Buffer, splitIndex int) {
-       if splitIndex < 0 {
-               splitIndex = 0
-       }
-
-       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
-
-               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 = 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}
-               }
-               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()
-}
-
-// Delete deletes a split
-func (l *LeafNode) Delete() {
-       i := search(l.parent.children, l)
-
-       copy(l.parent.children[i:], l.parent.children[i+1:])
-       l.parent.children[len(l.parent.children)-1] = nil
-       l.parent.children = l.parent.children[:len(l.parent.children)-1]
-
-       tab := tabs[l.parent.tabNum]
-       j := findView(tab.Views, l.view)
-       copy(tab.Views[j:], tab.Views[j+1:])
-       tab.Views[len(tab.Views)-1] = nil // or the zero value of T
-       tab.Views = tab.Views[:len(tab.Views)-1]
-
-       for i, v := range tab.Views {
-               v.Num = i
-       }
-       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 {
-                       if len(n.children) == 1 {
-                               if child, ok := n.children[0].(*LeafNode); ok {
-                                       s.children[i] = child
-                                       child.parent = s
-                                       continue
-                               }
-                       }
-                       n.Cleanup()
-               }
-       }
-}
-
-// ResizeSplits resizes all the splits correctly
-func (s *SplitTree) ResizeSplits() {
-       lockedWidth := 0
-       lockedHeight := 0
-       lockedChildren := 0
-       for _, 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.x = s.x + x
-                               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.y = s.y + y
-                               n.view.x = s.x
-                               y += n.view.Height
-                       }
-                       if n.view.Buf.Settings["statusline"].(bool) {
-                               n.view.Height--
-                       }
-
-                       n.view.ToggleTabbar()
-               } else if n, ok := node.(*SplitTree); ok {
-                       if s.kind == VerticalSplit {
-                               if !n.lockWidth {
-                                       n.width = (s.width - lockedWidth) / (len(s.children) - lockedChildren)
-                               }
-                               n.height = s.height
-
-                               n.x = s.x + x
-                               n.y = s.y
-                               x += n.width
-                       } else {
-                               if !n.lockHeight {
-                                       n.height = (s.height - lockedHeight) / (len(s.children) - lockedChildren)
-                               }
-                               n.width = s.width
-
-                               n.y = s.y + y
-                               n.x = s.x
-                               y += n.height
-                       }
-                       n.ResizeSplits()
-               }
-       }
-}
-
-func (l *LeafNode) String() string {
-       return l.view.Buf.GetName()
-}
-
-func search(haystack []Node, needle Node) int {
-       for i, x := range haystack {
-               if x == needle {
-                       return i
-               }
-       }
-       return 0
-}
-
-func findView(haystack []*View, needle *View) int {
-       for i, x := range haystack {
-               if x == needle {
-                       return i
-               }
-       }
-       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) String() string {
-       str := "["
-       for _, child := range s.children {
-               str += child.String() + ", "
-       }
-       return str + "]"
-}
index 070bd76920d274fbcbb3a632b34d2a988f37666f..bbb6b971dd6a17025fcb896d98f33f1266fceb3f 100644 (file)
@@ -1,7 +1,7 @@
 package main
 
-// Stack is a simple implementation of a LIFO stack for text events
-type Stack struct {
+// TEStack is a simple implementation of a LIFO stack for text events
+type TEStack struct {
        Top  *Element
        Size int
 }
@@ -13,19 +13,19 @@ type Element struct {
 }
 
 // Len returns the stack's length
-func (s *Stack) Len() int {
+func (s *TEStack) Len() int {
        return s.Size
 }
 
 // Push a new element onto the stack
-func (s *Stack) Push(value *TextEvent) {
+func (s *TEStack) Push(value *TextEvent) {
        s.Top = &Element{value, s.Top}
        s.Size++
 }
 
 // Pop removes the top element from the stack and returns its value
 // If the stack is empty, return nil
-func (s *Stack) Pop() (value *TextEvent) {
+func (s *TEStack) Pop() (value *TextEvent) {
        if s.Size > 0 {
                value, s.Top = s.Top.Value, s.Top.Next
                s.Size--
@@ -35,7 +35,7 @@ func (s *Stack) Pop() (value *TextEvent) {
 }
 
 // Peek returns the top element of the stack without removing it
-func (s *Stack) Peek() *TextEvent {
+func (s *TEStack) Peek() *TextEvent {
        if s.Size > 0 {
                return s.Top.Value
        }
diff --git a/cmd/micro/stack_test.go b/cmd/micro/stack_test.go
new file mode 100644 (file)
index 0000000..51e4aa7
--- /dev/null
@@ -0,0 +1,35 @@
+package main
+
+import (
+       "testing"
+       "time"
+
+       "github.com/stretchr/testify/assert"
+)
+
+func TestStack(t *testing.T) {
+       s := new(TEStack)
+       e1 := &TextEvent{
+               EventType: TextEventReplace,
+               Time:      time.Now(),
+       }
+       e2 := &TextEvent{
+               EventType: TextEventInsert,
+               Time:      time.Now(),
+       }
+       s.Push(e1)
+       s.Push(e2)
+
+       p := s.Peek()
+       assert.Equal(t, p.EventType, TextEventInsert)
+       p = s.Pop()
+       assert.Equal(t, p.EventType, TextEventInsert)
+       p = s.Peek()
+       assert.Equal(t, p.EventType, TextEventReplace)
+       p = s.Pop()
+       assert.Equal(t, p.EventType, TextEventReplace)
+       p = s.Pop()
+       assert.Nil(t, p)
+       p = s.Peek()
+       assert.Nil(t, p)
+}
index d346a033852e0fd4a370896fa2b6fdf0f61038aa..e906298591fac0003cb58360ec118ccc132ae174 100644 (file)
 package main
 
 import (
+       "bytes"
+       "fmt"
        "path"
+       "regexp"
        "strconv"
+       "unicode/utf8"
 )
 
-// Statusline represents the information line at the bottom
-// of each view
+// StatusLine represents the information line at the bottom
+// of each window
 // It gives information such as filename, whether the file has been
 // modified, filetype, cursor location
-type Statusline struct {
-       view *View
-}
+type StatusLine struct {
+       FormatLeft  string
+       FormatRight string
+       Info        map[string]func(*Buffer) string
 
-// Display draws the statusline to the screen
-func (sline *Statusline) Display() {
-       if messenger.hasPrompt && !GetGlobalOption("infobar").(bool) {
-               return
-       }
+       win *Window
+}
 
-       // We'll draw the line at the lowest line in the view
-       y := sline.view.Height + sline.view.y
+// TODO: plugin modify status line formatter
 
-       file := sline.view.Buf.GetName()
-       if sline.view.Buf.Settings["basename"].(bool) {
-               file = path.Base(file)
+// NewStatusLine returns a statusline bound to a window
+func NewStatusLine(win *Window) *StatusLine {
+       s := new(StatusLine)
+       // s.FormatLeft = "$(filename) $(modified)($(line),$(col)) $(opt:filetype) $(opt:fileformat)"
+       s.FormatLeft = "$(filename) $(modified)(line,col) $(opt:filetype) $(opt:fileformat)"
+       s.FormatRight = "$(bind:ToggleKeyMenu): show bindings, $(bind:ToggleHelp): open help"
+       s.Info = map[string]func(*Buffer) string{
+               "filename": func(b *Buffer) string {
+                       if b.Settings["basename"].(bool) {
+                               return path.Base(b.GetName())
+                       }
+                       return b.GetName()
+               },
+               "line": func(b *Buffer) string {
+                       return strconv.Itoa(b.GetActiveCursor().Y)
+               },
+               "col": func(b *Buffer) string {
+                       return strconv.Itoa(b.GetActiveCursor().X)
+               },
+               "modified": func(b *Buffer) string {
+                       if b.Modified() {
+                               return "+ "
+                       }
+                       return ""
+               },
        }
+       s.win = win
+       return s
+}
 
-       // If the buffer is dirty (has been modified) write a little '+'
-       if sline.view.Buf.Modified() {
-               file += " +"
+// FindOpt finds a given option in the current buffer's settings
+func (s *StatusLine) FindOpt(opt string) interface{} {
+       if val, ok := s.win.Buf.Settings[opt]; ok {
+               return val
        }
+       return "null"
+}
 
-       // Add one to cursor.x and cursor.y because (0,0) is the top left,
-       // but users will be used to (1,1) (first line,first column)
-       // We use GetVisualX() here because otherwise we get the column number in runes
-       // so a '\t' is only 1, when it should be tabSize
-       columnNum := strconv.Itoa(sline.view.Cursor.GetVisualX() + 1)
-       lineNum := strconv.Itoa(sline.view.Cursor.Y + 1)
-
-       file += " (" + lineNum + "," + columnNum + ")"
+var formatParser = regexp.MustCompile(`\$\(.+?\)`)
 
-       // Add the filetype
-       file += " " + sline.view.Buf.FileType()
+// Display draws the statusline to the screen
+func (s *StatusLine) Display() {
+       // TODO: don't display if infobar off and has message
+       // if !GetGlobalOption("infobar").(bool) {
+       //      return
+       // }
 
-       file += " " + sline.view.Buf.Settings["fileformat"].(string)
+       // We'll draw the line at the lowest line in the window
+       y := s.win.Height + s.win.Y
 
-       rightText := ""
-       if !sline.view.Buf.Settings["hidehelp"].(bool) {
-               if len(kmenuBinding) > 0 {
-                       if globalSettings["keymenu"].(bool) {
-                               rightText += kmenuBinding + ": hide bindings"
-                       } else {
-                               rightText += kmenuBinding + ": show bindings"
-                       }
-               }
-               if len(helpBinding) > 0 {
-                       if len(kmenuBinding) > 0 {
-                               rightText += ", "
-                       }
-                       if sline.view.Type == vtHelp {
-                               rightText += helpBinding + ": close help"
-                       } else {
-                               rightText += helpBinding + ": open help"
-                       }
+       formatter := func(match []byte) []byte {
+               name := match[2 : len(match)-1]
+               if bytes.HasPrefix(name, []byte("opt")) {
+                       option := name[4:]
+                       return []byte(fmt.Sprint(s.FindOpt(string(option))))
+               } else if bytes.HasPrefix(name, []byte("bind")) {
+                       // TODO: status line bindings
+                       return []byte("TODO")
+               } else {
+                       return []byte(s.Info[string(name)](s.win.Buf))
                }
-               rightText += " "
        }
 
+       leftText := []byte(s.FormatLeft)
+       leftText = formatParser.ReplaceAllFunc([]byte(s.FormatLeft), formatter)
+       rightText := []byte(s.FormatRight)
+       rightText = formatParser.ReplaceAllFunc([]byte(s.FormatRight), formatter)
+
        statusLineStyle := defStyle.Reverse(true)
        if style, ok := colorscheme["statusline"]; ok {
                statusLineStyle = style
        }
 
-       // Maybe there is a unicode filename?
-       fileRunes := []rune(file)
-
-       if sline.view.Type == vtTerm {
-               fileRunes = []rune(sline.view.term.title)
-               rightText = ""
-       }
+       leftLen := utf8.RuneCount(leftText)
+       rightLen := utf8.RuneCount(rightText)
 
-       viewX := sline.view.x
-       if viewX != 0 {
-               screen.SetContent(viewX, y, ' ', nil, statusLineStyle)
-               viewX++
-       }
-       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)
+       winX := s.win.X
+       for x := 0; x < s.win.Width; x++ {
+               if x < leftLen {
+                       r, size := utf8.DecodeRune(leftText)
+                       leftText = leftText[size:]
+                       screen.SetContent(winX+x, y, r, nil, statusLineStyle)
+               } else if x >= s.win.Width-rightLen && x < rightLen+s.win.Width-rightLen {
+                       r, size := utf8.DecodeRune(rightText)
+                       rightText = rightText[size:]
+                       screen.SetContent(winX+x, y, r, nil, statusLineStyle)
                } else {
-                       screen.SetContent(viewX+x, y, ' ', nil, statusLineStyle)
+                       screen.SetContent(winX+x, y, ' ', nil, statusLineStyle)
                }
        }
 }
diff --git a/cmd/micro/tab.go b/cmd/micro/tab.go
deleted file mode 100644 (file)
index 88c260a..0000000
+++ /dev/null
@@ -1,293 +0,0 @@
-package main
-
-import (
-       "path/filepath"
-       "sort"
-
-       "github.com/zyedidia/tcell"
-)
-
-var tabBarOffset int
-
-// A Tab holds an array of views and a splitTree to determine how the
-// views should be arranged
-type Tab struct {
-       // This contains all the views in this tab
-       // There is generally only one view per tab, but you can have
-       // multiple views with splits
-       Views []*View
-       // This is the current view for this tab
-       CurView int
-
-       tree *SplitTree
-}
-
-// NewTabFromView creates a new tab and puts the given view in the tab
-func NewTabFromView(v *View) *Tab {
-       t := new(Tab)
-       t.Views = append(t.Views, v)
-       t.Views[0].Num = 0
-
-       t.tree = new(SplitTree)
-       t.tree.kind = VerticalSplit
-       t.tree.children = []Node{NewLeafNode(t.Views[0], t.tree)}
-
-       w, h := screen.Size()
-       t.tree.width = w
-       t.tree.height = h
-
-       if globalSettings["infobar"].(bool) {
-               t.tree.height--
-       }
-       if globalSettings["keymenu"].(bool) {
-               t.tree.height -= 2
-       }
-
-       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
-       }
-}
-
-// Cleanup cleans up the tree (for example if views have closed)
-func (t *Tab) Cleanup() {
-       t.tree.Cleanup()
-}
-
-// Resize handles a resize event from the terminal and resizes
-// all child views correctly
-func (t *Tab) Resize() {
-       w, h := screen.Size()
-       t.tree.width = w
-       t.tree.height = h
-
-       if globalSettings["infobar"].(bool) {
-               t.tree.height--
-       }
-       if globalSettings["keymenu"].(bool) {
-               t.tree.height -= 2
-       }
-
-       t.tree.ResizeSplits()
-
-       for i, v := range t.Views {
-               v.Num = i
-               if v.Type == vtTerm {
-                       v.term.Resize(v.Width, v.Height)
-               }
-       }
-}
-
-// CurView returns the current view
-func CurView() *View {
-       curTab := tabs[curTab]
-       return curTab.Views[curTab.CurView]
-}
-
-// TabbarString returns the string that should be displayed in the tabbar
-// It also returns a map containing which indicies correspond to which tab number
-// This is useful when we know that the mouse click has occurred at an x location
-// but need to know which tab that corresponds to to accurately change the tab
-func TabbarString() (string, map[int]int) {
-       str := ""
-       indicies := make(map[int]int)
-       unique := make(map[string]int)
-
-       for _, t := range tabs {
-               unique[filepath.Base(t.Views[t.CurView].Buf.GetName())]++
-       }
-
-       for i, t := range tabs {
-               buf := t.Views[t.CurView].Buf
-               name := filepath.Base(buf.GetName())
-
-               if i == curTab {
-                       str += "["
-               } else {
-                       str += " "
-               }
-               if unique[name] == 1 {
-                       str += name
-               } else {
-                       str += buf.GetName()
-               }
-               if buf.Modified() {
-                       str += " +"
-               }
-               if i == curTab {
-                       str += "]"
-               } else {
-                       str += " "
-               }
-               str += " "
-
-               indicies[Count(str)-2] = i + 1
-       }
-       return str, indicies
-}
-
-// TabbarHandleMouseEvent checks the given mouse event if it is clicking on the tabbar
-// If it is it changes the current tab accordingly
-// This function returns true if the tab is changed
-func TabbarHandleMouseEvent(event tcell.Event) bool {
-       // There is no tabbar displayed if there are less than 2 tabs
-       if len(tabs) <= 1 {
-               return false
-       }
-
-       switch e := event.(type) {
-       case *tcell.EventMouse:
-               button := e.Buttons()
-               // Must be a left click
-               if button == tcell.Button1 {
-                       x, y := e.Position()
-                       if y != 0 {
-                               return false
-                       }
-                       str, indicies := TabbarString()
-                       if x+tabBarOffset >= len(str) {
-                               return false
-                       }
-                       var tabnum int
-                       var keys []int
-                       for k := range indicies {
-                               keys = append(keys, k)
-                       }
-                       sort.Ints(keys)
-                       for _, k := range keys {
-                               if x+tabBarOffset <= k {
-                                       tabnum = indicies[k] - 1
-                                       break
-                               }
-                       }
-                       curTab = tabnum
-                       return true
-               }
-       }
-
-       return false
-}
-
-// DisplayTabs displays the tabbar at the top of the editor if there are multiple tabs
-func DisplayTabs() {
-       if len(tabs) <= 1 {
-               return
-       }
-
-       str, indicies := TabbarString()
-
-       tabBarStyle := defStyle.Reverse(true)
-       if style, ok := colorscheme["tabbar"]; ok {
-               tabBarStyle = style
-       }
-
-       // Maybe there is a unicode filename?
-       fileRunes := []rune(str)
-       w, _ := screen.Size()
-       tooWide := (w < len(fileRunes))
-
-       // if the entire tab-bar is longer than the screen is wide,
-       // then it should be truncated appropriately to keep the
-       // active tab visible on the UI.
-       if tooWide == true {
-               // first we have to work out where the selected tab is
-               // out of the total length of the tab bar. this is done
-               // by extracting the hit-areas from the indicies map
-               // that was constructed by `TabbarString()`
-               var keys []int
-               for offset := range indicies {
-                       keys = append(keys, offset)
-               }
-               // sort them to be in ascending order so that values will
-               // correctly reflect the displayed ordering of the tabs
-               sort.Ints(keys)
-               // record the offset of each tab and the previous tab so
-               // we can find the position of the tab's hit-box.
-               previousTabOffset := 0
-               currentTabOffset := 0
-               for _, k := range keys {
-                       tabIndex := indicies[k] - 1
-                       if tabIndex == curTab {
-                               currentTabOffset = k
-                               break
-                       }
-                       // this is +2 because there are two padding spaces that aren't accounted
-                       // for in the display. please note that this is for cosmetic purposes only.
-                       previousTabOffset = k + 2
-               }
-               // get the width of the hitbox of the active tab, from there calculate the offsets
-               // to the left and right of it to approximately center it on the tab bar display.
-               centeringOffset := (w - (currentTabOffset - previousTabOffset))
-               leftBuffer := previousTabOffset - (centeringOffset / 2)
-               rightBuffer := currentTabOffset + (centeringOffset / 2)
-
-               // check to make sure we haven't overshot the bounds of the string,
-               // if we have, then take that remainder and put it on the left side
-               overshotRight := rightBuffer - len(fileRunes)
-               if overshotRight > 0 {
-                       leftBuffer = leftBuffer + overshotRight
-               }
-
-               overshotLeft := leftBuffer - 0
-               if overshotLeft < 0 {
-                       leftBuffer = 0
-                       rightBuffer = leftBuffer + (w - 1)
-               } else {
-                       rightBuffer = leftBuffer + (w - 2)
-               }
-
-               if rightBuffer > len(fileRunes)-1 {
-                       rightBuffer = len(fileRunes) - 1
-               }
-
-               // construct a new buffer of text to put the
-               // newly formatted tab bar text into.
-               var displayText []rune
-
-               // if the left-side of the tab bar isn't at the start
-               // of the constructed tab bar text, then show that are
-               // more tabs to the left by displaying a "+"
-               if leftBuffer != 0 {
-                       displayText = append(displayText, '+')
-               }
-               // copy the runes in from the original tab bar text string
-               // into the new display buffer
-               for x := leftBuffer; x < rightBuffer; x++ {
-                       displayText = append(displayText, fileRunes[x])
-               }
-               // if there is more text to the right of the right-most
-               // column in the tab bar text, then indicate there are more
-               // tabs to the right by displaying a "+"
-               if rightBuffer < len(fileRunes)-1 {
-                       displayText = append(displayText, '+')
-               }
-
-               // now store the offset from zero of the left-most text
-               // that is being displayed. This is to ensure that when
-               // clicking on the tab bar, the correct tab gets selected.
-               tabBarOffset = leftBuffer
-
-               // use the constructed buffer as the display buffer to print
-               // onscreen.
-               fileRunes = displayText
-       } else {
-               tabBarOffset = 0
-       }
-
-       // iterate over the width of the terminal display and for each column,
-       // write a character into the tab display area with the appropriate style.
-       for x := 0; x < w; x++ {
-               if x < len(fileRunes) {
-                       screen.SetContent(x, 0, fileRunes[x], nil, tabBarStyle)
-               } else {
-                       screen.SetContent(x, 0, ' ', nil, tabBarStyle)
-               }
-       }
-}
diff --git a/cmd/micro/terminal.go b/cmd/micro/terminal.go
deleted file mode 100644 (file)
index 04ca124..0000000
+++ /dev/null
@@ -1,228 +0,0 @@
-package main
-
-import (
-       "bytes"
-       "fmt"
-       "os"
-       "os/exec"
-       "strconv"
-       "strings"
-
-       "github.com/zyedidia/clipboard"
-       "github.com/zyedidia/tcell"
-       "github.com/zyedidia/terminal"
-)
-
-const (
-       VTIdle    = iota // Waiting for a new command
-       VTRunning        // Currently running a command
-       VTDone           // Finished running a command
-)
-
-// A Terminal holds information for the terminal emulator
-type Terminal struct {
-       state     terminal.State
-       view      *View
-       vtOld     ViewType
-       term      *terminal.VT
-       title     string
-       status    int
-       selection [2]Loc
-       wait      bool
-       getOutput bool
-       output    *bytes.Buffer
-       callback  string
-}
-
-// HasSelection returns whether this terminal has a valid selection
-func (t *Terminal) HasSelection() bool {
-       return t.selection[0] != t.selection[1]
-}
-
-// GetSelection returns the selected text
-func (t *Terminal) GetSelection(width int) string {
-       start := t.selection[0]
-       end := t.selection[1]
-       if start.GreaterThan(end) {
-               start, end = end, start
-       }
-       var ret string
-       var l Loc
-       for y := start.Y; y <= end.Y; y++ {
-               for x := 0; x < width; x++ {
-                       l.X, l.Y = x, y
-                       if l.GreaterEqual(start) && l.LessThan(end) {
-                               c, _, _ := t.state.Cell(x, y)
-                               ret += string(c)
-                       }
-               }
-       }
-       return ret
-}
-
-// Start begins a new command in this terminal with a given view
-func (t *Terminal) Start(execCmd []string, view *View, getOutput bool) error {
-       if len(execCmd) <= 0 {
-               return nil
-       }
-
-       cmd := exec.Command(execCmd[0], execCmd[1:]...)
-       t.output = nil
-       if getOutput {
-               t.output = bytes.NewBuffer([]byte{})
-       }
-       term, _, err := terminal.Start(&t.state, cmd, t.output)
-       if err != nil {
-               return err
-       }
-       t.term = term
-       t.view = view
-       t.getOutput = getOutput
-       t.vtOld = view.Type
-       t.status = VTRunning
-       t.title = execCmd[0] + ":" + strconv.Itoa(cmd.Process.Pid)
-
-       go func() {
-               for {
-                       err := term.Parse()
-                       if err != nil {
-                               fmt.Fprintln(os.Stderr, "[Press enter to close]")
-                               break
-                       }
-                       updateterm <- true
-               }
-               closeterm <- view.Num
-       }()
-
-       return nil
-}
-
-// Resize informs the terminal of a resize event
-func (t *Terminal) Resize(width, height int) {
-       t.term.Resize(width, height)
-}
-
-// HandleEvent handles a tcell event by forwarding it to the terminal emulator
-// If the event is a mouse event and the program running in the emulator
-// does not have mouse support, the emulator will support selections and
-// copy-paste
-func (t *Terminal) HandleEvent(event tcell.Event) {
-       if e, ok := event.(*tcell.EventKey); ok {
-               if t.status == VTDone {
-                       switch e.Key() {
-                       case tcell.KeyEscape, tcell.KeyCtrlQ, tcell.KeyEnter:
-                               t.Close()
-                               t.view.Type = vtDefault
-                       default:
-                       }
-               }
-               if e.Key() == tcell.KeyCtrlC && t.HasSelection() {
-                       clipboard.WriteAll(t.GetSelection(t.view.Width), "clipboard")
-                       messenger.Message("Copied selection to clipboard")
-               } else if t.status != VTDone {
-                       t.WriteString(event.EscSeq())
-               }
-       } else if e, ok := event.(*tcell.EventMouse); !ok || t.state.Mode(terminal.ModeMouseMask) {
-               t.WriteString(event.EscSeq())
-       } else {
-               x, y := e.Position()
-               x -= t.view.x
-               y += t.view.y
-
-               if e.Buttons() == tcell.Button1 {
-                       if !t.view.mouseReleased {
-                               // drag
-                               t.selection[1].X = x
-                               t.selection[1].Y = y
-                       } else {
-                               t.selection[0].X = x
-                               t.selection[0].Y = y
-                               t.selection[1].X = x
-                               t.selection[1].Y = y
-                       }
-
-                       t.view.mouseReleased = false
-               } else if e.Buttons() == tcell.ButtonNone {
-                       if !t.view.mouseReleased {
-                               t.selection[1].X = x
-                               t.selection[1].Y = y
-                       }
-                       t.view.mouseReleased = true
-               }
-       }
-}
-
-// Stop stops execution of the terminal and sets the status
-// to VTDone
-func (t *Terminal) Stop() {
-       t.term.File().Close()
-       t.term.Close()
-       if t.wait {
-               t.status = VTDone
-       } else {
-               t.Close()
-               t.view.Type = t.vtOld
-       }
-}
-
-// Close sets the status to VTIdle indicating that the terminal
-// is ready for a new command to execute
-func (t *Terminal) Close() {
-       t.status = VTIdle
-       // call the lua function that the user has given as a callback
-       if t.getOutput {
-               _, err := Call(t.callback, t.output.String())
-               if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") {
-                       TermMessage(err)
-               }
-       }
-}
-
-// WriteString writes a given string to this terminal's pty
-func (t *Terminal) WriteString(str string) {
-       t.term.File().WriteString(str)
-}
-
-// Display displays this terminal in a view
-func (t *Terminal) Display() {
-       divider := 0
-       if t.view.x != 0 {
-               divider = 1
-               dividerStyle := defStyle
-               if style, ok := colorscheme["divider"]; ok {
-                       dividerStyle = style
-               }
-               for i := 0; i < t.view.Height; i++ {
-                       screen.SetContent(t.view.x, t.view.y+i, '|', nil, dividerStyle.Reverse(true))
-               }
-       }
-       t.state.Lock()
-       defer t.state.Unlock()
-
-       var l Loc
-       for y := 0; y < t.view.Height; y++ {
-               for x := 0; x < t.view.Width; x++ {
-                       l.X, l.Y = x, y
-                       c, f, b := t.state.Cell(x, y)
-
-                       fg, bg := int(f), int(b)
-                       if f == terminal.DefaultFG {
-                               fg = int(tcell.ColorDefault)
-                       }
-                       if b == terminal.DefaultBG {
-                               bg = int(tcell.ColorDefault)
-                       }
-                       st := tcell.StyleDefault.Foreground(GetColor256(int(fg))).Background(GetColor256(int(bg)))
-
-                       if l.LessThan(t.selection[1]) && l.GreaterEqual(t.selection[0]) || l.LessThan(t.selection[0]) && l.GreaterEqual(t.selection[1]) {
-                               st = st.Reverse(true)
-                       }
-
-                       screen.SetContent(t.view.x+x+divider, t.view.y+y, c, nil, st)
-               }
-       }
-       if t.state.CursorVisible() && tabs[curTab].CurView == t.view.Num {
-               curx, cury := t.state.Cursor()
-               screen.ShowCursor(curx+t.view.x+divider, cury+t.view.y)
-       }
-}
index 7d457d6e27eee2bb0d7069459b3d4cdca0ef3ea9..b43152209cfd7978b9089f66e86f87bbacfc11a3 100644 (file)
@@ -1,45 +1,20 @@
 package main
 
 import (
+       "errors"
        "os"
        "os/user"
-       "path/filepath"
-       "reflect"
-       "runtime"
-       "strconv"
+       "regexp"
        "strings"
        "time"
        "unicode/utf8"
 
-       "github.com/go-errors/errors"
-       "github.com/mattn/go-runewidth"
-       "regexp"
+       runewidth "github.com/mattn/go-runewidth"
 )
 
-// Util.go is a collection of utility functions that are used throughout
-// the program
-
-// Count returns the length of a string in runes
-// This is exactly equivalent to utf8.RuneCountInString(), just less characters
-func Count(s string) int {
-       return utf8.RuneCountInString(s)
-}
-
-// Convert byte array to rune array
-func toRunes(b []byte) []rune {
-       runes := make([]rune, 0, utf8.RuneCount(b))
-
-       for len(b) > 0 {
-               r, size := utf8.DecodeRune(b)
-               runes = append(runes, r)
-
-               b = b[size:]
-       }
-
-       return runes
-}
-
-func sliceStart(slc []byte, index int) []byte {
+// SliceEnd returns a byte slice where the index is a rune index
+// Slices off the start of the slice
+func SliceEnd(slc []byte, index int) []byte {
        len := len(slc)
        i := 0
        totalSize := 0
@@ -56,7 +31,9 @@ func sliceStart(slc []byte, index int) []byte {
        return slc[totalSize:]
 }
 
-func sliceEnd(slc []byte, index int) []byte {
+// SliceStart returns a byte slice where the index is a rune index
+// Slices off the end of the slice
+func SliceStart(slc []byte, index int) []byte {
        len := len(slc)
        i := 0
        totalSize := 0
@@ -73,20 +50,63 @@ func sliceEnd(slc []byte, index int) []byte {
        return slc[:totalSize]
 }
 
-// NumOccurrences counts the number of occurrences of a byte in a string
-func NumOccurrences(s string, c byte) int {
-       var n int
-       for i := 0; i < len(s); i++ {
-               if s[i] == c {
-                       n++
+// SliceVisualEnd will take a byte slice and slice off the start
+// up to a given visual index. If the index is in the middle of a
+// rune the number of visual columns into the rune will be returned
+func SliceVisualEnd(b []byte, n, tabsize int) ([]byte, int) {
+       width := 0
+       for len(b) > 0 {
+               r, size := utf8.DecodeRune(b)
+
+               w := 0
+               switch r {
+               case '\t':
+                       ts := tabsize - (width % tabsize)
+                       w = ts
+               default:
+                       w = runewidth.RuneWidth(r)
                }
+               if width+w > n {
+                       return b, n - width
+               }
+               width += w
+               b = b[size:]
+       }
+       return b, width
+}
+
+// Abs is a simple absolute value function for ints
+func Abs(n int) int {
+       if n < 0 {
+               return -n
        }
        return n
 }
 
-// Spaces returns a string with n spaces
-func Spaces(n int) string {
-       return strings.Repeat(" ", n)
+// StringWidth returns the visual width of a byte array indexed from 0 to n (rune index)
+// with a given tabsize
+func StringWidth(b []byte, n, tabsize int) int {
+       i := 0
+       width := 0
+       for len(b) > 0 {
+               r, size := utf8.DecodeRune(b)
+               b = b[size:]
+
+               switch r {
+               case '\t':
+                       ts := tabsize - (width % tabsize)
+                       width += ts
+               default:
+                       width += runewidth.RuneWidth(r)
+               }
+
+               i++
+
+               if i == n {
+                       return width
+               }
+       }
+       return width
 }
 
 // Min takes the min of two ints
@@ -108,7 +128,6 @@ func Max(a, b int) int {
 // FSize gets the size of a file
 func FSize(f *os.File) int64 {
        fi, _ := f.Stat()
-       // get the size
        return fi.Size()
 }
 
@@ -131,6 +150,7 @@ func IsWhitespace(c rune) bool {
 
 // IsStrWhitespace returns true if the given string is all whitespace
 func IsStrWhitespace(str string) bool {
+       // Range loop for unicode correctness
        for _, c := range str {
                if !IsWhitespace(c) {
                        return false
@@ -139,197 +159,12 @@ func IsStrWhitespace(str string) bool {
        return true
 }
 
-// Contains returns whether or not a string array contains a given string
-func Contains(list []string, a string) bool {
-       for _, b := range list {
-               if b == a {
-                       return true
-               }
-       }
-       return false
-}
-
-// Insert makes a simple insert into a string at the given position
-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 := ""
-       for _, c := range str {
-               if c == ' ' || c == '\t' {
-                       ws += string(c)
-               } else {
-                       break
-               }
-       }
-       return ws
-}
-
-// IsSpaces checks if a given string is only spaces
-func IsSpaces(str []byte) bool {
-       for _, c := range str {
-               if c != ' ' {
-                       return false
-               }
-       }
-
-       return true
-}
-
-// IsSpacesOrTabs checks if a given string contains only spaces and tabs
-func IsSpacesOrTabs(str string) bool {
-       for _, c := range str {
-               if c != ' ' && c != '\t' {
-                       return false
-               }
-       }
-
-       return true
-}
-
-// ParseBool is almost exactly like strconv.ParseBool, except it also accepts 'on' and 'off'
-// as 'true' and 'false' respectively
-func ParseBool(str string) (bool, error) {
-       if str == "on" {
-               return true, nil
-       }
-       if str == "off" {
-               return false, nil
-       }
-       return strconv.ParseBool(str)
-}
-
-// EscapePath replaces every path separator in a given path with a %
-func EscapePath(path string) string {
-       path = filepath.ToSlash(path)
-       return strings.Replace(path, "/", "%", -1)
-}
-
-// GetModTime returns the last modification time for a given file
-// It also returns a boolean if there was a problem accessing the file
-func GetModTime(path string) (time.Time, bool) {
-       info, err := os.Stat(path)
-       if err != nil {
-               return time.Now(), false
-       }
-       return info.ModTime(), true
-}
-
-// 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++
-               }
-       }
-       return sw
-}
-
-// WidthOfLargeRunes searches all the runes in a string and counts up all the widths of runes
-// 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)
-               } else {
-                       w = runewidth.RuneWidth(ch)
-               }
-               if w > 1 {
-                       count += (w - 1)
-               }
-               if ch == '\n' {
-                       lineIdx = 0
-               } else {
-                       lineIdx += w
-               }
-       }
-       return count
-}
-
-// RunePos returns the rune index of a given byte index
-// This could cause problems if the byte index is between code points
-func runePos(p int, str string) int {
-       return utf8.RuneCountInString(str[:p])
-}
-
-func lcs(a, b string) string {
-       arunes := []rune(a)
-       brunes := []rune(b)
-
-       lcs := ""
-       for i, r := range arunes {
-               if i >= len(brunes) {
-                       break
-               }
-               if r == brunes[i] {
-                       lcs += string(r)
-               } else {
-                       break
-               }
-       }
-       return lcs
-}
-
-// CommonSubstring gets a common substring among the inputs
-func CommonSubstring(arr ...string) string {
-       commonStr := arr[0]
-
-       for _, str := range arr[1:] {
-               commonStr = lcs(commonStr, str)
-       }
-
-       return commonStr
-}
-
-// Abs is a simple absolute value function for ints
-func Abs(n int) int {
-       if n < 0 {
-               return -n
-       }
-       return n
-}
-
-// FuncName returns the full name of a given function object
-func FuncName(i interface{}) string {
-       return runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name()
-}
-
-// ShortFuncName returns the name only of a given function object
-func ShortFuncName(i interface{}) string {
-       return strings.TrimPrefix(runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name(), "main.(*View).")
-}
-
+// TODO: consider changing because of snap segfault
 // ReplaceHome takes a path as input and replaces ~ at the start of the path with the user's
 // home directory. Does nothing if the path does not start with '~'.
-func ReplaceHome(path string) string {
+func ReplaceHome(path string) (string, error) {
        if !strings.HasPrefix(path, "~") {
-               return path
+               return path, nil
        }
 
        var userData *user.User
@@ -339,23 +174,18 @@ func ReplaceHome(path string) string {
        if homeString == "~" {
                userData, err = user.Current()
                if err != nil {
-                       messenger.Error("Could not find user: ", err)
+                       return "", errors.New("Could not find user: " + err.Error())
                }
        } else {
                userData, err = user.Lookup(homeString[1:])
                if err != nil {
-                       if messenger != nil {
-                               messenger.Error("Could not find user: ", err)
-                       } else {
-                               TermMessage("Could not find user: ", err)
-                       }
-                       return ""
+                       return "", errors.New("Could not find user: " + err.Error())
                }
        }
 
        home := userData.HomeDir
 
-       return strings.Replace(path, homeString, home, 1)
+       return strings.Replace(path, homeString, home, 1), nil
 }
 
 // GetPathAndCursorPosition returns a filename without everything following a `:`
@@ -375,26 +205,11 @@ func GetPathAndCursorPosition(path string) (string, []string) {
        return match[1], []string{match[2], "0"}
 }
 
-func ParseCursorLocation(cursorPositions []string) (Loc, error) {
-       startpos := Loc{0, 0}
-       var err error
-
-       // if no positions are available exit early
-       if cursorPositions == nil {
-               return startpos, errors.New("No cursor positions were provided.")
-       }
-
-       startpos.Y, err = strconv.Atoi(cursorPositions[0])
+// GetModTime returns the last modification time for a given file
+func GetModTime(path string) (time.Time, error) {
+       info, err := os.Stat(path)
        if err != nil {
-               messenger.Error("Error parsing cursor position: ", err)
-       } else {
-               if len(cursorPositions) > 1 {
-                       startpos.X, err = strconv.Atoi(cursorPositions[1])
-                       if err != nil {
-                               messenger.Error("Error parsing cursor position: ", err)
-                       }
-               }
+               return time.Now(), err
        }
-
-       return startpos, err
+       return info.ModTime(), nil
 }
index 849f52cbab4c72494c38d445b299f1757917edec..0f900db98f0dae7f12bc664184265cdd6fd52b0b 100644 (file)
@@ -2,330 +2,32 @@ package main
 
 import (
        "testing"
-)
-
-func TestNumOccurences(t *testing.T) {
-       var tests = []struct {
-               inputStr  string
-               inputChar byte
-               want      int
-       }{
-               {"aaaa", 'a', 4},
-               {"\trfd\ta", '\t', 2},
-               {"∆ƒ\tø ® \t\t", '\t', 3},
-       }
-       for _, test := range tests {
-               if got := NumOccurrences(test.inputStr, test.inputChar); got != test.want {
-                       t.Errorf("NumOccurences(%s, %c) = %d", test.inputStr, test.inputChar, got)
-               }
-       }
-}
-
-func TestSpaces(t *testing.T) {
-       var tests = []struct {
-               input int
-               want  string
-       }{
-               {4, "    "},
-               {0, ""},
-       }
-       for _, test := range tests {
-               if got := Spaces(test.input); got != test.want {
-                       t.Errorf("Spaces(%d) = \"%s\"", test.input, got)
-               }
-       }
-}
 
-func TestIsWordChar(t *testing.T) {
-       if IsWordChar("t") == false {
-               t.Errorf("IsWordChar(t) = false")
-       }
-       if IsWordChar("T") == false {
-               t.Errorf("IsWordChar(T) = false")
-       }
-       if IsWordChar("5") == false {
-               t.Errorf("IsWordChar(5) = false")
-       }
-       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("\n") == true {
-               t.Errorf("IsWordChar(\n)) = true")
-       }
-}
+       "github.com/stretchr/testify/assert"
+)
 
 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)
-       }
-}
-
-func assertEqual(t *testing.T, expected interface{}, result interface{}) {
-       if expected != result {
-               t.Fatalf("Expected: %d != Got: %d", expected, result)
-       }
-}
-
-func assertTrue(t *testing.T, condition bool) {
-       if !condition {
-               t.Fatalf("Condition was not true. Got false")
-       }
-}
-
-func TestGetPathRelativeWithDot(t *testing.T) {
-       path, cursorPosition := GetPathAndCursorPosition("./myfile:10:5")
-
-       assertEqual(t, path, "./myfile")
-       assertEqual(t, "10", cursorPosition[0])
-       assertEqual(t, "5", cursorPosition[1])
-}
-func TestGetPathRelativeWithDotWindows(t *testing.T) {
-       path, cursorPosition := GetPathAndCursorPosition(".\\myfile:10:5")
-
-       assertEqual(t, path, ".\\myfile")
-       assertEqual(t, "10", cursorPosition[0])
-       assertEqual(t, cursorPosition[1], "5")
-}
-func TestGetPathRelativeNoDot(t *testing.T) {
-       path, cursorPosition := GetPathAndCursorPosition("myfile:10:5")
-
-       assertEqual(t, path, "myfile")
-       assertEqual(t, "10", cursorPosition[0])
-
-       assertEqual(t, cursorPosition[1], "5")
-}
-func TestGetPathAbsoluteWindows(t *testing.T) {
-       path, cursorPosition := GetPathAndCursorPosition("C:\\myfile:10:5")
-
-       assertEqual(t, path, "C:\\myfile")
-       assertEqual(t, "10", cursorPosition[0])
-
-       assertEqual(t, cursorPosition[1], "5")
-
-       path, cursorPosition = GetPathAndCursorPosition("C:/myfile:10:5")
-
-       assertEqual(t, path, "C:/myfile")
-       assertEqual(t, "10", cursorPosition[0])
-       assertEqual(t, "5", cursorPosition[1])
-}
-
-func TestGetPathAbsoluteUnix(t *testing.T) {
-       path, cursorPosition := GetPathAndCursorPosition("/home/user/myfile:10:5")
-
-       assertEqual(t, path, "/home/user/myfile")
-       assertEqual(t, "10", cursorPosition[0])
-       assertEqual(t, "5", cursorPosition[1])
-}
-
-func TestGetPathRelativeWithDotWithoutLineAndColumn(t *testing.T) {
-       path, cursorPosition := GetPathAndCursorPosition("./myfile")
-
-       assertEqual(t, path, "./myfile")
-       // no cursor position in filename, nil should be returned
-       assertTrue(t, cursorPosition == nil)
-}
-func TestGetPathRelativeWithDotWindowsWithoutLineAndColumn(t *testing.T) {
-       path, cursorPosition := GetPathAndCursorPosition(".\\myfile")
-
-       assertEqual(t, path, ".\\myfile")
-       assertTrue(t, cursorPosition == nil)
-
-}
-func TestGetPathRelativeNoDotWithoutLineAndColumn(t *testing.T) {
-       path, cursorPosition := GetPathAndCursorPosition("myfile")
-
-       assertEqual(t, path, "myfile")
-       assertTrue(t, cursorPosition == nil)
-
-}
-func TestGetPathAbsoluteWindowsWithoutLineAndColumn(t *testing.T) {
-       path, cursorPosition := GetPathAndCursorPosition("C:\\myfile")
-
-       assertEqual(t, path, "C:\\myfile")
-       assertTrue(t, cursorPosition == nil)
-
-       path, cursorPosition = GetPathAndCursorPosition("C:/myfile")
-
-       assertEqual(t, path, "C:/myfile")
-       assertTrue(t, cursorPosition == nil)
-
-}
-func TestGetPathAbsoluteUnixWithoutLineAndColumn(t *testing.T) {
-       path, cursorPosition := GetPathAndCursorPosition("/home/user/myfile")
-
-       assertEqual(t, path, "/home/user/myfile")
-       assertTrue(t, cursorPosition == nil)
-
-}
-func TestGetPathSingleLetterFileRelativePath(t *testing.T) {
-       path, cursorPosition := GetPathAndCursorPosition("a:5:6")
-
-       assertEqual(t, path, "a")
-       assertEqual(t, "5", cursorPosition[0])
-       assertEqual(t, "6", cursorPosition[1])
-}
-func TestGetPathSingleLetterFileAbsolutePathWindows(t *testing.T) {
-       path, cursorPosition := GetPathAndCursorPosition("C:\\a:5:6")
-
-       assertEqual(t, path, "C:\\a")
-       assertEqual(t, "5", cursorPosition[0])
-       assertEqual(t, "6", cursorPosition[1])
-
-       path, cursorPosition = GetPathAndCursorPosition("C:/a:5:6")
-
-       assertEqual(t, path, "C:/a")
-       assertEqual(t, "5", cursorPosition[0])
-       assertEqual(t, "6", cursorPosition[1])
-}
-func TestGetPathSingleLetterFileAbsolutePathUnix(t *testing.T) {
-       path, cursorPosition := GetPathAndCursorPosition("/home/user/a:5:6")
-
-       assertEqual(t, path, "/home/user/a")
-       assertEqual(t, "5", cursorPosition[0])
-       assertEqual(t, "6", cursorPosition[1])
-}
-func TestGetPathSingleLetterFileAbsolutePathWindowsWithoutLineAndColumn(t *testing.T) {
-       path, cursorPosition := GetPathAndCursorPosition("C:\\a")
-
-       assertEqual(t, path, "C:\\a")
-       assertTrue(t, cursorPosition == nil)
-
-       path, cursorPosition = GetPathAndCursorPosition("C:/a")
+       bytes := []byte("\tPot să \tmănânc sticlă și ea nu mă rănește.")
 
-       assertEqual(t, path, "C:/a")
-       assertTrue(t, cursorPosition == nil)
-
-}
-func TestGetPathSingleLetterFileAbsolutePathUnixWithoutLineAndColumn(t *testing.T) {
-       path, cursorPosition := GetPathAndCursorPosition("/home/user/a")
-
-       assertEqual(t, path, "/home/user/a")
-       assertTrue(t, cursorPosition == nil)
-
-}
-
-func TestGetPathRelativeWithDotOnlyLine(t *testing.T) {
-       path, cursorPosition := GetPathAndCursorPosition("./myfile:10")
-
-       assertEqual(t, path, "./myfile")
-       assertEqual(t, "10", cursorPosition[0])
-       assertEqual(t, "0", cursorPosition[1])
+       n := StringWidth(bytes, 23, 4)
+       assert.Equal(t, 26, n)
 }
-func TestGetPathRelativeWithDotWindowsOnlyLine(t *testing.T) {
-       path, cursorPosition := GetPathAndCursorPosition(".\\myfile:10")
 
-       assertEqual(t, path, ".\\myfile")
-       assertEqual(t, "10", cursorPosition[0])
-       assertEqual(t, "0", cursorPosition[1])
-}
-func TestGetPathRelativeNoDotOnlyLine(t *testing.T) {
-       path, cursorPosition := GetPathAndCursorPosition("myfile:10")
-
-       assertEqual(t, path, "myfile")
-       assertEqual(t, "10", cursorPosition[0])
-       assertEqual(t, "0", cursorPosition[1])
-}
-func TestGetPathAbsoluteWindowsOnlyLine(t *testing.T) {
-       path, cursorPosition := GetPathAndCursorPosition("C:\\myfile:10")
+func TestSliceVisualEnd(t *testing.T) {
+       s := []byte("\thello")
+       slc, n := SliceVisualEnd(s, 2, 4)
+       assert.Equal(t, []byte("\thello"), slc)
+       assert.Equal(t, 2, n)
 
-       assertEqual(t, path, "C:\\myfile")
-       assertEqual(t, "10", cursorPosition[0])
-       assertEqual(t, "0", cursorPosition[1])
+       slc, n = SliceVisualEnd(s, 1, 4)
+       assert.Equal(t, []byte("\thello"), slc)
+       assert.Equal(t, 1, n)
 
-       path, cursorPosition = GetPathAndCursorPosition("C:/myfile:10")
+       slc, n = SliceVisualEnd(s, 4, 4)
+       assert.Equal(t, []byte("hello"), slc)
+       assert.Equal(t, 0, n)
 
-       assertEqual(t, path, "C:/myfile")
-       assertEqual(t, "10", cursorPosition[0])
-       assertEqual(t, "0", cursorPosition[1])
-}
-func TestGetPathAbsoluteUnixOnlyLine(t *testing.T) {
-       path, cursorPosition := GetPathAndCursorPosition("/home/user/myfile:10")
-
-       assertEqual(t, path, "/home/user/myfile")
-       assertEqual(t, "10", cursorPosition[0])
-       assertEqual(t, "0", cursorPosition[1])
-}
-func TestParseCursorLocationOneArg(t *testing.T) {
-       location, err := ParseCursorLocation([]string{"3"})
-
-       assertEqual(t, 3, location.Y)
-       assertEqual(t, 0, location.X)
-       assertEqual(t, nil, err)
-}
-func TestParseCursorLocationTwoArgs(t *testing.T) {
-       location, err := ParseCursorLocation([]string{"3", "15"})
-
-       assertEqual(t, 3, location.Y)
-       assertEqual(t, 15, location.X)
-       assertEqual(t, nil, err)
-}
-func TestParseCursorLocationNoArgs(t *testing.T) {
-       location, err := ParseCursorLocation(nil)
-       // the expected result is the start position - 0, 0
-       assertEqual(t, 0, location.Y)
-       assertEqual(t, 0, location.X)
-       // an error will be present here as the positions we're parsing are a nil
-       assertTrue(t, err != nil)
-}
-func TestParseCursorLocationFirstArgNotValidNumber(t *testing.T) {
-       // the messenger is necessary as ParseCursorLocation
-       // puts a message in it on error
-       messenger = new(Messenger)
-       _, err := ParseCursorLocation([]string{"apples", "1"})
-       // the expected result is the start position - 0, 0
-       assertTrue(t, messenger.hasMessage)
-       assertTrue(t, err != nil)
-}
-func TestParseCursorLocationSecondArgNotValidNumber(t *testing.T) {
-       // the messenger is necessary as ParseCursorLocation
-       // puts a message in it on error
-       messenger = new(Messenger)
-       _, err := ParseCursorLocation([]string{"1", "apples"})
-       // the expected result is the start position - 0, 0
-       assertTrue(t, messenger.hasMessage)
-       assertTrue(t, err != nil)
+       slc, n = SliceVisualEnd(s, 5, 4)
+       assert.Equal(t, []byte("ello"), slc)
+       assert.Equal(t, 0, n)
 }
diff --git a/cmd/micro/view.go b/cmd/micro/view.go
deleted file mode 100644 (file)
index 7c9a0f1..0000000
+++ /dev/null
@@ -1,1117 +0,0 @@
-package main
-
-import (
-       "fmt"
-       "reflect"
-       "strconv"
-       "strings"
-       "time"
-
-       "github.com/zyedidia/tcell"
-)
-
-// The ViewType defines what kind of view this is
-type ViewType struct {
-       Kind     int
-       Readonly bool // The file cannot be edited
-       Scratch  bool // The file cannot be saved
-}
-
-var (
-       vtDefault = ViewType{0, false, false}
-       vtHelp    = ViewType{1, true, true}
-       vtLog     = ViewType{2, true, true}
-       vtScratch = ViewType{3, false, true}
-       vtRaw     = ViewType{4, true, true}
-       vtTerm    = ViewType{5, true, true}
-)
-
-// 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.
-type View struct {
-       // A pointer to the buffer's cursor for ease of access
-       Cursor *Cursor
-
-       // The topmost line, used for vertical scrolling
-       Topline int
-       // The leftmost column, used for horizontal scrolling
-       leftCol int
-
-       // Specifies whether or not this view holds a help buffer
-       Type ViewType
-
-       // Actual width and height
-       Width  int
-       Height int
-
-       LockWidth  bool
-       LockHeight bool
-
-       // Where this view is located
-       x, y int
-
-       // How much to offset because of line numbers
-       lineNumOffset int
-
-       // Holds the list of gutter messages
-       messages map[string][]GutterMessage
-
-       // This is the index of this view in the views array
-       Num int
-       // What tab is this view stored in
-       TabNum int
-
-       // The buffer
-       Buf *Buffer
-       // The statusline
-       sline *Statusline
-
-       // Since tcell doesn't differentiate between a mouse release event
-       // and a mouse move event with no keys pressed, we need to keep
-       // track of whether or not the mouse was pressed (or not released) last event to determine
-       // mouse release events
-       mouseReleased bool
-
-       // We need to keep track of insert key press toggle
-       isOverwriteMode bool
-       // This stores when the last click was
-       // This is useful for detecting double and triple clicks
-       lastClickTime time.Time
-       lastLoc       Loc
-
-       // lastCutTime stores when the last ctrl+k was issued.
-       // It is used for clearing the clipboard to replace it with fresh cut lines.
-       lastCutTime time.Time
-
-       // freshClip returns true if the clipboard has never been pasted.
-       freshClip bool
-
-       // Was the last mouse event actually a double click?
-       // Useful for detecting triple clicks -- if a double click is detected
-       // but the last mouse event was actually a double click, it's a triple click
-       doubleClick bool
-       // Same here, just to keep track for mouse move events
-       tripleClick bool
-
-       // The cellview used for displaying and syntax highlighting
-       cellview *CellView
-
-       splitNode *LeafNode
-
-       // The scrollbar
-       scrollbar *ScrollBar
-
-       // Virtual terminal
-       term *Terminal
-}
-
-// NewView returns a new fullscreen view
-func NewView(buf *Buffer) *View {
-       screenW, screenH := screen.Size()
-       return NewViewWidthHeight(buf, screenW, screenH)
-}
-
-// NewViewWidthHeight returns a new view with the specified width and height
-// Note that w and h are raw column and row values
-func NewViewWidthHeight(buf *Buffer, w, h int) *View {
-       v := new(View)
-
-       v.x, v.y = 0, 0
-
-       v.Width = w
-       v.Height = h
-       v.cellview = new(CellView)
-
-       v.ToggleTabbar()
-
-       v.OpenBuffer(buf)
-
-       v.messages = make(map[string][]GutterMessage)
-
-       v.sline = &Statusline{
-               view: v,
-       }
-
-       v.scrollbar = &ScrollBar{
-               view: v,
-       }
-
-       if v.Buf.Settings["statusline"].(bool) {
-               v.Height--
-       }
-
-       v.term = new(Terminal)
-
-       for pl := range loadedPlugins {
-               _, err := Call(pl+".onViewOpen", v)
-               if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") {
-                       TermMessage(err)
-                       continue
-               }
-       }
-
-       return v
-}
-
-// ToggleStatusLine creates an extra row for the statusline if necessary
-func (v *View) ToggleStatusLine() {
-       if v.Buf.Settings["statusline"].(bool) {
-               v.Height--
-       } else {
-               v.Height++
-       }
-}
-
-// StartTerminal execs a command in this view
-func (v *View) StartTerminal(execCmd []string, wait bool, getOutput bool, luaCallback string) error {
-       err := v.term.Start(execCmd, v, getOutput)
-       v.term.wait = wait
-       v.term.callback = luaCallback
-       if err == nil {
-               v.term.Resize(v.Width, v.Height)
-               v.Type = vtTerm
-       }
-       return err
-}
-
-// CloseTerminal shuts down the tty running in this view
-// and returns it to the default view type
-func (v *View) CloseTerminal() {
-       v.term.Stop()
-}
-
-// 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.y = 1
-               }
-       } else {
-               if v.y == 1 {
-                       v.y = 0
-                       v.Height++
-               }
-       }
-}
-
-func (v *View) paste(clip string) {
-       if v.Buf.Settings["smartpaste"].(bool) {
-               if v.Cursor.X > 0 && GetLeadingWhitespace(strings.TrimLeft(clip, "\r\n")) == "" {
-                       leadingWS := GetLeadingWhitespace(v.Buf.Line(v.Cursor.Y))
-                       clip = strings.Replace(clip, "\n", "\n"+leadingWS, -1)
-               }
-       }
-
-       if v.Cursor.HasSelection() {
-               v.Cursor.DeleteSelection()
-               v.Cursor.ResetSelection()
-       }
-       
-       v.Buf.Insert(v.Cursor.Loc, clip)
-       // v.Cursor.Loc = v.Cursor.Loc.Move(Count(clip), v.Buf)
-       v.freshClip = false
-       messenger.Message("Pasted clipboard")
-}
-
-// ScrollUp scrolls the view up n lines (if possible)
-func (v *View) ScrollUp(n int) {
-       // Try to scroll by n but if it would overflow, scroll by 1
-       if v.Topline-n >= 0 {
-               v.Topline -= n
-       } else if v.Topline > 0 {
-               v.Topline--
-       }
-}
-
-// 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.Topline += n
-       } else if v.Topline < v.Buf.NumLines-1 {
-               v.Topline++
-       }
-}
-
-// 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
-func (v *View) CanClose() bool {
-       if v.Type == vtDefault && v.Buf.Modified() {
-               var choice bool
-               var canceled bool
-               if v.Buf.Settings["autosave"].(bool) {
-                       choice = true
-               } else {
-                       choice, canceled = messenger.YesNoPrompt("Save changes to " + v.Buf.GetName() + " before closing? (y,n,esc) ")
-               }
-               if !canceled {
-                       //if char == 'y' {
-                       if choice {
-                               v.Save(true)
-                       }
-               } else {
-                       return false
-               }
-       }
-       return true
-}
-
-// OpenBuffer opens a new buffer in this view.
-// This resets the topline, event handler and cursor.
-func (v *View) OpenBuffer(buf *Buffer) {
-       screen.Clear()
-       v.CloseBuffer()
-       v.Buf = buf
-       v.Cursor = &buf.Cursor
-       v.Topline = 0
-       v.leftCol = 0
-       v.Cursor.ResetSelection()
-       v.Relocate()
-       v.Center(false)
-       v.messages = make(map[string][]GutterMessage)
-
-       // Set mouseReleased to true because we assume the mouse is not being pressed when
-       // the editor is opened
-       v.mouseReleased = true
-       // Set isOverwriteMode to false, because we assume we are in the default mode when editor
-       // is opened
-       v.isOverwriteMode = false
-       v.lastClickTime = time.Time{}
-
-       GlobalPluginCall("onBufferOpen", v.Buf)
-       GlobalPluginCall("onViewOpen", v)
-}
-
-// Open opens the given file in the view
-func (v *View) Open(path string) {
-       buf, err := NewBufferFromFile(path)
-       if err != nil {
-               messenger.Error(err)
-               return
-       }
-       v.OpenBuffer(buf)
-}
-
-// CloseBuffer performs any closing functions on the buffer
-func (v *View) CloseBuffer() {
-       if v.Buf != nil {
-               v.Buf.Serialize()
-       }
-}
-
-// ReOpen reloads the current buffer
-func (v *View) ReOpen() {
-       if v.CanClose() {
-               screen.Clear()
-               v.Buf.ReOpen()
-               v.Relocate()
-       }
-}
-
-// 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)
-}
-
-// 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
-}
-
-// Bottomline returns the line number of the lowest line in the view
-// You might think that this is obviously just v.Topline + v.Height
-// but if softwrap is enabled things get complicated since one buffer
-// line can take up multiple lines in the view
-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
-}
-
-// 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))
-       if cy < v.Topline+scrollmargin && cy > scrollmargin-1 {
-               v.Topline = cy - scrollmargin
-               ret = true
-       } else if cy < v.Topline {
-               v.Topline = cy
-               ret = true
-       }
-       if cy > v.Topline+height-1-scrollmargin && cy < v.Buf.NumLines-scrollmargin {
-               v.Topline = cy - height + 1 + scrollmargin
-               ret = true
-       } else if cy >= v.Buf.NumLines-scrollmargin && cy >= height {
-               v.Topline = v.Buf.NumLines - 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
-               }
-       }
-       return ret
-}
-
-// GetMouseClickLocation gets the location in the buffer from a mouse click
-// on the screen
-func (v *View) GetMouseClickLocation(x, y int) (int, int) {
-       x -= v.lineNumOffset - v.leftCol + v.x
-       y += v.Topline - v.y
-
-       if y-v.Topline > v.Height-1 {
-               v.ScrollDown(1)
-               y = v.Height + v.Topline - 1
-       }
-       if y < 0 {
-               y = 0
-       }
-       if x < 0 {
-               x = 0
-       }
-
-       newX, newY := v.GetSoftWrapLocation(x, y)
-       if newX > Count(v.Buf.Line(newY)) {
-               newX = Count(v.Buf.Line(newY))
-       }
-
-       return newX, newY
-}
-
-// 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 {
-               v.ScrollDown(1)
-               y = v.Height + v.Topline - 1
-       }
-       if y < 0 {
-               y = 0
-       }
-       if x < 0 {
-               x = 0
-       }
-
-       x, y = v.GetSoftWrapLocation(x, y)
-       if x > Count(v.Buf.Line(y)) {
-               x = Count(v.Buf.Line(y))
-       }
-       v.Cursor.X = x
-       v.Cursor.Y = y
-       v.Cursor.LastVisualX = v.Cursor.GetVisualX()
-}
-
-// Execute actions executes the supplied actions
-func (v *View) ExecuteActions(actions []func(*View, bool) bool) bool {
-       relocate := false
-       readonlyBindingsList := []string{"Delete", "Insert", "Backspace", "Cut", "Play", "Paste", "Move", "Add", "DuplicateLine", "Macro"}
-       for _, action := range actions {
-               readonlyBindingsResult := false
-               funcName := ShortFuncName(action)
-               curv := CurView()
-               if curv.Type.Readonly == true {
-                       // check for readonly and if true only let key bindings get called if they do not change the contents.
-                       for _, readonlyBindings := range readonlyBindingsList {
-                               if strings.Contains(funcName, readonlyBindings) {
-                                       readonlyBindingsResult = true
-                               }
-                       }
-               }
-               if !readonlyBindingsResult {
-                       // call the key binding
-                       relocate = action(curv, true) || relocate
-                       // Macro
-                       if funcName != "ToggleMacro" && funcName != "PlayMacro" {
-                               if recordingMacro {
-                                       curMacro = append(curMacro, action)
-                               }
-                       }
-               }
-       }
-
-       return relocate
-}
-
-// SetCursor sets the view's and buffer's cursor
-func (v *View) SetCursor(c *Cursor) bool {
-       if c == nil {
-               return false
-       }
-       v.Cursor = c
-       v.Buf.curCursor = c.Num
-
-       return true
-}
-
-// HandleEvent handles an event passed by the main loop
-func (v *View) HandleEvent(event tcell.Event) {
-       if v.Type == vtTerm {
-               v.term.HandleEvent(event)
-               return
-       }
-
-       if v.Type == vtRaw {
-               v.Buf.Insert(v.Cursor.Loc, reflect.TypeOf(event).String()[7:])
-               v.Buf.Insert(v.Cursor.Loc, fmt.Sprintf(": %q\n", event.EscSeq()))
-
-               switch e := event.(type) {
-               case *tcell.EventKey:
-                       if e.Key() == tcell.KeyCtrlQ {
-                               v.Quit(true)
-                       }
-               }
-
-               return
-       }
-
-       // This bool determines whether the view is relocated at the end of the function
-       // By default it's true because most events should cause a relocate
-       relocate := true
-
-       v.Buf.CheckModTime()
-
-       switch e := event.(type) {
-       case *tcell.EventRaw:
-               for key, actions := range bindings {
-                       if key.keyCode == -1 {
-                               if e.EscSeq() == key.escape {
-                                       for _, c := range v.Buf.cursors {
-                                               ok := v.SetCursor(c)
-                                               if !ok {
-                                                       break
-                                               }
-                                               relocate = false
-                                               relocate = v.ExecuteActions(actions) || relocate
-                                       }
-                                       v.SetCursor(&v.Buf.Cursor)
-                                       v.Buf.MergeCursors()
-                                       break
-                               }
-                       }
-               }
-       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
-               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 _, c := range v.Buf.cursors {
-                                               ok := v.SetCursor(c)
-                                               if !ok {
-                                                       break
-                                               }
-                                               relocate = false
-                                               isBinding = true
-                                               relocate = v.ExecuteActions(actions) || relocate
-                                       }
-                                       v.SetCursor(&v.Buf.Cursor)
-                                       v.Buf.MergeCursors()
-                                       break
-                               }
-                       }
-               }
-
-               if !isBinding && e.Key() == tcell.KeyRune {
-                       // Check viewtype if readonly don't insert a rune (readonly help and log view etc.)
-                       if v.Type.Readonly == false {
-                               for _, c := range v.Buf.cursors {
-                                       v.SetCursor(c)
-
-                                       // Insert a character
-                                       if v.Cursor.HasSelection() {
-                                               v.Cursor.DeleteSelection()
-                                               v.Cursor.ResetSelection()
-                                       }
-
-                                       if v.isOverwriteMode {
-                                               next := v.Cursor.Loc
-                                               next.X++
-                                               v.Buf.Replace(v.Cursor.Loc, next, string(e.Rune()))
-                                       } else {
-                                               v.Buf.Insert(v.Cursor.Loc, string(e.Rune()))
-                                       }
-
-                                       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)
-                                               }
-                                       }
-
-                                       if recordingMacro {
-                                               curMacro = append(curMacro, e.Rune())
-                                       }
-                               }
-                               v.SetCursor(&v.Buf.Cursor)
-                       }
-               }
-       case *tcell.EventPaste:
-               // Check viewtype if readonly don't paste (readonly help and log view etc.)
-               if v.Type.Readonly == false {
-                       if !PreActionCall("Paste", v) {
-                               break
-                       }
-
-                       for _, c := range v.Buf.cursors {
-                               v.SetCursor(c)
-                               v.paste(e.Text())
-                       }
-                       v.SetCursor(&v.Buf.Cursor)
-
-                       PostActionCall("Paste", v)
-               }
-       case *tcell.EventMouse:
-               // Don't relocate for mouse events
-               relocate = false
-
-               button := e.Buttons()
-
-               for key, actions := range bindings {
-                       if button == key.buttons && e.Modifiers() == key.modifiers {
-                               for _, c := range v.Buf.cursors {
-                                       ok := v.SetCursor(c)
-                                       if !ok {
-                                               break
-                                       }
-                                       relocate = v.ExecuteActions(actions) || relocate
-                               }
-                               v.SetCursor(&v.Buf.Cursor)
-                               v.Buf.MergeCursors()
-                       }
-               }
-
-               for key, actions := range mouseBindings {
-                       if button == key.buttons && e.Modifiers() == key.modifiers {
-                               for _, action := range actions {
-                                       action(v, true, e)
-                               }
-                       }
-               }
-
-               switch button {
-               case tcell.ButtonNone:
-                       // Mouse event with no click
-                       if !v.mouseReleased {
-                               // Mouse was just released
-
-                               x, y := e.Position()
-                               x -= v.lineNumOffset - v.leftCol + v.x
-                               y += v.Topline - v.y
-
-                               // Relocating here isn't really necessary because the cursor will
-                               // be in the right place from the last mouse event
-                               // However, if we are running in a terminal that doesn't support mouse motion
-                               // events, this still allows the user to make selections, except only after they
-                               // release the mouse
-
-                               if !v.doubleClick && !v.tripleClick {
-                                       v.MoveToMouseClick(x, y)
-                                       v.Cursor.SetSelectionEnd(v.Cursor.Loc)
-                                       v.Cursor.CopySelection("primary")
-                               }
-                               v.mouseReleased = true
-                       }
-               }
-       }
-
-       if relocate {
-               v.Relocate()
-               // We run relocate again because there's a bug with relocating with softwrap
-               // when for example you jump to the bottom of the buffer and it tries to
-               // calculate where to put the topline so that the bottom line is at the bottom
-               // of the terminal and it runs into problems with visual lines vs real lines.
-               // This is (hopefully) a temporary solution
-               v.Relocate()
-       }
-}
-
-func (v *View) mainCursor() bool {
-       return v.Buf.curCursor == len(v.Buf.cursors)-1
-}
-
-// GutterMessage creates a message in this view's gutter
-func (v *View) GutterMessage(section string, lineN int, msg string, kind int) {
-       lineN--
-       gutterMsg := GutterMessage{
-               lineNum: lineN,
-               msg:     msg,
-               kind:    kind,
-       }
-       for _, v := range v.messages {
-               for _, gmsg := range v {
-                       if gmsg.lineNum == lineN {
-                               return
-                       }
-               }
-       }
-       messages := v.messages[section]
-       v.messages[section] = append(messages, gutterMsg)
-}
-
-// ClearGutterMessages clears all gutter messages from a given section
-func (v *View) ClearGutterMessages(section string) {
-       v.messages[section] = []GutterMessage{}
-}
-
-// ClearAllGutterMessages clears all the gutter messages
-func (v *View) ClearAllGutterMessages() {
-       for k := range v.messages {
-               v.messages[k] = []GutterMessage{}
-       }
-}
-
-// 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)
-       } else {
-               helpBuffer := NewBufferFromString(string(data), helpPage+".md")
-               helpBuffer.name = "Help"
-
-               if v.Type == vtHelp {
-                       v.OpenBuffer(helpBuffer)
-               } else {
-                       v.HSplit(helpBuffer)
-                       CurView().Type = vtHelp
-               }
-       }
-}
-
-// DisplayView draws the view to the screen
-func (v *View) DisplayView() {
-       if v.Type == vtTerm {
-               v.term.Display()
-               return
-       }
-
-       if v.Buf.Settings["softwrap"].(bool) && v.leftCol != 0 {
-               v.leftCol = 0
-       }
-
-       if v.Type == vtLog || v.Type == vtRaw {
-               // Log or raw views should always follow the cursor...
-               v.Relocate()
-       }
-
-       // We need to know the string length of the largest line number
-       // so we can pad appropriately when displaying line numbers
-       maxLineNumLength := len(strconv.Itoa(v.Buf.NumLines))
-
-       if v.Buf.Settings["ruler"] == true {
-               // + 1 for the little space after the line number
-               v.lineNumOffset = maxLineNumLength + 1
-       } else {
-               v.lineNumOffset = 0
-       }
-
-       // We need to add to the line offset if there are gutter messages
-       var hasGutterMessages bool
-       for _, v := range v.messages {
-               if len(v) > 0 {
-                       hasGutterMessages = true
-               }
-       }
-       if hasGutterMessages {
-               v.lineNumOffset += 2
-       }
-
-       divider := 0
-       if v.x != 0 {
-               // One space for the extra split divider
-               v.lineNumOffset++
-               divider = 1
-       }
-
-       xOffset := v.x + v.lineNumOffset
-       yOffset := v.y
-
-       height := v.Height
-       width := v.Width
-       left := v.leftCol
-       top := v.Topline
-
-       v.cellview.Draw(v.Buf, top, height, left, width-v.lineNumOffset)
-
-       screenX := v.x
-       realLineN := top - 1
-       visualLineN := 0
-       var line []*Char
-       for visualLineN, line = range v.cellview.lines {
-               var firstChar *Char
-               if len(line) > 0 {
-                       firstChar = line[0]
-               }
-
-               var softwrapped bool
-               if firstChar != nil {
-                       if firstChar.realLoc.Y == realLineN {
-                               softwrapped = true
-                       }
-                       realLineN = firstChar.realLoc.Y
-               } else {
-                       realLineN++
-               }
-
-               colorcolumn := int(v.Buf.Settings["colorcolumn"].(float64))
-               if colorcolumn != 0 && xOffset+colorcolumn-v.leftCol < v.Width {
-                       style := GetColor("color-column")
-                       fg, _, _ := style.Decompose()
-                       st := defStyle.Background(fg)
-                       screen.SetContent(xOffset+colorcolumn-v.leftCol, yOffset+visualLineN, ' ', nil, st)
-               }
-
-               screenX = v.x
-
-               // If there are gutter messages we need to display the '>>' symbol here
-               if hasGutterMessages {
-                       // msgOnLine stores whether or not there is a gutter message on this line in particular
-                       msgOnLine := false
-                       for k := range v.messages {
-                               for _, msg := range v.messages[k] {
-                                       if msg.lineNum == realLineN {
-                                               msgOnLine = true
-                                               gutterStyle := defStyle
-                                               switch msg.kind {
-                                               case GutterInfo:
-                                                       if style, ok := colorscheme["gutter-info"]; ok {
-                                                               gutterStyle = style
-                                                       }
-                                               case GutterWarning:
-                                                       if style, ok := colorscheme["gutter-warning"]; ok {
-                                                               gutterStyle = style
-                                                       }
-                                               case GutterError:
-                                                       if style, ok := colorscheme["gutter-error"]; ok {
-                                                               gutterStyle = style
-                                                       }
-                                               }
-                                               screen.SetContent(screenX, yOffset+visualLineN, '>', nil, gutterStyle)
-                                               screenX++
-                                               screen.SetContent(screenX, yOffset+visualLineN, '>', nil, gutterStyle)
-                                               screenX++
-                                               if v.Cursor.Y == realLineN && !messenger.hasPrompt {
-                                                       messenger.Message(msg.msg)
-                                                       messenger.gutterMessage = true
-                                               }
-                                       }
-                               }
-                       }
-                       // If there is no message on this line we just display an empty offset
-                       if !msgOnLine {
-                               screen.SetContent(screenX, yOffset+visualLineN, ' ', nil, defStyle)
-                               screenX++
-                               screen.SetContent(screenX, yOffset+visualLineN, ' ', nil, defStyle)
-                               screenX++
-                               if v.Cursor.Y == realLineN && messenger.gutterMessage {
-                                       messenger.Reset()
-                                       messenger.gutterMessage = false
-                               }
-                       }
-               }
-
-               lineNumStyle := defStyle
-               if v.Buf.Settings["ruler"] == true {
-                       // Write the line number
-                       if style, ok := colorscheme["line-number"]; ok {
-                               lineNumStyle = style
-                       }
-                       if style, ok := colorscheme["current-line-number"]; ok {
-                               if realLineN == v.Cursor.Y && tabs[curTab].CurView == v.Num && !v.Cursor.HasSelection() {
-                                       lineNumStyle = style
-                               }
-                       }
-
-                       lineNum := strconv.Itoa(realLineN + 1)
-
-                       // Write the spaces before the line number if necessary
-                       for i := 0; i < maxLineNumLength-len(lineNum); i++ {
-                               screen.SetContent(screenX+divider, yOffset+visualLineN, ' ', nil, lineNumStyle)
-                               screenX++
-                       }
-                       if softwrapped && visualLineN != 0 {
-                               // Pad without the line number because it was written on the visual line before
-                               for range lineNum {
-                                       screen.SetContent(screenX+divider, yOffset+visualLineN, ' ', nil, lineNumStyle)
-                                       screenX++
-                               }
-                       } else {
-                               // Write the actual line number
-                               for _, ch := range lineNum {
-                                       screen.SetContent(screenX+divider, yOffset+visualLineN, ch, nil, lineNumStyle)
-                                       screenX++
-                               }
-                       }
-
-                       // Write the extra space
-                       screen.SetContent(screenX+divider, yOffset+visualLineN, ' ', nil, lineNumStyle)
-                       screenX++
-               }
-
-               var lastChar *Char
-               cursorSet := false
-               for _, char := range line {
-                       if char != nil {
-                               lineStyle := char.style
-
-                               colorcolumn := int(v.Buf.Settings["colorcolumn"].(float64))
-                               if colorcolumn != 0 && char.visualLoc.X == colorcolumn {
-                                       style := GetColor("color-column")
-                                       fg, _, _ := style.Decompose()
-                                       lineStyle = lineStyle.Background(fg)
-                               }
-
-                               charLoc := char.realLoc
-                               for _, c := range v.Buf.cursors {
-                                       v.SetCursor(c)
-                                       if v.Cursor.HasSelection() &&
-                                               (charLoc.GreaterEqual(v.Cursor.CurSelection[0]) && charLoc.LessThan(v.Cursor.CurSelection[1]) ||
-                                                       charLoc.LessThan(v.Cursor.CurSelection[0]) && charLoc.GreaterEqual(v.Cursor.CurSelection[1])) {
-                                               // The current character is selected
-                                               lineStyle = defStyle.Reverse(true)
-
-                                               if style, ok := colorscheme["selection"]; ok {
-                                                       lineStyle = style
-                                               }
-                                       }
-                               }
-                               v.SetCursor(&v.Buf.Cursor)
-
-                               if v.Buf.Settings["cursorline"].(bool) && tabs[curTab].CurView == v.Num &&
-                                       !v.Cursor.HasSelection() && v.Cursor.Y == realLineN {
-                                       style := GetColor("cursor-line")
-                                       fg, _, _ := style.Decompose()
-                                       lineStyle = lineStyle.Background(fg)
-                               }
-
-                               screen.SetContent(xOffset+char.visualLoc.X, yOffset+char.visualLoc.Y, char.drawChar, nil, lineStyle)
-
-                               for i, c := range v.Buf.cursors {
-                                       v.SetCursor(c)
-                                       if tabs[curTab].CurView == v.Num && !v.Cursor.HasSelection() &&
-                                               v.Cursor.Y == char.realLoc.Y && v.Cursor.X == char.realLoc.X && (!cursorSet || i != 0) {
-                                               ShowMultiCursor(xOffset+char.visualLoc.X, yOffset+char.visualLoc.Y, i)
-                                               cursorSet = true
-                                       }
-                               }
-                               v.SetCursor(&v.Buf.Cursor)
-
-                               lastChar = char
-                       }
-               }
-
-               lastX := 0
-               var realLoc Loc
-               var visualLoc Loc
-               var cx, cy int
-               if lastChar != nil {
-                       lastX = xOffset + lastChar.visualLoc.X + lastChar.width
-                       for i, c := range v.Buf.cursors {
-                               v.SetCursor(c)
-                               if tabs[curTab].CurView == v.Num && !v.Cursor.HasSelection() &&
-                                       v.Cursor.Y == lastChar.realLoc.Y && v.Cursor.X == lastChar.realLoc.X+1 {
-                                       ShowMultiCursor(lastX, yOffset+lastChar.visualLoc.Y, i)
-                                       cx, cy = lastX, yOffset+lastChar.visualLoc.Y
-                               }
-                       }
-                       v.SetCursor(&v.Buf.Cursor)
-                       realLoc = Loc{lastChar.realLoc.X + 1, realLineN}
-                       visualLoc = Loc{lastX - xOffset, lastChar.visualLoc.Y}
-               } else if len(line) == 0 {
-                       for i, c := range v.Buf.cursors {
-                               v.SetCursor(c)
-                               if tabs[curTab].CurView == v.Num && !v.Cursor.HasSelection() &&
-                                       v.Cursor.Y == realLineN {
-                                       ShowMultiCursor(xOffset, yOffset+visualLineN, i)
-                                       cx, cy = xOffset, yOffset+visualLineN
-                               }
-                       }
-                       v.SetCursor(&v.Buf.Cursor)
-                       lastX = xOffset
-                       realLoc = Loc{0, realLineN}
-                       visualLoc = Loc{0, visualLineN}
-               }
-
-               if v.Cursor.HasSelection() &&
-                       (realLoc.GreaterEqual(v.Cursor.CurSelection[0]) && realLoc.LessThan(v.Cursor.CurSelection[1]) ||
-                               realLoc.LessThan(v.Cursor.CurSelection[0]) && realLoc.GreaterEqual(v.Cursor.CurSelection[1])) {
-                       // The current character is selected
-                       selectStyle := defStyle.Reverse(true)
-
-                       if style, ok := colorscheme["selection"]; ok {
-                               selectStyle = style
-                       }
-                       screen.SetContent(xOffset+visualLoc.X, yOffset+visualLoc.Y, ' ', nil, selectStyle)
-               }
-
-               if v.Buf.Settings["cursorline"].(bool) && tabs[curTab].CurView == v.Num &&
-                       !v.Cursor.HasSelection() && v.Cursor.Y == realLineN {
-                       for i := lastX; i < xOffset+v.Width-v.lineNumOffset; i++ {
-                               style := GetColor("cursor-line")
-                               fg, _, _ := style.Decompose()
-                               style = style.Background(fg)
-                               if !(tabs[curTab].CurView == v.Num && !v.Cursor.HasSelection() && i == cx && yOffset+visualLineN == cy) {
-                                       screen.SetContent(i, yOffset+visualLineN, ' ', nil, style)
-                               }
-                       }
-               }
-       }
-
-       if divider != 0 {
-               dividerStyle := defStyle
-               if style, ok := colorscheme["divider"]; ok {
-                       dividerStyle = style
-               }
-               for i := 0; i < v.Height; i++ {
-                       screen.SetContent(v.x, yOffset+i, '|', nil, dividerStyle.Reverse(true))
-               }
-       }
-}
-
-// ShowMultiCursor will display a cursor at a location
-// If i == 0 then the terminal cursor will be used
-// Otherwise a fake cursor will be drawn at the position
-func ShowMultiCursor(x, y, i int) {
-       if i == 0 {
-               screen.ShowCursor(x, y)
-       } else {
-               r, _, _, _ := screen.GetContent(x, y)
-               screen.SetContent(x, y, r, nil, defStyle.Reverse(true))
-       }
-}
-
-// Display renders the view, the cursor, and statusline
-func (v *View) Display() {
-       if globalSettings["termtitle"].(bool) {
-               screen.SetTitle("micro: " + v.Buf.GetName())
-       }
-       v.DisplayView()
-       // Don't draw the cursor if it is out of the viewport or if it has a selection
-       if v.Num == tabs[curTab].CurView && (v.Cursor.Y-v.Topline < 0 || v.Cursor.Y-v.Topline > v.Height-1 || v.Cursor.HasSelection()) {
-               screen.HideCursor()
-       }
-       _, screenH := screen.Size()
-
-       if v.Buf.Settings["scrollbar"].(bool) {
-               v.scrollbar.Display()
-       }
-
-       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))
-               }
-       }
-}
diff --git a/cmd/micro/window.go b/cmd/micro/window.go
new file mode 100644 (file)
index 0000000..4a14aed
--- /dev/null
@@ -0,0 +1,187 @@
+package main
+
+import (
+       "strconv"
+       "unicode/utf8"
+
+       runewidth "github.com/mattn/go-runewidth"
+       "github.com/zyedidia/tcell"
+)
+
+type Window struct {
+       // X and Y coordinates for the top left of the window
+       X int
+       Y int
+
+       // Width and Height for the window
+       Width  int
+       Height int
+
+       // Which line in the buffer to start displaying at (vertical scroll)
+       StartLine int
+       // Which visual column in the to start displaying at (horizontal scroll)
+       StartCol int
+
+       // Buffer being shown in this window
+       Buf *Buffer
+
+       sline *StatusLine
+}
+
+func NewWindow(x, y, width, height int, buf *Buffer) *Window {
+       w := new(Window)
+       w.X, w.Y, w.Width, w.Height, w.Buf = x, y, width, height, buf
+
+       w.sline = NewStatusLine(w)
+
+       return w
+}
+
+func (w *Window) DrawLineNum(lineNumStyle tcell.Style, softwrapped bool, maxLineNumLength int, vloc *Loc, bloc *Loc) {
+       lineNum := strconv.Itoa(bloc.Y + 1)
+
+       // Write the spaces before the line number if necessary
+       for i := 0; i < maxLineNumLength-len(lineNum); i++ {
+               screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, ' ', nil, lineNumStyle)
+               vloc.X++
+       }
+       // Write the actual line number
+       for _, ch := range lineNum {
+               if softwrapped {
+                       screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, ' ', nil, lineNumStyle)
+               } else {
+                       screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, ch, nil, lineNumStyle)
+               }
+               vloc.X++
+       }
+
+       // Write the extra space
+       screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, ' ', nil, lineNumStyle)
+       vloc.X++
+}
+
+// GetStyle returns the highlight style for the given character position
+// If there is no change to the current highlight style it just returns that
+func (w *Window) GetStyle(style tcell.Style, bloc Loc, r rune) tcell.Style {
+       if group, ok := w.Buf.Match(bloc.Y)[bloc.X]; ok {
+               s := GetColor(group.String())
+               return s
+       }
+       return style
+}
+
+// DisplayBuffer draws the buffer being shown in this window on the screen
+func (w *Window) DisplayBuffer() {
+       b := w.Buf
+
+       bufHeight := w.Height
+       if b.Settings["statusline"].(bool) {
+               bufHeight--
+       }
+
+       // TODO: Rehighlighting
+       // start := w.StartLine
+       if b.Settings["syntax"].(bool) && b.syntaxDef != nil {
+               //      if start > 0 && b.lines[start-1].rehighlight {
+               //              b.highlighter.ReHighlightLine(b, start-1)
+               //              b.lines[start-1].rehighlight = false
+               //      }
+               //
+               //      b.highlighter.ReHighlightStates(b, start)
+               //
+               b.highlighter.HighlightMatches(b, w.StartLine, w.StartLine+bufHeight)
+       }
+
+       lineNumStyle := defStyle
+       if style, ok := colorscheme["line-number"]; ok {
+               lineNumStyle = style
+       }
+
+       // We need to know the string length of the largest line number
+       // so we can pad appropriately when displaying line numbers
+       maxLineNumLength := len(strconv.Itoa(len(b.lines)))
+
+       tabsize := int(b.Settings["tabsize"].(float64))
+       softwrap := b.Settings["softwrap"].(bool)
+
+       // this represents the current draw position
+       // within the current window
+       vloc := Loc{0, 0}
+
+       // this represents the current draw position in the buffer (char positions)
+       bloc := Loc{w.StartCol, w.StartLine}
+
+       curStyle := defStyle
+       for vloc.Y = 0; vloc.Y < bufHeight; vloc.Y++ {
+               vloc.X = 0
+               if b.Settings["ruler"].(bool) {
+                       w.DrawLineNum(lineNumStyle, false, maxLineNumLength, &vloc, &bloc)
+               }
+
+               line := b.LineBytes(bloc.Y)
+               line, nColsBeforeStart := SliceVisualEnd(line, bloc.X, tabsize)
+               totalwidth := bloc.X - nColsBeforeStart
+               for len(line) > 0 {
+                       r, size := utf8.DecodeRune(line)
+
+                       curStyle = w.GetStyle(curStyle, bloc, r)
+
+                       if nColsBeforeStart <= 0 {
+                               screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, r, nil, curStyle)
+                               vloc.X++
+                       }
+                       nColsBeforeStart--
+
+                       width := 0
+
+                       char := ' '
+                       switch r {
+                       case '\t':
+                               ts := tabsize - (totalwidth % tabsize)
+                               width = ts
+                       default:
+                               width = runewidth.RuneWidth(r)
+                               char = '@'
+                       }
+
+                       bloc.X++
+                       line = line[size:]
+
+                       // Draw any extra characters either spaces for tabs or @ for incomplete wide runes
+                       if width > 1 {
+                               for i := 1; i < width; i++ {
+                                       if nColsBeforeStart <= 0 {
+                                               screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, char, nil, curStyle)
+                                               vloc.X++
+                                       }
+                                       nColsBeforeStart--
+                               }
+                       }
+                       totalwidth += width
+
+                       // If we reach the end of the window then we either stop or we wrap for softwrap
+                       if vloc.X >= w.Width {
+                               if !softwrap {
+                                       break
+                               } else {
+                                       vloc.Y++
+                                       if vloc.Y >= bufHeight {
+                                               break
+                                       }
+                                       vloc.X = 0
+                                       // This will draw an empty line number because the current line is wrapped
+                                       w.DrawLineNum(lineNumStyle, true, maxLineNumLength, &vloc, &bloc)
+                               }
+                       }
+               }
+               bloc.X = w.StartCol
+               bloc.Y++
+               if bloc.Y >= len(b.lines) {
+                       break
+               }
+       }
+}
+
+func (w *Window) DisplayStatusLine() {
+       w.sline.Display()
+}