]> git.lizzy.rs Git - micro.git/blobdiff - cmd/micro/actions.go
Minor optimizations
[micro.git] / cmd / micro / actions.go
index a4e6cd0b5e352c8b1f6ac31af7602135fdbba9ca..41c89e7103ed6d482462a3e0ca89e0e68d66121b 100644 (file)
@@ -1,13 +1,17 @@
 package main
 
 import (
+       "fmt"
        "os"
+       "regexp"
        "strconv"
        "strings"
        "time"
+       "unicode/utf8"
 
        "github.com/yuin/gopher-lua"
        "github.com/zyedidia/clipboard"
+       "github.com/zyedidia/micro/cmd/micro/shellwords"
        "github.com/zyedidia/tcell"
 )
 
@@ -76,7 +80,7 @@ func (v *View) MousePress(usePlugin bool, e *tcell.EventMouse) bool {
                        v.Cursor.ResetSelection()
                        v.Relocate()
                }
-               if time.Since(v.lastClickTime)/time.Millisecond < doubleClickThreshold {
+               if time.Since(v.lastClickTime)/time.Millisecond < doubleClickThreshold && (x == v.lastLoc.X && y == v.lastLoc.Y) {
                        if v.doubleClick {
                                // Triple click
                                v.lastClickTime = time.Now()
@@ -117,6 +121,8 @@ func (v *View) MousePress(usePlugin bool, e *tcell.EventMouse) bool {
                }
        }
 
+       v.lastLoc = Loc{x, y}
+
        if usePlugin {
                PostActionCall("MousePress", v, e)
        }
@@ -248,7 +254,7 @@ func (v *View) CursorRight(usePlugin bool) bool {
        }
 
        if v.Cursor.HasSelection() {
-               v.Cursor.Loc = v.Cursor.CurSelection[1].Move(-1, v.Buf)
+               v.Cursor.Loc = v.Cursor.CurSelection[1]
                v.Cursor.ResetSelection()
                v.Cursor.StoreVisualX()
        } else {
@@ -346,7 +352,7 @@ func (v *View) SelectLeft(usePlugin bool) bool {
        }
 
        loc := v.Cursor.Loc
-       count := v.Buf.End().Move(-1, v.Buf)
+       count := v.Buf.End()
        if loc.GreaterThan(count) {
                loc = count
        }
@@ -369,7 +375,7 @@ func (v *View) SelectRight(usePlugin bool) bool {
        }
 
        loc := v.Cursor.Loc
-       count := v.Buf.End().Move(-1, v.Buf)
+       count := v.Buf.End()
        if loc.GreaterThan(count) {
                loc = count
        }
@@ -453,6 +459,20 @@ func (v *View) EndOfLine(usePlugin bool) bool {
        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) {
@@ -489,6 +509,91 @@ func (v *View) SelectToEndOfLine(usePlugin bool) bool {
        return true
 }
 
+// ParagraphPrevious moves the cursor to the previous empty line, or beginning of the buffer if there's none
+func (v *View) ParagraphPrevious(usePlugin bool) bool {
+       if usePlugin && !PreActionCall("ParagraphPrevious", v) {
+               return false
+       }
+       var line int
+       for line = v.Cursor.Y; line > 0; line-- {
+               if len(v.Buf.lines[line].data) == 0 && line != v.Cursor.Y {
+                       v.Cursor.X = 0
+                       v.Cursor.Y = line
+                       break
+               }
+       }
+       // If no empty line found. move cursor to end of buffer
+       if line == 0 {
+               v.Cursor.Loc = v.Buf.Start()
+       }
+
+       if usePlugin {
+               return PostActionCall("ParagraphPrevious", v)
+       }
+       return true
+}
+
+// ParagraphNext moves the cursor to the next empty line, or end of the buffer if there's none
+func (v *View) ParagraphNext(usePlugin bool) bool {
+       if usePlugin && !PreActionCall("ParagraphNext", v) {
+               return false
+       }
+
+       var line int
+       for line = v.Cursor.Y; line < len(v.Buf.lines); line++ {
+               if len(v.Buf.lines[line].data) == 0 && line != v.Cursor.Y {
+                       v.Cursor.X = 0
+                       v.Cursor.Y = line
+                       break
+               }
+       }
+       // If no empty line found. move cursor to end of buffer
+       if line == len(v.Buf.lines) {
+               v.Cursor.Loc = v.Buf.End()
+       }
+
+       if usePlugin {
+               return PostActionCall("ParagraphNext", v)
+       }
+       return true
+}
+
+// Retab changes all tabs to spaces or all spaces to tabs depending
+// on the user's settings
+func (v *View) Retab(usePlugin bool) bool {
+       if usePlugin && !PreActionCall("Retab", v) {
+               return false
+       }
+
+       toSpaces := v.Buf.Settings["tabstospaces"].(bool)
+       tabsize := int(v.Buf.Settings["tabsize"].(float64))
+       dirty := false
+
+       for i := 0; i < v.Buf.NumLines; i++ {
+               l := v.Buf.Line(i)
+
+               ws := GetLeadingWhitespace(l)
+               if ws != "" {
+                       if toSpaces {
+                               ws = strings.Replace(ws, "\t", Spaces(tabsize), -1)
+                       } else {
+                               ws = strings.Replace(ws, Spaces(tabsize), "\t", -1)
+                       }
+               }
+
+               l = strings.TrimLeft(l, " \t")
+               v.Buf.lines[i].data = []byte(ws + l)
+               dirty = true
+       }
+
+       v.Buf.IsModified = dirty
+
+       if usePlugin {
+               return PostActionCall("Retab", v)
+       }
+       return true
+}
+
 // CursorStart moves the cursor to the start of the buffer
 func (v *View) CursorStart(usePlugin bool) bool {
        if usePlugin && !PreActionCall("CursorStart", v) {
@@ -591,10 +696,14 @@ func (v *View) InsertNewline(usePlugin bool) bool {
        }
 
        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()
@@ -634,9 +743,9 @@ func (v *View) Backspace(usePlugin bool) bool {
                // 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 {
@@ -721,13 +830,15 @@ func (v *View) IndentSelection(usePlugin bool) bool {
                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))
@@ -781,23 +892,18 @@ func (v *View) OutdentSelection(usePlugin bool) bool {
                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
                for y := startY; y <= endY; y++ {
                        for x := 0; x < len(v.Buf.IndentString()); x++ {
                                if len(GetLeadingWhitespace(v.Buf.Line(y))) == 0 {
                                        break
                                }
                                v.Buf.Remove(Loc{0, y}, Loc{1, y})
-                               if y == startY && start.X > 0 {
-                                       v.Cursor.SetSelectionStart(start.Move(-1, v.Buf))
-                               }
-                               if y == endY {
-                                       v.Cursor.SetSelectionEnd(Loc{endX - x, endY})
-                               }
                        }
                }
                v.Cursor.Relocate()
@@ -860,7 +966,7 @@ func (v *View) Save(usePlugin bool) bool {
                        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
                }
@@ -917,7 +1023,12 @@ func (v *View) SaveAs(usePlugin bool) bool {
                filename, canceled := messenger.Prompt("Filename: ", "", "Save", NoCompletion)
                if !canceled {
                        // the filename might or might not be quoted, so unquote first then join the strings.
-                       filename = strings.Join(SplitCommandArgs(filename), " ")
+                       args, err := shellwords.Split(filename)
+                       filename = strings.Join(args, " ")
+                       if err != nil {
+                               messenger.Error("Error parsing arguments: ", err)
+                               return false
+                       }
                        v.saveToFile(filename)
                }
 
@@ -1002,6 +1113,10 @@ func (v *View) Undo(usePlugin bool) bool {
                return false
        }
 
+       if v.Buf.curCursor == 0 {
+               v.Buf.clearCursors()
+       }
+
        v.Buf.Undo()
        messenger.Message("Undid action")
 
@@ -1017,6 +1132,10 @@ func (v *View) Redo(usePlugin bool) bool {
                return false
        }
 
+       if v.Buf.curCursor == 0 {
+               v.Buf.clearCursors()
+       }
+
        v.Buf.Redo()
        messenger.Message("Redid action")
 
@@ -1154,12 +1273,16 @@ func (v *View) MoveLinesUp(usePlugin bool) bool {
                        messenger.Message("Can not move further up")
                        return true
                }
+               start := v.Cursor.CurSelection[0].Y
+               end := v.Cursor.CurSelection[1].Y
+               if start > end {
+                       end, start = start, end
+               }
+
                v.Buf.MoveLinesUp(
-                       v.Cursor.CurSelection[0].Y,
-                       v.Cursor.CurSelection[1].Y,
+                       start,
+                       end,
                )
-               v.Cursor.UpN(1)
-               v.Cursor.CurSelection[0].Y -= 1
                v.Cursor.CurSelection[1].Y -= 1
                messenger.Message("Moved up selected line(s)")
        } else {
@@ -1171,7 +1294,6 @@ func (v *View) MoveLinesUp(usePlugin bool) bool {
                        v.Cursor.Loc.Y,
                        v.Cursor.Loc.Y+1,
                )
-               v.Cursor.UpN(1)
                messenger.Message("Moved up current line")
        }
        v.Buf.IsModified = true
@@ -1193,13 +1315,16 @@ func (v *View) MoveLinesDown(usePlugin bool) bool {
                        messenger.Message("Can not move further down")
                        return true
                }
+               start := v.Cursor.CurSelection[0].Y
+               end := v.Cursor.CurSelection[1].Y
+               if start > end {
+                       end, start = start, end
+               }
+
                v.Buf.MoveLinesDown(
-                       v.Cursor.CurSelection[0].Y,
-                       v.Cursor.CurSelection[1].Y,
+                       start,
+                       end,
                )
-               v.Cursor.DownN(1)
-               v.Cursor.CurSelection[0].Y += 1
-               v.Cursor.CurSelection[1].Y += 1
                messenger.Message("Moved down selected line(s)")
        } else {
                if v.Cursor.Loc.Y >= len(v.Buf.lines)-1 {
@@ -1210,7 +1335,6 @@ func (v *View) MoveLinesDown(usePlugin bool) bool {
                        v.Cursor.Loc.Y,
                        v.Cursor.Loc.Y+1,
                )
-               v.Cursor.DownN(1)
                messenger.Message("Moved down current line")
        }
        v.Buf.IsModified = true
@@ -1252,6 +1376,27 @@ func (v *View) PastePrimary(usePlugin bool) bool {
        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) {
@@ -1479,20 +1624,38 @@ func (v *View) JumpLine(usePlugin bool) bool {
        }
 
        // Prompt for line number
-       linestring, canceled := messenger.Prompt("Jump to line # ", "", "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)
@@ -1540,6 +1703,25 @@ func (v *View) ToggleHelp(usePlugin bool) bool {
        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() {
@@ -1578,6 +1760,22 @@ func (v *View) CommandMode(usePlugin bool) bool {
        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() {
@@ -1629,6 +1827,7 @@ func (v *View) Quit(usePlugin bool) bool {
                                }
 
                                screen.Fini()
+                               messenger.SaveHistory()
                                os.Exit(0)
                        }
                }
@@ -1672,6 +1871,7 @@ func (v *View) QuitAll(usePlugin bool) bool {
                                }
 
                                screen.Fini()
+                               messenger.SaveHistory()
                                os.Exit(0)
                        }
                }
@@ -1903,7 +2103,7 @@ func (v *View) PlayMacro(usePlugin bool) bool {
        return true
 }
 
-// SpawnMultiCursor creates a new multiple cursor at the next occurence of the current selection or current word
+// SpawnMultiCursor creates a new multiple cursor at the next occurrence of the current selection or current word
 func (v *View) SpawnMultiCursor(usePlugin bool) bool {
        spawner := v.Buf.cursors[len(v.Buf.cursors)-1]
        // You can only spawn a cursor from the main cursor
@@ -1923,7 +2123,7 @@ func (v *View) SpawnMultiCursor(usePlugin bool) bool {
 
                        searchStart = spawner.CurSelection[1]
                        v.Cursor = c
-                       Search(sel, v, true)
+                       Search(regexp.QuoteMeta(sel), v, true)
 
                        for _, cur := range v.Buf.cursors {
                                if c.Loc == cur.Loc {
@@ -1963,6 +2163,7 @@ func (v *View) MouseMultiCursor(usePlugin bool, e *tcell.EventMouse) bool {
                v.Cursor = &v.Buf.Cursor
 
                v.Buf.cursors = append(v.Buf.cursors, c)
+               v.Buf.MergeCursors()
                v.Buf.UpdateCursors()
 
                if usePlugin {
@@ -1984,7 +2185,7 @@ func (v *View) SkipMultiCursor(usePlugin bool) bool {
 
                searchStart = cursor.CurSelection[1]
                v.Cursor = cursor
-               Search(sel, v, true)
+               Search(regexp.QuoteMeta(sel), v, true)
                v.Relocate()
                v.Cursor = cursor
 
@@ -2027,12 +2228,7 @@ func (v *View) RemoveAllMultiCursors(usePlugin bool) bool {
                        return false
                }
 
-               for i := 1; i < len(v.Buf.cursors); i++ {
-                       v.Buf.cursors[i] = nil
-               }
-               v.Buf.cursors = v.Buf.cursors[:1]
-               v.Buf.UpdateCursors()
-               v.Cursor.ResetSelection()
+               v.Buf.clearCursors()
                v.Relocate()
 
                if usePlugin {