X-Git-Url: https://git.lizzy.rs/?a=blobdiff_plain;f=cmd%2Fmicro%2Factions.go;h=83eec1440d82768b809879d8db588f46a847c3e9;hb=96284a1feb8c2aad02cc6927bb3581de6335246b;hp=54873e822ce5eb8dd655f340e7bdbf58cd0755d1;hpb=ec221c0bc403b3501ff046b146573cdfd6640465;p=micro.git diff --git a/cmd/micro/actions.go b/cmd/micro/actions.go index 54873e82..83eec144 100644 --- a/cmd/micro/actions.go +++ b/cmd/micro/actions.go @@ -7,9 +7,11 @@ import ( "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" ) @@ -433,7 +435,11 @@ func (v *View) StartOfLine(usePlugin bool) bool { v.deselect(0) - v.Cursor.Start() + if v.Cursor.X != 0 { + v.Cursor.Start() + } else { + v.Cursor.StartOfText() + } if usePlugin { return PostActionCall("StartOfLine", v) @@ -457,6 +463,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) { @@ -542,6 +562,42 @@ func (v *View) ParagraphNext(usePlugin bool) bool { 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) { @@ -644,10 +700,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() @@ -687,9 +747,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 { @@ -774,13 +834,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)) @@ -834,6 +896,8 @@ 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 @@ -887,7 +951,7 @@ func (v *View) SaveAll(usePlugin bool) bool { } for _, t := range tabs { - for _, v := range t.views { + for _, v := range t.Views { v.Save(false) } } @@ -906,7 +970,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 } @@ -956,19 +1020,24 @@ func (v *View) saveToFile(filename string) { // 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 @@ -1150,9 +1219,9 @@ func (v *View) Cut(usePlugin bool) bool { return PostActionCall("Cut", v) } return true + } else { + return v.CutLine(usePlugin) } - - return false } // DuplicateLine duplicates the current line or selection @@ -1311,6 +1380,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) { @@ -1425,6 +1515,42 @@ func (v *View) PageDown(usePlugin bool) bool { 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) { @@ -1538,21 +1664,38 @@ func (v *View) JumpLine(usePlugin bool) bool { } // 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) @@ -1600,6 +1743,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() { @@ -1638,6 +1800,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() { @@ -1666,12 +1844,12 @@ func (v *View) Quit(usePlugin bool) bool { // 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) @@ -1689,6 +1867,7 @@ func (v *View) Quit(usePlugin bool) bool { } screen.Fini() + messenger.SaveHistory() os.Exit(0) } } @@ -1709,7 +1888,7 @@ func (v *View) QuitAll(usePlugin bool) bool { closeAll := true for _, tab := range tabs { - for _, v := range tab.views { + for _, v := range tab.Views { if !v.CanClose() { closeAll = false } @@ -1722,7 +1901,7 @@ func (v *View) QuitAll(usePlugin bool) bool { if shouldQuit { for _, tab := range tabs { - for _, v := range tab.views { + for _, v := range tab.Views { v.CloseBuffer() } } @@ -1732,6 +1911,7 @@ func (v *View) QuitAll(usePlugin bool) bool { } screen.Fini() + messenger.SaveHistory() os.Exit(0) } } @@ -1753,7 +1933,7 @@ func (v *View) AddTab(usePlugin bool) bool { curTab = len(tabs) - 1 if len(tabs) == 2 { for _, t := range tabs { - for _, v := range t.views { + for _, v := range t.Views { v.ToggleTabbar() } } @@ -1846,8 +2026,8 @@ func (v *View) Unsplit(usePlugin bool) bool { } 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) @@ -1869,7 +2049,7 @@ func (v *View) NextSplit(usePlugin bool) bool { } tab := tabs[curTab] - if tab.CurView < len(tab.views)-1 { + if tab.CurView < len(tab.Views)-1 { tab.CurView++ } else { tab.CurView = 0 @@ -1893,7 +2073,7 @@ func (v *View) PreviousSplit(usePlugin bool) bool { if tab.CurView > 0 { tab.CurView-- } else { - tab.CurView = len(tab.views) - 1 + tab.CurView = len(tab.Views) - 1 } if usePlugin { @@ -2004,6 +2184,54 @@ func (v *View) SpawnMultiCursor(usePlugin bool) bool { 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 {