"runtime"
"strings"
"time"
- "unicode/utf8"
-
- "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/micro/pkg/shellwords"
+
+ shellquote "github.com/kballard/go-shellquote"
+ "github.com/zyedidia/micro/v2/internal/buffer"
+ "github.com/zyedidia/micro/v2/internal/clipboard"
+ "github.com/zyedidia/micro/v2/internal/config"
+ "github.com/zyedidia/micro/v2/internal/screen"
+ "github.com/zyedidia/micro/v2/internal/shell"
+ "github.com/zyedidia/micro/v2/internal/util"
"github.com/zyedidia/tcell"
)
if v.StartLine >= n {
v.StartLine -= n
h.SetView(v)
+ } else {
+ v.StartLine = 0
}
}
func (h *BufPane) MousePress(e *tcell.EventMouse) bool {
b := h.Buf
mx, my := e.Position()
- mouseLoc := h.GetMouseLoc(buffer.Loc{mx, my})
+ mouseLoc := h.LocFromVisual(buffer.Loc{mx, my})
h.Cursor.Loc = mouseLoc
if h.mouseReleased {
if b.NumCursors() > 1 {
h.doubleClick = false
h.Cursor.SelectLine()
- h.Cursor.CopySelection("primary")
+ h.Cursor.CopySelection(clipboard.PrimaryReg)
} else {
// Double click
h.lastClickTime = time.Now()
h.tripleClick = false
h.Cursor.SelectWord()
- h.Cursor.CopySelection("primary")
+ h.Cursor.CopySelection(clipboard.PrimaryReg)
}
} else {
h.doubleClick = false
h.Cursor.AddWordToSelection()
} else {
h.Cursor.SetSelectionEnd(h.Cursor.Loc)
- h.Cursor.CopySelection("primary")
}
}
h.Cursor.StoreVisualX()
h.lastLoc = mouseLoc
- return false
+ return true
}
// ScrollUpAction scrolls the view up
func (h *BufPane) ScrollUpAction() bool {
h.ScrollUp(util.IntOpt(h.Buf.Settings["scrollspeed"]))
- return false
+ return true
}
// ScrollDownAction scrolls the view up
func (h *BufPane) ScrollDownAction() bool {
h.ScrollDown(util.IntOpt(h.Buf.Settings["scrollspeed"]))
- return false
+ return true
}
// Center centers the view on the cursor
v.StartLine = 0
}
h.SetView(v)
+ h.Relocate()
return true
}
func (h *BufPane) CursorUp() bool {
h.Cursor.Deselect(true)
h.Cursor.Up()
+ h.Relocate()
return true
}
func (h *BufPane) CursorDown() bool {
h.Cursor.Deselect(true)
h.Cursor.Down()
+ h.Relocate()
return true
}
h.Cursor.Left()
}
}
+ h.Relocate()
return true
}
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]) {
+ if h.Cursor.X+tabsize < util.CharacterCount(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()
}
}
}
+ h.Relocate()
return true
}
func (h *BufPane) WordRight() bool {
h.Cursor.Deselect(false)
h.Cursor.WordRight()
+ h.Relocate()
return true
}
func (h *BufPane) WordLeft() bool {
h.Cursor.Deselect(true)
h.Cursor.WordLeft()
+ h.Relocate()
return true
}
}
h.Cursor.Up()
h.Cursor.SelectTo(h.Cursor.Loc)
+ h.Relocate()
return true
}
}
h.Cursor.Down()
h.Cursor.SelectTo(h.Cursor.Loc)
+ h.Relocate()
return true
}
}
h.Cursor.Left()
h.Cursor.SelectTo(h.Cursor.Loc)
+ h.Relocate()
return true
}
}
h.Cursor.Right()
h.Cursor.SelectTo(h.Cursor.Loc)
+ h.Relocate()
return true
}
}
h.Cursor.WordRight()
h.Cursor.SelectTo(h.Cursor.Loc)
+ h.Relocate()
return true
}
}
h.Cursor.WordLeft()
h.Cursor.SelectTo(h.Cursor.Loc)
+ h.Relocate()
+ return true
+}
+
+// StartOfText 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
+}
+
+// StartOfTextToggle toggles the cursor between the start of the text of the line
+// and the start of the line
+func (h *BufPane) StartOfTextToggle() bool {
+ h.Cursor.Deselect(true)
+ if h.Cursor.IsStartOfText() {
+ h.Cursor.Start()
+ } else {
+ 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.StartOfText()
- // if h.Cursor.X != 0 {
- // h.Cursor.Start()
- // } else {
- // h.Cursor.StartOfText()
- // }
+ h.Cursor.Start()
+ h.Relocate()
return true
}
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
+}
+
+// SelectToStartOfTextToggle toggles the selection between the start of the text
+// on the current line and the start of the line
+func (h *BufPane) SelectToStartOfTextToggle() bool {
+ if !h.Cursor.HasSelection() {
+ h.Cursor.OrigSelection[0] = h.Cursor.Loc
+ }
+ if h.Cursor.IsStartOfText() {
+ h.Cursor.Start()
+ } else {
+ h.Cursor.StartOfText()
+ }
+ h.Cursor.SelectTo(h.Cursor.Loc)
+ h.Relocate()
return true
}
}
h.Cursor.Start()
h.Cursor.SelectTo(h.Cursor.Loc)
+ h.Relocate()
return true
}
}
h.Cursor.End()
h.Cursor.SelectTo(h.Cursor.Loc)
+ h.Relocate()
return true
}
if line == 0 {
h.Cursor.Loc = h.Buf.Start()
}
+ h.Relocate()
return true
}
if line == h.Buf.LinesNum() {
h.Cursor.Loc = h.Buf.End()
}
+ h.Relocate()
return true
}
// on the user's settings
func (h *BufPane) Retab() bool {
h.Buf.Retab()
+ h.Relocate()
return true
}
h.Cursor.Deselect(true)
h.Cursor.X = 0
h.Cursor.Y = 0
+ h.Cursor.StoreVisualX()
+ h.Relocate()
return true
}
h.Cursor.Deselect(true)
h.Cursor.Loc = h.Buf.End()
h.Cursor.StoreVisualX()
+ h.Relocate()
return true
}
}
h.CursorStart()
h.Cursor.SelectTo(h.Buf.Start())
+ h.Relocate()
return true
}
}
h.CursorEnd()
h.Cursor.SelectTo(h.Buf.End())
+ h.Relocate()
return true
}
// 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.Buf.Remove(buffer.Loc{X: 0, Y: h.Cursor.Y - 1}, buffer.Loc{X: util.CharacterCount(line), Y: h.Cursor.Y - 1})
}
}
h.Cursor.LastVisualX = h.Cursor.GetVisualX()
+ h.Relocate()
return true
}
// 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 {
+ if h.Buf.Settings["tabstospaces"].(bool) && util.IsSpaces(lineStart) && len(lineStart) != 0 && util.CharacterCount(lineStart)%tabSize == 0 {
loc := h.Cursor.Loc
h.Buf.Remove(loc.Move(-tabSize, h.Buf), loc)
} else {
}
}
h.Cursor.LastVisualX = h.Cursor.GetVisualX()
+ h.Relocate()
return true
}
h.Cursor.DeleteSelection()
h.Cursor.ResetSelection()
}
+ h.Relocate()
return true
}
h.Cursor.DeleteSelection()
h.Cursor.ResetSelection()
}
+ h.Relocate()
return true
}
h.Buf.Remove(loc, loc.Move(1, h.Buf))
}
}
+ h.Relocate()
return true
}
tabsize := int(h.Buf.Settings["tabsize"].(float64))
indentsize := len(h.Buf.IndentString(tabsize))
for y := startY; y <= endY; y++ {
- 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})
+ 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
}
+// IndentLine moves the current line forward one indentation
+func (h *BufPane) IndentLine() bool {
+ if h.Cursor.HasSelection() {
+ return false
+ }
+
+ tabsize := int(h.Buf.Settings["tabsize"].(float64))
+ indentstr := h.Buf.IndentString(tabsize)
+ h.Buf.Insert(buffer.Loc{X: 0, Y: h.Cursor.Y}, indentstr)
+ h.Buf.RelocateCursors()
+ h.Relocate()
+ return true
+}
+
// OutdentLine moves the current line back one indentation
func (h *BufPane) OutdentLine() bool {
if h.Cursor.HasSelection() {
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
}
}
h.Buf.RelocateCursors()
+ h.Relocate()
return true
}
return false
}
-// InsertTab inserts a tab or spaces
-func (h *BufPane) InsertTab() bool {
+// 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 h.Cursor.X == 0 {
+ return false
+ }
+ r := h.Cursor.RuneUnder(h.Cursor.X)
+ prev := h.Cursor.RuneUnder(h.Cursor.X - 1)
+ if !util.IsAutocomplete(prev) || !util.IsNonAlphaNumeric(r) {
+ // don't autocomplete if cursor is on alpha numeric character (middle of a word)
+ 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
+ }
- l := b.LineBytes(h.Cursor.Y)
- l = util.SliceStart(l, h.Cursor.X)
- hasComplete := b.Autocomplete(buffer.BufferComplete)
- if !hasComplete {
- indent := b.IndentString(util.IntOpt(b.Settings["tabsize"]))
- tabBytes := len(indent)
- bytesUntilIndent := tabBytes - (h.Cursor.GetVisualX() % tabBytes)
- b.Insert(h.Cursor.Loc, indent[:bytesUntilIndent])
+ 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
}
for _, b := range buffer.OpenBuffers {
b.Save()
}
- return false
+ return true
}
-// Save the buffer to disk
-func (h *BufPane) Save() bool {
+// SaveCB performs a save and does a callback at the very end (after all prompts have been resolved)
+func (h *BufPane) SaveCB(action string, callback func()) bool {
// If this is an empty buffer, ask for a filename
if h.Buf.Path == "" {
- h.SaveAs()
+ h.SaveAsCB(action, callback)
} else {
- h.saveBufToFile(h.Buf.Path)
+ noPrompt := h.saveBufToFile(h.Buf.Path, action, callback)
+ if noPrompt {
+ return true
+ }
}
-
return false
}
-// SaveAs saves the buffer to disk with the given name
-func (h *BufPane) SaveAs() bool {
+// Save the buffer to disk
+func (h *BufPane) Save() bool {
+ return h.SaveCB("Save", nil)
+}
+
+// SaveAsCB performs a save as and does a callback at the very end (after all prompts have been resolved)
+// The callback is only called if the save was successful
+func (h *BufPane) SaveAsCB(action string, callback func()) 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 := shellwords.Split(resp)
- filename := strings.Join(args, " ")
+ args, err := shellquote.Split(resp)
if err != nil {
InfoBar.Error("Error parsing arguments: ", err)
return
}
- h.saveBufToFile(filename)
-
+ if len(args) == 0 {
+ InfoBar.Error("No filename given")
+ return
+ }
+ filename := strings.Join(args, " ")
+ noPrompt := h.saveBufToFile(filename, action, callback)
+ if noPrompt {
+ h.completeAction(action)
+ }
}
})
return false
}
+// SaveAs saves the buffer to disk with the given name
+func (h *BufPane) SaveAs() bool {
+ return h.SaveAsCB("SaveAs", nil)
+}
+
// 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) {
+// The callback is only called if the save was successful
+func (h *BufPane) saveBufToFile(filename string, action string, callback func()) 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)
+ saveWithSudo := func() {
+ err = h.Buf.SaveAsWithSudo(filename)
+ if err != nil {
+ InfoBar.Error(err)
+ } else {
+ h.Buf.Path = filename
+ h.Buf.SetName(filename)
+ InfoBar.Message("Saved " + filename)
+ if callback != nil {
+ callback()
}
}
- })
+ }
+ if h.Buf.Settings["autosu"].(bool) {
+ saveWithSudo()
+ } else {
+ InfoBar.YNPrompt("Permission denied. Do you want to save this file using sudo? (y,n)", func(yes, canceled bool) {
+ if yes && !canceled {
+ saveWithSudo()
+ h.completeAction(action)
+ }
+ })
+ return false
+ }
} else {
InfoBar.Error(err)
}
h.Buf.Path = filename
h.Buf.SetName(filename)
InfoBar.Message("Saved " + filename)
+ if callback != nil {
+ callback()
+ }
}
+ return true
}
// Find opens a prompt and searches forward for the input
func (h *BufPane) Find() bool {
+ return h.find(true)
+}
+
+// FindLiteral is the same as Find() but does not support regular expressions
+func (h *BufPane) FindLiteral() bool {
+ return h.find(false)
+}
+
+// Search searches for a given string/regex in the buffer and selects the next
+// match if a match is found
+// This function affects lastSearch and lastSearchRegex (saved searches) for
+// use with FindNext and FindPrevious
+func (h *BufPane) Search(str string, useRegex bool, searchDown bool) error {
+ match, found, err := h.Buf.FindNext(str, h.Buf.Start(), h.Buf.End(), h.Cursor.Loc, searchDown, useRegex)
+ if err != nil {
+ return 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 = str
+ h.lastSearchRegex = useRegex
+ h.Relocate()
+ } else {
+ h.Cursor.ResetSelection()
+ }
+ return nil
+}
+
+func (h *BufPane) find(useRegex bool) bool {
h.searchOrig = h.Cursor.Loc
- InfoBar.Prompt("Find: ", "", "Find", func(resp string) {
+ prompt := "Find: "
+ if useRegex {
+ prompt = "Find (regex): "
+ }
+ InfoBar.Prompt(prompt, "", "Find", func(resp string) {
// Event callback
- match, found, _ := h.Buf.FindNext(resp, h.Buf.Start(), h.Buf.End(), h.searchOrig, true, true)
+ match, found, _ := h.Buf.FindNext(resp, h.Buf.Start(), h.Buf.End(), h.searchOrig, true, useRegex)
if found {
h.Cursor.SetSelectionStart(match[0])
h.Cursor.SetSelectionEnd(match[1])
}, 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)
+ match, found, err := h.Buf.FindNext(resp, h.Buf.Start(), h.Buf.End(), h.searchOrig, true, useRegex)
if err != nil {
InfoBar.Error(err)
}
h.Cursor.OrigSelection[1] = h.Cursor.CurSelection[1]
h.Cursor.GotoLoc(h.Cursor.CurSelection[1])
h.lastSearch = resp
+ h.lastSearchRegex = useRegex
} else {
h.Cursor.ResetSelection()
InfoBar.Message("No matches found")
h.Relocate()
})
- return false
+ return true
}
// FindNext searches forwards for the last used search term
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)
+ match, found, err := h.Buf.FindNext(h.lastSearch, h.Buf.Start(), h.Buf.End(), searchLoc, true, h.lastSearchRegex)
if err != nil {
InfoBar.Error(err)
}
} else {
h.Cursor.ResetSelection()
}
+ h.Relocate()
return true
}
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)
+ match, found, err := h.Buf.FindNext(h.lastSearch, h.Buf.Start(), h.Buf.End(), searchLoc, false, h.lastSearchRegex)
if err != nil {
InfoBar.Error(err)
}
} else {
h.Cursor.ResetSelection()
}
+ h.Relocate()
return true
}
func (h *BufPane) Undo() bool {
h.Buf.Undo()
InfoBar.Message("Undid action")
+ h.Relocate()
return true
}
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.Cursor.CopySelection(clipboard.ClipboardReg)
h.freshClip = true
InfoBar.Message("Copied selection")
}
+ h.Relocate()
+ return true
+}
+
+// Copy the current line to the clipboard
+func (h *BufPane) CopyLine() bool {
+ if h.Cursor.HasSelection() {
+ return false
+ } else {
+ h.Cursor.SelectLine()
+ h.Cursor.CopySelection(clipboard.ClipboardReg)
+ h.freshClip = true
+ InfoBar.Message("Copied line")
+ }
+ h.Cursor.Deselect(true)
+ h.Relocate()
return true
}
}
if h.freshClip == true {
if h.Cursor.HasSelection() {
- if clip, err := clipboard.ReadAll("clipboard"); err != nil {
- // messenger.Error(err)
+ if clip, err := clipboard.Read(clipboard.ClipboardReg); err != nil {
+ InfoBar.Error(err)
} else {
- clipboard.WriteAll(clip+string(h.Cursor.GetSelection()), "clipboard")
+ clipboard.Write(clip+string(h.Cursor.GetSelection()), clipboard.ClipboardReg)
}
}
} else if time.Since(h.lastCutTime)/time.Second > 10*time.Second || h.freshClip == false {
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.CopySelection(clipboard.ClipboardReg)
h.Cursor.DeleteSelection()
h.Cursor.ResetSelection()
h.freshClip = true
InfoBar.Message("Cut selection")
+ h.Relocate()
return true
} else {
return h.CutLine()
}
InfoBar.Message("Duplicated line")
+ h.Relocate()
return true
}
h.Cursor.DeleteSelection()
h.Cursor.ResetSelection()
InfoBar.Message("Deleted line")
+ h.Relocate()
return true
}
func (h *BufPane) MoveLinesUp() bool {
if h.Cursor.HasSelection() {
if h.Cursor.CurSelection[0].Y == 0 {
- InfoBar.Message("Can not move further up")
- return true
+ InfoBar.Message("Cannot move further up")
+ return false
}
start := h.Cursor.CurSelection[0].Y
end := h.Cursor.CurSelection[1].Y
+ sel := 1
if start > end {
end, start = start, end
+ sel = 0
+ }
+
+ compensate := false
+ if h.Cursor.CurSelection[sel].X != 0 {
+ end++
+ } else {
+ compensate = true
}
h.Buf.MoveLinesUp(
start,
end,
)
- h.Cursor.CurSelection[1].Y -= 1
+ if compensate {
+ h.Cursor.CurSelection[sel].Y -= 1
+ }
} else {
if h.Cursor.Loc.Y == 0 {
- InfoBar.Message("Can not move further up")
- return true
+ InfoBar.Message("Cannot move further up")
+ return false
}
h.Buf.MoveLinesUp(
h.Cursor.Loc.Y,
)
}
+ h.Relocate()
return true
}
func (h *BufPane) MoveLinesDown() bool {
if h.Cursor.HasSelection() {
if h.Cursor.CurSelection[1].Y >= h.Buf.LinesNum() {
- InfoBar.Message("Can not move further down")
- return true
+ InfoBar.Message("Cannot move further down")
+ return false
}
start := h.Cursor.CurSelection[0].Y
end := h.Cursor.CurSelection[1].Y
+ sel := 1
if start > end {
end, start = start, end
+ sel = 0
+ }
+
+ if h.Cursor.CurSelection[sel].X != 0 {
+ end++
}
h.Buf.MoveLinesDown(
)
} else {
if h.Cursor.Loc.Y >= h.Buf.LinesNum()-1 {
- InfoBar.Message("Can not move further down")
- return true
+ InfoBar.Message("Cannot move further down")
+ return false
}
h.Buf.MoveLinesDown(
h.Cursor.Loc.Y,
)
}
+ 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")
+ clip, err := clipboard.Read(clipboard.ClipboardReg)
+ if err != nil {
+ InfoBar.Error(err)
+ }
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")
+ clip, err := clipboard.Read(clipboard.PrimaryReg)
+ if err != nil {
+ InfoBar.Error(err)
+ }
h.paste(clip)
+ h.Relocate()
return true
}
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)
+ matchingBrace, left, found := h.Buf.FindMatchingBrace(bp, h.Cursor.Loc)
+ if found {
+ if left {
+ h.Cursor.GotoLoc(matchingBrace)
+ } else {
+ h.Cursor.GotoLoc(matchingBrace.Move(1, h.Buf))
+ }
} else {
- h.Cursor.GotoLoc(matchingBrace.Move(1, h.Buf))
+ return false
}
}
}
+ h.Relocate()
return true
}
// Put the cursor at the beginning
h.Cursor.X = 0
h.Cursor.Y = 0
+ h.Relocate()
return true
}
h.HandleCommand(resp)
}
})
- return false
+ return true
+}
+
+// OpenFile opens a new file in the buffer
+func (h *BufPane) JumpLine() bool {
+ InfoBar.Prompt("> ", "goto ", "Command", nil, func(resp string, canceled bool) {
+ if !canceled {
+ h.HandleCommand(resp)
+ }
+ })
+ return true
}
// Start moves the viewport to the start of the buffer
v := h.GetView()
v.StartLine = 0
h.SetView(v)
- return false
+ return true
}
// End moves the viewport to the end of the buffer
v.StartLine = h.Buf.LinesNum() - v.Height
h.SetView(v)
}
- return false
+ return true
}
// PageUp scrolls the view up a page
v.StartLine = 0
}
h.SetView(v)
- return false
+ return true
}
// PageDown scrolls the view down a page
} else if h.Buf.LinesNum() >= v.Height {
v.StartLine = h.Buf.LinesNum() - v.Height
}
- return false
+ return true
}
// SelectPageUp selects up one page
}
h.Cursor.UpN(h.GetView().Height)
h.Cursor.SelectTo(h.Cursor.Loc)
+ h.Relocate()
return true
}
}
h.Cursor.DownN(h.GetView().Height)
h.Cursor.SelectTo(h.Cursor.Loc)
+ h.Relocate()
return true
}
h.Cursor.StoreVisualX()
}
h.Cursor.UpN(h.GetView().Height)
+ h.Relocate()
return true
}
h.Cursor.StoreVisualX()
}
h.Cursor.DownN(h.GetView().Height)
+ h.Relocate()
return true
}
v.StartLine = 0
}
h.SetView(v)
- return false
+ return true
}
// HalfPageDown scrolls the view down half a page
}
}
h.SetView(v)
- return false
+ return true
+}
+
+// ToggleDiffGutter turns the diff gutter off and on
+func (h *BufPane) ToggleDiffGutter() bool {
+ if !h.Buf.Settings["diffgutter"].(bool) {
+ h.Buf.Settings["diffgutter"] = true
+ h.Buf.UpdateDiff(func(synchronous bool) {
+ screen.Redraw()
+ })
+ InfoBar.Message("Enabled diff gutter")
+ } else {
+ h.Buf.Settings["diffgutter"] = false
+ InfoBar.Message("Disabled diff gutter")
+ }
+ return true
}
// ToggleRuler turns line numbers off and on
h.Buf.Settings["ruler"] = false
InfoBar.Message("Disabled ruler")
}
- return false
+ return true
}
// ClearStatus clears the messenger bar
func (h *BufPane) ClearStatus() bool {
InfoBar.Message("")
- return false
+ return true
}
// ToggleHelp toggles the help screen
} else {
h.openHelp("help")
}
- return false
+ 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 false
+ return true
}
// ShellMode opens a terminal to run a shell command
}
})
- return false
+ return true
}
// CommandMode lets the user enter a command
h.HandleCommand(resp)
}
})
- return false
+ return true
}
// ToggleOverwriteMode lets the user toggle the text overwrite mode
func (h *BufPane) ToggleOverwriteMode() bool {
h.isOverwriteMode = !h.isOverwriteMode
- return false
+ return true
}
// Escape leaves current mode
func (h *BufPane) Escape() bool {
- return false
+ return true
+}
+
+// Deselect deselects on the current cursor
+func (h *BufPane) Deselect() bool {
+ h.Cursor.Deselect(true)
+ return true
+}
+
+// ClearInfo clears the infobar
+func (h *BufPane) ClearInfo() bool {
+ InfoBar.Message("")
+ return true
}
// Quit this will close the current tab or view that is open
if h.Buf.Modified() {
if config.GlobalSettings["autosave"].(float64) > 0 {
// autosave on means we automatically save when quitting
- h.Save()
- quit()
+ h.SaveCB("Quit", func() {
+ 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()
+ h.SaveCB("Quit", func() {
+ quit()
+ })
}
})
}
} else {
quit()
}
- return false
+ return true
}
// QuitAll quits the whole editor; all splits and tabs
quit()
}
- return false
+ return true
}
// AddTab adds a new tab with an empty buffer
Tabs.AddTab(tp)
Tabs.SetActive(len(Tabs.List) - 1)
- return false
+ 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))
+ tabsLen := len(Tabs.List)
+ a := Tabs.Active() + tabsLen
+ Tabs.SetActive((a - 1) % tabsLen)
- return false
+ 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 false
+ Tabs.SetActive((a + 1) % len(Tabs.List))
+
+ return true
}
// VSplitAction opens an empty vertical split
func (h *BufPane) VSplitAction() bool {
h.VSplitBuf(buffer.NewBufferFromString("", "", buffer.BTDefault))
- return false
+ return true
}
// HSplitAction opens an empty horizontal split
func (h *BufPane) HSplitAction() bool {
h.HSplitBuf(buffer.NewBufferFromString("", "", buffer.BTDefault))
- return false
+ 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()
+ tab := h.tab
+ n := tab.GetNode(h.splitID)
+ ok := n.Unsplit()
+ if ok {
+ tab.RemovePane(tab.GetPane(h.splitID))
+ tab.Resize()
+ tab.SetActive(len(tab.Panes) - 1)
- MainTab().RemovePane(MainTab().GetPane(h.splitID))
- MainTab().Resize()
- MainTab().SetActive(len(MainTab().Panes) - 1)
+ return true
+ }
return false
}
// NextSplit changes the view to the next split
func (h *BufPane) NextSplit() bool {
- a := MainTab().active
- if a < len(MainTab().Panes)-1 {
+ a := h.tab.active
+ if a < len(h.tab.Panes)-1 {
a++
} else {
a = 0
}
- MainTab().SetActive(a)
+ h.tab.SetActive(a)
- return false
+ return true
}
// PreviousSplit changes the view to the previous split
func (h *BufPane) PreviousSplit() bool {
- a := MainTab().active
+ a := h.tab.active
if a > 0 {
a--
} else {
- a = len(MainTab().Panes) - 1
+ a = len(h.tab.Panes) - 1
}
- MainTab().SetActive(a)
+ h.tab.SetActive(a)
- return false
+ return true
}
var curmacro []interface{}
} else {
InfoBar.Message("Stopped recording")
}
+ h.Relocate()
return true
}
switch t := action.(type) {
case rune:
h.DoRuneInsert(t)
- case Event:
- h.DoKeyEvent(t)
+ case func(*BufPane) bool:
+ t(h)
}
}
+ h.Relocate()
return true
}
if !spawner.HasSelection() {
spawner.SelectWord()
h.multiWord = true
+ h.Relocate()
return true
}
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
+}
+
+// SpawnMultiCursorDown 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
}
return false
}
InfoBar.Message("Added cursors from selection")
- return false
+ 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.GetMouseLoc(buffer.Loc{X: mx, Y: my})
+ mouseLoc := h.LocFromVisual(buffer.Loc{X: mx, Y: my})
c := buffer.NewCursor(b, mouseLoc)
b.AddCursor(c)
b.MergeCursors()
- return false
+ return true
}
// SkipMultiCursor moves the current multiple cursor to the next available position
} else {
InfoBar.Message("No matches found")
}
+ h.Relocate()
return true
}
} else {
h.multiWord = false
}
+ h.Relocate()
return true
}
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 false
+ return true
}