]> git.lizzy.rs Git - micro.git/commitdiff
Merge branch 'master' of https://github.com/dbeef/micro into dbeef-master
authorZachary Yedidia <zyedidia@gmail.com>
Sat, 8 Feb 2020 00:37:56 +0000 (19:37 -0500)
committerZachary Yedidia <zyedidia@gmail.com>
Sat, 8 Feb 2020 00:37:56 +0000 (19:37 -0500)
1  2 
internal/action/actions.go
internal/action/bufpane.go
internal/action/defaults_darwin.go
internal/action/defaults_other.go

index 4aeba6f3b51e08847745728247ac19959a3bd77e,0000000000000000000000000000000000000000..3db9af31dec6130e7a7e1e4ba3e1dffbc9993ce4
mode 100644,000000..100644
--- /dev/null
@@@ -1,1588 -1,0 +1,1623 @@@
 +package action
 +
 +import (
 +      "regexp"
 +      "runtime"
 +      "strings"
 +      "time"
 +      "unicode/utf8"
 +
 +      shellquote "github.com/kballard/go-shellquote"
 +      "github.com/zyedidia/clipboard"
 +      "github.com/zyedidia/micro/internal/buffer"
 +      "github.com/zyedidia/micro/internal/config"
 +      "github.com/zyedidia/micro/internal/screen"
 +      "github.com/zyedidia/micro/internal/shell"
 +      "github.com/zyedidia/micro/internal/util"
 +      "github.com/zyedidia/tcell"
 +)
 +
 +// ScrollUp is not an action
 +func (h *BufPane) ScrollUp(n int) {
 +      v := h.GetView()
 +      if v.StartLine >= n {
 +              v.StartLine -= n
 +              h.SetView(v)
 +      } else {
 +              v.StartLine = 0
 +      }
 +}
 +
 +// ScrollDown is not an action
 +func (h *BufPane) ScrollDown(n int) {
 +      v := h.GetView()
 +      if v.StartLine <= h.Buf.LinesNum()-1-n {
 +              v.StartLine += n
 +              h.SetView(v)
 +      }
 +}
 +
 +// MousePress is the event that should happen when a normal click happens
 +// This is almost always bound to left click
 +func (h *BufPane) MousePress(e *tcell.EventMouse) bool {
 +      b := h.Buf
 +      mx, my := e.Position()
 +      mouseLoc := h.LocFromVisual(buffer.Loc{mx, my})
 +      h.Cursor.Loc = mouseLoc
 +      if h.mouseReleased {
 +              if b.NumCursors() > 1 {
 +                      b.ClearCursors()
 +                      h.Relocate()
 +                      h.Cursor = h.Buf.GetActiveCursor()
 +                      h.Cursor.Loc = mouseLoc
 +              }
 +              if time.Since(h.lastClickTime)/time.Millisecond < config.DoubleClickThreshold && (mouseLoc.X == h.lastLoc.X && mouseLoc.Y == h.lastLoc.Y) {
 +                      if h.doubleClick {
 +                              // Triple click
 +                              h.lastClickTime = time.Now()
 +
 +                              h.tripleClick = true
 +                              h.doubleClick = false
 +
 +                              h.Cursor.SelectLine()
 +                              h.Cursor.CopySelection("primary")
 +                      } else {
 +                              // Double click
 +                              h.lastClickTime = time.Now()
 +
 +                              h.doubleClick = true
 +                              h.tripleClick = false
 +
 +                              h.Cursor.SelectWord()
 +                              h.Cursor.CopySelection("primary")
 +                      }
 +              } else {
 +                      h.doubleClick = false
 +                      h.tripleClick = false
 +                      h.lastClickTime = time.Now()
 +
 +                      h.Cursor.OrigSelection[0] = h.Cursor.Loc
 +                      h.Cursor.CurSelection[0] = h.Cursor.Loc
 +                      h.Cursor.CurSelection[1] = h.Cursor.Loc
 +              }
 +              h.mouseReleased = false
 +      } else if !h.mouseReleased {
 +              if h.tripleClick {
 +                      h.Cursor.AddLineToSelection()
 +              } else if h.doubleClick {
 +                      h.Cursor.AddWordToSelection()
 +              } else {
 +                      h.Cursor.SetSelectionEnd(h.Cursor.Loc)
 +              }
 +      }
 +
 +      h.Cursor.StoreVisualX()
 +      h.lastLoc = mouseLoc
 +      return true
 +}
 +
 +// ScrollUpAction scrolls the view up
 +func (h *BufPane) ScrollUpAction() bool {
 +      h.ScrollUp(util.IntOpt(h.Buf.Settings["scrollspeed"]))
 +      return true
 +}
 +
 +// ScrollDownAction scrolls the view up
 +func (h *BufPane) ScrollDownAction() bool {
 +      h.ScrollDown(util.IntOpt(h.Buf.Settings["scrollspeed"]))
 +      return true
 +}
 +
 +// Center centers the view on the cursor
 +func (h *BufPane) Center() bool {
 +      v := h.GetView()
 +      v.StartLine = h.Cursor.Y - v.Height/2
 +      if v.StartLine+v.Height > h.Buf.LinesNum() {
 +              v.StartLine = h.Buf.LinesNum() - v.Height
 +      }
 +      if v.StartLine < 0 {
 +              v.StartLine = 0
 +      }
 +      h.SetView(v)
 +      h.Relocate()
 +      return true
 +}
 +
 +// CursorUp moves the cursor up
 +func (h *BufPane) CursorUp() bool {
 +      h.Cursor.Deselect(true)
 +      h.Cursor.Up()
 +      h.Relocate()
 +      return true
 +}
 +
 +// CursorDown moves the cursor down
 +func (h *BufPane) CursorDown() bool {
 +      h.Cursor.Deselect(true)
 +      h.Cursor.Down()
 +      h.Relocate()
 +      return true
 +}
 +
 +// CursorLeft moves the cursor left
 +func (h *BufPane) CursorLeft() bool {
 +      if h.Cursor.HasSelection() {
 +              h.Cursor.Deselect(true)
 +      } else {
 +              tabstospaces := h.Buf.Settings["tabstospaces"].(bool)
 +              tabmovement := h.Buf.Settings["tabmovement"].(bool)
 +              if tabstospaces && tabmovement {
 +                      tabsize := int(h.Buf.Settings["tabsize"].(float64))
 +                      line := h.Buf.LineBytes(h.Cursor.Y)
 +                      if h.Cursor.X-tabsize >= 0 && util.IsSpaces(line[h.Cursor.X-tabsize:h.Cursor.X]) && util.IsBytesWhitespace(line[0:h.Cursor.X-tabsize]) {
 +                              for i := 0; i < tabsize; i++ {
 +                                      h.Cursor.Left()
 +                              }
 +                      } else {
 +                              h.Cursor.Left()
 +                      }
 +              } else {
 +                      h.Cursor.Left()
 +              }
 +      }
 +      h.Relocate()
 +      return true
 +}
 +
 +// CursorRight moves the cursor right
 +func (h *BufPane) CursorRight() bool {
 +      if h.Cursor.HasSelection() {
 +              h.Cursor.Deselect(false)
 +              h.Cursor.Loc = h.Cursor.Loc.Move(1, h.Buf)
 +      } else {
 +              tabstospaces := h.Buf.Settings["tabstospaces"].(bool)
 +              tabmovement := h.Buf.Settings["tabmovement"].(bool)
 +              if tabstospaces && tabmovement {
 +                      tabsize := int(h.Buf.Settings["tabsize"].(float64))
 +                      line := h.Buf.LineBytes(h.Cursor.Y)
 +                      if h.Cursor.X+tabsize < utf8.RuneCount(line) && util.IsSpaces(line[h.Cursor.X:h.Cursor.X+tabsize]) && util.IsBytesWhitespace(line[0:h.Cursor.X]) {
 +                              for i := 0; i < tabsize; i++ {
 +                                      h.Cursor.Right()
 +                              }
 +                      } else {
 +                              h.Cursor.Right()
 +                      }
 +              } else {
 +                      h.Cursor.Right()
 +              }
 +      }
 +
 +      h.Relocate()
 +      return true
 +}
 +
 +// WordRight moves the cursor one word to the right
 +func (h *BufPane) WordRight() bool {
 +      h.Cursor.Deselect(false)
 +      h.Cursor.WordRight()
 +      h.Relocate()
 +      return true
 +}
 +
 +// WordLeft moves the cursor one word to the left
 +func (h *BufPane) WordLeft() bool {
 +      h.Cursor.Deselect(true)
 +      h.Cursor.WordLeft()
 +      h.Relocate()
 +      return true
 +}
 +
 +// SelectUp selects up one line
 +func (h *BufPane) SelectUp() bool {
 +      if !h.Cursor.HasSelection() {
 +              h.Cursor.OrigSelection[0] = h.Cursor.Loc
 +      }
 +      h.Cursor.Up()
 +      h.Cursor.SelectTo(h.Cursor.Loc)
 +      h.Relocate()
 +      return true
 +}
 +
 +// SelectDown selects down one line
 +func (h *BufPane) SelectDown() bool {
 +      if !h.Cursor.HasSelection() {
 +              h.Cursor.OrigSelection[0] = h.Cursor.Loc
 +      }
 +      h.Cursor.Down()
 +      h.Cursor.SelectTo(h.Cursor.Loc)
 +      h.Relocate()
 +      return true
 +}
 +
 +// SelectLeft selects the character to the left of the cursor
 +func (h *BufPane) SelectLeft() bool {
 +      loc := h.Cursor.Loc
 +      count := h.Buf.End()
 +      if loc.GreaterThan(count) {
 +              loc = count
 +      }
 +      if !h.Cursor.HasSelection() {
 +              h.Cursor.OrigSelection[0] = loc
 +      }
 +      h.Cursor.Left()
 +      h.Cursor.SelectTo(h.Cursor.Loc)
 +      h.Relocate()
 +      return true
 +}
 +
 +// SelectRight selects the character to the right of the cursor
 +func (h *BufPane) SelectRight() bool {
 +      loc := h.Cursor.Loc
 +      count := h.Buf.End()
 +      if loc.GreaterThan(count) {
 +              loc = count
 +      }
 +      if !h.Cursor.HasSelection() {
 +              h.Cursor.OrigSelection[0] = loc
 +      }
 +      h.Cursor.Right()
 +      h.Cursor.SelectTo(h.Cursor.Loc)
 +      h.Relocate()
 +      return true
 +}
 +
 +// SelectWordRight selects the word to the right of the cursor
 +func (h *BufPane) SelectWordRight() bool {
 +      if !h.Cursor.HasSelection() {
 +              h.Cursor.OrigSelection[0] = h.Cursor.Loc
 +      }
 +      h.Cursor.WordRight()
 +      h.Cursor.SelectTo(h.Cursor.Loc)
 +      h.Relocate()
 +      return true
 +}
 +
 +// SelectWordLeft selects the word to the left of the cursor
 +func (h *BufPane) SelectWordLeft() bool {
 +      if !h.Cursor.HasSelection() {
 +              h.Cursor.OrigSelection[0] = h.Cursor.Loc
 +      }
 +      h.Cursor.WordLeft()
 +      h.Cursor.SelectTo(h.Cursor.Loc)
 +      h.Relocate()
 +      return true
 +}
 +
 +// StartOfLine moves the cursor to the start of the text of the line
 +func (h *BufPane) StartOfText() bool {
 +      h.Cursor.Deselect(true)
 +      h.Cursor.StartOfText()
 +      h.Relocate()
 +      return true
 +}
 +
 +// StartOfLine moves the cursor to the start of the line
 +func (h *BufPane) StartOfLine() bool {
 +      h.Cursor.Deselect(true)
 +      h.Cursor.Start()
 +      h.Relocate()
 +      return true
 +}
 +
 +// EndOfLine moves the cursor to the end of the line
 +func (h *BufPane) EndOfLine() bool {
 +      h.Cursor.Deselect(true)
 +      h.Cursor.End()
 +      h.Relocate()
 +      return true
 +}
 +
 +// SelectLine selects the entire current line
 +func (h *BufPane) SelectLine() bool {
 +      h.Cursor.SelectLine()
 +      h.Relocate()
 +      return true
 +}
 +
 +// SelectToStartOfText selects to the start of the text on the current line
 +func (h *BufPane) SelectToStartOfText() bool {
 +      if !h.Cursor.HasSelection() {
 +              h.Cursor.OrigSelection[0] = h.Cursor.Loc
 +      }
 +      h.Cursor.StartOfText()
 +      h.Cursor.SelectTo(h.Cursor.Loc)
 +      h.Relocate()
 +      return true
 +}
 +
 +// SelectToStartOfLine selects to the start of the current line
 +func (h *BufPane) SelectToStartOfLine() bool {
 +      if !h.Cursor.HasSelection() {
 +              h.Cursor.OrigSelection[0] = h.Cursor.Loc
 +      }
 +      h.Cursor.Start()
 +      h.Cursor.SelectTo(h.Cursor.Loc)
 +      h.Relocate()
 +      return true
 +}
 +
 +// SelectToEndOfLine selects to the end of the current line
 +func (h *BufPane) SelectToEndOfLine() bool {
 +      if !h.Cursor.HasSelection() {
 +              h.Cursor.OrigSelection[0] = h.Cursor.Loc
 +      }
 +      h.Cursor.End()
 +      h.Cursor.SelectTo(h.Cursor.Loc)
 +      h.Relocate()
 +      return true
 +}
 +
 +// ParagraphPrevious moves the cursor to the previous empty line, or beginning of the buffer if there's none
 +func (h *BufPane) ParagraphPrevious() bool {
 +      var line int
 +      for line = h.Cursor.Y; line > 0; line-- {
 +              if len(h.Buf.LineBytes(line)) == 0 && line != h.Cursor.Y {
 +                      h.Cursor.X = 0
 +                      h.Cursor.Y = line
 +                      break
 +              }
 +      }
 +      // If no empty line found. move cursor to end of buffer
 +      if line == 0 {
 +              h.Cursor.Loc = h.Buf.Start()
 +      }
 +      h.Relocate()
 +      return true
 +}
 +
 +// ParagraphNext moves the cursor to the next empty line, or end of the buffer if there's none
 +func (h *BufPane) ParagraphNext() bool {
 +      var line int
 +      for line = h.Cursor.Y; line < h.Buf.LinesNum(); line++ {
 +              if len(h.Buf.LineBytes(line)) == 0 && line != h.Cursor.Y {
 +                      h.Cursor.X = 0
 +                      h.Cursor.Y = line
 +                      break
 +              }
 +      }
 +      // If no empty line found. move cursor to end of buffer
 +      if line == h.Buf.LinesNum() {
 +              h.Cursor.Loc = h.Buf.End()
 +      }
 +      h.Relocate()
 +      return true
 +}
 +
 +// Retab changes all tabs to spaces or all spaces to tabs depending
 +// on the user's settings
 +func (h *BufPane) Retab() bool {
 +      h.Buf.Retab()
 +      h.Relocate()
 +      return true
 +}
 +
 +// CursorStart moves the cursor to the start of the buffer
 +func (h *BufPane) CursorStart() bool {
 +      h.Cursor.Deselect(true)
 +      h.Cursor.X = 0
 +      h.Cursor.Y = 0
 +      h.Relocate()
 +      return true
 +}
 +
 +// CursorEnd moves the cursor to the end of the buffer
 +func (h *BufPane) CursorEnd() bool {
 +      h.Cursor.Deselect(true)
 +      h.Cursor.Loc = h.Buf.End()
 +      h.Cursor.StoreVisualX()
 +      h.Relocate()
 +      return true
 +}
 +
 +// SelectToStart selects the text from the cursor to the start of the buffer
 +func (h *BufPane) SelectToStart() bool {
 +      if !h.Cursor.HasSelection() {
 +              h.Cursor.OrigSelection[0] = h.Cursor.Loc
 +      }
 +      h.CursorStart()
 +      h.Cursor.SelectTo(h.Buf.Start())
 +      h.Relocate()
 +      return true
 +}
 +
 +// SelectToEnd selects the text from the cursor to the end of the buffer
 +func (h *BufPane) SelectToEnd() bool {
 +      if !h.Cursor.HasSelection() {
 +              h.Cursor.OrigSelection[0] = h.Cursor.Loc
 +      }
 +      h.CursorEnd()
 +      h.Cursor.SelectTo(h.Buf.End())
 +      h.Relocate()
 +      return true
 +}
 +
 +// InsertNewline inserts a newline plus possible some whitespace if autoindent is on
 +func (h *BufPane) InsertNewline() bool {
 +      // Insert a newline
 +      if h.Cursor.HasSelection() {
 +              h.Cursor.DeleteSelection()
 +              h.Cursor.ResetSelection()
 +      }
 +
 +      ws := util.GetLeadingWhitespace(h.Buf.LineBytes(h.Cursor.Y))
 +      cx := h.Cursor.X
 +      h.Buf.Insert(h.Cursor.Loc, "\n")
 +      // h.Cursor.Right()
 +
 +      if h.Buf.Settings["autoindent"].(bool) {
 +              if cx < len(ws) {
 +                      ws = ws[0:cx]
 +              }
 +              h.Buf.Insert(h.Cursor.Loc, string(ws))
 +              // for i := 0; i < len(ws); i++ {
 +              //      h.Cursor.Right()
 +              // }
 +
 +              // Remove the whitespaces if keepautoindent setting is off
 +              if util.IsSpacesOrTabs(h.Buf.LineBytes(h.Cursor.Y-1)) && !h.Buf.Settings["keepautoindent"].(bool) {
 +                      line := h.Buf.LineBytes(h.Cursor.Y - 1)
 +                      h.Buf.Remove(buffer.Loc{X: 0, Y: h.Cursor.Y - 1}, buffer.Loc{X: utf8.RuneCount(line), Y: h.Cursor.Y - 1})
 +              }
 +      }
 +      h.Cursor.LastVisualX = h.Cursor.GetVisualX()
 +      h.Relocate()
 +      return true
 +}
 +
 +// Backspace deletes the previous character
 +func (h *BufPane) Backspace() bool {
 +      if h.Cursor.HasSelection() {
 +              h.Cursor.DeleteSelection()
 +              h.Cursor.ResetSelection()
 +      } else if h.Cursor.Loc.GreaterThan(h.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 := util.SliceStart(h.Buf.LineBytes(h.Cursor.Y), h.Cursor.X)
 +              tabSize := int(h.Buf.Settings["tabsize"].(float64))
 +              if h.Buf.Settings["tabstospaces"].(bool) && util.IsSpaces(lineStart) && len(lineStart) != 0 && utf8.RuneCount(lineStart)%tabSize == 0 {
 +                      loc := h.Cursor.Loc
 +                      h.Buf.Remove(loc.Move(-tabSize, h.Buf), loc)
 +              } else {
 +                      loc := h.Cursor.Loc
 +                      h.Buf.Remove(loc.Move(-1, h.Buf), loc)
 +              }
 +      }
 +      h.Cursor.LastVisualX = h.Cursor.GetVisualX()
 +      h.Relocate()
 +      return true
 +}
 +
 +// DeleteWordRight deletes the word to the right of the cursor
 +func (h *BufPane) DeleteWordRight() bool {
 +      h.SelectWordRight()
 +      if h.Cursor.HasSelection() {
 +              h.Cursor.DeleteSelection()
 +              h.Cursor.ResetSelection()
 +      }
 +      h.Relocate()
 +      return true
 +}
 +
 +// DeleteWordLeft deletes the word to the left of the cursor
 +func (h *BufPane) DeleteWordLeft() bool {
 +      h.SelectWordLeft()
 +      if h.Cursor.HasSelection() {
 +              h.Cursor.DeleteSelection()
 +              h.Cursor.ResetSelection()
 +      }
 +      h.Relocate()
 +      return true
 +}
 +
 +// Delete deletes the next character
 +func (h *BufPane) Delete() bool {
 +      if h.Cursor.HasSelection() {
 +              h.Cursor.DeleteSelection()
 +              h.Cursor.ResetSelection()
 +      } else {
 +              loc := h.Cursor.Loc
 +              if loc.LessThan(h.Buf.End()) {
 +                      h.Buf.Remove(loc, loc.Move(1, h.Buf))
 +              }
 +      }
 +      h.Relocate()
 +      return true
 +}
 +
 +// IndentSelection indents the current selection
 +func (h *BufPane) IndentSelection() bool {
 +      if h.Cursor.HasSelection() {
 +              start := h.Cursor.CurSelection[0]
 +              end := h.Cursor.CurSelection[1]
 +              if end.Y < start.Y {
 +                      start, end = end, start
 +                      h.Cursor.SetSelectionStart(start)
 +                      h.Cursor.SetSelectionEnd(end)
 +              }
 +
 +              startY := start.Y
 +              endY := end.Move(-1, h.Buf).Y
 +              endX := end.Move(-1, h.Buf).X
 +              tabsize := int(h.Buf.Settings["tabsize"].(float64))
 +              indentsize := len(h.Buf.IndentString(tabsize))
 +              for y := startY; y <= endY; y++ {
 +                      if len(h.Buf.LineBytes(y)) > 0 {
 +                              h.Buf.Insert(buffer.Loc{X: 0, Y: y}, h.Buf.IndentString(tabsize))
 +                              if y == startY && start.X > 0 {
 +                                      h.Cursor.SetSelectionStart(start.Move(indentsize, h.Buf))
 +                              }
 +                              if y == endY {
 +                                      h.Cursor.SetSelectionEnd(buffer.Loc{X: endX + indentsize + 1, Y: endY})
 +                              }
 +                      }
 +              }
 +              h.Buf.RelocateCursors()
 +
 +              h.Relocate()
 +              return true
 +      }
 +      return false
 +}
 +
 +// OutdentLine moves the current line back one indentation
 +func (h *BufPane) OutdentLine() bool {
 +      if h.Cursor.HasSelection() {
 +              return false
 +      }
 +
 +      for x := 0; x < len(h.Buf.IndentString(util.IntOpt(h.Buf.Settings["tabsize"]))); x++ {
 +              if len(util.GetLeadingWhitespace(h.Buf.LineBytes(h.Cursor.Y))) == 0 {
 +                      break
 +              }
 +              h.Buf.Remove(buffer.Loc{X: 0, Y: h.Cursor.Y}, buffer.Loc{X: 1, Y: h.Cursor.Y})
 +      }
 +      h.Buf.RelocateCursors()
 +      h.Relocate()
 +      return true
 +}
 +
 +// OutdentSelection takes the current selection and moves it back one indent level
 +func (h *BufPane) OutdentSelection() bool {
 +      if h.Cursor.HasSelection() {
 +              start := h.Cursor.CurSelection[0]
 +              end := h.Cursor.CurSelection[1]
 +              if end.Y < start.Y {
 +                      start, end = end, start
 +                      h.Cursor.SetSelectionStart(start)
 +                      h.Cursor.SetSelectionEnd(end)
 +              }
 +
 +              startY := start.Y
 +              endY := end.Move(-1, h.Buf).Y
 +              for y := startY; y <= endY; y++ {
 +                      for x := 0; x < len(h.Buf.IndentString(util.IntOpt(h.Buf.Settings["tabsize"]))); x++ {
 +                              if len(util.GetLeadingWhitespace(h.Buf.LineBytes(y))) == 0 {
 +                                      break
 +                              }
 +                              h.Buf.Remove(buffer.Loc{X: 0, Y: y}, buffer.Loc{X: 1, Y: y})
 +                      }
 +              }
 +              h.Buf.RelocateCursors()
 +
 +              h.Relocate()
 +              return true
 +      }
 +      return false
 +}
 +
 +// Autocomplete cycles the suggestions and performs autocompletion if there are suggestions
 +func (h *BufPane) Autocomplete() bool {
 +      b := h.Buf
 +
 +      if h.Cursor.HasSelection() {
 +              return false
 +      }
 +
 +      if b.HasSuggestions {
 +              b.CycleAutocomplete(true)
 +              return true
 +      }
 +      return b.Autocomplete(buffer.BufferComplete)
 +}
 +
 +// CycleAutocompleteBack cycles back in the autocomplete suggestion list
 +func (h *BufPane) CycleAutocompleteBack() bool {
 +      if h.Cursor.HasSelection() {
 +              return false
 +      }
 +
 +      if h.Buf.HasSuggestions {
 +              h.Buf.CycleAutocomplete(false)
 +              return true
 +      }
 +      return false
 +}
 +
 +// InsertTab inserts a tab or spaces
 +func (h *BufPane) InsertTab() bool {
 +      b := h.Buf
 +      indent := b.IndentString(util.IntOpt(b.Settings["tabsize"]))
 +      tabBytes := len(indent)
 +      bytesUntilIndent := tabBytes - (h.Cursor.GetVisualX() % tabBytes)
 +      b.Insert(h.Cursor.Loc, indent[:bytesUntilIndent])
 +      h.Relocate()
 +      return true
 +}
 +
 +// SaveAll saves all open buffers
 +func (h *BufPane) SaveAll() bool {
 +      for _, b := range buffer.OpenBuffers {
 +              b.Save()
 +      }
 +      return true
 +}
 +
 +// Save the buffer to disk
 +func (h *BufPane) Save() bool {
 +      // If this is an empty buffer, ask for a filename
 +      if h.Buf.Path == "" {
 +              h.SaveAs()
 +      } else {
 +              noPrompt := h.saveBufToFile(h.Buf.Path, "Save")
 +              if noPrompt {
 +                      return true
 +              }
 +      }
 +
 +      return false
 +}
 +
 +// SaveAs saves the buffer to disk with the given name
 +func (h *BufPane) SaveAs() bool {
 +      InfoBar.Prompt("Filename: ", "", "Save", nil, func(resp string, canceled bool) {
 +              if !canceled {
 +                      // the filename might or might not be quoted, so unquote first then join the strings.
 +                      args, err := shellquote.Split(resp)
 +                      if err != nil {
 +                              InfoBar.Error("Error parsing arguments: ", err)
 +                              return
 +                      }
 +                      if len(args) == 0 {
 +                              InfoBar.Error("No filename given")
 +                              return
 +                      }
 +                      filename := strings.Join(args, " ")
 +                      noPrompt := h.saveBufToFile(filename, "SaveAs")
 +                      if noPrompt {
 +                              h.completeAction("SaveAs")
 +                      }
 +              }
 +      })
 +      return false
 +}
 +
 +// This function saves the buffer to `filename` and changes the buffer's path and name
 +// to `filename` if the save is successful
 +func (h *BufPane) saveBufToFile(filename string, action string) bool {
 +      err := h.Buf.SaveAs(filename)
 +      if err != nil {
 +              if strings.HasSuffix(err.Error(), "permission denied") {
 +                      InfoBar.YNPrompt("Permission denied. Do you want to save this file using sudo? (y,n)", func(yes, canceled bool) {
 +                              if yes && !canceled {
 +                                      err = h.Buf.SaveAsWithSudo(filename)
 +                                      if err != nil {
 +                                              InfoBar.Error(err)
 +                                      } else {
 +                                              h.Buf.Path = filename
 +                                              h.Buf.SetName(filename)
 +                                              InfoBar.Message("Saved " + filename)
 +                                      }
 +                                      h.completeAction(action)
 +                              }
 +                      })
 +                      return false
 +              } else {
 +                      InfoBar.Error(err)
 +              }
 +      } else {
 +              h.Buf.Path = filename
 +              h.Buf.SetName(filename)
 +              InfoBar.Message("Saved " + filename)
 +      }
 +      return true
 +}
 +
 +// Find opens a prompt and searches forward for the input
 +func (h *BufPane) Find() bool {
 +      h.searchOrig = h.Cursor.Loc
 +      InfoBar.Prompt("Find: ", "", "Find", func(resp string) {
 +              // Event callback
 +              match, found, _ := h.Buf.FindNext(resp, h.Buf.Start(), h.Buf.End(), h.searchOrig, true, true)
 +              if found {
 +                      h.Cursor.SetSelectionStart(match[0])
 +                      h.Cursor.SetSelectionEnd(match[1])
 +                      h.Cursor.OrigSelection[0] = h.Cursor.CurSelection[0]
 +                      h.Cursor.OrigSelection[1] = h.Cursor.CurSelection[1]
 +                      h.Cursor.GotoLoc(match[1])
 +              } else {
 +                      h.Cursor.GotoLoc(h.searchOrig)
 +                      h.Cursor.ResetSelection()
 +              }
 +              h.Relocate()
 +      }, func(resp string, canceled bool) {
 +              // Finished callback
 +              if !canceled {
 +                      match, found, err := h.Buf.FindNext(resp, h.Buf.Start(), h.Buf.End(), h.searchOrig, true, true)
 +                      if err != nil {
 +                              InfoBar.Error(err)
 +                      }
 +                      if found {
 +                              h.Cursor.SetSelectionStart(match[0])
 +                              h.Cursor.SetSelectionEnd(match[1])
 +                              h.Cursor.OrigSelection[0] = h.Cursor.CurSelection[0]
 +                              h.Cursor.OrigSelection[1] = h.Cursor.CurSelection[1]
 +                              h.Cursor.GotoLoc(h.Cursor.CurSelection[1])
 +                              h.lastSearch = resp
 +                      } else {
 +                              h.Cursor.ResetSelection()
 +                              InfoBar.Message("No matches found")
 +                      }
 +              } else {
 +                      h.Cursor.ResetSelection()
 +              }
 +              h.Relocate()
 +      })
 +
 +      return true
 +}
 +
 +// FindNext searches forwards for the last used search term
 +func (h *BufPane) FindNext() bool {
 +      // If the cursor is at the start of a selection and we search we want
 +      // to search from the end of the selection in the case that
 +      // the selection is a search result in which case we wouldn't move at
 +      // at all which would be bad
 +      searchLoc := h.Cursor.Loc
 +      if h.Cursor.HasSelection() {
 +              searchLoc = h.Cursor.CurSelection[1]
 +      }
 +      match, found, err := h.Buf.FindNext(h.lastSearch, h.Buf.Start(), h.Buf.End(), searchLoc, true, true)
 +      if err != nil {
 +              InfoBar.Error(err)
 +      }
 +      if found {
 +              h.Cursor.SetSelectionStart(match[0])
 +              h.Cursor.SetSelectionEnd(match[1])
 +              h.Cursor.OrigSelection[0] = h.Cursor.CurSelection[0]
 +              h.Cursor.OrigSelection[1] = h.Cursor.CurSelection[1]
 +              h.Cursor.Loc = h.Cursor.CurSelection[1]
 +      } else {
 +              h.Cursor.ResetSelection()
 +      }
 +      h.Relocate()
 +      return true
 +}
 +
 +// FindPrevious searches backwards for the last used search term
 +func (h *BufPane) FindPrevious() bool {
 +      // If the cursor is at the end of a selection and we search we want
 +      // to search from the beginning of the selection in the case that
 +      // the selection is a search result in which case we wouldn't move at
 +      // at all which would be bad
 +      searchLoc := h.Cursor.Loc
 +      if h.Cursor.HasSelection() {
 +              searchLoc = h.Cursor.CurSelection[0]
 +      }
 +      match, found, err := h.Buf.FindNext(h.lastSearch, h.Buf.Start(), h.Buf.End(), searchLoc, false, true)
 +      if err != nil {
 +              InfoBar.Error(err)
 +      }
 +      if found {
 +              h.Cursor.SetSelectionStart(match[0])
 +              h.Cursor.SetSelectionEnd(match[1])
 +              h.Cursor.OrigSelection[0] = h.Cursor.CurSelection[0]
 +              h.Cursor.OrigSelection[1] = h.Cursor.CurSelection[1]
 +              h.Cursor.Loc = h.Cursor.CurSelection[1]
 +      } else {
 +              h.Cursor.ResetSelection()
 +      }
 +      h.Relocate()
 +      return true
 +}
 +
 +// Undo undoes the last action
 +func (h *BufPane) Undo() bool {
 +      h.Buf.Undo()
 +      InfoBar.Message("Undid action")
 +      h.Relocate()
 +      return true
 +}
 +
 +// Redo redoes the last action
 +func (h *BufPane) Redo() bool {
 +      h.Buf.Redo()
 +      InfoBar.Message("Redid action")
 +      h.Relocate()
 +      return true
 +}
 +
 +// Copy the selection to the system clipboard
 +func (h *BufPane) Copy() bool {
 +      if h.Cursor.HasSelection() {
 +              h.Cursor.CopySelection("clipboard")
 +              h.freshClip = true
 +              if clipboard.Unsupported {
 +                      InfoBar.Message("Copied selection (install xclip for external clipboard)")
 +              } else {
 +                      InfoBar.Message("Copied selection")
 +              }
 +      }
 +      h.Relocate()
 +      return true
 +}
 +
 +// CutLine cuts the current line to the clipboard
 +func (h *BufPane) CutLine() bool {
 +      h.Cursor.SelectLine()
 +      if !h.Cursor.HasSelection() {
 +              return false
 +      }
 +      if h.freshClip == true {
 +              if h.Cursor.HasSelection() {
 +                      if clip, err := clipboard.ReadAll("clipboard"); err != nil {
 +                              // messenger.Error(err)
 +                      } else {
 +                              clipboard.WriteAll(clip+string(h.Cursor.GetSelection()), "clipboard")
 +                      }
 +              }
 +      } else if time.Since(h.lastCutTime)/time.Second > 10*time.Second || h.freshClip == false {
 +              h.Copy()
 +      }
 +      h.freshClip = true
 +      h.lastCutTime = time.Now()
 +      h.Cursor.DeleteSelection()
 +      h.Cursor.ResetSelection()
 +      InfoBar.Message("Cut line")
 +      h.Relocate()
 +      return true
 +}
 +
 +// Cut the selection to the system clipboard
 +func (h *BufPane) Cut() bool {
 +      if h.Cursor.HasSelection() {
 +              h.Cursor.CopySelection("clipboard")
 +              h.Cursor.DeleteSelection()
 +              h.Cursor.ResetSelection()
 +              h.freshClip = true
 +              InfoBar.Message("Cut selection")
 +
 +              h.Relocate()
 +              return true
 +      } else {
 +              return h.CutLine()
 +      }
 +}
 +
 +// DuplicateLine duplicates the current line or selection
 +func (h *BufPane) DuplicateLine() bool {
 +      if h.Cursor.HasSelection() {
 +              h.Buf.Insert(h.Cursor.CurSelection[1], string(h.Cursor.GetSelection()))
 +      } else {
 +              h.Cursor.End()
 +              h.Buf.Insert(h.Cursor.Loc, "\n"+string(h.Buf.LineBytes(h.Cursor.Y)))
 +              // h.Cursor.Right()
 +      }
 +
 +      InfoBar.Message("Duplicated line")
 +      h.Relocate()
 +      return true
 +}
 +
 +// DeleteLine deletes the current line
 +func (h *BufPane) DeleteLine() bool {
 +      h.Cursor.SelectLine()
 +      if !h.Cursor.HasSelection() {
 +              return false
 +      }
 +      h.Cursor.DeleteSelection()
 +      h.Cursor.ResetSelection()
 +      InfoBar.Message("Deleted line")
 +      h.Relocate()
 +      return true
 +}
 +
 +// MoveLinesUp moves up the current line or selected lines if any
 +func (h *BufPane) MoveLinesUp() bool {
 +      if h.Cursor.HasSelection() {
 +              if h.Cursor.CurSelection[0].Y == 0 {
 +                      InfoBar.Message("Cannot move further up")
 +                      return false
 +              }
 +              start := h.Cursor.CurSelection[0].Y
 +              end := h.Cursor.CurSelection[1].Y
 +              if start > end {
 +                      end, start = start, end
 +              }
 +
 +              h.Buf.MoveLinesUp(
 +                      start,
 +                      end,
 +              )
 +              h.Cursor.CurSelection[1].Y -= 1
 +      } else {
 +              if h.Cursor.Loc.Y == 0 {
 +                      InfoBar.Message("Cannot move further up")
 +                      return false
 +              }
 +              h.Buf.MoveLinesUp(
 +                      h.Cursor.Loc.Y,
 +                      h.Cursor.Loc.Y+1,
 +              )
 +      }
 +
 +      h.Relocate()
 +      return true
 +}
 +
 +// MoveLinesDown moves down the current line or selected lines if any
 +func (h *BufPane) MoveLinesDown() bool {
 +      if h.Cursor.HasSelection() {
 +              if h.Cursor.CurSelection[1].Y >= h.Buf.LinesNum() {
 +                      InfoBar.Message("Cannot move further down")
 +                      return false
 +              }
 +              start := h.Cursor.CurSelection[0].Y
 +              end := h.Cursor.CurSelection[1].Y
 +              if start > end {
 +                      end, start = start, end
 +              }
 +
 +              h.Buf.MoveLinesDown(
 +                      start,
 +                      end,
 +              )
 +      } else {
 +              if h.Cursor.Loc.Y >= h.Buf.LinesNum()-1 {
 +                      InfoBar.Message("Cannot move further down")
 +                      return false
 +              }
 +              h.Buf.MoveLinesDown(
 +                      h.Cursor.Loc.Y,
 +                      h.Cursor.Loc.Y+1,
 +              )
 +      }
 +
 +      h.Relocate()
 +      return true
 +}
 +
 +// Paste whatever is in the system clipboard into the buffer
 +// Delete and paste if the user has a selection
 +func (h *BufPane) Paste() bool {
 +      clip, _ := clipboard.ReadAll("clipboard")
 +      h.paste(clip)
 +      h.Relocate()
 +      return true
 +}
 +
 +// PastePrimary pastes from the primary clipboard (only use on linux)
 +func (h *BufPane) PastePrimary() bool {
 +      clip, _ := clipboard.ReadAll("primary")
 +      h.paste(clip)
 +      h.Relocate()
 +      return true
 +}
 +
 +func (h *BufPane) paste(clip string) {
 +      if h.Buf.Settings["smartpaste"].(bool) {
 +              if h.Cursor.X > 0 && len(util.GetLeadingWhitespace([]byte(strings.TrimLeft(clip, "\r\n")))) == 0 {
 +                      leadingWS := util.GetLeadingWhitespace(h.Buf.LineBytes(h.Cursor.Y))
 +                      clip = strings.Replace(clip, "\n", "\n"+string(leadingWS), -1)
 +              }
 +      }
 +
 +      if h.Cursor.HasSelection() {
 +              h.Cursor.DeleteSelection()
 +              h.Cursor.ResetSelection()
 +      }
 +
 +      h.Buf.Insert(h.Cursor.Loc, clip)
 +      // h.Cursor.Loc = h.Cursor.Loc.Move(Count(clip), h.Buf)
 +      h.freshClip = false
 +      if clipboard.Unsupported {
 +              InfoBar.Message("Pasted clipboard (install xclip for external clipboard)")
 +      } else {
 +              InfoBar.Message("Pasted clipboard")
 +      }
 +}
 +
 +// JumpToMatchingBrace moves the cursor to the matching brace if it is
 +// currently on a brace
 +func (h *BufPane) JumpToMatchingBrace() bool {
 +      for _, bp := range buffer.BracePairs {
 +              r := h.Cursor.RuneUnder(h.Cursor.X)
 +              rl := h.Cursor.RuneUnder(h.Cursor.X - 1)
 +              if r == bp[0] || r == bp[1] || rl == bp[0] || rl == bp[1] {
 +                      matchingBrace, left := h.Buf.FindMatchingBrace(bp, h.Cursor.Loc)
 +                      if left {
 +                              h.Cursor.GotoLoc(matchingBrace)
 +                      } else {
 +                              h.Cursor.GotoLoc(matchingBrace.Move(1, h.Buf))
 +                      }
 +              }
 +      }
 +
 +      h.Relocate()
 +      return true
 +}
 +
 +// SelectAll selects the entire buffer
 +func (h *BufPane) SelectAll() bool {
 +      h.Cursor.SetSelectionStart(h.Buf.Start())
 +      h.Cursor.SetSelectionEnd(h.Buf.End())
 +      // Put the cursor at the beginning
 +      h.Cursor.X = 0
 +      h.Cursor.Y = 0
 +      h.Relocate()
 +      return true
 +}
 +
 +// OpenFile opens a new file in the buffer
 +func (h *BufPane) OpenFile() bool {
 +      InfoBar.Prompt("> ", "open ", "Open", nil, func(resp string, canceled bool) {
 +              if !canceled {
 +                      h.HandleCommand(resp)
 +              }
 +      })
 +      return true
 +}
 +
 +// Start moves the viewport to the start of the buffer
 +func (h *BufPane) Start() bool {
 +      v := h.GetView()
 +      v.StartLine = 0
 +      h.SetView(v)
 +      return true
 +}
 +
 +// End moves the viewport to the end of the buffer
 +func (h *BufPane) End() bool {
 +      // TODO: softwrap problems?
 +      v := h.GetView()
 +      if v.Height > h.Buf.LinesNum() {
 +              v.StartLine = 0
 +              h.SetView(v)
 +      } else {
 +              v.StartLine = h.Buf.LinesNum() - v.Height
 +              h.SetView(v)
 +      }
 +      return true
 +}
 +
 +// PageUp scrolls the view up a page
 +func (h *BufPane) PageUp() bool {
 +      v := h.GetView()
 +      if v.StartLine > v.Height {
 +              h.ScrollUp(v.Height)
 +      } else {
 +              v.StartLine = 0
 +      }
 +      h.SetView(v)
 +      return true
 +}
 +
 +// PageDown scrolls the view down a page
 +func (h *BufPane) PageDown() bool {
 +      v := h.GetView()
 +      if h.Buf.LinesNum()-(v.StartLine+v.Height) > v.Height {
 +              h.ScrollDown(v.Height)
 +      } else if h.Buf.LinesNum() >= v.Height {
 +              v.StartLine = h.Buf.LinesNum() - v.Height
 +      }
 +      return true
 +}
 +
 +// SelectPageUp selects up one page
 +func (h *BufPane) SelectPageUp() bool {
 +      if !h.Cursor.HasSelection() {
 +              h.Cursor.OrigSelection[0] = h.Cursor.Loc
 +      }
 +      h.Cursor.UpN(h.GetView().Height)
 +      h.Cursor.SelectTo(h.Cursor.Loc)
 +      h.Relocate()
 +      return true
 +}
 +
 +// SelectPageDown selects down one page
 +func (h *BufPane) SelectPageDown() bool {
 +      if !h.Cursor.HasSelection() {
 +              h.Cursor.OrigSelection[0] = h.Cursor.Loc
 +      }
 +      h.Cursor.DownN(h.GetView().Height)
 +      h.Cursor.SelectTo(h.Cursor.Loc)
 +      h.Relocate()
 +      return true
 +}
 +
 +// CursorPageUp places the cursor a page up
 +func (h *BufPane) CursorPageUp() bool {
 +      h.Cursor.Deselect(true)
 +
 +      if h.Cursor.HasSelection() {
 +              h.Cursor.Loc = h.Cursor.CurSelection[0]
 +              h.Cursor.ResetSelection()
 +              h.Cursor.StoreVisualX()
 +      }
 +      h.Cursor.UpN(h.GetView().Height)
 +      h.Relocate()
 +      return true
 +}
 +
 +// CursorPageDown places the cursor a page up
 +func (h *BufPane) CursorPageDown() bool {
 +      h.Cursor.Deselect(false)
 +
 +      if h.Cursor.HasSelection() {
 +              h.Cursor.Loc = h.Cursor.CurSelection[1]
 +              h.Cursor.ResetSelection()
 +              h.Cursor.StoreVisualX()
 +      }
 +      h.Cursor.DownN(h.GetView().Height)
 +      h.Relocate()
 +      return true
 +}
 +
 +// HalfPageUp scrolls the view up half a page
 +func (h *BufPane) HalfPageUp() bool {
 +      v := h.GetView()
 +      if v.StartLine > v.Height/2 {
 +              h.ScrollUp(v.Height / 2)
 +      } else {
 +              v.StartLine = 0
 +      }
 +      h.SetView(v)
 +      return true
 +}
 +
 +// HalfPageDown scrolls the view down half a page
 +func (h *BufPane) HalfPageDown() bool {
 +      v := h.GetView()
 +      if h.Buf.LinesNum()-(v.StartLine+v.Height) > v.Height/2 {
 +              h.ScrollDown(v.Height / 2)
 +      } else {
 +              if h.Buf.LinesNum() >= v.Height {
 +                      v.StartLine = h.Buf.LinesNum() - v.Height
 +              }
 +      }
 +      h.SetView(v)
 +      return true
 +}
 +
 +// ToggleRuler turns line numbers off and on
 +func (h *BufPane) ToggleRuler() bool {
 +      if !h.Buf.Settings["ruler"].(bool) {
 +              h.Buf.Settings["ruler"] = true
 +              InfoBar.Message("Enabled ruler")
 +      } else {
 +              h.Buf.Settings["ruler"] = false
 +              InfoBar.Message("Disabled ruler")
 +      }
 +      return true
 +}
 +
 +// ClearStatus clears the messenger bar
 +func (h *BufPane) ClearStatus() bool {
 +      InfoBar.Message("")
 +      return true
 +}
 +
 +// ToggleHelp toggles the help screen
 +func (h *BufPane) ToggleHelp() bool {
 +      if h.Buf.Type == buffer.BTHelp {
 +              h.Quit()
 +      } else {
 +              h.openHelp("help")
 +      }
 +      return true
 +}
 +
 +// ToggleKeyMenu toggles the keymenu option and resizes all tabs
 +func (h *BufPane) ToggleKeyMenu() bool {
 +      config.GlobalSettings["keymenu"] = !config.GetGlobalOption("keymenu").(bool)
 +      Tabs.Resize()
 +      return true
 +}
 +
 +// ShellMode opens a terminal to run a shell command
 +func (h *BufPane) ShellMode() bool {
 +      InfoBar.Prompt("$ ", "", "Shell", nil, func(resp string, canceled bool) {
 +              if !canceled {
 +                      // The true here is for openTerm to make the command interactive
 +                      shell.RunInteractiveShell(resp, true, false)
 +              }
 +      })
 +
 +      return true
 +}
 +
 +// CommandMode lets the user enter a command
 +func (h *BufPane) CommandMode() bool {
 +      InfoBar.Prompt("> ", "", "Command", nil, func(resp string, canceled bool) {
 +              if !canceled {
 +                      h.HandleCommand(resp)
 +              }
 +      })
 +      return true
 +}
 +
 +// ToggleOverwriteMode lets the user toggle the text overwrite mode
 +func (h *BufPane) ToggleOverwriteMode() bool {
 +      h.isOverwriteMode = !h.isOverwriteMode
 +      return true
 +}
 +
 +// Escape leaves current mode
 +func (h *BufPane) Escape() bool {
 +      return true
 +}
 +
 +// Quit this will close the current tab or view that is open
 +func (h *BufPane) Quit() bool {
 +      quit := func() {
 +              h.Buf.Close()
 +              if len(MainTab().Panes) > 1 {
 +                      h.Unsplit()
 +              } else if len(Tabs.List) > 1 {
 +                      Tabs.RemoveTab(h.splitID)
 +              } else {
 +                      screen.Screen.Fini()
 +                      InfoBar.Close()
 +                      runtime.Goexit()
 +              }
 +      }
 +      if h.Buf.Modified() {
 +              // if config.GlobalSettings["autosave"].(float64) > 0 {
 +              // autosave on means we automatically save when quitting
 +              // h.Save()
 +              // quit()
 +              // } else {
 +              InfoBar.YNPrompt("Save changes to "+h.Buf.GetName()+" before closing? (y,n,esc)", func(yes, canceled bool) {
 +                      if !canceled && !yes {
 +                              quit()
 +                      } else if !canceled && yes {
 +                              h.Save()
 +                              quit()
 +                      }
 +              })
 +              // }
 +      } else {
 +              quit()
 +      }
 +      return true
 +}
 +
 +// QuitAll quits the whole editor; all splits and tabs
 +func (h *BufPane) QuitAll() bool {
 +      anyModified := false
 +      for _, b := range buffer.OpenBuffers {
 +              if b.Modified() {
 +                      anyModified = true
 +                      break
 +              }
 +      }
 +
 +      quit := func() {
 +              for _, b := range buffer.OpenBuffers {
 +                      b.Close()
 +              }
 +              screen.Screen.Fini()
 +              InfoBar.Close()
 +              runtime.Goexit()
 +      }
 +
 +      if anyModified {
 +              InfoBar.YNPrompt("Quit micro? (all open buffers will be closed without saving)", func(yes, canceled bool) {
 +                      if !canceled && yes {
 +                              quit()
 +                      }
 +              })
 +      } else {
 +              quit()
 +      }
 +
 +      return true
 +}
 +
 +// AddTab adds a new tab with an empty buffer
 +func (h *BufPane) AddTab() bool {
 +      width, height := screen.Screen.Size()
 +      iOffset := config.GetInfoBarOffset()
 +      b := buffer.NewBufferFromString("", "", buffer.BTDefault)
 +      tp := NewTabFromBuffer(0, 0, width, height-iOffset, b)
 +      Tabs.AddTab(tp)
 +      Tabs.SetActive(len(Tabs.List) - 1)
 +
 +      return true
 +}
 +
 +// PreviousTab switches to the previous tab in the tab list
 +func (h *BufPane) PreviousTab() bool {
 +      a := Tabs.Active()
 +      Tabs.SetActive(util.Clamp(a-1, 0, len(Tabs.List)-1))
 +
 +      return true
 +}
 +
 +// NextTab switches to the next tab in the tab list
 +func (h *BufPane) NextTab() bool {
 +      a := Tabs.Active()
 +      Tabs.SetActive(util.Clamp(a+1, 0, len(Tabs.List)-1))
 +      return true
 +}
 +
 +// VSplitAction opens an empty vertical split
 +func (h *BufPane) VSplitAction() bool {
 +      h.VSplitBuf(buffer.NewBufferFromString("", "", buffer.BTDefault))
 +
 +      return true
 +}
 +
 +// HSplitAction opens an empty horizontal split
 +func (h *BufPane) HSplitAction() bool {
 +      h.HSplitBuf(buffer.NewBufferFromString("", "", buffer.BTDefault))
 +
 +      return true
 +}
 +
 +// Unsplit closes all splits in the current tab except the active one
 +func (h *BufPane) Unsplit() bool {
 +      n := MainTab().GetNode(h.splitID)
 +      n.Unsplit()
 +
 +      MainTab().RemovePane(MainTab().GetPane(h.splitID))
 +      MainTab().Resize()
 +      MainTab().SetActive(len(MainTab().Panes) - 1)
 +      return true
 +}
 +
 +// NextSplit changes the view to the next split
 +func (h *BufPane) NextSplit() bool {
 +      a := MainTab().active
 +      if a < len(MainTab().Panes)-1 {
 +              a++
 +      } else {
 +              a = 0
 +      }
 +
 +      MainTab().SetActive(a)
 +
 +      return true
 +}
 +
 +// PreviousSplit changes the view to the previous split
 +func (h *BufPane) PreviousSplit() bool {
 +      a := MainTab().active
 +      if a > 0 {
 +              a--
 +      } else {
 +              a = len(MainTab().Panes) - 1
 +      }
 +      MainTab().SetActive(a)
 +
 +      return true
 +}
 +
 +var curmacro []interface{}
 +var recording_macro bool
 +
 +// ToggleMacro toggles recording of a macro
 +func (h *BufPane) ToggleMacro() bool {
 +      recording_macro = !recording_macro
 +      if recording_macro {
 +              curmacro = []interface{}{}
 +              InfoBar.Message("Recording")
 +      } else {
 +              InfoBar.Message("Stopped recording")
 +      }
 +      h.Relocate()
 +      return true
 +}
 +
 +// PlayMacro plays back the most recently recorded macro
 +func (h *BufPane) PlayMacro() bool {
 +      if recording_macro {
 +              return false
 +      }
 +      for _, action := range curmacro {
 +              switch t := action.(type) {
 +              case rune:
 +                      h.DoRuneInsert(t)
 +              case func(*BufPane) bool:
 +                      t(h)
 +              }
 +      }
 +      h.Relocate()
 +      return true
 +}
 +
 +// SpawnMultiCursor creates a new multiple cursor at the next occurrence of the current selection or current word
 +func (h *BufPane) SpawnMultiCursor() bool {
 +      spawner := h.Buf.GetCursor(h.Buf.NumCursors() - 1)
 +      if !spawner.HasSelection() {
 +              spawner.SelectWord()
 +              h.multiWord = true
 +              h.Relocate()
 +              return true
 +      }
 +
 +      sel := spawner.GetSelection()
 +      searchStart := spawner.CurSelection[1]
 +
 +      search := string(sel)
 +      search = regexp.QuoteMeta(search)
 +      if h.multiWord {
 +              search = "\\b" + search + "\\b"
 +      }
 +      match, found, err := h.Buf.FindNext(search, h.Buf.Start(), h.Buf.End(), searchStart, true, true)
 +      if err != nil {
 +              InfoBar.Error(err)
 +      }
 +      if found {
 +              c := buffer.NewCursor(h.Buf, buffer.Loc{})
 +              c.SetSelectionStart(match[0])
 +              c.SetSelectionEnd(match[1])
 +              c.OrigSelection[0] = c.CurSelection[0]
 +              c.OrigSelection[1] = c.CurSelection[1]
 +              c.Loc = c.CurSelection[1]
 +
 +              h.Buf.AddCursor(c)
 +              h.Buf.SetCurCursor(h.Buf.NumCursors() - 1)
 +              h.Buf.MergeCursors()
 +      } else {
 +              InfoBar.Message("No matches found")
 +      }
 +
 +      h.Relocate()
 +      return true
 +}
 +
