mirror of
https://github.com/zyedidia/micro.git
synced 2026-03-30 14:47:16 +09:00
Add multiple cursor support
This commit creates new keybindings and actions to handle multiple
cursors.
Here are the defaults:
"Alt-n": "SpawnMultiCursor",
"Alt-p": "RemoveMultiCursor",
"Alt-c": "RemoveAllMultiCursors",
"Alt-x": "SkipMultiCursor",
This commit is contained in:
@@ -1829,7 +1829,112 @@ func (v *View) PlayMacro(usePlugin bool) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// None is no action
|
func (v *View) SpawnMultiCursor(usePlugin bool) bool {
|
||||||
func None() bool {
|
spawner := v.Buf.cursors[len(v.Buf.cursors)-1]
|
||||||
|
// You can only spawn a cursor from the main cursor
|
||||||
|
if v.Cursor == spawner {
|
||||||
|
if usePlugin && !PreActionCall("SpawnMultiCursor", v) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if !spawner.HasSelection() {
|
||||||
|
spawner.SelectWord()
|
||||||
|
} else {
|
||||||
|
c := &Cursor{
|
||||||
|
buf: v.Buf,
|
||||||
|
}
|
||||||
|
|
||||||
|
sel := spawner.GetSelection()
|
||||||
|
|
||||||
|
searchStart = ToCharPos(spawner.CurSelection[1], v.Buf)
|
||||||
|
v.Cursor = c
|
||||||
|
Search(sel, v, true)
|
||||||
|
messenger.Message(v.Cursor.Loc)
|
||||||
|
|
||||||
|
for _, cur := range v.Buf.cursors {
|
||||||
|
if c.Loc == cur.Loc {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
v.Buf.cursors = append(v.Buf.cursors, c)
|
||||||
|
v.Relocate()
|
||||||
|
v.Cursor = spawner
|
||||||
|
}
|
||||||
|
|
||||||
|
if usePlugin {
|
||||||
|
PostActionCall("SpawnMultiCursor", v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) SkipMultiCursor(usePlugin bool) bool {
|
||||||
|
cursor := v.Buf.cursors[len(v.Buf.cursors)-1]
|
||||||
|
|
||||||
|
if v.Cursor == cursor {
|
||||||
|
messenger.Message("SKIP")
|
||||||
|
if usePlugin && !PreActionCall("SkipMultiCursor", v) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
sel := cursor.GetSelection()
|
||||||
|
|
||||||
|
searchStart = ToCharPos(cursor.CurSelection[1], v.Buf)
|
||||||
|
v.Cursor = cursor
|
||||||
|
Search(sel, v, true)
|
||||||
|
v.Relocate()
|
||||||
|
v.Cursor = cursor
|
||||||
|
|
||||||
|
if usePlugin {
|
||||||
|
PostActionCall("SkipMultiCursor", v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) RemoveMultiCursor(usePlugin bool) bool {
|
||||||
|
end := len(v.Buf.cursors)
|
||||||
|
if end > 1 {
|
||||||
|
nextOne := v.Buf.cursors[len(v.Buf.cursors)-2]
|
||||||
|
if v.Cursor == nextOne {
|
||||||
|
if usePlugin && !PreActionCall("RemoveMultiCursor", v) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if end > 1 {
|
||||||
|
v.Buf.cursors[end-1] = nil
|
||||||
|
v.Buf.cursors = v.Buf.cursors[:end-1]
|
||||||
|
}
|
||||||
|
v.Relocate()
|
||||||
|
|
||||||
|
if usePlugin {
|
||||||
|
return PostActionCall("RemoveMultiCursor", v)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
v.RemoveAllMultiCursors(usePlugin)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) RemoveAllMultiCursors(usePlugin bool) bool {
|
||||||
|
if v.Cursor == &v.Buf.Cursor {
|
||||||
|
if usePlugin && !PreActionCall("RemoveAllMultiCursors", v) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 1; i < len(v.Buf.cursors); i++ {
|
||||||
|
v.Buf.cursors[i] = nil
|
||||||
|
}
|
||||||
|
v.Buf.cursors = v.Buf.cursors[:1]
|
||||||
|
v.Cursor.ResetSelection()
|
||||||
|
v.Relocate()
|
||||||
|
|
||||||
|
if usePlugin {
|
||||||
|
return PostActionCall("RemoveAllMultiCursors", v)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -98,6 +98,10 @@ var bindingActions = map[string]func(*View, bool) bool{
|
|||||||
"Suspend": (*View).Suspend,
|
"Suspend": (*View).Suspend,
|
||||||
"ScrollUp": (*View).ScrollUpAction,
|
"ScrollUp": (*View).ScrollUpAction,
|
||||||
"ScrollDown": (*View).ScrollDownAction,
|
"ScrollDown": (*View).ScrollDownAction,
|
||||||
|
"SpawnMultiCursor": (*View).SpawnMultiCursor,
|
||||||
|
"RemoveMultiCursor": (*View).RemoveMultiCursor,
|
||||||
|
"RemoveAllMultiCursors": (*View).RemoveAllMultiCursors,
|
||||||
|
"SkipMultiCursor": (*View).SkipMultiCursor,
|
||||||
|
|
||||||
// This was changed to InsertNewline but I don't want to break backwards compatibility
|
// This was changed to InsertNewline but I don't want to break backwards compatibility
|
||||||
"InsertEnter": (*View).InsertNewline,
|
"InsertEnter": (*View).InsertNewline,
|
||||||
@@ -496,8 +500,8 @@ func DefaultBindings() map[string]string {
|
|||||||
"Alt-b": "WordLeft",
|
"Alt-b": "WordLeft",
|
||||||
"Alt-a": "StartOfLine",
|
"Alt-a": "StartOfLine",
|
||||||
"Alt-e": "EndOfLine",
|
"Alt-e": "EndOfLine",
|
||||||
"Alt-p": "CursorUp",
|
// "Alt-p": "CursorUp",
|
||||||
"Alt-n": "CursorDown",
|
// "Alt-n": "CursorDown",
|
||||||
|
|
||||||
// Integration with file managers
|
// Integration with file managers
|
||||||
"F1": "ToggleHelp",
|
"F1": "ToggleHelp",
|
||||||
@@ -513,5 +517,10 @@ func DefaultBindings() map[string]string {
|
|||||||
"MouseWheelDown": "ScrollDown",
|
"MouseWheelDown": "ScrollDown",
|
||||||
"MouseLeft": "MousePress",
|
"MouseLeft": "MousePress",
|
||||||
"MouseMiddle": "PastePrimary",
|
"MouseMiddle": "PastePrimary",
|
||||||
|
|
||||||
|
"Alt-n": "SpawnMultiCursor",
|
||||||
|
"Alt-p": "RemoveMultiCursor",
|
||||||
|
"Alt-c": "RemoveAllMultiCursors",
|
||||||
|
"Alt-x": "SkipMultiCursor",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ type Buffer struct {
|
|||||||
*LineArray
|
*LineArray
|
||||||
|
|
||||||
Cursor Cursor
|
Cursor Cursor
|
||||||
|
cursors []*Cursor // for multiple cursors
|
||||||
|
|
||||||
// Path to the file on disk
|
// Path to the file on disk
|
||||||
Path string
|
Path string
|
||||||
@@ -169,6 +170,8 @@ func NewBuffer(reader io.Reader, size int64, path string) *Buffer {
|
|||||||
file.Close()
|
file.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
b.cursors = []*Cursor{&b.Cursor}
|
||||||
|
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -153,7 +153,7 @@ func (m *Messenger) YesNoPrompt(prompt string) (bool, bool) {
|
|||||||
for {
|
for {
|
||||||
m.Clear()
|
m.Clear()
|
||||||
m.Display()
|
m.Display()
|
||||||
screen.ShowCursor(Count(m.message), h-1)
|
ShowCursor(Count(m.message), h-1)
|
||||||
screen.Show()
|
screen.Show()
|
||||||
event := <-events
|
event := <-events
|
||||||
|
|
||||||
@@ -190,7 +190,7 @@ func (m *Messenger) LetterPrompt(prompt string, responses ...rune) (rune, bool)
|
|||||||
for {
|
for {
|
||||||
m.Clear()
|
m.Clear()
|
||||||
m.Display()
|
m.Display()
|
||||||
screen.ShowCursor(Count(m.message), h-1)
|
ShowCursor(Count(m.message), h-1)
|
||||||
screen.Show()
|
screen.Show()
|
||||||
event := <-events
|
event := <-events
|
||||||
|
|
||||||
@@ -470,7 +470,7 @@ func (m *Messenger) Display() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if m.hasPrompt {
|
if m.hasPrompt {
|
||||||
screen.ShowCursor(Count(m.message)+m.cursorx, h-1)
|
ShowCursor(Count(m.message)+m.cursorx, h-1)
|
||||||
screen.Show()
|
screen.Show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -499,9 +499,13 @@ func (v *View) HandleEvent(event tcell.Event) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if e.Modifiers() == key.modifiers {
|
if e.Modifiers() == key.modifiers {
|
||||||
|
for _, c := range v.Buf.cursors {
|
||||||
|
v.Cursor = c
|
||||||
relocate = false
|
relocate = false
|
||||||
isBinding = true
|
isBinding = true
|
||||||
relocate = v.ExecuteActions(actions)
|
relocate = v.ExecuteActions(actions) || relocate
|
||||||
|
}
|
||||||
|
v.Cursor = &v.Buf.Cursor
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -509,6 +513,9 @@ func (v *View) HandleEvent(event tcell.Event) {
|
|||||||
if !isBinding && e.Key() == tcell.KeyRune {
|
if !isBinding && e.Key() == tcell.KeyRune {
|
||||||
// Check viewtype if readonly don't insert a rune (readonly help and log view etc.)
|
// Check viewtype if readonly don't insert a rune (readonly help and log view etc.)
|
||||||
if v.Type.readonly == false {
|
if v.Type.readonly == false {
|
||||||
|
for _, c := range v.Buf.cursors {
|
||||||
|
v.Cursor = c
|
||||||
|
|
||||||
// Insert a character
|
// Insert a character
|
||||||
if v.Cursor.HasSelection() {
|
if v.Cursor.HasSelection() {
|
||||||
v.Cursor.DeleteSelection()
|
v.Cursor.DeleteSelection()
|
||||||
@@ -528,6 +535,8 @@ func (v *View) HandleEvent(event tcell.Event) {
|
|||||||
curMacro = append(curMacro, e.Rune())
|
curMacro = append(curMacro, e.Rune())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
v.Cursor = &v.Buf.Cursor
|
||||||
|
}
|
||||||
}
|
}
|
||||||
case *tcell.EventPaste:
|
case *tcell.EventPaste:
|
||||||
// Check viewtype if readonly don't paste (readonly help and log view etc.)
|
// Check viewtype if readonly don't paste (readonly help and log view etc.)
|
||||||
@@ -536,14 +545,16 @@ func (v *View) HandleEvent(event tcell.Event) {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, c := range v.Buf.cursors {
|
||||||
|
v.Cursor = c
|
||||||
v.paste(e.Text())
|
v.paste(e.Text())
|
||||||
|
|
||||||
|
}
|
||||||
|
v.Cursor = &v.Buf.Cursor
|
||||||
|
|
||||||
PostActionCall("Paste", v)
|
PostActionCall("Paste", v)
|
||||||
}
|
}
|
||||||
case *tcell.EventMouse:
|
case *tcell.EventMouse:
|
||||||
x, y := e.Position()
|
|
||||||
x -= v.lineNumOffset - v.leftCol + v.x
|
|
||||||
y += v.Topline - v.y
|
|
||||||
// Don't relocate for mouse events
|
// Don't relocate for mouse events
|
||||||
relocate = false
|
relocate = false
|
||||||
|
|
||||||
@@ -551,7 +562,11 @@ func (v *View) HandleEvent(event tcell.Event) {
|
|||||||
|
|
||||||
for key, actions := range bindings {
|
for key, actions := range bindings {
|
||||||
if button == key.buttons {
|
if button == key.buttons {
|
||||||
relocate = v.ExecuteActions(actions)
|
for _, c := range v.Buf.cursors {
|
||||||
|
v.Cursor = c
|
||||||
|
relocate = v.ExecuteActions(actions) || relocate
|
||||||
|
}
|
||||||
|
v.Cursor = &v.Buf.Cursor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -569,6 +584,10 @@ func (v *View) HandleEvent(event tcell.Event) {
|
|||||||
if !v.mouseReleased {
|
if !v.mouseReleased {
|
||||||
// Mouse was just released
|
// Mouse was just released
|
||||||
|
|
||||||
|
x, y := e.Position()
|
||||||
|
x -= v.lineNumOffset - v.leftCol + v.x
|
||||||
|
y += v.Topline - v.y
|
||||||
|
|
||||||
// Relocating here isn't really necessary because the cursor will
|
// Relocating here isn't really necessary because the cursor will
|
||||||
// be in the right place from the last mouse event
|
// be in the right place from the last mouse event
|
||||||
// However, if we are running in a terminal that doesn't support mouse motion
|
// However, if we are running in a terminal that doesn't support mouse motion
|
||||||
@@ -822,6 +841,8 @@ func (v *View) DisplayView() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
charLoc := char.realLoc
|
charLoc := char.realLoc
|
||||||
|
for _, c := range v.Buf.cursors {
|
||||||
|
v.Cursor = c
|
||||||
if v.Cursor.HasSelection() &&
|
if v.Cursor.HasSelection() &&
|
||||||
(charLoc.GreaterEqual(v.Cursor.CurSelection[0]) && charLoc.LessThan(v.Cursor.CurSelection[1]) ||
|
(charLoc.GreaterEqual(v.Cursor.CurSelection[0]) && charLoc.LessThan(v.Cursor.CurSelection[1]) ||
|
||||||
charLoc.LessThan(v.Cursor.CurSelection[0]) && charLoc.GreaterEqual(v.Cursor.CurSelection[1])) {
|
charLoc.LessThan(v.Cursor.CurSelection[0]) && charLoc.GreaterEqual(v.Cursor.CurSelection[1])) {
|
||||||
@@ -832,12 +853,8 @@ func (v *View) DisplayView() {
|
|||||||
lineStyle = style
|
lineStyle = style
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if tabs[curTab].CurView == v.Num && !v.Cursor.HasSelection() &&
|
|
||||||
v.Cursor.Y == char.realLoc.Y && v.Cursor.X == char.realLoc.X && !cursorSet {
|
|
||||||
screen.ShowCursor(xOffset+char.visualLoc.X, yOffset+char.visualLoc.Y)
|
|
||||||
cursorSet = true
|
|
||||||
}
|
}
|
||||||
|
v.Cursor = &v.Buf.Cursor
|
||||||
|
|
||||||
if v.Buf.Settings["cursorline"].(bool) && tabs[curTab].CurView == v.Num &&
|
if v.Buf.Settings["cursorline"].(bool) && tabs[curTab].CurView == v.Num &&
|
||||||
!v.Cursor.HasSelection() && v.Cursor.Y == realLineN {
|
!v.Cursor.HasSelection() && v.Cursor.Y == realLineN {
|
||||||
@@ -848,6 +865,16 @@ func (v *View) DisplayView() {
|
|||||||
|
|
||||||
screen.SetContent(xOffset+char.visualLoc.X, yOffset+char.visualLoc.Y, char.drawChar, nil, lineStyle)
|
screen.SetContent(xOffset+char.visualLoc.X, yOffset+char.visualLoc.Y, char.drawChar, nil, lineStyle)
|
||||||
|
|
||||||
|
for _, c := range v.Buf.cursors {
|
||||||
|
v.Cursor = c
|
||||||
|
if tabs[curTab].CurView == v.Num && !v.Cursor.HasSelection() &&
|
||||||
|
v.Cursor.Y == char.realLoc.Y && v.Cursor.X == char.realLoc.X && !cursorSet {
|
||||||
|
ShowCursor(xOffset+char.visualLoc.X, yOffset+char.visualLoc.Y)
|
||||||
|
// cursorSet = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
v.Cursor = &v.Buf.Cursor
|
||||||
|
|
||||||
lastChar = char
|
lastChar = char
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -858,19 +885,27 @@ func (v *View) DisplayView() {
|
|||||||
var cx, cy int
|
var cx, cy int
|
||||||
if lastChar != nil {
|
if lastChar != nil {
|
||||||
lastX = xOffset + lastChar.visualLoc.X + lastChar.width
|
lastX = xOffset + lastChar.visualLoc.X + lastChar.width
|
||||||
|
for _, c := range v.Buf.cursors {
|
||||||
|
v.Cursor = c
|
||||||
if tabs[curTab].CurView == v.Num && !v.Cursor.HasSelection() &&
|
if tabs[curTab].CurView == v.Num && !v.Cursor.HasSelection() &&
|
||||||
v.Cursor.Y == lastChar.realLoc.Y && v.Cursor.X == lastChar.realLoc.X+1 {
|
v.Cursor.Y == lastChar.realLoc.Y && v.Cursor.X == lastChar.realLoc.X+1 {
|
||||||
screen.ShowCursor(lastX, yOffset+lastChar.visualLoc.Y)
|
ShowCursor(lastX, yOffset+lastChar.visualLoc.Y)
|
||||||
cx, cy = lastX, yOffset+lastChar.visualLoc.Y
|
cx, cy = lastX, yOffset+lastChar.visualLoc.Y
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
v.Cursor = &v.Buf.Cursor
|
||||||
realLoc = Loc{lastChar.realLoc.X + 1, realLineN}
|
realLoc = Loc{lastChar.realLoc.X + 1, realLineN}
|
||||||
visualLoc = Loc{lastX - xOffset, lastChar.visualLoc.Y}
|
visualLoc = Loc{lastX - xOffset, lastChar.visualLoc.Y}
|
||||||
} else if len(line) == 0 {
|
} else if len(line) == 0 {
|
||||||
|
for _, c := range v.Buf.cursors {
|
||||||
|
v.Cursor = c
|
||||||
if tabs[curTab].CurView == v.Num && !v.Cursor.HasSelection() &&
|
if tabs[curTab].CurView == v.Num && !v.Cursor.HasSelection() &&
|
||||||
v.Cursor.Y == realLineN {
|
v.Cursor.Y == realLineN {
|
||||||
screen.ShowCursor(xOffset, yOffset+visualLineN)
|
ShowCursor(xOffset, yOffset+visualLineN)
|
||||||
cx, cy = xOffset, yOffset+visualLineN
|
cx, cy = xOffset, yOffset+visualLineN
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
v.Cursor = &v.Buf.Cursor
|
||||||
lastX = xOffset
|
lastX = xOffset
|
||||||
realLoc = Loc{0, realLineN}
|
realLoc = Loc{0, realLineN}
|
||||||
visualLoc = Loc{0, visualLineN}
|
visualLoc = Loc{0, visualLineN}
|
||||||
@@ -912,6 +947,11 @@ func (v *View) DisplayView() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ShowCursor(x, y int) {
|
||||||
|
r, _, _, _ := screen.GetContent(x, y)
|
||||||
|
screen.SetContent(x, y, r, nil, defStyle.Reverse(true))
|
||||||
|
}
|
||||||
|
|
||||||
// Display renders the view, the cursor, and statusline
|
// Display renders the view, the cursor, and statusline
|
||||||
func (v *View) Display() {
|
func (v *View) Display() {
|
||||||
if globalSettings["termtitle"].(bool) {
|
if globalSettings["termtitle"].(bool) {
|
||||||
|
|||||||
Reference in New Issue
Block a user