468 lines
14 KiB
Go
468 lines
14 KiB
Go
package action
|
|
|
|
import (
|
|
"strings"
|
|
"time"
|
|
|
|
luar "layeh.com/gopher-luar"
|
|
|
|
"github.com/zyedidia/micro/internal/buffer"
|
|
"github.com/zyedidia/micro/internal/config"
|
|
"github.com/zyedidia/micro/internal/display"
|
|
ulua "github.com/zyedidia/micro/internal/lua"
|
|
"github.com/zyedidia/micro/internal/screen"
|
|
"github.com/zyedidia/tcell"
|
|
)
|
|
|
|
type BufKeyAction func(*BufPane) bool
|
|
type BufMouseAction func(*BufPane, *tcell.EventMouse) bool
|
|
|
|
var BufKeyBindings map[Event]BufKeyAction
|
|
var BufKeyStrings map[Event]string
|
|
var BufMouseBindings map[MouseEvent]BufMouseAction
|
|
|
|
func init() {
|
|
BufKeyBindings = make(map[Event]BufKeyAction)
|
|
BufKeyStrings = make(map[Event]string)
|
|
BufMouseBindings = make(map[MouseEvent]BufMouseAction)
|
|
}
|
|
|
|
// BufMapKey maps a key event to an action
|
|
func BufMapKey(k Event, action string) {
|
|
if strings.HasPrefix(action, "command:") {
|
|
action = strings.SplitN(action, ":", 2)[1]
|
|
BufKeyStrings[k] = action
|
|
BufKeyBindings[k] = CommandAction(action)
|
|
} else if strings.HasPrefix(action, "command-edit:") {
|
|
action = strings.SplitN(action, ":", 2)[1]
|
|
BufKeyStrings[k] = action
|
|
BufKeyBindings[k] = CommandEditAction(action)
|
|
} else if f, ok := BufKeyActions[action]; ok {
|
|
BufKeyStrings[k] = action
|
|
BufKeyBindings[k] = f
|
|
} else {
|
|
screen.TermMessage("Error:", action, "does not exist")
|
|
}
|
|
}
|
|
|
|
// BufMapMouse maps a mouse event to an action
|
|
func BufMapMouse(k MouseEvent, action string) {
|
|
if f, ok := BufMouseActions[action]; ok {
|
|
BufMouseBindings[k] = f
|
|
} else {
|
|
delete(BufMouseBindings, k)
|
|
BufMapKey(k, action)
|
|
}
|
|
}
|
|
|
|
// The BufPane connects the buffer and the window
|
|
// It provides a cursor (or multiple) and defines a set of actions
|
|
// that can be taken on the buffer
|
|
// The ActionHandler can access the window for necessary info about
|
|
// visual positions for mouse clicks and scrolling
|
|
type BufPane struct {
|
|
display.BWindow
|
|
|
|
Buf *buffer.Buffer
|
|
|
|
Cursor *buffer.Cursor // the active cursor
|
|
|
|
// Since tcell doesn't differentiate between a mouse release event
|
|
// and a mouse move event with no keys pressed, we need to keep
|
|
// track of whether or not the mouse was pressed (or not released) last event to determine
|
|
// mouse release events
|
|
mouseReleased bool
|
|
|
|
// We need to keep track of insert key press toggle
|
|
isOverwriteMode bool
|
|
// This stores when the last click was
|
|
// This is useful for detecting double and triple clicks
|
|
lastClickTime time.Time
|
|
lastLoc buffer.Loc
|
|
|
|
// lastCutTime stores when the last ctrl+k was issued.
|
|
// It is used for clearing the clipboard to replace it with fresh cut lines.
|
|
lastCutTime time.Time
|
|
|
|
// freshClip returns true if the clipboard has never been pasted.
|
|
freshClip bool
|
|
|
|
// Was the last mouse event actually a double click?
|
|
// Useful for detecting triple clicks -- if a double click is detected
|
|
// but the last mouse event was actually a double click, it's a triple click
|
|
doubleClick bool
|
|
// Same here, just to keep track for mouse move events
|
|
tripleClick bool
|
|
|
|
// Last search stores the last successful search for FindNext and FindPrev
|
|
lastSearch string
|
|
// Should the current multiple cursor selection search based on word or
|
|
// based on selection (false for selection, true for word)
|
|
multiWord bool
|
|
|
|
splitID uint64
|
|
|
|
// remember original location of a search in case the search is canceled
|
|
searchOrig buffer.Loc
|
|
}
|
|
|
|
func NewBufPane(buf *buffer.Buffer, win display.BWindow) *BufPane {
|
|
h := new(BufPane)
|
|
h.Buf = buf
|
|
h.BWindow = win
|
|
|
|
h.Cursor = h.Buf.GetActiveCursor()
|
|
h.mouseReleased = true
|
|
|
|
config.RunPluginFn("onBufPaneOpen", luar.New(ulua.L, h))
|
|
|
|
return h
|
|
}
|
|
|
|
func NewBufPaneFromBuf(buf *buffer.Buffer) *BufPane {
|
|
w := display.NewBufWindow(0, 0, 0, 0, buf)
|
|
return NewBufPane(buf, w)
|
|
}
|
|
|
|
func (h *BufPane) OpenBuffer(b *buffer.Buffer) {
|
|
h.Buf.Close()
|
|
h.Buf = b
|
|
h.BWindow.SetBuffer(b)
|
|
h.Cursor = b.GetActiveCursor()
|
|
h.Resize(h.GetView().Width, h.GetView().Height)
|
|
v := new(display.View)
|
|
h.SetView(v)
|
|
h.Relocate()
|
|
// Set mouseReleased to true because we assume the mouse is not being pressed when
|
|
// the editor is opened
|
|
h.mouseReleased = true
|
|
// Set isOverwriteMode to false, because we assume we are in the default mode when editor
|
|
// is opened
|
|
h.isOverwriteMode = false
|
|
h.lastClickTime = time.Time{}
|
|
}
|
|
|
|
func (h *BufPane) ID() uint64 {
|
|
return h.splitID
|
|
}
|
|
|
|
func (h *BufPane) SetID(i uint64) {
|
|
h.splitID = i
|
|
}
|
|
|
|
func (h *BufPane) Name() string {
|
|
return h.Buf.GetName()
|
|
}
|
|
|
|
// HandleEvent executes the tcell event properly
|
|
// TODO: multiple actions bound to one key
|
|
func (h *BufPane) HandleEvent(event tcell.Event) {
|
|
switch e := event.(type) {
|
|
case *tcell.EventRaw:
|
|
re := RawEvent{
|
|
esc: e.EscSeq(),
|
|
}
|
|
h.DoKeyEvent(re)
|
|
case *tcell.EventKey:
|
|
ke := KeyEvent{
|
|
code: e.Key(),
|
|
mod: e.Modifiers(),
|
|
r: e.Rune(),
|
|
}
|
|
|
|
done := h.DoKeyEvent(ke)
|
|
if !done && e.Key() == tcell.KeyRune {
|
|
h.DoRuneInsert(e.Rune())
|
|
}
|
|
case *tcell.EventMouse:
|
|
switch e.Buttons() {
|
|
case tcell.ButtonNone:
|
|
// Mouse event with no click
|
|
if !h.mouseReleased {
|
|
// Mouse was just released
|
|
|
|
mx, my := e.Position()
|
|
mouseLoc := h.GetMouseLoc(buffer.Loc{X: mx, Y: my})
|
|
|
|
// Relocating here isn't really necessary because the cursor will
|
|
// be in the right place from the last mouse event
|
|
// However, if we are running in a terminal that doesn't support mouse motion
|
|
// events, this still allows the user to make selections, except only after they
|
|
// release the mouse
|
|
|
|
if !h.doubleClick && !h.tripleClick {
|
|
h.Cursor.Loc = mouseLoc
|
|
h.Cursor.SetSelectionEnd(h.Cursor.Loc)
|
|
h.Cursor.CopySelection("primary")
|
|
}
|
|
h.mouseReleased = true
|
|
}
|
|
}
|
|
|
|
me := MouseEvent{
|
|
btn: e.Buttons(),
|
|
mod: e.Modifiers(),
|
|
}
|
|
h.DoMouseEvent(me, e)
|
|
}
|
|
h.Buf.MergeCursors()
|
|
|
|
// Display any gutter messages for this line
|
|
c := h.Buf.GetActiveCursor()
|
|
none := true
|
|
for _, m := range h.Buf.Messages {
|
|
if c.Y == m.Start.Y || c.Y == m.End.Y {
|
|
InfoBar.GutterMessage(m.Msg)
|
|
none = false
|
|
break
|
|
}
|
|
}
|
|
if none && InfoBar.HasGutter {
|
|
InfoBar.ClearGutter()
|
|
}
|
|
}
|
|
|
|
// DoKeyEvent executes a key event by finding the action it is bound
|
|
// to and executing it (possibly multiple times for multiple cursors)
|
|
func (h *BufPane) DoKeyEvent(e Event) bool {
|
|
if action, ok := BufKeyBindings[e]; ok {
|
|
estr := BufKeyStrings[e]
|
|
for _, s := range MultiActions {
|
|
if s == estr {
|
|
cursors := h.Buf.GetCursors()
|
|
for _, c := range cursors {
|
|
h.Buf.SetCurCursor(c.Num)
|
|
h.Cursor = c
|
|
if action(h) {
|
|
h.Relocate()
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
}
|
|
if action(h) {
|
|
h.Relocate()
|
|
}
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (h *BufPane) HasKeyEvent(e Event) bool {
|
|
_, ok := BufKeyBindings[e]
|
|
return ok
|
|
}
|
|
|
|
// DoMouseEvent executes a mouse event by finding the action it is bound
|
|
// to and executing it
|
|
func (h *BufPane) DoMouseEvent(e MouseEvent, te *tcell.EventMouse) bool {
|
|
if action, ok := BufMouseBindings[e]; ok {
|
|
if action(h, te) {
|
|
h.Relocate()
|
|
}
|
|
return true
|
|
} else if h.HasKeyEvent(e) {
|
|
return h.DoKeyEvent(e)
|
|
}
|
|
return false
|
|
}
|
|
|
|
// DoRuneInsert inserts a given rune into the current buffer
|
|
// (possibly multiple times for multiple cursors)
|
|
func (h *BufPane) DoRuneInsert(r rune) {
|
|
cursors := h.Buf.GetCursors()
|
|
for _, c := range cursors {
|
|
// Insert a character
|
|
h.Buf.SetCurCursor(c.Num)
|
|
if c.HasSelection() {
|
|
c.DeleteSelection()
|
|
c.ResetSelection()
|
|
}
|
|
|
|
if h.isOverwriteMode {
|
|
next := c.Loc
|
|
next.X++
|
|
h.Buf.Replace(c.Loc, next, string(r))
|
|
} else {
|
|
h.Buf.Insert(c.Loc, string(r))
|
|
}
|
|
}
|
|
}
|
|
|
|
func (h *BufPane) VSplitBuf(buf *buffer.Buffer) {
|
|
e := NewBufPaneFromBuf(buf)
|
|
e.splitID = MainTab().GetNode(h.splitID).VSplit(h.Buf.Settings["splitright"].(bool))
|
|
MainTab().Panes = append(MainTab().Panes, e)
|
|
MainTab().Resize()
|
|
MainTab().SetActive(len(MainTab().Panes) - 1)
|
|
}
|
|
func (h *BufPane) HSplitBuf(buf *buffer.Buffer) {
|
|
e := NewBufPaneFromBuf(buf)
|
|
e.splitID = MainTab().GetNode(h.splitID).HSplit(h.Buf.Settings["splitbottom"].(bool))
|
|
MainTab().Panes = append(MainTab().Panes, e)
|
|
MainTab().Resize()
|
|
MainTab().SetActive(len(MainTab().Panes) - 1)
|
|
}
|
|
func (h *BufPane) Close() {
|
|
h.Buf.Close()
|
|
}
|
|
|
|
// BufKeyActions contains the list of all possible key actions the bufhandler could execute
|
|
var BufKeyActions = map[string]BufKeyAction{
|
|
"CursorUp": (*BufPane).CursorUp,
|
|
"CursorDown": (*BufPane).CursorDown,
|
|
"CursorPageUp": (*BufPane).CursorPageUp,
|
|
"CursorPageDown": (*BufPane).CursorPageDown,
|
|
"CursorLeft": (*BufPane).CursorLeft,
|
|
"CursorRight": (*BufPane).CursorRight,
|
|
"CursorStart": (*BufPane).CursorStart,
|
|
"CursorEnd": (*BufPane).CursorEnd,
|
|
"SelectToStart": (*BufPane).SelectToStart,
|
|
"SelectToEnd": (*BufPane).SelectToEnd,
|
|
"SelectUp": (*BufPane).SelectUp,
|
|
"SelectDown": (*BufPane).SelectDown,
|
|
"SelectLeft": (*BufPane).SelectLeft,
|
|
"SelectRight": (*BufPane).SelectRight,
|
|
"WordRight": (*BufPane).WordRight,
|
|
"WordLeft": (*BufPane).WordLeft,
|
|
"SelectWordRight": (*BufPane).SelectWordRight,
|
|
"SelectWordLeft": (*BufPane).SelectWordLeft,
|
|
"DeleteWordRight": (*BufPane).DeleteWordRight,
|
|
"DeleteWordLeft": (*BufPane).DeleteWordLeft,
|
|
"SelectLine": (*BufPane).SelectLine,
|
|
"SelectToStartOfLine": (*BufPane).SelectToStartOfLine,
|
|
"SelectToEndOfLine": (*BufPane).SelectToEndOfLine,
|
|
"ParagraphPrevious": (*BufPane).ParagraphPrevious,
|
|
"ParagraphNext": (*BufPane).ParagraphNext,
|
|
"InsertNewline": (*BufPane).InsertNewline,
|
|
"Backspace": (*BufPane).Backspace,
|
|
"Delete": (*BufPane).Delete,
|
|
"InsertTab": (*BufPane).InsertTab,
|
|
"Save": (*BufPane).Save,
|
|
"SaveAll": (*BufPane).SaveAll,
|
|
"SaveAs": (*BufPane).SaveAs,
|
|
"Find": (*BufPane).Find,
|
|
"FindNext": (*BufPane).FindNext,
|
|
"FindPrevious": (*BufPane).FindPrevious,
|
|
"Center": (*BufPane).Center,
|
|
"Undo": (*BufPane).Undo,
|
|
"Redo": (*BufPane).Redo,
|
|
"Copy": (*BufPane).Copy,
|
|
"Cut": (*BufPane).Cut,
|
|
"CutLine": (*BufPane).CutLine,
|
|
"DuplicateLine": (*BufPane).DuplicateLine,
|
|
"DeleteLine": (*BufPane).DeleteLine,
|
|
"MoveLinesUp": (*BufPane).MoveLinesUp,
|
|
"MoveLinesDown": (*BufPane).MoveLinesDown,
|
|
"IndentSelection": (*BufPane).IndentSelection,
|
|
"OutdentSelection": (*BufPane).OutdentSelection,
|
|
"OutdentLine": (*BufPane).OutdentLine,
|
|
"Paste": (*BufPane).Paste,
|
|
"PastePrimary": (*BufPane).PastePrimary,
|
|
"SelectAll": (*BufPane).SelectAll,
|
|
"OpenFile": (*BufPane).OpenFile,
|
|
"Start": (*BufPane).Start,
|
|
"End": (*BufPane).End,
|
|
"PageUp": (*BufPane).PageUp,
|
|
"PageDown": (*BufPane).PageDown,
|
|
"SelectPageUp": (*BufPane).SelectPageUp,
|
|
"SelectPageDown": (*BufPane).SelectPageDown,
|
|
"HalfPageUp": (*BufPane).HalfPageUp,
|
|
"HalfPageDown": (*BufPane).HalfPageDown,
|
|
"StartOfLine": (*BufPane).StartOfLine,
|
|
"EndOfLine": (*BufPane).EndOfLine,
|
|
"ToggleHelp": (*BufPane).ToggleHelp,
|
|
"ToggleKeyMenu": (*BufPane).ToggleKeyMenu,
|
|
"ToggleRuler": (*BufPane).ToggleRuler,
|
|
"JumpLine": (*BufPane).JumpLine,
|
|
"ClearStatus": (*BufPane).ClearStatus,
|
|
"ShellMode": (*BufPane).ShellMode,
|
|
"CommandMode": (*BufPane).CommandMode,
|
|
"ToggleOverwriteMode": (*BufPane).ToggleOverwriteMode,
|
|
"Escape": (*BufPane).Escape,
|
|
"Quit": (*BufPane).Quit,
|
|
"QuitAll": (*BufPane).QuitAll,
|
|
"AddTab": (*BufPane).AddTab,
|
|
"PreviousTab": (*BufPane).PreviousTab,
|
|
"NextTab": (*BufPane).NextTab,
|
|
"NextSplit": (*BufPane).NextSplit,
|
|
"PreviousSplit": (*BufPane).PreviousSplit,
|
|
"Unsplit": (*BufPane).Unsplit,
|
|
"VSplit": (*BufPane).VSplitAction,
|
|
"HSplit": (*BufPane).HSplitAction,
|
|
"ToggleMacro": (*BufPane).ToggleMacro,
|
|
"PlayMacro": (*BufPane).PlayMacro,
|
|
"Suspend": (*BufPane).Suspend,
|
|
"ScrollUp": (*BufPane).ScrollUpAction,
|
|
"ScrollDown": (*BufPane).ScrollDownAction,
|
|
"SpawnMultiCursor": (*BufPane).SpawnMultiCursor,
|
|
"SpawnMultiCursorSelect": (*BufPane).SpawnMultiCursorSelect,
|
|
"RemoveMultiCursor": (*BufPane).RemoveMultiCursor,
|
|
"RemoveAllMultiCursors": (*BufPane).RemoveAllMultiCursors,
|
|
"SkipMultiCursor": (*BufPane).SkipMultiCursor,
|
|
"JumpToMatchingBrace": (*BufPane).JumpToMatchingBrace,
|
|
|
|
// This was changed to InsertNewline but I don't want to break backwards compatibility
|
|
"InsertEnter": (*BufPane).InsertNewline,
|
|
}
|
|
|
|
// BufMouseActions contains the list of all possible mouse actions the bufhandler could execute
|
|
var BufMouseActions = map[string]BufMouseAction{
|
|
"MousePress": (*BufPane).MousePress,
|
|
"MouseMultiCursor": (*BufPane).MouseMultiCursor,
|
|
}
|
|
|
|
// MultiActions is a list of actions that should be executed multiple
|
|
// times if there are multiple cursors (one per cursor)
|
|
// Generally actions that modify global editor state like quitting or
|
|
// saving should not be included in this list
|
|
var MultiActions = []string{
|
|
"CursorUp",
|
|
"CursorDown",
|
|
"CursorPageUp",
|
|
"CursorPageDown",
|
|
"CursorLeft",
|
|
"CursorRight",
|
|
"CursorStart",
|
|
"CursorEnd",
|
|
"SelectToStart",
|
|
"SelectToEnd",
|
|
"SelectUp",
|
|
"SelectDown",
|
|
"SelectLeft",
|
|
"SelectRight",
|
|
"WordRight",
|
|
"WordLeft",
|
|
"SelectWordRight",
|
|
"SelectWordLeft",
|
|
"DeleteWordRight",
|
|
"DeleteWordLeft",
|
|
"SelectLine",
|
|
"SelectToStartOfLine",
|
|
"SelectToEndOfLine",
|
|
"ParagraphPrevious",
|
|
"ParagraphNext",
|
|
"InsertNewline",
|
|
"Backspace",
|
|
"Delete",
|
|
"InsertTab",
|
|
"FindNext",
|
|
"FindPrevious",
|
|
"Cut",
|
|
"CutLine",
|
|
"DuplicateLine",
|
|
"DeleteLine",
|
|
"MoveLinesUp",
|
|
"MoveLinesDown",
|
|
"IndentSelection",
|
|
"OutdentSelection",
|
|
"OutdentLine",
|
|
"Paste",
|
|
"PastePrimary",
|
|
"SelectPageUp",
|
|
"SelectPageDown",
|
|
"StartOfLine",
|
|
"EndOfLine",
|
|
"JumpToMatchingBrace",
|
|
}
|