++// SpawnMultiCursorUp creates additional cursor, at the same X (if possible), one Y less.
++func (h *BufPane) SpawnMultiCursorUp() bool {
++      if h.Cursor.Y == 0 {
++              return false
++      } else {
++              h.Cursor.GotoLoc(buffer.Loc{h.Cursor.X, h.Cursor.Y - 1})
++              h.Cursor.Relocate()
++      }
++
++      c := buffer.NewCursor(h.Buf, buffer.Loc{h.Cursor.X, h.Cursor.Y + 1})
++      h.Buf.AddCursor(c)
++      h.Buf.SetCurCursor(h.Buf.NumCursors() - 1)
++      h.Buf.MergeCursors()
++
++      h.Relocate()
++      return true
++}
++
++// SpawnMultiCursorUp creates additional cursor, at the same X (if possible), one Y more.
++func (h *BufPane) SpawnMultiCursorDown() bool {
++      if h.Cursor.Y+1 == h.Buf.LinesNum() {
++              return false
++      } else {
++              h.Cursor.GotoLoc(buffer.Loc{h.Cursor.X, h.Cursor.Y + 1})
++              h.Cursor.Relocate()
++      }
++
++      c := buffer.NewCursor(h.Buf, buffer.Loc{h.Cursor.X, h.Cursor.Y - 1})
++      h.Buf.AddCursor(c)
++      h.Buf.SetCurCursor(h.Buf.NumCursors() - 1)
++      h.Buf.MergeCursors()
++      h.Relocate()
++      return true
++}
++
 +// SpawnMultiCursorSelect adds a cursor at the beginning of each line of a selection
 +func (h *BufPane) SpawnMultiCursorSelect() bool {
 +      // Avoid cases where multiple cursors already exist, that would create problems
 +      if h.Buf.NumCursors() > 1 {
 +              return false
 +      }
 +
 +      var startLine int
 +      var endLine int
 +
 +      a, b := h.Cursor.CurSelection[0].Y, h.Cursor.CurSelection[1].Y
 +      if a > b {
 +              startLine, endLine = b, a
 +      } else {
 +              startLine, endLine = a, b
 +      }
 +
 +      if h.Cursor.HasSelection() {
 +              h.Cursor.ResetSelection()
 +              h.Cursor.GotoLoc(buffer.Loc{0, startLine})
 +
 +              for i := startLine; i <= endLine; i++ {
 +                      c := buffer.NewCursor(h.Buf, buffer.Loc{0, i})
 +                      c.StoreVisualX()
 +                      h.Buf.AddCursor(c)
 +              }
 +              h.Buf.MergeCursors()
 +      } else {
 +              return false
 +      }
 +      InfoBar.Message("Added cursors from selection")
 +      return true
 +}
 +
 +// MouseMultiCursor is a mouse action which puts a new cursor at the mouse position
 +func (h *BufPane) MouseMultiCursor(e *tcell.EventMouse) bool {
 +      b := h.Buf
 +      mx, my := e.Position()
 +      mouseLoc := h.LocFromVisual(buffer.Loc{X: mx, Y: my})
 +      c := buffer.NewCursor(b, mouseLoc)
 +      b.AddCursor(c)
 +      b.MergeCursors()
 +
 +      return true
 +}
 +
 +// SkipMultiCursor moves the current multiple cursor to the next available position
 +func (h *BufPane) SkipMultiCursor() bool {
 +      lastC := h.Buf.GetCursor(h.Buf.NumCursors() - 1)
 +      sel := lastC.GetSelection()
 +      searchStart := lastC.CurSelection[1]
 +
 +      search := string(sel)
 +      search = regexp.QuoteMeta(search)
 +      if h.multiWord {
 +              search = "\\b" + search + "\\b"
 +      }
 +
 +      match, found, err := h.Buf.FindNext(search, h.Buf.Start(), h.Buf.End(), searchStart, true, true)
 +      if err != nil {
 +              InfoBar.Error(err)
 +      }
 +      if found {
 +              lastC.SetSelectionStart(match[0])
 +              lastC.SetSelectionEnd(match[1])
 +              lastC.OrigSelection[0] = lastC.CurSelection[0]
 +              lastC.OrigSelection[1] = lastC.CurSelection[1]
 +              lastC.Loc = lastC.CurSelection[1]
 +
 +              h.Buf.MergeCursors()
 +              h.Buf.SetCurCursor(h.Buf.NumCursors() - 1)
 +      } else {
 +              InfoBar.Message("No matches found")
 +      }
 +      h.Relocate()
 +      return true
 +}
 +
 +// RemoveMultiCursor removes the latest multiple cursor
 +func (h *BufPane) RemoveMultiCursor() bool {
 +      if h.Buf.NumCursors() > 1 {
 +              h.Buf.RemoveCursor(h.Buf.NumCursors() - 1)
 +              h.Buf.SetCurCursor(h.Buf.NumCursors() - 1)
 +              h.Buf.UpdateCursors()
 +      } else {
 +              h.multiWord = false
 +      }
 +      h.Relocate()
 +      return true
 +}
 +
 +// RemoveAllMultiCursors removes all cursors except the base cursor
 +func (h *BufPane) RemoveAllMultiCursors() bool {
 +      h.Buf.ClearCursors()
 +      h.multiWord = false
 +      h.Relocate()
 +      return true
 +}
 +
 +// None is an action that does nothing
 +func (h *BufPane) None() bool {
 +      return true
 +}
