"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"
)
v.deselect(0)
- v.Cursor.Start()
+ if v.Cursor.X != 0 {
+ v.Cursor.Start()
+ } else {
+ v.Cursor.StartOfText()
+ }
if usePlugin {
return PostActionCall("StartOfLine", 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 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) {
}
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()
// 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 := v.Buf.Line(v.Cursor.Y)[:v.Cursor.X]
+ 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) && len(lineStart) != 0 && len(lineStart)%tabSize == 0 {
+ 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 {
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++ {
- tabsize := len(v.Buf.IndentString())
v.Buf.Insert(Loc{0, y}, v.Buf.IndentString())
if y == startY && start.X > 0 {
v.Cursor.SetSelectionStart(start.Move(tabsize, v.Buf))
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
}
for _, t := range tabs {
- for _, v := range t.views {
+ for _, v := range t.Views {
v.Save(false)
}
}
return false
}
- if v.Type.scratch == true {
+ if v.Type.Scratch == true {
// We can't save any view type with scratch set. eg help and log text
return false
}
// SaveAs saves the buffer to disk with the given name
func (v *View) SaveAs(usePlugin bool) bool {
if v.mainCursor() {
- if usePlugin && !PreActionCall("Find", v) {
+ 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.
- filename = strings.Join(SplitCommandArgs(filename), " ")
+ 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("Find", v)
+ PostActionCall("SaveAs", v)
}
}
return false
return PostActionCall("Cut", v)
}
return true
+ } else {
+ return v.CutLine(usePlugin)
}
-
- return false
}
// DuplicateLine duplicates the current line or selection
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
}
+// 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) {
}
// Prompt for line number
- message := fmt.Sprintf("Jump to line (1 - %v) # ", v.Buf.NumLines)
- linestring, canceled := messenger.Prompt(message, "", "LineNumber", NoCompletion)
+ message := fmt.Sprintf("Jump to line:col (1 - %v) # ", v.Buf.NumLines)
+ input, canceled := messenger.Prompt(message, "", "LineNumber", NoCompletion)
if canceled {
return false
}
- lineint, err := strconv.Atoi(linestring)
- lineint = lineint - 1 // fix offset
- if err != nil {
- messenger.Error(err) // return errors
- 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 = 0
- v.Cursor.Y = lineint
+ if lineInt < v.Buf.NumLines && lineInt >= 0 {
+ v.Cursor.X = colInt
+ v.Cursor.Y = lineInt
if usePlugin {
return PostActionCall("JumpLine", 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() {
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() {
// Make sure not to quit if there are unsaved changes
if v.CanClose() {
v.CloseBuffer()
- if len(tabs[curTab].views) > 1 {
+ 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 {
+ 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)
}
screen.Fini()
+ messenger.SaveHistory()
os.Exit(0)
}
}
closeAll := true
for _, tab := range tabs {
- for _, v := range tab.views {
+ for _, v := range tab.Views {
if !v.CanClose() {
closeAll = false
}
if shouldQuit {
for _, tab := range tabs {
- for _, v := range tab.views {
+ for _, v := range tab.Views {
v.CloseBuffer()
}
}
}
screen.Fini()
+ messenger.SaveHistory()
os.Exit(0)
}
}
curTab = len(tabs) - 1
if len(tabs) == 2 {
for _, t := range tabs {
- for _, v := range t.views {
+ for _, v := range t.Views {
v.ToggleTabbar()
}
}
}
curView := tabs[curTab].CurView
- for i := len(tabs[curTab].views) - 1; i >= 0; i-- {
- view := tabs[curTab].views[i]
+ 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)
}
tab := tabs[curTab]
- if tab.CurView < len(tab.views)-1 {
+ if tab.CurView < len(tab.Views)-1 {
tab.CurView++
} else {
tab.CurView = 0
if tab.CurView > 0 {
tab.CurView--
} else {
- tab.CurView = len(tab.views) - 1
+ tab.CurView = len(tab.Views) - 1
}
if usePlugin {
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 {