From f63c72c50dbc05813b55ffb229ce875c7035e2b3 Mon Sep 17 00:00:00 2001 From: Zachary Yedidia Date: Wed, 2 Jan 2019 23:26:40 -0500 Subject: [PATCH] Add multi cursor support --- cmd/micro/action/actions.go | 14 +++- cmd/micro/action/bufhandler.go | 118 +++++++++++++++++++++++++-------- cmd/micro/buffer/buffer.go | 72 +++++++++++--------- cmd/micro/display/window.go | 90 ++++++++++++++----------- 4 files changed, 199 insertions(+), 95 deletions(-) diff --git a/cmd/micro/action/actions.go b/cmd/micro/action/actions.go index 60722813..eaf5a8e2 100644 --- a/cmd/micro/action/actions.go +++ b/cmd/micro/action/actions.go @@ -985,6 +985,13 @@ func (h *BufHandler) SpawnMultiCursorSelect() bool { // 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.Win.GetMouseLoc(buffer.Loc{mx, my}) + c := buffer.NewCursor(b, mouseLoc) + b.AddCursor(c) + b.MergeCursors() + return false } @@ -995,10 +1002,15 @@ func (h *BufHandler) SkipMultiCursor() bool { // 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() + } return false } // RemoveAllMultiCursors removes all cursors except the base cursor func (h *BufHandler) RemoveAllMultiCursors() bool { - return false + h.Buf.ClearCursors() + return true } diff --git a/cmd/micro/action/bufhandler.go b/cmd/micro/action/bufhandler.go index aa49a9e1..7469a609 100644 --- a/cmd/micro/action/bufhandler.go +++ b/cmd/micro/action/bufhandler.go @@ -13,15 +13,18 @@ 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) } 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") @@ -106,16 +109,11 @@ func (h *BufHandler) HandleEvent(event tcell.Event) { mod: e.Modifiers(), r: e.Rune(), } - cursors := h.Buf.GetCursors() - for _, c := range cursors { - h.Buf.SetCurCursor(c.Num) - h.Cursor = c - done := h.DoKeyEvent(ke) - if !done && e.Key() == tcell.KeyRune { - h.DoRuneInsert(e.Rune()) - } + + done := h.DoKeyEvent(ke) + if !done && e.Key() == tcell.KeyRune { + h.DoRuneInsert(e.Rune()) } - // TODO: maybe reset curcursor to 0 case *tcell.EventMouse: switch e.Buttons() { case tcell.ButtonNone: @@ -145,17 +143,25 @@ func (h *BufHandler) HandleEvent(event tcell.Event) { btn: e.Buttons(), mod: e.Modifiers(), } - cursors := h.Buf.GetCursors() - for _, c := range cursors { - h.Buf.SetCurCursor(c.Num) - h.Cursor = c - h.DoMouseEvent(me, e) - } + h.DoMouseEvent(me, e) } } func (h *BufHandler) DoKeyEvent(e KeyEvent) bool { if action, ok := BufKeyBindings[e]; ok { + for _, a := range MultiActions { + if a == BufKeyStrings[e] { + 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() } @@ -175,18 +181,21 @@ func (h *BufHandler) DoMouseEvent(e MouseEvent, te *tcell.EventMouse) bool { } func (h *BufHandler) DoRuneInsert(r rune) { - // Insert a character - if h.Cursor.HasSelection() { - h.Cursor.DeleteSelection() - h.Cursor.ResetSelection() - } + cursors := h.Buf.GetCursors() + for _, c := range cursors { + // Insert a character + if c.HasSelection() { + c.DeleteSelection() + c.ResetSelection() + } - if h.isOverwriteMode { - next := h.Cursor.Loc - next.X++ - h.Buf.Replace(h.Cursor.Loc, next, string(r)) - } else { - h.Buf.Insert(h.Cursor.Loc, string(r)) + if h.isOverwriteMode { + next := c.Loc + next.X++ + h.Buf.Replace(c.Loc, next, string(r)) + } else { + h.Buf.Insert(c.Loc, string(r)) + } } } @@ -292,3 +301,60 @@ var BufMouseActions = map[string]BufMouseAction{ "MousePress": (*BufHandler).MousePress, "MouseMultiCursor": (*BufHandler).MouseMultiCursor, } + +const funcPrefixLen = 21 // length of "action.(*BufHandler)." + +// 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", +} diff --git a/cmd/micro/buffer/buffer.go b/cmd/micro/buffer/buffer.go index 91373ba0..ffacc16b 100644 --- a/cmd/micro/buffer/buffer.go +++ b/cmd/micro/buffer/buffer.go @@ -236,36 +236,6 @@ func (b *Buffer) ReOpen() error { return err } -// SetCursors resets this buffer's cursors to a new list -func (b *Buffer) SetCursors(c []*Cursor) { - b.cursors = c -} - -// SetCurCursor sets the current cursor -func (b *Buffer) SetCurCursor(n int) { - b.curCursor = n -} - -// GetActiveCursor returns the main cursor in this buffer -func (b *Buffer) GetActiveCursor() *Cursor { - return b.cursors[b.curCursor] -} - -// GetCursor returns the nth cursor -func (b *Buffer) GetCursor(n int) *Cursor { - return b.cursors[n] -} - -// GetCursors returns the list of cursors in this buffer -func (b *Buffer) GetCursors() []*Cursor { - return b.cursors -} - -// NumCursors returns the number of cursors -func (b *Buffer) NumCursors() int { - return len(b.cursors) -} - // LineBytes returns line n as an array of bytes func (b *Buffer) LineBytes(n int) []byte { if n >= len(b.lines) || n < 0 { @@ -429,6 +399,42 @@ func (b *Buffer) IndentString(tabsize int) string { return "\t" } +// SetCursors resets this buffer's cursors to a new list +func (b *Buffer) SetCursors(c []*Cursor) { + b.cursors = c +} + +// AddCursor adds a new cursor to the list +func (b *Buffer) AddCursor(c *Cursor) { + b.cursors = append(b.cursors, c) + b.UpdateCursors() +} + +// SetCurCursor sets the current cursor +func (b *Buffer) SetCurCursor(n int) { + b.curCursor = n +} + +// GetActiveCursor returns the main cursor in this buffer +func (b *Buffer) GetActiveCursor() *Cursor { + return b.cursors[b.curCursor] +} + +// GetCursor returns the nth cursor +func (b *Buffer) GetCursor(n int) *Cursor { + return b.cursors[n] +} + +// GetCursors returns the list of cursors in this buffer +func (b *Buffer) GetCursors() []*Cursor { + return b.cursors +} + +// NumCursors returns the number of cursors +func (b *Buffer) NumCursors() int { + return len(b.cursors) +} + // MergeCursors merges any cursors that are at the same position // into one cursor func (b *Buffer) MergeCursors() { @@ -464,6 +470,12 @@ func (b *Buffer) UpdateCursors() { } } +func (b *Buffer) RemoveCursor(i int) { + copy(b.cursors[i:], b.cursors[i+1:]) + b.cursors[len(b.cursors)-1] = nil + b.cursors = b.cursors[:len(b.cursors)-1] +} + // ClearCursors removes all extra cursors func (b *Buffer) ClearCursors() { for i := 1; i < len(b.cursors); i++ { diff --git a/cmd/micro/display/window.go b/cmd/micro/display/window.go index 4243a97f..a188b203 100644 --- a/cmd/micro/display/window.go +++ b/cmd/micro/display/window.go @@ -317,7 +317,7 @@ func (w *BufWindow) getStyle(style tcell.Style, bloc buffer.Loc, r rune) (tcell. } func (w *BufWindow) showCursor(x, y int, main bool) { - if main { + if !main { screen.Screen.ShowCursor(x, y) } else { r, _, _, _ := screen.Screen.GetContent(x, y) @@ -370,15 +370,18 @@ func (w *BufWindow) displayBuffer() { // this represents the current draw position in the buffer (char positions) bloc := buffer.Loc{X: -1, Y: w.StartLine} - activeC := b.GetActiveCursor() + cursors := b.GetCursors() curStyle := config.DefStyle for vloc.Y = 0; vloc.Y < bufHeight; vloc.Y++ { vloc.X = 0 if b.Settings["ruler"].(bool) { s := lineNumStyle - if bloc.Y == activeC.Y { - s = curNumStyle + for _, c := range cursors { + if bloc.Y == c.Y { + s = curNumStyle + break + } } w.drawLineNum(s, false, maxLineNumLength, &vloc, &bloc) } @@ -391,28 +394,38 @@ func (w *BufWindow) displayBuffer() { } bloc.X = bslice - draw := func(r rune, style tcell.Style) { + draw := func(r rune, style tcell.Style, showcursor bool) { if nColsBeforeStart <= 0 { - if activeC.HasSelection() && - (bloc.GreaterEqual(activeC.CurSelection[0]) && bloc.LessThan(activeC.CurSelection[1]) || - bloc.LessThan(activeC.CurSelection[0]) && bloc.GreaterEqual(activeC.CurSelection[1])) { - // The current character is selected - style = config.DefStyle.Reverse(true) + for _, c := range cursors { + if c.HasSelection() && + (bloc.GreaterEqual(c.CurSelection[0]) && bloc.LessThan(c.CurSelection[1]) || + bloc.LessThan(c.CurSelection[0]) && bloc.GreaterEqual(c.CurSelection[1])) { + // The current character is selected + style = config.DefStyle.Reverse(true) - if s, ok := config.Colorscheme["selection"]; ok { - style = s + if s, ok := config.Colorscheme["selection"]; ok { + style = s + } } - } - if b.Settings["cursorline"].(bool) && - !activeC.HasSelection() && activeC.Y == bloc.Y { - if s, ok := config.Colorscheme["cursor-line"]; ok { - fg, _, _ := s.Decompose() - style = style.Background(fg) + if b.Settings["cursorline"].(bool) && + !c.HasSelection() && c.Y == bloc.Y { + if s, ok := config.Colorscheme["cursor-line"]; ok { + fg, _, _ := s.Decompose() + style = style.Background(fg) + } } } screen.Screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, r, nil, style) + + if showcursor { + for _, c := range cursors { + if c.X == bloc.X && c.Y == bloc.Y { + w.showCursor(w.X+vloc.X, w.Y+vloc.Y, true) + } + } + } vloc.X++ } nColsBeforeStart-- @@ -422,14 +435,10 @@ func (w *BufWindow) displayBuffer() { totalwidth := w.StartCol - nColsBeforeStart for len(line) > 0 { - if activeC.X == bloc.X && activeC.Y == bloc.Y { - w.showCursor(vloc.X, vloc.Y, true) - } - r, size := utf8.DecodeRune(line) curStyle, _ = w.getStyle(curStyle, bloc, r) - draw(r, curStyle) + draw(r, curStyle, true) width := 0 @@ -443,15 +452,15 @@ func (w *BufWindow) displayBuffer() { char = '@' } - bloc.X++ - line = line[size:] - // Draw any extra characters either spaces for tabs or @ for incomplete wide runes if width > 1 { for i := 1; i < width; i++ { - draw(char, curStyle) + draw(char, curStyle, false) } } + bloc.X++ + line = line[size:] + totalwidth += width // If we reach the end of the window then we either stop or we wrap for softwrap @@ -470,19 +479,24 @@ func (w *BufWindow) displayBuffer() { } } } - if activeC.X == bloc.X && activeC.Y == bloc.Y { - w.showCursor(vloc.X, vloc.Y, true) + + for _, c := range cursors { + if b.Settings["cursorline"].(bool) && + !c.HasSelection() && c.Y == bloc.Y { + style := config.DefStyle + if s, ok := config.Colorscheme["cursor-line"]; ok { + fg, _, _ := s.Decompose() + style = style.Background(fg) + } + for i := vloc.X; i < w.Width; i++ { + screen.Screen.SetContent(i, vloc.Y, ' ', nil, style) + } + } } - if b.Settings["cursorline"].(bool) && - !activeC.HasSelection() && activeC.Y == bloc.Y { - style := config.DefStyle - if s, ok := config.Colorscheme["cursor-line"]; ok { - fg, _, _ := s.Decompose() - style = style.Background(fg) - } - for i := vloc.X; i < w.Width; i++ { - screen.Screen.SetContent(i, vloc.Y, ' ', nil, style) + for _, c := range cursors { + if c.X == bloc.X && c.Y == bloc.Y { + w.showCursor(w.X+vloc.X, w.Y+vloc.Y, true) } }