index 12c50e19b3d78b546e7cdaf91564b3b068424840,0000000000000000000000000000000000000000..bb79d756243198a6e876f45472c51fc782bc3d89
mode 100644,000000..100644
--- /dev/null
@@@ -1,669 -1,0 +1,671 @@@
 +package action
 +
 +import (
 +      "strings"
 +      "time"
 +
 +      luar "layeh.com/gopher-luar"
 +
 +      lua "github.com/yuin/gopher-lua"
 +      "github.com/zyedidia/micro/internal/buffer"
 +      "github.com/zyedidia/micro/internal/config"
 +      "github.com/zyedidia/micro/internal/display"
 +      ulua "github.com/zyedidia/micro/internal/lua"
 +      "github.com/zyedidia/micro/internal/screen"
 +      "github.com/zyedidia/tcell"
 +)
 +
 +type BufKeyAction func(*BufPane) bool
 +type BufMouseAction func(*BufPane, *tcell.EventMouse) bool
 +
 +var BufKeyBindings map[Event]BufKeyAction
 +var BufKeyStrings map[Event]string
 +var BufMouseBindings map[MouseEvent]BufMouseAction
 +
 +func init() {
 +      BufKeyBindings = make(map[Event]BufKeyAction)
 +      BufKeyStrings = make(map[Event]string)
 +      BufMouseBindings = make(map[MouseEvent]BufMouseAction)
 +}
 +
 +func LuaAction(fn string) func(*BufPane) bool {
 +      luaFn := strings.Split(fn, ".")
 +      if len(luaFn) <= 1 {
 +              return nil
 +      }
 +      plName, plFn := luaFn[0], luaFn[1]
 +      pl := config.FindPlugin(plName)
 +      if pl == nil {
 +              return nil
 +      }
 +      return func(h *BufPane) bool {
 +              val, err := pl.Call(plFn, luar.New(ulua.L, h))
 +              if err != nil {
 +                      screen.TermMessage(err)
 +              }
 +              if v, ok := val.(lua.LBool); !ok {
 +                      return false
 +              } else {
 +                      return bool(v)
 +              }
 +      }
 +}
 +
 +// BufMapKey maps a key event to an action
 +func BufMapKey(k Event, action string) {
 +      BufKeyStrings[k] = action
 +      var actionfns []func(*BufPane) bool
 +      var names []string
 +      var types []byte
 +      for i := 0; ; i++ {
 +              if action == "" {
 +                      break
 +              }
 +
 +              // TODO: fix problem when complex bindings have these
 +              // characters (escape them?)
 +              idx := strings.IndexAny(action, "&|,")
 +              a := action
 +              if idx >= 0 {
 +                      a = action[:idx]
 +                      types = append(types, action[idx])
 +                      action = action[idx+1:]
 +              } else {
 +                      types = append(types, ' ')
 +                      action = ""
 +              }
 +
 +              var afn func(*BufPane) bool
 +              if strings.HasPrefix(a, "command:") {
 +                      a = strings.SplitN(a, ":", 2)[1]
 +                      afn = CommandAction(a)
 +                      names = append(names, "")
 +              } else if strings.HasPrefix(a, "command-edit:") {
 +                      a = strings.SplitN(a, ":", 2)[1]
 +                      afn = CommandEditAction(a)
 +                      names = append(names, "")
 +              } else if strings.HasPrefix(a, "lua:") {
 +                      a = strings.SplitN(a, ":", 2)[1]
 +                      afn = LuaAction(a)
 +                      if afn == nil {
 +                              screen.TermMessage("Lua Error:", a, "does not exist")
 +                              continue
 +                      }
 +                      split := strings.SplitN(a, ".", 2)
 +                      if len(split) > 1 {
 +                              a = strings.Title(split[0]) + strings.Title(split[1])
 +                      } else {
 +                              a = strings.Title(a)
 +                      }
 +
 +                      names = append(names, a)
 +              } else if f, ok := BufKeyActions[a]; ok {
 +                      afn = f
 +                      names = append(names, a)
 +              } else {
 +                      screen.TermMessage("Error:", a, "does not exist")
 +                      continue
 +              }
 +              actionfns = append(actionfns, afn)
 +      }
 +      BufKeyBindings[k] = func(h *BufPane) bool {
 +              cursors := h.Buf.GetCursors()
 +              success := true
 +              for i, a := range actionfns {
 +                      for j, c := range cursors {
 +                              h.Buf.SetCurCursor(c.Num)
 +                              h.Cursor = c
 +                              if i == 0 || (success && types[i-1] == '&') || (!success && types[i-1] == '|') || (types[i-1] == ',') {
 +                                      success = h.execAction(a, names[i], j)
 +                              } else {
 +                                      break
 +                              }
 +                      }
 +              }
 +              return true
 +      }
 +}
 +
 +// BufMapMouse maps a mouse event to an action
 +func BufMapMouse(k MouseEvent, action string) {
 +      if f, ok := BufMouseActions[action]; ok {
 +              BufMouseBindings[k] = f
 +      } else {
 +              delete(BufMouseBindings, k)
 +              BufMapKey(k, action)
 +      }
 +}
 +
 +// The BufPane 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 BufPane struct {
 +      display.BWindow
 +
 +      Buf *buffer.Buffer
 +
 +      Cursor *buffer.Cursor // the active cursor
 +
 +      // 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       buffer.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
 +
 +      // Last search stores the last successful search for FindNext and FindPrev
 +      lastSearch string
 +      // Should the current multiple cursor selection search based on word or
 +      // based on selection (false for selection, true for word)
 +      multiWord bool
 +
 +      splitID uint64
 +      tab     *Tab
 +
 +      // remember original location of a search in case the search is canceled
 +      searchOrig buffer.Loc
 +}
 +
 +func NewBufPane(buf *buffer.Buffer, win display.BWindow, tab *Tab) *BufPane {
 +      h := new(BufPane)
 +      h.Buf = buf
 +      h.BWindow = win
 +      h.tab = tab
 +
 +      h.Cursor = h.Buf.GetActiveCursor()
 +      h.mouseReleased = true
 +
 +      config.RunPluginFn("onBufPaneOpen", luar.New(ulua.L, h))
 +
 +      return h
 +}
 +
 +func NewBufPaneFromBuf(buf *buffer.Buffer, tab *Tab) *BufPane {
 +      w := display.NewBufWindow(0, 0, 0, 0, buf)
 +      return NewBufPane(buf, w, tab)
 +}
 +
 +func (h *BufPane) SetTab(t *Tab) {
 +      h.tab = t
 +}
 +
 +func (h *BufPane) Tab() *Tab {
 +      return h.tab
 +}
 +
 +func (h *BufPane) ResizePane(size int) {
 +      n := h.tab.GetNode(h.splitID)
 +      n.ResizeSplit(size)
 +      h.tab.Resize()
 +}
 +
 +// PluginCB calls all plugin callbacks with a certain name and
 +// displays an error if there is one and returns the aggregrate
 +// boolean response
 +func (h *BufPane) PluginCB(cb string) bool {
 +      b, err := config.RunPluginFnBool(cb, luar.New(ulua.L, h))
 +      if err != nil {
 +              screen.TermMessage(err)
 +      }
 +      return b
 +}
 +
 +// PluginCBRune is the same as PluginCB but also passes a rune to
 +// the plugins
 +func (h *BufPane) PluginCBRune(cb string, r rune) bool {
 +      b, err := config.RunPluginFnBool(cb, luar.New(ulua.L, h), luar.New(ulua.L, string(r)))
 +      if err != nil {
 +              screen.TermMessage(err)
 +      }
 +      return b
 +}
 +
 +func (h *BufPane) OpenBuffer(b *buffer.Buffer) {
 +      h.Buf.Close()
 +      h.Buf = b
 +      h.BWindow.SetBuffer(b)
 +      h.Cursor = b.GetActiveCursor()
 +      h.Resize(h.GetView().Width, h.GetView().Height)
 +      h.Relocate()
 +      // Set mouseReleased to true because we assume the mouse is not being pressed when
 +      // the editor is opened
 +      h.mouseReleased = true
 +      // Set isOverwriteMode to false, because we assume we are in the default mode when editor
 +      // is opened
 +      h.isOverwriteMode = false
 +      h.lastClickTime = time.Time{}
 +}
 +
 +func (h *BufPane) ID() uint64 {
 +      return h.splitID
 +}
 +
 +func (h *BufPane) SetID(i uint64) {
 +      h.splitID = i
 +}
 +
 +func (h *BufPane) Name() string {
 +      return h.Buf.GetName()
 +}
 +
 +// HandleEvent executes the tcell event properly
 +func (h *BufPane) HandleEvent(event tcell.Event) {
 +      if h.Buf.ExternallyModified() {
 +              InfoBar.YNPrompt("The file on disk has changed. Reload file? (y,n)", func(yes, canceled bool) {
 +                      if !yes || canceled {
 +                              h.Buf.UpdateModTime()
 +                      } else {
 +                              h.Buf.ReOpen()
 +                      }
 +              })
 +
 +      }
 +
 +      switch e := event.(type) {
 +      case *tcell.EventRaw:
 +              re := RawEvent{
 +                      esc: e.EscSeq(),
 +              }
 +              h.DoKeyEvent(re)
 +      case *tcell.EventPaste:
 +              h.paste(e.Text())
 +              h.Relocate()
 +      case *tcell.EventKey:
 +              ke := KeyEvent{
 +                      code: e.Key(),
 +                      mod:  e.Modifiers(),
 +                      r:    e.Rune(),
 +              }
 +
 +              done := h.DoKeyEvent(ke)
 +              if !done && e.Key() == tcell.KeyRune {
 +                      h.DoRuneInsert(e.Rune())
 +              }
 +      case *tcell.EventMouse:
 +              cancel := false
 +              switch e.Buttons() {
 +              case tcell.Button1:
 +                      _, my := e.Position()
 +                      if h.Buf.Settings["statusline"].(bool) && my >= h.GetView().Y+h.GetView().Height-1 {
 +                              cancel = true
 +                      }
 +              case tcell.ButtonNone:
 +                      // Mouse event with no click
 +                      if !h.mouseReleased {
 +                              // Mouse was just released
 +
 +                              // mx, my := e.Position()
 +                              // mouseLoc := h.LocFromVisual(buffer.Loc{X: mx, Y: my})
 +
 +                              // we could finish the selection based on the release location as described
 +                              // below but when the mouse click is within the scroll margin this will
 +                              // cause a scroll and selection even for a simple mouse click which is
 +                              // not good
 +                              // for terminals that don't support mouse motion events, selection via
 +                              // the mouse won't work but this is ok
 +
 +                              // Relocating here isn't really necessary because the cursor will
 +                              // be in the right place from the last mouse event
 +                              // 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 !h.doubleClick && !h.tripleClick {
 +                              //      h.Cursor.Loc = mouseLoc
 +                              //      h.Cursor.SetSelectionEnd(h.Cursor.Loc)
 +                              //      h.Cursor.CopySelection("primary")
 +                              // }
 +                              h.mouseReleased = true
 +                      }
 +              }
 +
 +              if !cancel {
 +                      me := MouseEvent{
 +                              btn: e.Buttons(),
 +                              mod: e.Modifiers(),
 +                      }
 +                      h.DoMouseEvent(me, e)
 +              }
 +      }
 +      h.Buf.MergeCursors()
 +
 +      if h.IsActive() {
 +              // Display any gutter messages for this line
 +              c := h.Buf.GetActiveCursor()
 +              none := true
 +              for _, m := range h.Buf.Messages {
 +                      if c.Y == m.Start.Y || c.Y == m.End.Y {
 +                              InfoBar.GutterMessage(m.Msg)
 +                              none = false
 +                              break
 +                      }
 +              }
 +              if none && InfoBar.HasGutter {
 +                      InfoBar.ClearGutter()
 +              }
 +      }
 +}
 +
 +// DoKeyEvent executes a key event by finding the action it is bound
 +// to and executing it (possibly multiple times for multiple cursors)
 +func (h *BufPane) DoKeyEvent(e Event) bool {
 +      if action, ok := BufKeyBindings[e]; ok {
 +              return action(h)
 +      }
 +      return false
 +}
 +
 +func (h *BufPane) execAction(action func(*BufPane) bool, name string, cursor int) bool {
 +      if name != "Autocomplete" && name != "CycleAutocompleteBack" {
 +              h.Buf.HasSuggestions = false
 +      }
 +
 +      _, isMulti := MultiActions[name]
 +      if (!isMulti && cursor == 0) || isMulti {
 +              if h.PluginCB("pre" + name) {
 +                      success := action(h)
 +                      success = success && h.PluginCB("on"+name)
 +
 +                      if isMulti {
 +                              if recording_macro {
 +                                      if name != "ToggleMacro" && name != "PlayMacro" {
 +                                              curmacro = append(curmacro, action)
 +                                      }
 +                              }
 +                      }
 +
 +                      return success
 +              }
 +      }
 +
 +      return false
 +}
 +
 +func (h *BufPane) completeAction(action string) {
 +      h.PluginCB("on" + action)
 +}
 +
 +func (h *BufPane) HasKeyEvent(e Event) bool {
 +      _, ok := BufKeyBindings[e]
 +      return ok
 +}
 +
 +// DoMouseEvent executes a mouse event by finding the action it is bound
 +// to and executing it
 +func (h *BufPane) DoMouseEvent(e MouseEvent, te *tcell.EventMouse) bool {
 +      if action, ok := BufMouseBindings[e]; ok {
 +              if action(h, te) {
 +                      h.Relocate()
 +              }
 +              return true
 +      } else if h.HasKeyEvent(e) {
 +              return h.DoKeyEvent(e)
 +      }
 +      return false
 +}
 +
 +// DoRuneInsert inserts a given rune into the current buffer
 +// (possibly multiple times for multiple cursors)
 +func (h *BufPane) DoRuneInsert(r rune) {
 +      cursors := h.Buf.GetCursors()
 +      for _, c := range cursors {
 +              // Insert a character
 +              h.Buf.SetCurCursor(c.Num)
 +              h.Cursor = c
 +              if !h.PluginCBRune("preRune", r) {
 +                      continue
 +              }
 +              if c.HasSelection() {
 +                      c.DeleteSelection()
 +                      c.ResetSelection()
 +              }
 +
 +              if h.isOverwriteMode {
 +                      next := c.Loc
 +                      next.X++
 +                      h.Buf.Replace(c.Loc, next, string(r))
 +              } else {
 +                      h.Buf.Insert(c.Loc, string(r))
 +              }
 +              if recording_macro {
 +                      curmacro = append(curmacro, r)
 +              }
 +              h.PluginCBRune("onRune", r)
 +      }
 +}
 +
 +func (h *BufPane) VSplitIndex(buf *buffer.Buffer, right bool) *BufPane {
 +      e := NewBufPaneFromBuf(buf, h.tab)
 +      e.splitID = MainTab().GetNode(h.splitID).VSplit(right)
 +      MainTab().Panes = append(MainTab().Panes, e)
 +      MainTab().Resize()
 +      MainTab().SetActive(len(MainTab().Panes) - 1)
 +      return e
 +}
 +func (h *BufPane) HSplitIndex(buf *buffer.Buffer, bottom bool) *BufPane {
 +      e := NewBufPaneFromBuf(buf, h.tab)
 +      e.splitID = MainTab().GetNode(h.splitID).HSplit(bottom)
 +      MainTab().Panes = append(MainTab().Panes, e)
 +      MainTab().Resize()
 +      MainTab().SetActive(len(MainTab().Panes) - 1)
 +      return e
 +}
 +
 +func (h *BufPane) VSplitBuf(buf *buffer.Buffer) *BufPane {
 +      return h.VSplitIndex(buf, h.Buf.Settings["splitright"].(bool))
 +}
 +func (h *BufPane) HSplitBuf(buf *buffer.Buffer) *BufPane {
 +      return h.HSplitIndex(buf, h.Buf.Settings["splitbottom"].(bool))
 +}
 +func (h *BufPane) Close() {
 +      h.Buf.Close()
 +}
 +
 +func (h *BufPane) SetActive(b bool) {
 +      h.BWindow.SetActive(b)
 +      if b {
 +              // Display any gutter messages for this line
 +              c := h.Buf.GetActiveCursor()
 +              none := true
 +              for _, m := range h.Buf.Messages {
 +                      if c.Y == m.Start.Y || c.Y == m.End.Y {
 +                              InfoBar.GutterMessage(m.Msg)
 +                              none = false
 +                              break
 +                      }
 +              }
 +              if none && InfoBar.HasGutter {
 +                      InfoBar.ClearGutter()
 +              }
 +      }
 +
 +}
 +
 +// BufKeyActions contains the list of all possible key actions the bufhandler could execute
 +var BufKeyActions = map[string]BufKeyAction{
 +      "CursorUp":               (*BufPane).CursorUp,
 +      "CursorDown":             (*BufPane).CursorDown,
 +      "CursorPageUp":           (*BufPane).CursorPageUp,
 +      "CursorPageDown":         (*BufPane).CursorPageDown,
 +      "CursorLeft":             (*BufPane).CursorLeft,
 +      "CursorRight":            (*BufPane).CursorRight,
 +      "CursorStart":            (*BufPane).CursorStart,
 +      "CursorEnd":              (*BufPane).CursorEnd,
 +      "SelectToStart":          (*BufPane).SelectToStart,
 +      "SelectToEnd":            (*BufPane).SelectToEnd,
 +      "SelectUp":               (*BufPane).SelectUp,
 +      "SelectDown":             (*BufPane).SelectDown,
 +      "SelectLeft":             (*BufPane).SelectLeft,
 +      "SelectRight":            (*BufPane).SelectRight,
 +      "WordRight":              (*BufPane).WordRight,
 +      "WordLeft":               (*BufPane).WordLeft,
 +      "SelectWordRight":        (*BufPane).SelectWordRight,
 +      "SelectWordLeft":         (*BufPane).SelectWordLeft,
 +      "DeleteWordRight":        (*BufPane).DeleteWordRight,
 +      "DeleteWordLeft":         (*BufPane).DeleteWordLeft,
 +      "SelectLine":             (*BufPane).SelectLine,
 +      "SelectToStartOfLine":    (*BufPane).SelectToStartOfLine,
 +      "SelectToStartOfText":    (*BufPane).SelectToStartOfText,
 +      "SelectToEndOfLine":      (*BufPane).SelectToEndOfLine,
 +      "ParagraphPrevious":      (*BufPane).ParagraphPrevious,
 +      "ParagraphNext":          (*BufPane).ParagraphNext,
 +      "InsertNewline":          (*BufPane).InsertNewline,
 +      "Backspace":              (*BufPane).Backspace,
 +      "Delete":                 (*BufPane).Delete,
 +      "InsertTab":              (*BufPane).InsertTab,
 +      "Save":                   (*BufPane).Save,
 +      "SaveAll":                (*BufPane).SaveAll,
 +      "SaveAs":                 (*BufPane).SaveAs,
 +      "Find":                   (*BufPane).Find,
 +      "FindNext":               (*BufPane).FindNext,
 +      "FindPrevious":           (*BufPane).FindPrevious,
 +      "Center":                 (*BufPane).Center,
 +      "Undo":                   (*BufPane).Undo,
 +      "Redo":                   (*BufPane).Redo,
 +      "Copy":                   (*BufPane).Copy,
 +      "Cut":                    (*BufPane).Cut,
 +      "CutLine":                (*BufPane).CutLine,
 +      "DuplicateLine":          (*BufPane).DuplicateLine,
 +      "DeleteLine":             (*BufPane).DeleteLine,
 +      "MoveLinesUp":            (*BufPane).MoveLinesUp,
 +      "MoveLinesDown":          (*BufPane).MoveLinesDown,
 +      "IndentSelection":        (*BufPane).IndentSelection,
 +      "OutdentSelection":       (*BufPane).OutdentSelection,
 +      "Autocomplete":           (*BufPane).Autocomplete,
 +      "CycleAutocompleteBack":  (*BufPane).CycleAutocompleteBack,
 +      "OutdentLine":            (*BufPane).OutdentLine,
 +      "Paste":                  (*BufPane).Paste,
 +      "PastePrimary":           (*BufPane).PastePrimary,
 +      "SelectAll":              (*BufPane).SelectAll,
 +      "OpenFile":               (*BufPane).OpenFile,
 +      "Start":                  (*BufPane).Start,
 +      "End":                    (*BufPane).End,
 +      "PageUp":                 (*BufPane).PageUp,
 +      "PageDown":               (*BufPane).PageDown,
 +      "SelectPageUp":           (*BufPane).SelectPageUp,
 +      "SelectPageDown":         (*BufPane).SelectPageDown,
 +      "HalfPageUp":             (*BufPane).HalfPageUp,
 +      "HalfPageDown":           (*BufPane).HalfPageDown,
 +      "StartOfText":            (*BufPane).StartOfText,
 +      "StartOfLine":            (*BufPane).StartOfLine,
 +      "EndOfLine":              (*BufPane).EndOfLine,
 +      "ToggleHelp":             (*BufPane).ToggleHelp,
 +      "ToggleKeyMenu":          (*BufPane).ToggleKeyMenu,
 +      "ToggleRuler":            (*BufPane).ToggleRuler,
 +      "ClearStatus":            (*BufPane).ClearStatus,
 +      "ShellMode":              (*BufPane).ShellMode,
 +      "CommandMode":            (*BufPane).CommandMode,
 +      "ToggleOverwriteMode":    (*BufPane).ToggleOverwriteMode,
 +      "Escape":                 (*BufPane).Escape,
 +      "Quit":                   (*BufPane).Quit,
 +      "QuitAll":                (*BufPane).QuitAll,
 +      "AddTab":                 (*BufPane).AddTab,
 +      "PreviousTab":            (*BufPane).PreviousTab,
 +      "NextTab":                (*BufPane).NextTab,
 +      "NextSplit":              (*BufPane).NextSplit,
 +      "PreviousSplit":          (*BufPane).PreviousSplit,
 +      "Unsplit":                (*BufPane).Unsplit,
 +      "VSplit":                 (*BufPane).VSplitAction,
 +      "HSplit":                 (*BufPane).HSplitAction,
 +      "ToggleMacro":            (*BufPane).ToggleMacro,
 +      "PlayMacro":              (*BufPane).PlayMacro,
 +      "Suspend":                (*BufPane).Suspend,
 +      "ScrollUp":               (*BufPane).ScrollUpAction,
 +      "ScrollDown":             (*BufPane).ScrollDownAction,
 +      "SpawnMultiCursor":       (*BufPane).SpawnMultiCursor,
++      "SpawnMultiCursorUp":     (*BufPane).SpawnMultiCursorUp,
++      "SpawnMultiCursorDown":   (*BufPane).SpawnMultiCursorDown,
 +      "SpawnMultiCursorSelect": (*BufPane).SpawnMultiCursorSelect,
 +      "RemoveMultiCursor":      (*BufPane).RemoveMultiCursor,
 +      "RemoveAllMultiCursors":  (*BufPane).RemoveAllMultiCursors,
 +      "SkipMultiCursor":        (*BufPane).SkipMultiCursor,
 +      "JumpToMatchingBrace":    (*BufPane).JumpToMatchingBrace,
 +      "None":                   (*BufPane).None,
 +
 +      // This was changed to InsertNewline but I don't want to break backwards compatibility
 +      "InsertEnter": (*BufPane).InsertNewline,
 +}
 +
 +// BufMouseActions contains the list of all possible mouse actions the bufhandler could execute
 +var BufMouseActions = map[string]BufMouseAction{
 +      "MousePress":       (*BufPane).MousePress,
 +      "MouseMultiCursor": (*BufPane).MouseMultiCursor,
 +}
 +
 +// MultiActions is a list of actions that should be executed multiple
 +// times if there are multiple cursors (one per cursor)
 +// Generally actions that modify global editor state like quitting or
 +// saving should not be included in this list
 +var MultiActions = map[string]bool{
 +      "CursorUp":            true,
 +      "CursorDown":          true,
 +      "CursorPageUp":        true,
 +      "CursorPageDown":      true,
 +      "CursorLeft":          true,
 +      "CursorRight":         true,
 +      "CursorStart":         true,
 +      "CursorEnd":           true,
 +      "SelectToStart":       true,
 +      "SelectToEnd":         true,
 +      "SelectUp":            true,
 +      "SelectDown":          true,
 +      "SelectLeft":          true,
 +      "SelectRight":         true,
 +      "WordRight":           true,
 +      "WordLeft":            true,
 +      "SelectWordRight":     true,
 +      "SelectWordLeft":      true,
 +      "DeleteWordRight":     true,
 +      "DeleteWordLeft":      true,
 +      "SelectLine":          true,
 +      "SelectToStartOfLine": true,
 +      "SelectToStartOfText": true,
 +      "SelectToEndOfLine":   true,
 +      "ParagraphPrevious":   true,
 +      "ParagraphNext":       true,
 +      "InsertNewline":       true,
 +      "Backspace":           true,
 +      "Delete":              true,
 +      "InsertTab":           true,
 +      "FindNext":            true,
 +      "FindPrevious":        true,
 +      "Cut":                 true,
 +      "CutLine":             true,
 +      "DuplicateLine":       true,
 +      "DeleteLine":          true,
 +      "MoveLinesUp":         true,
 +      "MoveLinesDown":       true,
 +      "IndentSelection":     true,
 +      "OutdentSelection":    true,
 +      "OutdentLine":         true,
 +      "Paste":               true,
 +      "PastePrimary":        true,
 +      "SelectPageUp":        true,
 +      "SelectPageDown":      true,
 +      "StartOfLine":         true,
 +      "StartOfText":         true,
 +      "EndOfLine":           true,
 +      "JumpToMatchingBrace": true,
 +}
