mirror of
https://github.com/zyedidia/micro.git
synced 2026-03-10 14:42:47 +09:00
1249 lines
30 KiB
Go
1249 lines
30 KiB
Go
package action
|
|
|
|
import (
|
|
"os"
|
|
"strings"
|
|
"time"
|
|
"unicode/utf8"
|
|
|
|
"github.com/zyedidia/clipboard"
|
|
"github.com/zyedidia/micro/cmd/micro/buffer"
|
|
"github.com/zyedidia/micro/cmd/micro/config"
|
|
"github.com/zyedidia/micro/cmd/micro/screen"
|
|
"github.com/zyedidia/micro/cmd/micro/shell"
|
|
"github.com/zyedidia/micro/cmd/micro/util"
|
|
"github.com/zyedidia/tcell"
|
|
)
|
|
|
|
// ScrollUp is not an action
|
|
func (h *BufHandler) ScrollUp(n int) {
|
|
v := h.GetView()
|
|
if v.StartLine >= n {
|
|
v.StartLine -= n
|
|
h.SetView(v)
|
|
}
|
|
}
|
|
|
|
// ScrollDown is not an action
|
|
func (h *BufHandler) ScrollDown(n int) {
|
|
v := h.GetView()
|
|
if v.StartLine <= h.Buf.LinesNum()-1-n {
|
|
v.StartLine += n
|
|
h.SetView(v)
|
|
}
|
|
}
|
|
|
|
// MousePress is the event that should happen when a normal click happens
|
|
// This is almost always bound to left click
|
|
func (h *BufHandler) MousePress(e *tcell.EventMouse) bool {
|
|
b := h.Buf
|
|
mx, my := e.Position()
|
|
mouseLoc := h.GetMouseLoc(buffer.Loc{mx, my})
|
|
h.Cursor.Loc = mouseLoc
|
|
if h.mouseReleased {
|
|
if b.NumCursors() > 1 {
|
|
b.ClearCursors()
|
|
h.Relocate()
|
|
}
|
|
if time.Since(h.lastClickTime)/time.Millisecond < config.DoubleClickThreshold && (mouseLoc.X == h.lastLoc.X && mouseLoc.Y == h.lastLoc.Y) {
|
|
if h.doubleClick {
|
|
// Triple click
|
|
h.lastClickTime = time.Now()
|
|
|
|
h.tripleClick = true
|
|
h.doubleClick = false
|
|
|
|
h.Cursor.SelectLine()
|
|
h.Cursor.CopySelection("primary")
|
|
} else {
|
|
// Double click
|
|
h.lastClickTime = time.Now()
|
|
|
|
h.doubleClick = true
|
|
h.tripleClick = false
|
|
|
|
h.Cursor.SelectWord()
|
|
h.Cursor.CopySelection("primary")
|
|
}
|
|
} else {
|
|
h.doubleClick = false
|
|
h.tripleClick = false
|
|
h.lastClickTime = time.Now()
|
|
|
|
h.Cursor.OrigSelection[0] = h.Cursor.Loc
|
|
h.Cursor.CurSelection[0] = h.Cursor.Loc
|
|
h.Cursor.CurSelection[1] = h.Cursor.Loc
|
|
}
|
|
h.mouseReleased = false
|
|
} else if !h.mouseReleased {
|
|
if h.tripleClick {
|
|
h.Cursor.AddLineToSelection()
|
|
} else if h.doubleClick {
|
|
h.Cursor.AddWordToSelection()
|
|
} else {
|
|
h.Cursor.SetSelectionEnd(h.Cursor.Loc)
|
|
h.Cursor.CopySelection("primary")
|
|
}
|
|
}
|
|
|
|
h.lastLoc = mouseLoc
|
|
return false
|
|
}
|
|
|
|
// ScrollUpAction scrolls the view up
|
|
func (h *BufHandler) ScrollUpAction() bool {
|
|
h.ScrollUp(util.IntOpt(h.Buf.Settings["scrollspeed"]))
|
|
return false
|
|
}
|
|
|
|
// ScrollDownAction scrolls the view up
|
|
func (h *BufHandler) ScrollDownAction() bool {
|
|
h.ScrollDown(util.IntOpt(h.Buf.Settings["scrollspeed"]))
|
|
return false
|
|
}
|
|
|
|
// Center centers the view on the cursor
|
|
func (h *BufHandler) Center() bool {
|
|
v := h.GetView()
|
|
v.StartLine = h.Cursor.Y - v.Height/2
|
|
if v.StartLine+v.Height > h.Buf.LinesNum() {
|
|
v.StartLine = h.Buf.LinesNum() - v.Height
|
|
}
|
|
if v.StartLine < 0 {
|
|
v.StartLine = 0
|
|
}
|
|
h.SetView(v)
|
|
return true
|
|
}
|
|
|
|
// CursorUp moves the cursor up
|
|
func (h *BufHandler) CursorUp() bool {
|
|
h.Cursor.Deselect(true)
|
|
h.Cursor.Up()
|
|
return true
|
|
}
|
|
|
|
// CursorDown moves the cursor down
|
|
func (h *BufHandler) CursorDown() bool {
|
|
h.Cursor.Deselect(true)
|
|
h.Cursor.Down()
|
|
return true
|
|
}
|
|
|
|
// CursorLeft moves the cursor left
|
|
func (h *BufHandler) CursorLeft() bool {
|
|
h.Cursor.Deselect(true)
|
|
h.Cursor.Left()
|
|
return true
|
|
}
|
|
|
|
// CursorRight moves the cursor right
|
|
func (h *BufHandler) CursorRight() bool {
|
|
h.Cursor.Deselect(false)
|
|
h.Cursor.Right()
|
|
return true
|
|
}
|
|
|
|
// WordRight moves the cursor one word to the right
|
|
func (h *BufHandler) WordRight() bool {
|
|
h.Cursor.Deselect(false)
|
|
h.Cursor.WordRight()
|
|
return true
|
|
}
|
|
|
|
// WordLeft moves the cursor one word to the left
|
|
func (h *BufHandler) WordLeft() bool {
|
|
h.Cursor.Deselect(true)
|
|
h.Cursor.WordLeft()
|
|
return true
|
|
}
|
|
|
|
// SelectUp selects up one line
|
|
func (h *BufHandler) SelectUp() bool {
|
|
if !h.Cursor.HasSelection() {
|
|
h.Cursor.OrigSelection[0] = h.Cursor.Loc
|
|
}
|
|
h.Cursor.Up()
|
|
h.Cursor.SelectTo(h.Cursor.Loc)
|
|
return true
|
|
}
|
|
|
|
// SelectDown selects down one line
|
|
func (h *BufHandler) SelectDown() bool {
|
|
if !h.Cursor.HasSelection() {
|
|
h.Cursor.OrigSelection[0] = h.Cursor.Loc
|
|
}
|
|
h.Cursor.Down()
|
|
h.Cursor.SelectTo(h.Cursor.Loc)
|
|
return true
|
|
}
|
|
|
|
// SelectLeft selects the character to the left of the cursor
|
|
func (h *BufHandler) SelectLeft() bool {
|
|
loc := h.Cursor.Loc
|
|
count := h.Buf.End()
|
|
if loc.GreaterThan(count) {
|
|
loc = count
|
|
}
|
|
if !h.Cursor.HasSelection() {
|
|
h.Cursor.OrigSelection[0] = loc
|
|
}
|
|
h.Cursor.Left()
|
|
h.Cursor.SelectTo(h.Cursor.Loc)
|
|
return true
|
|
}
|
|
|
|
// SelectRight selects the character to the right of the cursor
|
|
func (h *BufHandler) SelectRight() bool {
|
|
loc := h.Cursor.Loc
|
|
count := h.Buf.End()
|
|
if loc.GreaterThan(count) {
|
|
loc = count
|
|
}
|
|
if !h.Cursor.HasSelection() {
|
|
h.Cursor.OrigSelection[0] = loc
|
|
}
|
|
h.Cursor.Right()
|
|
h.Cursor.SelectTo(h.Cursor.Loc)
|
|
return true
|
|
}
|
|
|
|
// SelectWordRight selects the word to the right of the cursor
|
|
func (h *BufHandler) SelectWordRight() bool {
|
|
if !h.Cursor.HasSelection() {
|
|
h.Cursor.OrigSelection[0] = h.Cursor.Loc
|
|
}
|
|
h.Cursor.WordRight()
|
|
h.Cursor.SelectTo(h.Cursor.Loc)
|
|
return true
|
|
}
|
|
|
|
// SelectWordLeft selects the word to the left of the cursor
|
|
func (h *BufHandler) SelectWordLeft() bool {
|
|
if !h.Cursor.HasSelection() {
|
|
h.Cursor.OrigSelection[0] = h.Cursor.Loc
|
|
}
|
|
h.Cursor.WordLeft()
|
|
h.Cursor.SelectTo(h.Cursor.Loc)
|
|
return true
|
|
}
|
|
|
|
// StartOfLine moves the cursor to the start of the line
|
|
func (h *BufHandler) StartOfLine() bool {
|
|
h.Cursor.Deselect(true)
|
|
if h.Cursor.X != 0 {
|
|
h.Cursor.Start()
|
|
} else {
|
|
h.Cursor.StartOfText()
|
|
}
|
|
return true
|
|
}
|
|
|
|
// EndOfLine moves the cursor to the end of the line
|
|
func (h *BufHandler) EndOfLine() bool {
|
|
h.Cursor.Deselect(true)
|
|
h.Cursor.End()
|
|
return true
|
|
}
|
|
|
|
// SelectLine selects the entire current line
|
|
func (h *BufHandler) SelectLine() bool {
|
|
h.Cursor.SelectLine()
|
|
return true
|
|
}
|
|
|
|
// SelectToStartOfLine selects to the start of the current line
|
|
func (h *BufHandler) SelectToStartOfLine() bool {
|
|
if !h.Cursor.HasSelection() {
|
|
h.Cursor.OrigSelection[0] = h.Cursor.Loc
|
|
}
|
|
h.Cursor.Start()
|
|
h.Cursor.SelectTo(h.Cursor.Loc)
|
|
return true
|
|
}
|
|
|
|
// SelectToEndOfLine selects to the end of the current line
|
|
func (h *BufHandler) SelectToEndOfLine() bool {
|
|
if !h.Cursor.HasSelection() {
|
|
h.Cursor.OrigSelection[0] = h.Cursor.Loc
|
|
}
|
|
h.Cursor.End()
|
|
h.Cursor.SelectTo(h.Cursor.Loc)
|
|
return true
|
|
}
|
|
|
|
// ParagraphPrevious moves the cursor to the previous empty line, or beginning of the buffer if there's none
|
|
func (h *BufHandler) ParagraphPrevious() bool {
|
|
var line int
|
|
for line = h.Cursor.Y; line > 0; line-- {
|
|
if len(h.Buf.LineBytes(line)) == 0 && line != h.Cursor.Y {
|
|
h.Cursor.X = 0
|
|
h.Cursor.Y = line
|
|
break
|
|
}
|
|
}
|
|
// If no empty line found. move cursor to end of buffer
|
|
if line == 0 {
|
|
h.Cursor.Loc = h.Buf.Start()
|
|
}
|
|
return true
|
|
}
|
|
|
|
// ParagraphNext moves the cursor to the next empty line, or end of the buffer if there's none
|
|
func (h *BufHandler) ParagraphNext() bool {
|
|
var line int
|
|
for line = h.Cursor.Y; line < h.Buf.LinesNum(); line++ {
|
|
if len(h.Buf.LineBytes(line)) == 0 && line != h.Cursor.Y {
|
|
h.Cursor.X = 0
|
|
h.Cursor.Y = line
|
|
break
|
|
}
|
|
}
|
|
// If no empty line found. move cursor to end of buffer
|
|
if line == h.Buf.LinesNum() {
|
|
h.Cursor.Loc = h.Buf.End()
|
|
}
|
|
return true
|
|
}
|
|
|
|
// Retab changes all tabs to spaces or all spaces to tabs depending
|
|
// on the user's settings
|
|
func (h *BufHandler) Retab() bool {
|
|
// b := h.Buf
|
|
// toSpaces := b.Settings["tabstospaces"].(bool)
|
|
// tabsize := util.IntOpt(b.Settings["tabsize"])
|
|
// dirty := false
|
|
//
|
|
// for i := 0; i < b.LinesNum(); i++ {
|
|
// l := b.LineBytes(i)
|
|
//
|
|
// ws := util.GetLeadingWhitespace(l)
|
|
// if len(ws) != 0 {
|
|
// if toSpaces {
|
|
// ws = bytes.Replace(ws, []byte("\t"), []byte(util.Spaces(tabsize)), -1)
|
|
// } else {
|
|
// ws = bytes.Replace(ws, []byte(util.Spaces(tabsize)), []byte("\t"), -1)
|
|
// }
|
|
// }
|
|
//
|
|
// l = bytes.TrimLeft(l, " \t")
|
|
// b.lines[i].data = append(ws, l...)
|
|
// dirty = true
|
|
// }
|
|
//
|
|
// b.IsModified = dirty
|
|
return true
|
|
}
|
|
|
|
// CursorStart moves the cursor to the start of the buffer
|
|
func (h *BufHandler) CursorStart() bool {
|
|
h.Cursor.Deselect(true)
|
|
h.Cursor.X = 0
|
|
h.Cursor.Y = 0
|
|
return true
|
|
}
|
|
|
|
// CursorEnd moves the cursor to the end of the buffer
|
|
func (h *BufHandler) CursorEnd() bool {
|
|
h.Cursor.Deselect(true)
|
|
h.Cursor.Loc = h.Buf.End()
|
|
h.Cursor.StoreVisualX()
|
|
return true
|
|
}
|
|
|
|
// SelectToStart selects the text from the cursor to the start of the buffer
|
|
func (h *BufHandler) SelectToStart() bool {
|
|
if !h.Cursor.HasSelection() {
|
|
h.Cursor.OrigSelection[0] = h.Cursor.Loc
|
|
}
|
|
h.CursorStart()
|
|
h.Cursor.SelectTo(h.Buf.Start())
|
|
return true
|
|
}
|
|
|
|
// SelectToEnd selects the text from the cursor to the end of the buffer
|
|
func (h *BufHandler) SelectToEnd() bool {
|
|
if !h.Cursor.HasSelection() {
|
|
h.Cursor.OrigSelection[0] = h.Cursor.Loc
|
|
}
|
|
h.CursorEnd()
|
|
h.Cursor.SelectTo(h.Buf.End())
|
|
return true
|
|
}
|
|
|
|
// InsertNewline inserts a newline plus possible some whitespace if autoindent is on
|
|
func (h *BufHandler) InsertNewline() bool {
|
|
if h.Buf.Type == buffer.BTInfo {
|
|
InfoBar.DonePrompt(false)
|
|
return false
|
|
}
|
|
|
|
// Insert a newline
|
|
if h.Cursor.HasSelection() {
|
|
h.Cursor.DeleteSelection()
|
|
h.Cursor.ResetSelection()
|
|
}
|
|
|
|
ws := util.GetLeadingWhitespace(h.Buf.LineBytes(h.Cursor.Y))
|
|
cx := h.Cursor.X
|
|
h.Buf.Insert(h.Cursor.Loc, "\n")
|
|
// h.Cursor.Right()
|
|
|
|
if h.Buf.Settings["autoindent"].(bool) {
|
|
if cx < len(ws) {
|
|
ws = ws[0:cx]
|
|
}
|
|
h.Buf.Insert(h.Cursor.Loc, string(ws))
|
|
// for i := 0; i < len(ws); i++ {
|
|
// h.Cursor.Right()
|
|
// }
|
|
|
|
// 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.Cursor.LastVisualX = h.Cursor.GetVisualX()
|
|
return true
|
|
}
|
|
|
|
// Backspace deletes the previous character
|
|
func (h *BufHandler) Backspace() bool {
|
|
if h.Cursor.HasSelection() {
|
|
h.Cursor.DeleteSelection()
|
|
h.Cursor.ResetSelection()
|
|
} else if h.Cursor.Loc.GreaterThan(h.Buf.Start()) {
|
|
// We have to do something a bit hacky here because we want to
|
|
// delete the line by first moving left and then deleting backwards
|
|
// but the undo redo would place the cursor in the wrong place
|
|
// So instead we move left, save the position, move back, delete
|
|
// and restore the position
|
|
|
|
// 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 := 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 {
|
|
loc := h.Cursor.Loc
|
|
h.Buf.Remove(loc.Move(-tabSize, h.Buf), loc)
|
|
} else {
|
|
loc := h.Cursor.Loc
|
|
h.Buf.Remove(loc.Move(-1, h.Buf), loc)
|
|
}
|
|
}
|
|
h.Cursor.LastVisualX = h.Cursor.GetVisualX()
|
|
return true
|
|
}
|
|
|
|
// DeleteWordRight deletes the word to the right of the cursor
|
|
func (h *BufHandler) DeleteWordRight() bool {
|
|
h.SelectWordRight()
|
|
if h.Cursor.HasSelection() {
|
|
h.Cursor.DeleteSelection()
|
|
h.Cursor.ResetSelection()
|
|
}
|
|
return true
|
|
}
|
|
|
|
// DeleteWordLeft deletes the word to the left of the cursor
|
|
func (h *BufHandler) DeleteWordLeft() bool {
|
|
h.SelectWordLeft()
|
|
if h.Cursor.HasSelection() {
|
|
h.Cursor.DeleteSelection()
|
|
h.Cursor.ResetSelection()
|
|
}
|
|
return true
|
|
}
|
|
|
|
// Delete deletes the next character
|
|
func (h *BufHandler) Delete() bool {
|
|
if h.Cursor.HasSelection() {
|
|
h.Cursor.DeleteSelection()
|
|
h.Cursor.ResetSelection()
|
|
} else {
|
|
loc := h.Cursor.Loc
|
|
if loc.LessThan(h.Buf.End()) {
|
|
h.Buf.Remove(loc, loc.Move(1, h.Buf))
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
// IndentSelection indents the current selection
|
|
func (h *BufHandler) IndentSelection() bool {
|
|
if h.Cursor.HasSelection() {
|
|
start := h.Cursor.CurSelection[0]
|
|
end := h.Cursor.CurSelection[1]
|
|
if end.Y < start.Y {
|
|
start, end = end, start
|
|
h.Cursor.SetSelectionStart(start)
|
|
h.Cursor.SetSelectionEnd(end)
|
|
}
|
|
|
|
startY := start.Y
|
|
endY := end.Move(-1, h.Buf).Y
|
|
endX := end.Move(-1, h.Buf).X
|
|
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})
|
|
}
|
|
}
|
|
h.Cursor.Relocate()
|
|
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// OutdentLine moves the current line back one indentation
|
|
func (h *BufHandler) OutdentLine() bool {
|
|
if h.Cursor.HasSelection() {
|
|
return false
|
|
}
|
|
|
|
for x := 0; x < len(h.Buf.IndentString(util.IntOpt(h.Buf.Settings["tabsize"]))); x++ {
|
|
if len(util.GetLeadingWhitespace(h.Buf.LineBytes(h.Cursor.Y))) == 0 {
|
|
break
|
|
}
|
|
h.Buf.Remove(buffer.Loc{X: 0, Y: h.Cursor.Y}, buffer.Loc{X: 1, Y: h.Cursor.Y})
|
|
}
|
|
h.Cursor.Relocate()
|
|
return true
|
|
}
|
|
|
|
// OutdentSelection takes the current selection and moves it back one indent level
|
|
func (h *BufHandler) OutdentSelection() bool {
|
|
if h.Cursor.HasSelection() {
|
|
start := h.Cursor.CurSelection[0]
|
|
end := h.Cursor.CurSelection[1]
|
|
if end.Y < start.Y {
|
|
start, end = end, start
|
|
h.Cursor.SetSelectionStart(start)
|
|
h.Cursor.SetSelectionEnd(end)
|
|
}
|
|
|
|
startY := start.Y
|
|
endY := end.Move(-1, h.Buf).Y
|
|
for y := startY; y <= endY; y++ {
|
|
for x := 0; x < len(h.Buf.IndentString(util.IntOpt(h.Buf.Settings["tabsize"]))); x++ {
|
|
if len(util.GetLeadingWhitespace(h.Buf.LineBytes(y))) == 0 {
|
|
break
|
|
}
|
|
h.Buf.Remove(buffer.Loc{X: 0, Y: y}, buffer.Loc{X: 1, Y: y})
|
|
}
|
|
}
|
|
h.Cursor.Relocate()
|
|
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// InsertTab inserts a tab or spaces
|
|
func (h *BufHandler) InsertTab() bool {
|
|
indent := h.Buf.IndentString(util.IntOpt(h.Buf.Settings["tabsize"]))
|
|
tabBytes := len(indent)
|
|
bytesUntilIndent := tabBytes - (h.Cursor.GetVisualX() % tabBytes)
|
|
h.Buf.Insert(h.Cursor.Loc, indent[:bytesUntilIndent])
|
|
return true
|
|
}
|
|
|
|
// SaveAll saves all open buffers
|
|
func (h *BufHandler) SaveAll() bool {
|
|
return false
|
|
}
|
|
|
|
// Save the buffer to disk
|
|
func (h *BufHandler) Save() bool {
|
|
h.Buf.Save()
|
|
return false
|
|
}
|
|
|
|
// SaveAs saves the buffer to disk with the given name
|
|
func (h *BufHandler) SaveAs() bool {
|
|
return false
|
|
}
|
|
|
|
// Find opens a prompt and searches forward for the input
|
|
func (h *BufHandler) Find() bool {
|
|
InfoBar.Prompt("Find: ", "", "Find", func(resp string) {
|
|
// Event callback
|
|
match, found, _ := h.Buf.FindNext(resp, h.Cursor.Loc, true)
|
|
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]
|
|
} else {
|
|
h.Cursor.ResetSelection()
|
|
}
|
|
}, func(resp string, canceled bool) {
|
|
// Finished callback
|
|
if !canceled {
|
|
match, found, err := h.Buf.FindNext(resp, h.Cursor.Loc, true)
|
|
if err != nil {
|
|
InfoBar.Error(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.Loc = h.Cursor.CurSelection[1]
|
|
h.lastSearch = resp
|
|
} else {
|
|
h.Cursor.ResetSelection()
|
|
InfoBar.Message("No matches found")
|
|
}
|
|
} else {
|
|
h.Cursor.ResetSelection()
|
|
}
|
|
})
|
|
|
|
return true
|
|
}
|
|
|
|
// FindNext searches forwards for the last used search term
|
|
func (h *BufHandler) FindNext() bool {
|
|
// If the cursor is at the start of a selection and we search we want
|
|
// to search from the end of the selection in the case that
|
|
// the selection is a search result in which case we wouldn't move at
|
|
// at all which would be bad
|
|
searchLoc := h.Cursor.Loc
|
|
if h.Cursor.HasSelection() {
|
|
searchLoc = h.Cursor.CurSelection[1]
|
|
}
|
|
match, found, err := h.Buf.FindNext(h.lastSearch, searchLoc, true)
|
|
if err != nil {
|
|
InfoBar.Error(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.Loc = h.Cursor.CurSelection[1]
|
|
} else {
|
|
h.Cursor.ResetSelection()
|
|
}
|
|
return true
|
|
}
|
|
|
|
// FindPrevious searches backwards for the last used search term
|
|
func (h *BufHandler) FindPrevious() bool {
|
|
// If the cursor is at the end of a selection and we search we want
|
|
// to search from the beginning of the selection in the case that
|
|
// the selection is a search result in which case we wouldn't move at
|
|
// at all which would be bad
|
|
searchLoc := h.Cursor.Loc
|
|
if h.Cursor.HasSelection() {
|
|
searchLoc = h.Cursor.CurSelection[0]
|
|
}
|
|
match, found, err := h.Buf.FindNext(h.lastSearch, searchLoc, false)
|
|
if err != nil {
|
|
InfoBar.Error(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.Loc = h.Cursor.CurSelection[1]
|
|
} else {
|
|
h.Cursor.ResetSelection()
|
|
}
|
|
return true
|
|
}
|
|
|
|
// Undo undoes the last action
|
|
func (h *BufHandler) Undo() bool {
|
|
h.Buf.Undo()
|
|
InfoBar.Message("Undid action")
|
|
return true
|
|
}
|
|
|
|
// Redo redoes the last action
|
|
func (h *BufHandler) Redo() bool {
|
|
// TODO: clear cursors and message
|
|
h.Buf.Redo()
|
|
InfoBar.Message("Redid action")
|
|
return true
|
|
}
|
|
|
|
// Copy the selection to the system clipboard
|
|
func (h *BufHandler) Copy() bool {
|
|
if h.Cursor.HasSelection() {
|
|
h.Cursor.CopySelection("clipboard")
|
|
h.freshClip = true
|
|
InfoBar.Message("Copied selection")
|
|
}
|
|
return true
|
|
}
|
|
|
|
// CutLine cuts the current line to the clipboard
|
|
func (h *BufHandler) CutLine() bool {
|
|
h.Cursor.SelectLine()
|
|
if !h.Cursor.HasSelection() {
|
|
return false
|
|
}
|
|
if h.freshClip == true {
|
|
if h.Cursor.HasSelection() {
|
|
if clip, err := clipboard.ReadAll("clipboard"); err != nil {
|
|
// messenger.Error(err)
|
|
} else {
|
|
clipboard.WriteAll(clip+string(h.Cursor.GetSelection()), "clipboard")
|
|
}
|
|
}
|
|
} else if time.Since(h.lastCutTime)/time.Second > 10*time.Second || h.freshClip == false {
|
|
h.Copy()
|
|
}
|
|
h.freshClip = true
|
|
h.lastCutTime = time.Now()
|
|
h.Cursor.DeleteSelection()
|
|
h.Cursor.ResetSelection()
|
|
InfoBar.Message("Cut line")
|
|
return true
|
|
}
|
|
|
|
// Cut the selection to the system clipboard
|
|
func (h *BufHandler) Cut() bool {
|
|
if h.Cursor.HasSelection() {
|
|
h.Cursor.CopySelection("clipboard")
|
|
h.Cursor.DeleteSelection()
|
|
h.Cursor.ResetSelection()
|
|
h.freshClip = true
|
|
InfoBar.Message("Cut selection")
|
|
|
|
return true
|
|
} else {
|
|
return h.CutLine()
|
|
}
|
|
}
|
|
|
|
// DuplicateLine duplicates the current line or selection
|
|
func (h *BufHandler) DuplicateLine() bool {
|
|
if h.Cursor.HasSelection() {
|
|
h.Buf.Insert(h.Cursor.CurSelection[1], string(h.Cursor.GetSelection()))
|
|
} else {
|
|
h.Cursor.End()
|
|
h.Buf.Insert(h.Cursor.Loc, "\n"+string(h.Buf.LineBytes(h.Cursor.Y)))
|
|
// h.Cursor.Right()
|
|
}
|
|
|
|
InfoBar.Message("Duplicated line")
|
|
return true
|
|
}
|
|
|
|
// DeleteLine deletes the current line
|
|
func (h *BufHandler) DeleteLine() bool {
|
|
h.Cursor.SelectLine()
|
|
if !h.Cursor.HasSelection() {
|
|
return false
|
|
}
|
|
h.Cursor.DeleteSelection()
|
|
h.Cursor.ResetSelection()
|
|
InfoBar.Message("Deleted line")
|
|
return true
|
|
}
|
|
|
|
// MoveLinesUp moves up the current line or selected lines if any
|
|
func (h *BufHandler) MoveLinesUp() bool {
|
|
return true
|
|
}
|
|
|
|
// MoveLinesDown moves down the current line or selected lines if any
|
|
func (h *BufHandler) MoveLinesDown() bool {
|
|
return true
|
|
}
|
|
|
|
// Paste whatever is in the system clipboard into the buffer
|
|
// Delete and paste if the user has a selection
|
|
func (h *BufHandler) Paste() bool {
|
|
clip, _ := clipboard.ReadAll("clipboard")
|
|
h.paste(clip)
|
|
return true
|
|
}
|
|
|
|
// PastePrimary pastes from the primary clipboard (only use on linux)
|
|
func (h *BufHandler) PastePrimary() bool {
|
|
clip, _ := clipboard.ReadAll("primary")
|
|
h.paste(clip)
|
|
return true
|
|
}
|
|
|
|
func (h *BufHandler) paste(clip string) {
|
|
if h.Buf.Settings["smartpaste"].(bool) {
|
|
if h.Cursor.X > 0 && len(util.GetLeadingWhitespace([]byte(strings.TrimLeft(clip, "\r\n")))) == 0 {
|
|
leadingWS := util.GetLeadingWhitespace(h.Buf.LineBytes(h.Cursor.Y))
|
|
clip = strings.Replace(clip, "\n", "\n"+string(leadingWS), -1)
|
|
}
|
|
}
|
|
|
|
if h.Cursor.HasSelection() {
|
|
h.Cursor.DeleteSelection()
|
|
h.Cursor.ResetSelection()
|
|
}
|
|
|
|
h.Buf.Insert(h.Cursor.Loc, clip)
|
|
// h.Cursor.Loc = h.Cursor.Loc.Move(Count(clip), h.Buf)
|
|
h.freshClip = false
|
|
InfoBar.Message("Pasted clipboard")
|
|
}
|
|
|
|
// JumpToMatchingBrace moves the cursor to the matching brace if it is
|
|
// currently on a brace
|
|
func (h *BufHandler) JumpToMatchingBrace() bool {
|
|
return true
|
|
}
|
|
|
|
// SelectAll selects the entire buffer
|
|
func (h *BufHandler) SelectAll() bool {
|
|
h.Cursor.SetSelectionStart(h.Buf.Start())
|
|
h.Cursor.SetSelectionEnd(h.Buf.End())
|
|
// Put the cursor at the beginning
|
|
h.Cursor.X = 0
|
|
h.Cursor.Y = 0
|
|
return true
|
|
}
|
|
|
|
// OpenFile opens a new file in the buffer
|
|
func (h *BufHandler) OpenFile() bool {
|
|
InfoBar.Prompt("> ", "open ", "Open", nil, func(resp string, canceled bool) {
|
|
if !canceled {
|
|
h.HandleCommand(resp)
|
|
}
|
|
})
|
|
return false
|
|
}
|
|
|
|
// Start moves the viewport to the start of the buffer
|
|
func (h *BufHandler) Start() bool {
|
|
v := h.GetView()
|
|
v.StartLine = 0
|
|
h.SetView(v)
|
|
return false
|
|
}
|
|
|
|
// End moves the viewport to the end of the buffer
|
|
func (h *BufHandler) End() bool {
|
|
// TODO: softwrap problems?
|
|
v := h.GetView()
|
|
if v.Height > h.Buf.LinesNum() {
|
|
v.StartLine = 0
|
|
h.SetView(v)
|
|
} else {
|
|
h.StartLine = h.Buf.LinesNum() - v.Height
|
|
}
|
|
return false
|
|
}
|
|
|
|
// PageUp scrolls the view up a page
|
|
func (h *BufHandler) PageUp() bool {
|
|
v := h.GetView()
|
|
if v.StartLine > v.Height {
|
|
h.ScrollUp(v.Height)
|
|
} else {
|
|
v.StartLine = 0
|
|
}
|
|
h.SetView(v)
|
|
return false
|
|
}
|
|
|
|
// PageDown scrolls the view down a page
|
|
func (h *BufHandler) PageDown() bool {
|
|
v := h.GetView()
|
|
if h.Buf.LinesNum()-(v.StartLine+v.Height) > v.Height {
|
|
h.ScrollDown(v.Height)
|
|
} else if h.Buf.LinesNum() >= v.Height {
|
|
v.StartLine = h.Buf.LinesNum() - v.Height
|
|
}
|
|
return false
|
|
}
|
|
|
|
// SelectPageUp selects up one page
|
|
func (h *BufHandler) SelectPageUp() bool {
|
|
if !h.Cursor.HasSelection() {
|
|
h.Cursor.OrigSelection[0] = h.Cursor.Loc
|
|
}
|
|
h.Cursor.UpN(h.GetView().Height)
|
|
h.Cursor.SelectTo(h.Cursor.Loc)
|
|
return true
|
|
}
|
|
|
|
// SelectPageDown selects down one page
|
|
func (h *BufHandler) SelectPageDown() bool {
|
|
if !h.Cursor.HasSelection() {
|
|
h.Cursor.OrigSelection[0] = h.Cursor.Loc
|
|
}
|
|
h.Cursor.DownN(h.GetView().Height)
|
|
h.Cursor.SelectTo(h.Cursor.Loc)
|
|
return true
|
|
}
|
|
|
|
// CursorPageUp places the cursor a page up
|
|
func (h *BufHandler) CursorPageUp() bool {
|
|
h.Cursor.Deselect(true)
|
|
|
|
if h.Cursor.HasSelection() {
|
|
h.Cursor.Loc = h.Cursor.CurSelection[0]
|
|
h.Cursor.ResetSelection()
|
|
h.Cursor.StoreVisualX()
|
|
}
|
|
h.Cursor.UpN(h.GetView().Height)
|
|
return true
|
|
}
|
|
|
|
// CursorPageDown places the cursor a page up
|
|
func (h *BufHandler) CursorPageDown() bool {
|
|
h.Cursor.Deselect(false)
|
|
|
|
if h.Cursor.HasSelection() {
|
|
h.Cursor.Loc = h.Cursor.CurSelection[1]
|
|
h.Cursor.ResetSelection()
|
|
h.Cursor.StoreVisualX()
|
|
}
|
|
h.Cursor.DownN(h.GetView().Height)
|
|
return true
|
|
}
|
|
|
|
// HalfPageUp scrolls the view up half a page
|
|
func (h *BufHandler) HalfPageUp() bool {
|
|
v := h.GetView()
|
|
if v.StartLine > v.Height/2 {
|
|
h.ScrollUp(v.Height / 2)
|
|
} else {
|
|
v.StartLine = 0
|
|
}
|
|
h.SetView(v)
|
|
return false
|
|
}
|
|
|
|
// HalfPageDown scrolls the view down half a page
|
|
func (h *BufHandler) HalfPageDown() bool {
|
|
v := h.GetView()
|
|
if h.Buf.LinesNum()-(v.StartLine+v.Height) > v.Height/2 {
|
|
h.ScrollDown(v.Height / 2)
|
|
} else {
|
|
if h.Buf.LinesNum() >= v.Height {
|
|
v.StartLine = h.Buf.LinesNum() - v.Height
|
|
}
|
|
}
|
|
h.SetView(v)
|
|
return false
|
|
}
|
|
|
|
// ToggleRuler turns line numbers off and on
|
|
func (h *BufHandler) ToggleRuler() bool {
|
|
if !h.Buf.Settings["ruler"].(bool) {
|
|
h.Buf.Settings["ruler"] = true
|
|
InfoBar.Message("Enabled ruler")
|
|
} else {
|
|
h.Buf.Settings["ruler"] = false
|
|
InfoBar.Message("Disabled ruler")
|
|
}
|
|
return false
|
|
}
|
|
|
|
// JumpLine jumps to a line and moves the view accordingly.
|
|
func (h *BufHandler) JumpLine() bool {
|
|
return false
|
|
}
|
|
|
|
// ClearStatus clears the messenger bar
|
|
func (h *BufHandler) ClearStatus() bool {
|
|
InfoBar.Message("")
|
|
return false
|
|
}
|
|
|
|
// ToggleHelp toggles the help screen
|
|
func (h *BufHandler) ToggleHelp() bool {
|
|
return true
|
|
}
|
|
|
|
// ToggleKeyMenu toggles the keymenu option and resizes all tabs
|
|
func (h *BufHandler) ToggleKeyMenu() bool {
|
|
return true
|
|
}
|
|
|
|
// ShellMode opens a terminal to run a shell command
|
|
func (h *BufHandler) ShellMode() bool {
|
|
InfoBar.Prompt("$ ", "", "Shell", nil, func(resp string, canceled bool) {
|
|
if !canceled {
|
|
// The true here is for openTerm to make the command interactive
|
|
shell.RunInteractiveShell(resp, true, false)
|
|
}
|
|
})
|
|
|
|
return false
|
|
}
|
|
|
|
// CommandMode lets the user enter a command
|
|
func (h *BufHandler) CommandMode() bool {
|
|
InfoBar.Prompt("> ", "", "Command", nil, func(resp string, canceled bool) {
|
|
if !canceled {
|
|
h.HandleCommand(resp)
|
|
}
|
|
})
|
|
return false
|
|
}
|
|
|
|
// ToggleOverwriteMode lets the user toggle the text overwrite mode
|
|
func (h *BufHandler) ToggleOverwriteMode() bool {
|
|
h.isOverwriteMode = !h.isOverwriteMode
|
|
return false
|
|
}
|
|
|
|
// Escape leaves current mode
|
|
func (h *BufHandler) Escape() bool {
|
|
return false
|
|
}
|
|
|
|
// Quit this will close the current tab or view that is open
|
|
func (h *BufHandler) Quit() bool {
|
|
quit := func() {
|
|
if len(MainTab().Panes) > 1 {
|
|
h.Unsplit()
|
|
} else if len(Tabs.List) > 1 {
|
|
Tabs.RemoveTab(h.splitID)
|
|
} else {
|
|
screen.Screen.Fini()
|
|
InfoBar.Close()
|
|
os.Exit(0)
|
|
}
|
|
}
|
|
if h.Buf.Modified() {
|
|
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()
|
|
}
|
|
})
|
|
} else {
|
|
quit()
|
|
}
|
|
return false
|
|
}
|
|
|
|
// QuitAll quits the whole editor; all splits and tabs
|
|
func (h *BufHandler) QuitAll() bool {
|
|
return false
|
|
}
|
|
|
|
// AddTab adds a new tab with an empty buffer
|
|
func (h *BufHandler) AddTab() bool {
|
|
width, height := screen.Screen.Size()
|
|
b := buffer.NewBufferFromString("", "", buffer.BTDefault)
|
|
tp := NewTabFromBuffer(0, 0, width, height-1, b)
|
|
Tabs.AddTab(tp)
|
|
Tabs.SetActive(len(Tabs.List) - 1)
|
|
|
|
return false
|
|
}
|
|
|
|
// PreviousTab switches to the previous tab in the tab list
|
|
func (h *BufHandler) PreviousTab() bool {
|
|
a := Tabs.Active()
|
|
Tabs.SetActive(util.Clamp(a-1, 0, len(Tabs.List)-1))
|
|
|
|
return false
|
|
}
|
|
|
|
// NextTab switches to the next tab in the tab list
|
|
func (h *BufHandler) NextTab() bool {
|
|
a := Tabs.Active()
|
|
Tabs.SetActive(util.Clamp(a+1, 0, len(Tabs.List)-1))
|
|
return false
|
|
}
|
|
|
|
// VSplitBinding opens an empty vertical split
|
|
func (h *BufHandler) VSplitBinding() bool {
|
|
h.vsplit(buffer.NewBufferFromString("", "", buffer.BTDefault))
|
|
|
|
return false
|
|
}
|
|
|
|
// HSplitBinding opens an empty horizontal split
|
|
func (h *BufHandler) HSplitBinding() bool {
|
|
h.hsplit(buffer.NewBufferFromString("", "", buffer.BTDefault))
|
|
|
|
return false
|
|
}
|
|
|
|
// Unsplit closes all splits in the current tab except the active one
|
|
func (h *BufHandler) Unsplit() bool {
|
|
n := MainTab().GetNode(h.splitID)
|
|
n.Unsplit()
|
|
|
|
MainTab().RemovePane(MainTab().GetPane(h.splitID))
|
|
MainTab().Resize()
|
|
MainTab().SetActive(len(MainTab().Panes) - 1)
|
|
return false
|
|
}
|
|
|
|
// NextSplit changes the view to the next split
|
|
func (h *BufHandler) NextSplit() bool {
|
|
a := MainTab().active
|
|
a = util.Clamp(a+1, 0, len(MainTab().Panes))
|
|
MainTab().SetActive(a)
|
|
|
|
return false
|
|
}
|
|
|
|
// PreviousSplit changes the view to the previous split
|
|
func (h *BufHandler) PreviousSplit() bool {
|
|
a := MainTab().active
|
|
a = util.Clamp(a-1, 0, len(MainTab().Panes))
|
|
MainTab().SetActive(a)
|
|
|
|
return false
|
|
}
|
|
|
|
var curMacro []interface{}
|
|
var recordingMacro bool
|
|
|
|
// ToggleMacro toggles recording of a macro
|
|
func (h *BufHandler) ToggleMacro() bool {
|
|
return true
|
|
}
|
|
|
|
// PlayMacro plays back the most recently recorded macro
|
|
func (h *BufHandler) PlayMacro() bool {
|
|
return true
|
|
}
|
|
|
|
// SpawnMultiCursor creates a new multiple cursor at the next occurrence of the current selection or current word
|
|
func (h *BufHandler) SpawnMultiCursor() bool {
|
|
spawner := h.Buf.GetCursor(h.Buf.NumCursors() - 1)
|
|
if !spawner.HasSelection() {
|
|
spawner.SelectWord()
|
|
h.multiWord = true
|
|
return true
|
|
}
|
|
|
|
sel := spawner.GetSelection()
|
|
searchStart := spawner.CurSelection[1]
|
|
|
|
search := string(sel)
|
|
if h.multiWord {
|
|
search = "\\b" + search + "\\b"
|
|
}
|
|
match, found, err := h.Buf.FindNext(search, searchStart, true)
|
|
if err != nil {
|
|
InfoBar.Error(err)
|
|
}
|
|
if found {
|
|
c := buffer.NewCursor(h.Buf, buffer.Loc{})
|
|
c.SetSelectionStart(match[0])
|
|
c.SetSelectionEnd(match[1])
|
|
c.OrigSelection[0] = c.CurSelection[0]
|
|
c.OrigSelection[1] = c.CurSelection[1]
|
|
c.Loc = c.CurSelection[1]
|
|
|
|
h.Buf.AddCursor(c)
|
|
h.Buf.MergeCursors()
|
|
} else {
|
|
InfoBar.Message("No matches found")
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// SpawnMultiCursorSelect adds a cursor at the beginning of each line of a selection
|
|
func (h *BufHandler) SpawnMultiCursorSelect() bool {
|
|
// Avoid cases where multiple cursors already exist, that would create problems
|
|
if h.Buf.NumCursors() > 1 {
|
|
return false
|
|
}
|
|
|
|
var startLine int
|
|
var endLine int
|
|
|
|
a, b := h.Cursor.CurSelection[0].Y, h.Cursor.CurSelection[1].Y
|
|
if a > b {
|
|
startLine, endLine = b, a
|
|
} else {
|
|
startLine, endLine = a, b
|
|
}
|
|
|
|
if h.Cursor.HasSelection() {
|
|
h.Cursor.ResetSelection()
|
|
h.Cursor.GotoLoc(buffer.Loc{0, startLine})
|
|
|
|
for i := startLine; i <= endLine; i++ {
|
|
c := buffer.NewCursor(h.Buf, buffer.Loc{0, i})
|
|
c.StoreVisualX()
|
|
h.Buf.AddCursor(c)
|
|
}
|
|
h.Buf.MergeCursors()
|
|
} else {
|
|
return false
|
|
}
|
|
InfoBar.Message("Added cursors from selection")
|
|
return false
|
|
}
|
|
|
|
// MouseMultiCursor is a mouse action which puts a new cursor at the mouse position
|
|
func (h *BufHandler) MouseMultiCursor(e *tcell.EventMouse) bool {
|
|
b := h.Buf
|
|
mx, my := e.Position()
|
|
mouseLoc := h.GetMouseLoc(buffer.Loc{X: mx, Y: my})
|
|
c := buffer.NewCursor(b, mouseLoc)
|
|
b.AddCursor(c)
|
|
b.MergeCursors()
|
|
|
|
return false
|
|
}
|
|
|
|
// SkipMultiCursor moves the current multiple cursor to the next available position
|
|
func (h *BufHandler) SkipMultiCursor() bool {
|
|
lastC := h.Buf.GetCursor(h.Buf.NumCursors() - 1)
|
|
sel := lastC.GetSelection()
|
|
searchStart := lastC.CurSelection[1]
|
|
|
|
match, found, err := h.Buf.FindNext(string(sel), searchStart, true)
|
|
if err != nil {
|
|
InfoBar.Error(err)
|
|
}
|
|
if found {
|
|
lastC.SetSelectionStart(match[0])
|
|
lastC.SetSelectionEnd(match[1])
|
|
lastC.OrigSelection[0] = lastC.CurSelection[0]
|
|
lastC.OrigSelection[1] = lastC.CurSelection[1]
|
|
lastC.Loc = lastC.CurSelection[1]
|
|
|
|
h.Buf.MergeCursors()
|
|
h.Relocate()
|
|
} else {
|
|
InfoBar.Message("No matches found")
|
|
}
|
|
return false
|
|
}
|
|
|
|
// RemoveMultiCursor removes the latest multiple cursor
|
|
func (h *BufHandler) RemoveMultiCursor() bool {
|
|
if h.Buf.NumCursors() > 1 {
|
|
h.Buf.RemoveCursor(h.Buf.NumCursors() - 1)
|
|
h.Buf.UpdateCursors()
|
|
} else {
|
|
h.multiWord = false
|
|
}
|
|
return false
|
|
}
|
|
|
|
// RemoveAllMultiCursors removes all cursors except the base cursor
|
|
func (h *BufHandler) RemoveAllMultiCursors() bool {
|
|
h.Buf.ClearCursors()
|
|
h.multiWord = false
|
|
return true
|
|
}
|