micro/internal/action/bufpane.go

558 lines
16 KiB
Go
Raw Normal View History

2018-08-28 02:53:08 +03:00
package action
import (
"strings"
2018-08-28 02:53:08 +03:00
"time"
2019-03-20 01:28:51 +03:00
luar "layeh.com/gopher-luar"
2019-08-04 01:49:05 +03:00
lua "github.com/yuin/gopher-lua"
2019-02-04 07:17:24 +03:00
"github.com/zyedidia/micro/internal/buffer"
2019-03-20 01:28:51 +03:00
"github.com/zyedidia/micro/internal/config"
2019-02-04 07:17:24 +03:00
"github.com/zyedidia/micro/internal/display"
2019-03-20 01:28:51 +03:00
ulua "github.com/zyedidia/micro/internal/lua"
2019-02-04 07:17:24 +03:00
"github.com/zyedidia/micro/internal/screen"
2018-08-28 02:53:08 +03:00
"github.com/zyedidia/tcell"
)
2019-01-19 23:37:59 +03:00
type BufKeyAction func(*BufPane) bool
type BufMouseAction func(*BufPane, *tcell.EventMouse) bool
2018-08-28 02:53:08 +03:00
2019-01-14 01:58:08 +03:00
var BufKeyBindings map[Event]BufKeyAction
var BufKeyStrings map[Event]string
2018-08-28 02:53:08 +03:00
var BufMouseBindings map[MouseEvent]BufMouseAction
func init() {
2019-01-14 01:58:08 +03:00
BufKeyBindings = make(map[Event]BufKeyAction)
BufKeyStrings = make(map[Event]string)
2018-08-28 02:53:08 +03:00
BufMouseBindings = make(map[MouseEvent]BufMouseAction)
}
2019-08-04 01:49:05 +03:00
func LuaAction(fn string) func(*BufPane) bool {
luaFn := strings.Split(fn, ".")
plName, plFn := luaFn[0], luaFn[1]
pl := config.FindPlugin(plName)
return func(h *BufPane) bool {
val, err := pl.Call(plFn, luar.New(ulua.L, h))
if err != nil {
screen.TermMessage(err)
}
if v, ok := val.(lua.LBool); !ok {
return false
} else {
return bool(v)
}
}
}
2019-01-03 23:27:43 +03:00
// BufMapKey maps a key event to an action
2019-01-14 01:58:08 +03:00
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)
2019-08-04 01:49:05 +03:00
} else if strings.HasPrefix(action, "lua:") {
action = strings.SplitN(action, ":", 2)[1]
BufKeyStrings[k] = action
BufKeyBindings[k] = LuaAction(action)
} else if f, ok := BufKeyActions[action]; ok {
2019-01-03 07:26:40 +03:00
BufKeyStrings[k] = action
2018-08-29 06:30:39 +03:00
BufKeyBindings[k] = f
} else {
2019-01-14 05:06:58 +03:00
screen.TermMessage("Error:", action, "does not exist")
2018-08-29 06:30:39 +03:00
}
2018-08-28 02:53:08 +03:00
}
2019-01-03 23:27:43 +03:00
// BufMapMouse maps a mouse event to an action
2018-08-28 02:53:08 +03:00
func BufMapMouse(k MouseEvent, action string) {
2018-08-29 06:30:39 +03:00
if f, ok := BufMouseActions[action]; ok {
BufMouseBindings[k] = f
} else {
delete(BufMouseBindings, k)
BufMapKey(k, action)
2018-08-29 06:30:39 +03:00
}
2018-08-28 02:53:08 +03:00
}
2019-01-19 23:37:59 +03:00
// The BufPane connects the buffer and the window
2018-08-28 02:53:08 +03:00
// 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
2019-01-19 23:37:59 +03:00
type BufPane struct {
2019-01-14 08:57:39 +03:00
display.BWindow
2019-01-11 05:26:58 +03:00
2018-08-28 02:53:08 +03:00
Buf *buffer.Buffer
Cursor *buffer.Cursor // the active cursor
2018-08-28 02:53:08 +03:00
// 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
2019-01-03 23:35:24 +03:00
// Last search stores the last successful search for FindNext and FindPrev
lastSearch string
2019-01-05 05:48:19 +03:00
// Should the current multiple cursor selection search based on word or
// based on selection (false for selection, true for word)
multiWord bool
2019-01-05 01:40:56 +03:00
splitID uint64
2019-06-15 21:44:03 +03:00
// remember original location of a search in case the search is canceled
searchOrig buffer.Loc
2018-08-28 02:53:08 +03:00
}
2019-01-19 23:37:59 +03:00
func NewBufPane(buf *buffer.Buffer, win display.BWindow) *BufPane {
h := new(BufPane)
2018-08-29 01:44:52 +03:00
h.Buf = buf
2019-01-14 08:57:39 +03:00
h.BWindow = win
2018-08-28 02:53:08 +03:00
h.Cursor = h.Buf.GetActiveCursor()
2019-01-03 01:39:50 +03:00
h.mouseReleased = true
2018-08-28 02:53:08 +03:00
2019-03-20 01:28:51 +03:00
config.RunPluginFn("onBufPaneOpen", luar.New(ulua.L, h))
2018-08-29 01:44:52 +03:00
return h
2018-08-28 02:53:08 +03:00
}
2019-01-19 23:37:59 +03:00
func NewBufPaneFromBuf(buf *buffer.Buffer) *BufPane {
w := display.NewBufWindow(0, 0, 0, 0, buf)
return NewBufPane(buf, w)
}
2019-08-03 00:48:59 +03:00
// PluginCB calls all plugin callbacks with a certain name and
// displays an error if there is one and returns the aggregrate
// boolean response
func (h *BufPane) PluginCB(cb string) bool {
b, err := config.RunPluginFnBool(cb, luar.New(ulua.L, h))
if err != nil {
screen.TermMessage(err)
}
return b
}
// PluginCBRune is the same as PluginCB but also passes a rune to
// the plugins
func (h *BufPane) PluginCBRune(cb string, r rune) bool {
b, err := config.RunPluginFnBool(cb, luar.New(ulua.L, h), luar.New(ulua.L, string(r)))
if err != nil {
screen.TermMessage(err)
}
return b
}
2019-01-19 23:37:59 +03:00
func (h *BufPane) OpenBuffer(b *buffer.Buffer) {
2019-01-14 08:57:39 +03:00
h.Buf.Close()
h.Buf = b
h.BWindow.SetBuffer(b)
h.Cursor = b.GetActiveCursor()
2019-06-16 01:00:24 +03:00
h.Resize(h.GetView().Width, h.GetView().Height)
2019-01-14 08:57:39 +03:00
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{}
}
2019-01-19 23:37:59 +03:00
func (h *BufPane) ID() uint64 {
2019-01-11 22:49:22 +03:00
return h.splitID
}
2019-01-19 23:37:59 +03:00
func (h *BufPane) SetID(i uint64) {
h.splitID = i
}
func (h *BufPane) Name() string {
2019-01-11 22:49:22 +03:00
return h.Buf.GetName()
}
2018-08-28 02:53:08 +03:00
// HandleEvent executes the tcell event properly
// TODO: multiple actions bound to one key
2019-01-19 23:37:59 +03:00
func (h *BufPane) HandleEvent(event tcell.Event) {
2018-08-28 02:53:08 +03:00
switch e := event.(type) {
2019-01-14 01:58:08 +03:00
case *tcell.EventRaw:
re := RawEvent{
esc: e.EscSeq(),
}
h.DoKeyEvent(re)
2018-08-28 02:53:08 +03:00
case *tcell.EventKey:
ke := KeyEvent{
code: e.Key(),
mod: e.Modifiers(),
r: e.Rune(),
}
2019-01-03 07:26:40 +03:00
done := h.DoKeyEvent(ke)
if !done && e.Key() == tcell.KeyRune {
h.DoRuneInsert(e.Rune())
2018-08-29 06:30:39 +03:00
}
2018-08-28 02:53:08 +03:00
case *tcell.EventMouse:
2019-01-03 01:39:50 +03:00
switch e.Buttons() {
case tcell.ButtonNone:
// Mouse event with no click
if !h.mouseReleased {
// Mouse was just released
mx, my := e.Position()
2019-01-11 05:26:58 +03:00
mouseLoc := h.GetMouseLoc(buffer.Loc{X: mx, Y: my})
2019-01-03 01:39:50 +03:00
// 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
}
}
2018-08-28 02:53:08 +03:00
me := MouseEvent{
btn: e.Buttons(),
mod: e.Modifiers(),
}
2019-01-03 07:26:40 +03:00
h.DoMouseEvent(me, e)
2018-08-29 01:44:52 +03:00
}
2019-01-05 05:50:11 +03:00
h.Buf.MergeCursors()
2019-01-14 01:22:11 +03:00
2019-08-04 09:53:33 +03:00
if h.IsActive() {
// 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()
2019-01-14 01:22:11 +03:00
}
}
2018-08-29 01:44:52 +03:00
}
2019-01-03 23:27:43 +03:00
// DoKeyEvent executes a key event by finding the action it is bound
// to and executing it (possibly multiple times for multiple cursors)
2019-01-19 23:37:59 +03:00
func (h *BufPane) DoKeyEvent(e Event) bool {
2018-08-29 01:44:52 +03:00
if action, ok := BufKeyBindings[e]; ok {
2019-01-03 23:27:43 +03:00
estr := BufKeyStrings[e]
2019-06-16 22:56:39 +03:00
if estr != "InsertTab" {
h.Buf.HasSuggestions = false
}
2019-01-03 23:27:43 +03:00
for _, s := range MultiActions {
if s == estr {
2019-01-03 07:26:40 +03:00
cursors := h.Buf.GetCursors()
for _, c := range cursors {
h.Buf.SetCurCursor(c.Num)
h.Cursor = c
2019-08-03 00:48:59 +03:00
if !h.PluginCB("pre" + estr) {
// canceled by plugin
continue
}
2019-08-03 09:46:25 +03:00
rel := action(h)
if h.PluginCB("on"+estr) && rel {
2019-01-11 05:26:58 +03:00
h.Relocate()
2019-01-03 07:26:40 +03:00
}
2019-08-05 06:23:32 +03:00
if recording_macro {
if estr != "ToggleMacro" && estr != "PlayMacro" {
curmacro = append(curmacro, e)
}
}
2019-01-03 07:26:40 +03:00
}
return true
}
}
2019-08-03 00:48:59 +03:00
if !h.PluginCB("pre" + estr) {
return false
}
2019-08-03 09:46:25 +03:00
rel := action(h)
if h.PluginCB("on"+estr) && rel {
2019-01-11 05:26:58 +03:00
h.Relocate()
2019-01-01 00:36:54 +03:00
}
2018-08-29 01:44:52 +03:00
return true
}
return false
}
2019-01-19 23:37:59 +03:00
func (h *BufPane) HasKeyEvent(e Event) bool {
2019-01-14 01:58:08 +03:00
_, ok := BufKeyBindings[e]
return ok
}
2019-01-03 23:27:43 +03:00
// DoMouseEvent executes a mouse event by finding the action it is bound
// to and executing it
2019-01-19 23:37:59 +03:00
func (h *BufPane) DoMouseEvent(e MouseEvent, te *tcell.EventMouse) bool {
2018-08-29 01:44:52 +03:00
if action, ok := BufMouseBindings[e]; ok {
2019-01-01 00:36:54 +03:00
if action(h, te) {
2019-01-11 05:26:58 +03:00
h.Relocate()
2019-01-01 00:36:54 +03:00
}
2018-08-29 01:44:52 +03:00
return true
2019-01-14 01:58:08 +03:00
} else if h.HasKeyEvent(e) {
return h.DoKeyEvent(e)
2018-08-28 02:53:08 +03:00
}
2018-08-29 01:44:52 +03:00
return false
2018-08-28 02:53:08 +03:00
}
2019-01-03 23:27:43 +03:00
// DoRuneInsert inserts a given rune into the current buffer
// (possibly multiple times for multiple cursors)
2019-01-19 23:37:59 +03:00
func (h *BufPane) DoRuneInsert(r rune) {
2019-01-03 07:26:40 +03:00
cursors := h.Buf.GetCursors()
for _, c := range cursors {
// Insert a character
2019-01-17 01:52:30 +03:00
h.Buf.SetCurCursor(c.Num)
2019-08-05 06:23:32 +03:00
h.Cursor = c
2019-08-03 00:48:59 +03:00
if !h.PluginCBRune("preRune", r) {
continue
}
2019-01-03 07:26:40 +03:00
if c.HasSelection() {
c.DeleteSelection()
c.ResetSelection()
}
2018-08-29 06:30:39 +03:00
2019-01-03 07:26:40 +03:00
if h.isOverwriteMode {
next := c.Loc
next.X++
h.Buf.Replace(c.Loc, next, string(r))
2019-01-03 07:26:40 +03:00
} else {
h.Buf.Insert(c.Loc, string(r))
2019-01-03 07:26:40 +03:00
}
2019-08-05 06:23:32 +03:00
if recording_macro {
curmacro = append(curmacro, r)
}
2019-08-03 00:48:59 +03:00
h.PluginCBRune("onRune", r)
2018-08-29 06:30:39 +03:00
}
}
2019-01-19 23:37:59 +03:00
func (h *BufPane) VSplitBuf(buf *buffer.Buffer) {
e := NewBufPaneFromBuf(buf)
2019-01-10 04:07:18 +03:00
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)
2019-01-05 01:40:56 +03:00
}
2019-01-19 23:37:59 +03:00
func (h *BufPane) HSplitBuf(buf *buffer.Buffer) {
e := NewBufPaneFromBuf(buf)
2019-01-10 04:07:18 +03:00
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)
2019-01-05 01:40:56 +03:00
}
2019-01-19 23:37:59 +03:00
func (h *BufPane) Close() {
2019-01-14 05:06:58 +03:00
h.Buf.Close()
}
2019-01-05 01:40:56 +03:00
2019-08-04 09:53:33 +03:00
func (h *BufPane) SetActive(b bool) {
h.BWindow.SetActive(b)
if b {
// 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()
}
}
}
2019-01-03 23:27:43 +03:00
// BufKeyActions contains the list of all possible key actions the bufhandler could execute
2018-08-28 02:53:08 +03:00
var BufKeyActions = map[string]BufKeyAction{
2019-01-19 23:37:59 +03:00
"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,
"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,
2018-08-28 02:53:08 +03:00
// This was changed to InsertNewline but I don't want to break backwards compatibility
2019-01-19 23:37:59 +03:00
"InsertEnter": (*BufPane).InsertNewline,
2018-08-28 02:53:08 +03:00
}
2019-01-03 23:27:43 +03:00
// BufMouseActions contains the list of all possible mouse actions the bufhandler could execute
2018-08-28 02:53:08 +03:00
var BufMouseActions = map[string]BufMouseAction{
2019-01-19 23:37:59 +03:00
"MousePress": (*BufPane).MousePress,
"MouseMultiCursor": (*BufPane).MouseMultiCursor,
2018-08-28 02:53:08 +03:00
}
2019-01-03 07:26:40 +03:00
// 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",
}