index acd3a6ffae9932567e67b0ea85a723edae7d6873,0000000000000000000000000000000000000000..45b6359a30ac87ca678f8953717f8330dacbe943
mode 100644,000000..100644
--- /dev/null
@@@ -1,105 -1,0 +1,107 @@@
-               "Alt-n": "SpawnMultiCursor",
-               "Alt-m": "SpawnMultiCursorSelect",
-               "Alt-p": "RemoveMultiCursor",
-               "Alt-c": "RemoveAllMultiCursors",
-               "Alt-x": "SkipMultiCursor",
 +package action
 +
 +// DefaultBindings returns a map containing micro's default keybindings
 +func DefaultBindings() map[string]string {
 +      return map[string]string{
 +              "Up":             "CursorUp",
 +              "Down":           "CursorDown",
 +              "Right":          "CursorRight",
 +              "Left":           "CursorLeft",
 +              "ShiftUp":        "SelectUp",
 +              "ShiftDown":      "SelectDown",
 +              "ShiftLeft":      "SelectLeft",
 +              "ShiftRight":     "SelectRight",
 +              "AltLeft":        "WordLeft",
 +              "AltRight":       "WordRight",
 +              "AltUp":          "MoveLinesUp",
 +              "AltDown":        "MoveLinesDown",
 +              "AltShiftRight":  "SelectWordRight",
 +              "AltShiftLeft":   "SelectWordLeft",
 +              "CtrlLeft":       "StartOfText",
 +              "CtrlRight":      "EndOfLine",
 +              "CtrlShiftLeft":  "SelectToStartOfText",
 +              "ShiftHome":      "SelectToStartOfText",
 +              "CtrlShiftRight": "SelectToEndOfLine",
 +              "ShiftEnd":       "SelectToEndOfLine",
 +              "CtrlUp":         "CursorStart",
 +              "CtrlDown":       "CursorEnd",
 +              "CtrlShiftUp":    "SelectToStart",
 +              "CtrlShiftDown":  "SelectToEnd",
 +              "Alt-{":          "ParagraphPrevious",
 +              "Alt-}":          "ParagraphNext",
 +              "Enter":          "InsertNewline",
 +              "CtrlH":          "Backspace",
 +              "Backspace":      "Backspace",
 +              "Alt-CtrlH":      "DeleteWordLeft",
 +              "Alt-Backspace":  "DeleteWordLeft",
 +              "Tab":            "Autocomplete|IndentSelection|InsertTab",
 +              "Backtab":        "CycleAutocompleteBack|OutdentSelection|OutdentLine",
 +              "CtrlO":          "OpenFile",
 +              "CtrlS":          "Save",
 +              "CtrlF":          "Find",
 +              "CtrlN":          "FindNext",
 +              "CtrlP":          "FindPrevious",
 +              "CtrlZ":          "Undo",
 +              "CtrlY":          "Redo",
 +              "CtrlC":          "Copy",
 +              "CtrlX":          "Cut",
 +              "CtrlK":          "CutLine",
 +              "CtrlD":          "DuplicateLine",
 +              "CtrlV":          "Paste",
 +              "CtrlA":          "SelectAll",
 +              "CtrlT":          "AddTab",
 +              "Alt,":           "PreviousTab",
 +              "Alt.":           "NextTab",
 +              "Home":           "StartOfText",
 +              "End":            "EndOfLine",
 +              "CtrlHome":       "CursorStart",
 +              "CtrlEnd":        "CursorEnd",
 +              "PageUp":         "CursorPageUp",
 +              "PageDown":       "CursorPageDown",
 +              "CtrlPageUp":     "PreviousTab",
 +              "CtrlPageDown":   "NextTab",
 +              "CtrlG":          "ToggleHelp",
 +              "Alt-g":          "ToggleKeyMenu",
 +              "CtrlR":          "ToggleRuler",
 +              "CtrlL":          "command-edit:goto ",
 +              "Delete":         "Delete",
 +              "CtrlB":          "ShellMode",
 +              "CtrlQ":          "Quit",
 +              "CtrlE":          "CommandMode",
 +              "CtrlW":          "NextSplit",
 +              "CtrlU":          "ToggleMacro",
 +              "CtrlJ":          "PlayMacro",
 +              "Insert":         "ToggleOverwriteMode",
 +
 +              // Emacs-style keybindings
 +              "Alt-f": "WordRight",
 +              "Alt-b": "WordLeft",
 +              "Alt-a": "StartOfText",
 +              "Alt-e": "EndOfLine",
 +              // "Alt-p": "CursorUp",
 +              // "Alt-n": "CursorDown",
 +
 +              // Integration with file managers
 +              "F2":  "Save",
 +              "F3":  "Find",
 +              "F4":  "Quit",
 +              "F7":  "Find",
 +              "F10": "Quit",
 +              "Esc": "Escape",
 +
 +              // Mouse bindings
 +              "MouseWheelUp":   "ScrollUp",
 +              "MouseWheelDown": "ScrollDown",
 +              "MouseLeft":      "MousePress",
 +              "MouseMiddle":    "PastePrimary",
 +              "Ctrl-MouseLeft": "MouseMultiCursor",
 +
++              "Alt-n":        "SpawnMultiCursor",
++              "AltShiftUp":   "SpawnMultiCursorUp",
++              "AltShiftDown": "SpawnMultiCursorDown",
++              "Alt-m":        "SpawnMultiCursorSelect",
++              "Alt-p":        "RemoveMultiCursor",
++              "Alt-c":        "RemoveAllMultiCursors",
++              "Alt-x":        "SkipMultiCursor",
 +      }
 +}
