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"
)
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()
}
}
+ v.lastLoc = Loc{x, y}
+
if usePlugin {
PostActionCall("MousePress", v, e)
}
}
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 {
}
loc := v.Cursor.Loc
- count := v.Buf.End().Move(-1, v.Buf)
+ count := v.Buf.End()
if loc.GreaterThan(count) {
loc = count
}
}
loc := v.Cursor.Loc
- count := v.Buf.End().Move(-1, v.Buf)
+ count := v.Buf.End()
if loc.GreaterThan(count) {
loc = count
}
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
}
+// 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) {
}
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
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()
}
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 false
}
+ if v.Buf.curCursor == 0 {
+ v.Buf.clearCursors()
+ }
+
v.Buf.Undo()
messenger.Message("Undid action")
return false
}
+ if v.Buf.curCursor == 0 {
+ v.Buf.clearCursors()
+ }
+
v.Buf.Redo()
messenger.Message("Redid action")
return PostActionCall("Cut", v)
}
return true
+ } else {
+ return v.CutLine(usePlugin)
}
-
- return false
}
// DuplicateLine duplicates the current line or selection
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 {
v.Cursor.Loc.Y,
v.Cursor.Loc.Y+1,
)
- v.Cursor.UpN(1)
messenger.Message("Moved up current line")
}
v.Buf.IsModified = true
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 {
v.Cursor.Loc.Y,
v.Cursor.Loc.Y+1,
)
- v.Cursor.DownN(1)
messenger.Message("Moved down current line")
}
v.Buf.IsModified = true
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 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
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 {
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 {
searchStart = cursor.CurSelection[1]
v.Cursor = cursor
- Search(sel, v, true)
+ Search(regexp.QuoteMeta(sel), v, true)
v.Relocate()
v.Cursor = cursor
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 {