Add multi cursor support

This commit is contained in:
Zachary Yedidia
2019-01-02 23:26:40 -05:00
parent 80fe992957
commit 0f37c0b0bf
4 changed files with 199 additions and 95 deletions

View File

@@ -985,6 +985,13 @@ func (h *BufHandler) SpawnMultiCursorSelect() bool {
// MouseMultiCursor is a mouse action which puts a new cursor at the mouse position // MouseMultiCursor is a mouse action which puts a new cursor at the mouse position
func (h *BufHandler) MouseMultiCursor(e *tcell.EventMouse) bool { 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 return false
} }
@@ -995,10 +1002,15 @@ func (h *BufHandler) SkipMultiCursor() bool {
// RemoveMultiCursor removes the latest multiple cursor // RemoveMultiCursor removes the latest multiple cursor
func (h *BufHandler) RemoveMultiCursor() bool { func (h *BufHandler) RemoveMultiCursor() bool {
if h.Buf.NumCursors() > 1 {
h.Buf.RemoveCursor(h.Buf.NumCursors() - 1)
h.Buf.UpdateCursors()
}
return false return false
} }
// RemoveAllMultiCursors removes all cursors except the base cursor // RemoveAllMultiCursors removes all cursors except the base cursor
func (h *BufHandler) RemoveAllMultiCursors() bool { func (h *BufHandler) RemoveAllMultiCursors() bool {
return false h.Buf.ClearCursors()
return true
} }

View File

@@ -13,15 +13,18 @@ type BufKeyAction func(*BufHandler) bool
type BufMouseAction func(*BufHandler, *tcell.EventMouse) bool type BufMouseAction func(*BufHandler, *tcell.EventMouse) bool
var BufKeyBindings map[KeyEvent]BufKeyAction var BufKeyBindings map[KeyEvent]BufKeyAction
var BufKeyStrings map[KeyEvent]string
var BufMouseBindings map[MouseEvent]BufMouseAction var BufMouseBindings map[MouseEvent]BufMouseAction
func init() { func init() {
BufKeyBindings = make(map[KeyEvent]BufKeyAction) BufKeyBindings = make(map[KeyEvent]BufKeyAction)
BufKeyStrings = make(map[KeyEvent]string)
BufMouseBindings = make(map[MouseEvent]BufMouseAction) BufMouseBindings = make(map[MouseEvent]BufMouseAction)
} }
func BufMapKey(k KeyEvent, action string) { func BufMapKey(k KeyEvent, action string) {
if f, ok := BufKeyActions[action]; ok { if f, ok := BufKeyActions[action]; ok {
BufKeyStrings[k] = action
BufKeyBindings[k] = f BufKeyBindings[k] = f
} else { } else {
util.TermMessage("Error:", action, "does not exist") util.TermMessage("Error:", action, "does not exist")
@@ -106,16 +109,11 @@ func (h *BufHandler) HandleEvent(event tcell.Event) {
mod: e.Modifiers(), mod: e.Modifiers(),
r: e.Rune(), r: e.Rune(),
} }
cursors := h.Buf.GetCursors()
for _, c := range cursors { done := h.DoKeyEvent(ke)
h.Buf.SetCurCursor(c.Num) if !done && e.Key() == tcell.KeyRune {
h.Cursor = c 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: case *tcell.EventMouse:
switch e.Buttons() { switch e.Buttons() {
case tcell.ButtonNone: case tcell.ButtonNone:
@@ -145,17 +143,25 @@ func (h *BufHandler) HandleEvent(event tcell.Event) {
btn: e.Buttons(), btn: e.Buttons(),
mod: e.Modifiers(), mod: e.Modifiers(),
} }
cursors := h.Buf.GetCursors() h.DoMouseEvent(me, e)
for _, c := range cursors {
h.Buf.SetCurCursor(c.Num)
h.Cursor = c
h.DoMouseEvent(me, e)
}
} }
} }
func (h *BufHandler) DoKeyEvent(e KeyEvent) bool { func (h *BufHandler) DoKeyEvent(e KeyEvent) bool {
if action, ok := BufKeyBindings[e]; ok { 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) { if action(h) {
h.Win.Relocate() h.Win.Relocate()
} }
@@ -175,18 +181,21 @@ func (h *BufHandler) DoMouseEvent(e MouseEvent, te *tcell.EventMouse) bool {
} }
func (h *BufHandler) DoRuneInsert(r rune) { func (h *BufHandler) DoRuneInsert(r rune) {
// Insert a character cursors := h.Buf.GetCursors()
if h.Cursor.HasSelection() { for _, c := range cursors {
h.Cursor.DeleteSelection() // Insert a character
h.Cursor.ResetSelection() if c.HasSelection() {
} c.DeleteSelection()
c.ResetSelection()
}
if h.isOverwriteMode { if h.isOverwriteMode {
next := h.Cursor.Loc next := c.Loc
next.X++ next.X++
h.Buf.Replace(h.Cursor.Loc, next, string(r)) h.Buf.Replace(c.Loc, next, string(r))
} else { } else {
h.Buf.Insert(h.Cursor.Loc, string(r)) h.Buf.Insert(c.Loc, string(r))
}
} }
} }
@@ -292,3 +301,60 @@ var BufMouseActions = map[string]BufMouseAction{
"MousePress": (*BufHandler).MousePress, "MousePress": (*BufHandler).MousePress,
"MouseMultiCursor": (*BufHandler).MouseMultiCursor, "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",
}

View File

@@ -236,36 +236,6 @@ func (b *Buffer) ReOpen() error {
return err 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 // LineBytes returns line n as an array of bytes
func (b *Buffer) LineBytes(n int) []byte { func (b *Buffer) LineBytes(n int) []byte {
if n >= len(b.lines) || n < 0 { if n >= len(b.lines) || n < 0 {
@@ -429,6 +399,42 @@ func (b *Buffer) IndentString(tabsize int) string {
return "\t" 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 // MergeCursors merges any cursors that are at the same position
// into one cursor // into one cursor
func (b *Buffer) MergeCursors() { 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 // ClearCursors removes all extra cursors
func (b *Buffer) ClearCursors() { func (b *Buffer) ClearCursors() {
for i := 1; i < len(b.cursors); i++ { for i := 1; i < len(b.cursors); i++ {

View File

@@ -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) { func (w *BufWindow) showCursor(x, y int, main bool) {
if main { if !main {
screen.Screen.ShowCursor(x, y) screen.Screen.ShowCursor(x, y)
} else { } else {
r, _, _, _ := screen.Screen.GetContent(x, y) 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) // this represents the current draw position in the buffer (char positions)
bloc := buffer.Loc{X: -1, Y: w.StartLine} bloc := buffer.Loc{X: -1, Y: w.StartLine}
activeC := b.GetActiveCursor() cursors := b.GetCursors()
curStyle := config.DefStyle curStyle := config.DefStyle
for vloc.Y = 0; vloc.Y < bufHeight; vloc.Y++ { for vloc.Y = 0; vloc.Y < bufHeight; vloc.Y++ {
vloc.X = 0 vloc.X = 0
if b.Settings["ruler"].(bool) { if b.Settings["ruler"].(bool) {
s := lineNumStyle s := lineNumStyle
if bloc.Y == activeC.Y { for _, c := range cursors {
s = curNumStyle if bloc.Y == c.Y {
s = curNumStyle
break
}
} }
w.drawLineNum(s, false, maxLineNumLength, &vloc, &bloc) w.drawLineNum(s, false, maxLineNumLength, &vloc, &bloc)
} }
@@ -391,28 +394,38 @@ func (w *BufWindow) displayBuffer() {
} }
bloc.X = bslice bloc.X = bslice
draw := func(r rune, style tcell.Style) { draw := func(r rune, style tcell.Style, showcursor bool) {
if nColsBeforeStart <= 0 { if nColsBeforeStart <= 0 {
if activeC.HasSelection() && for _, c := range cursors {
(bloc.GreaterEqual(activeC.CurSelection[0]) && bloc.LessThan(activeC.CurSelection[1]) || if c.HasSelection() &&
bloc.LessThan(activeC.CurSelection[0]) && bloc.GreaterEqual(activeC.CurSelection[1])) { (bloc.GreaterEqual(c.CurSelection[0]) && bloc.LessThan(c.CurSelection[1]) ||
// The current character is selected bloc.LessThan(c.CurSelection[0]) && bloc.GreaterEqual(c.CurSelection[1])) {
style = config.DefStyle.Reverse(true) // The current character is selected
style = config.DefStyle.Reverse(true)
if s, ok := config.Colorscheme["selection"]; ok { if s, ok := config.Colorscheme["selection"]; ok {
style = s style = s
}
} }
}
if b.Settings["cursorline"].(bool) && if b.Settings["cursorline"].(bool) &&
!activeC.HasSelection() && activeC.Y == bloc.Y { !c.HasSelection() && c.Y == bloc.Y {
if s, ok := config.Colorscheme["cursor-line"]; ok { if s, ok := config.Colorscheme["cursor-line"]; ok {
fg, _, _ := s.Decompose() fg, _, _ := s.Decompose()
style = style.Background(fg) style = style.Background(fg)
}
} }
} }
screen.Screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, r, nil, style) 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++ vloc.X++
} }
nColsBeforeStart-- nColsBeforeStart--
@@ -422,14 +435,10 @@ func (w *BufWindow) displayBuffer() {
totalwidth := w.StartCol - nColsBeforeStart totalwidth := w.StartCol - nColsBeforeStart
for len(line) > 0 { 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) r, size := utf8.DecodeRune(line)
curStyle, _ = w.getStyle(curStyle, bloc, r) curStyle, _ = w.getStyle(curStyle, bloc, r)
draw(r, curStyle) draw(r, curStyle, true)
width := 0 width := 0
@@ -443,15 +452,15 @@ func (w *BufWindow) displayBuffer() {
char = '@' char = '@'
} }
bloc.X++
line = line[size:]
// Draw any extra characters either spaces for tabs or @ for incomplete wide runes // Draw any extra characters either spaces for tabs or @ for incomplete wide runes
if width > 1 { if width > 1 {
for i := 1; i < width; i++ { for i := 1; i < width; i++ {
draw(char, curStyle) draw(char, curStyle, false)
} }
} }
bloc.X++
line = line[size:]
totalwidth += width totalwidth += width
// If we reach the end of the window then we either stop or we wrap for softwrap // 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) && for _, c := range cursors {
!activeC.HasSelection() && activeC.Y == bloc.Y { if c.X == bloc.X && c.Y == bloc.Y {
style := config.DefStyle w.showCursor(w.X+vloc.X, w.Y+vloc.Y, true)
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)
} }
} }