index ff05a89d97c63d9433cf5cef97d1b32088493002,0000000000000000000000000000000000000000..1ee406483c24535528f3c4f381435bd0940d8ccc
mode 100644,000000..100644
--- /dev/null
@@@ -1,107 -1,0 +1,109 @@@
-               "Alt-n": "SpawnMultiCursor",
-               "Alt-m": "SpawnMultiCursorSelect",
-               "Alt-p": "RemoveMultiCursor",
-               "Alt-c": "RemoveAllMultiCursors",
-               "Alt-x": "SkipMultiCursor",
 +// +build !darwin
 +
 +package action
 +
 +// DefaultBindings returns a map containing micro's default keybindings
 +func DefaultBindings() map[string]string {
 +      return map[string]string{
 +              "Up":             "CursorUp",
 +              "Down":           "CursorDown",
 +              "Right":          "CursorRight",
 +              "Left":           "CursorLeft",
 +              "ShiftUp":        "SelectUp",
 +              "ShiftDown":      "SelectDown",
 +              "ShiftLeft":      "SelectLeft",
 +              "ShiftRight":     "SelectRight",
 +              "CtrlLeft":       "WordLeft",
 +              "CtrlRight":      "WordRight",
 +              "AltUp":          "MoveLinesUp",
 +              "AltDown":        "MoveLinesDown",
 +              "CtrlShiftRight": "SelectWordRight",
 +              "CtrlShiftLeft":  "SelectWordLeft",
 +              "AltLeft":        "StartOfText",
 +              "AltRight":       "EndOfLine",
 +              "AltShiftLeft":   "SelectToStartOfText",
 +              "ShiftHome":      "SelectToStartOfText",
 +              "AltShiftRight":  "SelectToEndOfLine",
 +              "ShiftEnd":       "SelectToEndOfLine",
 +              "CtrlUp":         "CursorStart",
 +              "CtrlDown":       "CursorEnd",
 +              "CtrlShiftUp":    "SelectToStart",
 +              "CtrlShiftDown":  "SelectToEnd",
 +              "Alt-{":          "ParagraphPrevious",
 +              "Alt-}":          "ParagraphNext",
 +              "Enter":          "InsertNewline",
 +              "CtrlH":          "Backspace",
 +              "Backspace":      "Backspace",
 +              "Alt-CtrlH":      "DeleteWordLeft",
 +              "Alt-Backspace":  "DeleteWordLeft",
 +              "Tab":            "Autocomplete|IndentSelection|InsertTab",
 +              "Backtab":        "CycleAutocompleteBack|OutdentSelection|OutdentLine",
 +              "CtrlO":          "OpenFile",
 +              "CtrlS":          "Save",
 +              "CtrlF":          "Find",
 +              "CtrlN":          "FindNext",
 +              "CtrlP":          "FindPrevious",
 +              "CtrlZ":          "Undo",
 +              "CtrlY":          "Redo",
 +              "CtrlC":          "Copy",
 +              "CtrlX":          "Cut",
 +              "CtrlK":          "CutLine",
 +              "CtrlD":          "DuplicateLine",
 +              "CtrlV":          "Paste",
 +              "CtrlA":          "SelectAll",
 +              "CtrlT":          "AddTab",
 +              "Alt,":           "PreviousTab",
 +              "Alt.":           "NextTab",
 +              "Home":           "StartOfText",
 +              "End":            "EndOfLine",
 +              "CtrlHome":       "CursorStart",
 +              "CtrlEnd":        "CursorEnd",
 +              "PageUp":         "CursorPageUp",
 +              "PageDown":       "CursorPageDown",
 +              "CtrlPageUp":     "PreviousTab",
 +              "CtrlPageDown":   "NextTab",
 +              "CtrlG":          "ToggleHelp",
 +              "Alt-g":          "ToggleKeyMenu",
 +              "CtrlR":          "ToggleRuler",
 +              "CtrlL":          "command-edit:goto ",
 +              "Delete":         "Delete",
 +              "CtrlB":          "ShellMode",
 +              "CtrlQ":          "Quit",
 +              "CtrlE":          "CommandMode",
 +              "CtrlW":          "NextSplit",
 +              "CtrlU":          "ToggleMacro",
 +              "CtrlJ":          "PlayMacro",
 +              "Insert":         "ToggleOverwriteMode",
 +
 +              // Emacs-style keybindings
 +              "Alt-f": "WordRight",
 +              "Alt-b": "WordLeft",
 +              "Alt-a": "StartOfText",
 +              "Alt-e": "EndOfLine",
 +              // "Alt-p": "CursorUp",
 +              // "Alt-n": "CursorDown",
 +
 +              // Integration with file managers
 +              "F2":  "Save",
 +              "F3":  "Find",
 +              "F4":  "Quit",
 +              "F7":  "Find",
 +              "F10": "Quit",
 +              "Esc": "Escape",
 +
 +              // Mouse bindings
 +              "MouseWheelUp":   "ScrollUp",
 +              "MouseWheelDown": "ScrollDown",
 +              "MouseLeft":      "MousePress",
 +              "MouseMiddle":    "PastePrimary",
 +              "Ctrl-MouseLeft": "MouseMultiCursor",
 +
++              "Alt-n":        "SpawnMultiCursor",
++              "Alt-m":        "SpawnMultiCursorSelect",
++              "AltShiftUp":   "SpawnMultiCursorUp",
++              "AltShiftDown": "SpawnMultiCursorDown",
++              "Alt-p":        "RemoveMultiCursor",
++              "Alt-c":        "RemoveAllMultiCursors",
++              "Alt-x":        "SkipMultiCursor",
 +      }
 +}