mirror of
https://github.com/zyedidia/micro.git
synced 2026-02-05 06:30:28 +09:00
396 lines
13 KiB
Go
396 lines
13 KiB
Go
package action
|
|
|
|
import (
|
|
"time"
|
|
|
|
"github.com/zyedidia/micro/cmd/micro/buffer"
|
|
"github.com/zyedidia/micro/cmd/micro/display"
|
|
"github.com/zyedidia/micro/cmd/micro/util"
|
|
"github.com/zyedidia/tcell"
|
|
)
|
|
|
|
type BufKeyAction func(*BufHandler) bool
|
|
type BufMouseAction func(*BufHandler, *tcell.EventMouse) bool
|
|
|
|
var BufKeyBindings map[KeyEvent]BufKeyAction
|
|
var BufKeyStrings map[KeyEvent]string
|
|
var BufMouseBindings map[MouseEvent]BufMouseAction
|
|
|
|
func init() {
|
|
BufKeyBindings = make(map[KeyEvent]BufKeyAction)
|
|
BufKeyStrings = make(map[KeyEvent]string)
|
|
BufMouseBindings = make(map[MouseEvent]BufMouseAction)
|
|
}
|
|
|
|
// BufMapKey maps a key event to an action
|
|
func BufMapKey(k KeyEvent, action string) {
|
|
if f, ok := BufKeyActions[action]; ok {
|
|
BufKeyStrings[k] = action
|
|
BufKeyBindings[k] = f
|
|
} else {
|
|
util.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 if f, ok := BufKeyActions[action]; ok {
|
|
// allowed to map mouse buttons to key actions
|
|
BufMouseBindings[k] = func(h *BufHandler, e *tcell.EventMouse) bool {
|
|
return f(h)
|
|
}
|
|
} else {
|
|
util.TermMessage("Error:", action, "does not exist")
|
|
}
|
|
}
|
|
|
|
// The BufHandler 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 BufHandler struct {
|
|
Buf *buffer.Buffer
|
|
Win display.Window
|
|
|
|
cursors []*buffer.Cursor
|
|
Cursor *buffer.Cursor // the active cursor
|
|
|
|
StartLine int // Vertical scrolling
|
|
StartCol int // Horizontal scrolling
|
|
|
|
// 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
|
|
}
|
|
|
|
func NewBufHandler(buf *buffer.Buffer, win display.Window) *BufHandler {
|
|
h := new(BufHandler)
|
|
h.Buf = buf
|
|
h.Win = win
|
|
|
|
h.cursors = []*buffer.Cursor{buffer.NewCursor(buf, buf.StartCursor)}
|
|
h.Cursor = h.cursors[0]
|
|
h.mouseReleased = true
|
|
|
|
buf.SetCursors(h.cursors)
|
|
return h
|
|
}
|
|
|
|
// HandleEvent executes the tcell event properly
|
|
// TODO: multiple actions bound to one key
|
|
func (h *BufHandler) HandleEvent(event tcell.Event) {
|
|
switch e := event.(type) {
|
|
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.Win.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()
|
|
}
|
|
|
|
// DoKeyEvent executes a key event by finding the action it is bound
|
|
// to and executing it (possibly multiple times for multiple cursors)
|
|
func (h *BufHandler) DoKeyEvent(e KeyEvent) 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.Win.Relocate()
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
}
|
|
if action(h) {
|
|
h.Win.Relocate()
|
|
}
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// DoMouseEvent executes a mouse event by finding the action it is bound
|
|
// to and executing it
|
|
func (h *BufHandler) DoMouseEvent(e MouseEvent, te *tcell.EventMouse) bool {
|
|
if action, ok := BufMouseBindings[e]; ok {
|
|
if action(h, te) {
|
|
h.Win.Relocate()
|
|
}
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// DoRuneInsert inserts a given rune into the current buffer
|
|
// (possibly multiple times for multiple cursors)
|
|
func (h *BufHandler) DoRuneInsert(r rune) {
|
|
cursors := h.Buf.GetCursors()
|
|
for _, c := range cursors {
|
|
// Insert a character
|
|
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 *BufHandler) vsplit(buf *buffer.Buffer) {
|
|
e := NewBufEditPane(0, 0, 0, 0, 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 *BufHandler) hsplit(buf *buffer.Buffer) {
|
|
e := NewBufEditPane(0, 0, 0, 0, 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)
|
|
}
|
|
|
|
// BufKeyActions contains the list of all possible key actions the bufhandler could execute
|
|
var BufKeyActions = map[string]BufKeyAction{
|
|
"CursorUp": (*BufHandler).CursorUp,
|
|
"CursorDown": (*BufHandler).CursorDown,
|
|
"CursorPageUp": (*BufHandler).CursorPageUp,
|
|
"CursorPageDown": (*BufHandler).CursorPageDown,
|
|
"CursorLeft": (*BufHandler).CursorLeft,
|
|
"CursorRight": (*BufHandler).CursorRight,
|
|
"CursorStart": (*BufHandler).CursorStart,
|
|
"CursorEnd": (*BufHandler).CursorEnd,
|
|
"SelectToStart": (*BufHandler).SelectToStart,
|
|
"SelectToEnd": (*BufHandler).SelectToEnd,
|
|
"SelectUp": (*BufHandler).SelectUp,
|
|
"SelectDown": (*BufHandler).SelectDown,
|
|
"SelectLeft": (*BufHandler).SelectLeft,
|
|
"SelectRight": (*BufHandler).SelectRight,
|
|
"WordRight": (*BufHandler).WordRight,
|
|
"WordLeft": (*BufHandler).WordLeft,
|
|
"SelectWordRight": (*BufHandler).SelectWordRight,
|
|
"SelectWordLeft": (*BufHandler).SelectWordLeft,
|
|
"DeleteWordRight": (*BufHandler).DeleteWordRight,
|
|
"DeleteWordLeft": (*BufHandler).DeleteWordLeft,
|
|
"SelectLine": (*BufHandler).SelectLine,
|
|
"SelectToStartOfLine": (*BufHandler).SelectToStartOfLine,
|
|
"SelectToEndOfLine": (*BufHandler).SelectToEndOfLine,
|
|
"ParagraphPrevious": (*BufHandler).ParagraphPrevious,
|
|
"ParagraphNext": (*BufHandler).ParagraphNext,
|
|
"InsertNewline": (*BufHandler).InsertNewline,
|
|
"InsertSpace": (*BufHandler).InsertSpace,
|
|
"Backspace": (*BufHandler).Backspace,
|
|
"Delete": (*BufHandler).Delete,
|
|
"InsertTab": (*BufHandler).InsertTab,
|
|
"Save": (*BufHandler).Save,
|
|
"SaveAll": (*BufHandler).SaveAll,
|
|
"SaveAs": (*BufHandler).SaveAs,
|
|
"Find": (*BufHandler).Find,
|
|
"FindNext": (*BufHandler).FindNext,
|
|
"FindPrevious": (*BufHandler).FindPrevious,
|
|
"Center": (*BufHandler).Center,
|
|
"Undo": (*BufHandler).Undo,
|
|
"Redo": (*BufHandler).Redo,
|
|
"Copy": (*BufHandler).Copy,
|
|
"Cut": (*BufHandler).Cut,
|
|
"CutLine": (*BufHandler).CutLine,
|
|
"DuplicateLine": (*BufHandler).DuplicateLine,
|
|
"DeleteLine": (*BufHandler).DeleteLine,
|
|
"MoveLinesUp": (*BufHandler).MoveLinesUp,
|
|
"MoveLinesDown": (*BufHandler).MoveLinesDown,
|
|
"IndentSelection": (*BufHandler).IndentSelection,
|
|
"OutdentSelection": (*BufHandler).OutdentSelection,
|
|
"OutdentLine": (*BufHandler).OutdentLine,
|
|
"Paste": (*BufHandler).Paste,
|
|
"PastePrimary": (*BufHandler).PastePrimary,
|
|
"SelectAll": (*BufHandler).SelectAll,
|
|
"OpenFile": (*BufHandler).OpenFile,
|
|
"Start": (*BufHandler).Start,
|
|
"End": (*BufHandler).End,
|
|
"PageUp": (*BufHandler).PageUp,
|
|
"PageDown": (*BufHandler).PageDown,
|
|
"SelectPageUp": (*BufHandler).SelectPageUp,
|
|
"SelectPageDown": (*BufHandler).SelectPageDown,
|
|
"HalfPageUp": (*BufHandler).HalfPageUp,
|
|
"HalfPageDown": (*BufHandler).HalfPageDown,
|
|
"StartOfLine": (*BufHandler).StartOfLine,
|
|
"EndOfLine": (*BufHandler).EndOfLine,
|
|
"ToggleHelp": (*BufHandler).ToggleHelp,
|
|
"ToggleKeyMenu": (*BufHandler).ToggleKeyMenu,
|
|
"ToggleRuler": (*BufHandler).ToggleRuler,
|
|
"JumpLine": (*BufHandler).JumpLine,
|
|
"ClearStatus": (*BufHandler).ClearStatus,
|
|
"ShellMode": (*BufHandler).ShellMode,
|
|
"CommandMode": (*BufHandler).CommandMode,
|
|
"ToggleOverwriteMode": (*BufHandler).ToggleOverwriteMode,
|
|
"Escape": (*BufHandler).Escape,
|
|
"Quit": (*BufHandler).Quit,
|
|
"QuitAll": (*BufHandler).QuitAll,
|
|
"AddTab": (*BufHandler).AddTab,
|
|
"PreviousTab": (*BufHandler).PreviousTab,
|
|
"NextTab": (*BufHandler).NextTab,
|
|
"NextSplit": (*BufHandler).NextSplit,
|
|
"PreviousSplit": (*BufHandler).PreviousSplit,
|
|
"Unsplit": (*BufHandler).Unsplit,
|
|
"VSplit": (*BufHandler).VSplitBinding,
|
|
"HSplit": (*BufHandler).HSplitBinding,
|
|
"ToggleMacro": (*BufHandler).ToggleMacro,
|
|
"PlayMacro": (*BufHandler).PlayMacro,
|
|
"Suspend": (*BufHandler).Suspend,
|
|
"ScrollUp": (*BufHandler).ScrollUpAction,
|
|
"ScrollDown": (*BufHandler).ScrollDownAction,
|
|
"SpawnMultiCursor": (*BufHandler).SpawnMultiCursor,
|
|
"SpawnMultiCursorSelect": (*BufHandler).SpawnMultiCursorSelect,
|
|
"RemoveMultiCursor": (*BufHandler).RemoveMultiCursor,
|
|
"RemoveAllMultiCursors": (*BufHandler).RemoveAllMultiCursors,
|
|
"SkipMultiCursor": (*BufHandler).SkipMultiCursor,
|
|
"JumpToMatchingBrace": (*BufHandler).JumpToMatchingBrace,
|
|
|
|
// This was changed to InsertNewline but I don't want to break backwards compatibility
|
|
"InsertEnter": (*BufHandler).InsertNewline,
|
|
}
|
|
|
|
// BufMouseActions contains the list of all possible mouse actions the bufhandler could execute
|
|
var BufMouseActions = map[string]BufMouseAction{
|
|
"MousePress": (*BufHandler).MousePress,
|
|
"MouseMultiCursor": (*BufHandler).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",
|
|
"InsertSpace",
|
|
"Backspace",
|
|
"Delete",
|
|
"InsertTab",
|
|
"FindNext",
|
|
"FindPrevious",
|
|
"Cut",
|
|
"CutLine",
|
|
"DuplicateLine",
|
|
"DeleteLine",
|
|
"MoveLinesUp",
|
|
"MoveLinesDown",
|
|
"IndentSelection",
|
|
"OutdentSelection",
|
|
"OutdentLine",
|
|
"Paste",
|
|
"PastePrimary",
|
|
"SelectPageUp",
|
|
"SelectPageDown",
|
|
"StartOfLine",
|
|
"EndOfLine",
|
|
"JumpToMatchingBrace",
|
|
}
|