]> git.lizzy.rs Git - micro.git/blobdiff - cmd/micro/view.go
Make cursor movement automatic on insert + remove
[micro.git] / cmd / micro / view.go
index e6d3833edbaf9aab10b1183a7ebd9734aad36d7f..8a009b6504903588095febb95be640c35d1b68ef 100644 (file)
@@ -11,15 +11,16 @@ import (
 )
 
 type ViewType struct {
+       kind     int
        readonly bool // The file cannot be edited
        scratch  bool // The file cannot be saved
 }
 
 var (
-       vtDefault = ViewType{false, false}
-       vtHelp    = ViewType{true, true}
-       vtLog     = ViewType{true, true}
-       vtScratch = ViewType{false, true}
+       vtDefault = ViewType{0, false, false}
+       vtHelp    = ViewType{1, true, true}
+       vtLog     = ViewType{2, true, true}
+       vtScratch = ViewType{3, false, true}
 )
 
 // The View struct stores information about a view into a buffer.
@@ -168,7 +169,7 @@ func (v *View) paste(clip string) {
        }
        clip = strings.Replace(clip, "\n", "\n"+leadingWS, -1)
        v.Buf.Insert(v.Cursor.Loc, clip)
-       v.Cursor.Loc = v.Cursor.Loc.Move(Count(clip), v.Buf)
+       // v.Cursor.Loc = v.Cursor.Loc.Move(Count(clip), v.Buf)
        v.freshClip = false
        messenger.Message("Pasted clipboard")
 }
@@ -198,18 +199,19 @@ func (v *View) ScrollDown(n int) {
 // causing them to lose the unsaved changes
 func (v *View) CanClose() bool {
        if v.Type == vtDefault && v.Buf.IsModified {
-               var char rune
+               var choice bool
                var canceled bool
                if v.Buf.Settings["autosave"].(bool) {
-                       char = 'y'
+                       choice = true
                } else {
-                       char, canceled = messenger.LetterPrompt("Save changes to "+v.Buf.GetName()+" before closing? (y,n,esc) ", 'y', 'n')
+                       choice, canceled = messenger.YesNoPrompt("Save changes to " + v.Buf.GetName() + " before closing? (y,n,esc) ")
                }
                if !canceled {
-                       if char == 'y' {
+                       //if char == 'y' {
+                       if choice {
                                v.Save(true)
                                return true
-                       } else if char == 'n' {
+                       } else {
                                return true
                        }
                }
@@ -257,9 +259,9 @@ func (v *View) Open(filename string) {
        if err != nil {
                messenger.Message(err.Error())
                // File does not exist -- create an empty buffer with that name
-               buf = NewBuffer(strings.NewReader(""), filename)
+               buf = NewBufferFromString("", filename)
        } else {
-               buf = NewBuffer(file, filename)
+               buf = NewBuffer(file, FSize(file), filename)
        }
        v.OpenBuffer(buf)
 }
@@ -448,6 +450,35 @@ func (v *View) MoveToMouseClick(x, y int) {
        v.Cursor.LastVisualX = v.Cursor.GetVisualX()
 }
 
+func (v *View) ExecuteActions(actions []func(*View, bool) bool) bool {
+       relocate := false
+       readonlyBindingsList := []string{"Delete", "Insert", "Backspace", "Cut", "Play", "Paste", "Move", "Add", "DuplicateLine", "Macro"}
+       for _, action := range actions {
+               readonlyBindingsResult := false
+               funcName := ShortFuncName(action)
+               if v.Type.readonly == true {
+                       // check for readonly and if true only let key bindings get called if they do not change the contents.
+                       for _, readonlyBindings := range readonlyBindingsList {
+                               if strings.Contains(funcName, readonlyBindings) {
+                                       readonlyBindingsResult = true
+                               }
+                       }
+               }
+               if !readonlyBindingsResult {
+                       // call the key binding
+                       relocate = action(v, true) || relocate
+                       // Macro
+                       if funcName != "ToggleMacro" && funcName != "PlayMacro" {
+                               if recordingMacro {
+                                       curMacro = append(curMacro, action)
+                               }
+                       }
+               }
+       }
+
+       return relocate
+}
+
 // HandleEvent handles an event passed by the main loop
 func (v *View) HandleEvent(event tcell.Event) {
        // This bool determines whether the view is relocated at the end of the function
@@ -460,123 +491,102 @@ func (v *View) HandleEvent(event tcell.Event) {
        case *tcell.EventKey:
                // Check first if input is a key binding, if it is we 'eat' the input and don't insert a rune
                isBinding := false
-               if e.Key() != tcell.KeyRune || e.Modifiers() != 0 {
-                       for key, actions := range bindings {
-                               if e.Key() == key.keyCode {
-                                       if e.Key() == tcell.KeyRune {
-                                               if e.Rune() != key.r {
-                                                       continue
-                                               }
+               for key, actions := range bindings {
+                       if e.Key() == key.keyCode {
+                               if e.Key() == tcell.KeyRune {
+                                       if e.Rune() != key.r {
+                                               continue
                                        }
-                                       if e.Modifiers() == key.modifiers {
+                               }
+                               if e.Modifiers() == key.modifiers {
+                                       for _, c := range v.Buf.cursors {
+                                               v.Cursor = c
                                                relocate = false
                                                isBinding = true
-                                               for _, action := range actions {
-                                                       relocate = action(v, true) || relocate
-                                                       funcName := FuncName(action)
-                                                       if funcName != "main.(*View).ToggleMacro" && funcName != "main.(*View).PlayMacro" {
-                                                               if recordingMacro {
-                                                                       curMacro = append(curMacro, action)
-                                                               }
-                                                       }
-                                               }
-                                               break
+                                               relocate = v.ExecuteActions(actions) || relocate
                                        }
+                                       v.Cursor = &v.Buf.Cursor
+                                       break
                                }
                        }
                }
                if !isBinding && e.Key() == tcell.KeyRune {
-                       // Insert a character
-                       if v.Cursor.HasSelection() {
-                               v.Cursor.DeleteSelection()
-                               v.Cursor.ResetSelection()
-                       }
-                       v.Buf.Insert(v.Cursor.Loc, string(e.Rune()))
-                       v.Cursor.Right()
+                       // Check viewtype if readonly don't insert a rune (readonly help and log view etc.)
+                       if v.Type.readonly == false {
+                               for _, c := range v.Buf.cursors {
+                                       v.Cursor = c
+
+                                       // Insert a character
+                                       if v.Cursor.HasSelection() {
+                                               v.Cursor.DeleteSelection()
+                                               v.Cursor.ResetSelection()
+                                       }
+                                       v.Buf.Insert(v.Cursor.Loc, string(e.Rune()))
 
-                       for pl := range loadedPlugins {
-                               _, err := Call(pl+".onRune", string(e.Rune()), v)
-                               if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") {
-                                       TermMessage(err)
-                               }
-                       }
+                                       for pl := range loadedPlugins {
+                                               _, err := Call(pl+".onRune", string(e.Rune()), v)
+                                               if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") {
+                                                       TermMessage(err)
+                                               }
+                                       }
 
-                       if recordingMacro {
-                               curMacro = append(curMacro, e.Rune())
+                                       if recordingMacro {
+                                               curMacro = append(curMacro, e.Rune())
+                                       }
+                               }
+                               v.Cursor = &v.Buf.Cursor
                        }
                }
        case *tcell.EventPaste:
-               if !PreActionCall("Paste", v) {
-                       break
-               }
+               // Check viewtype if readonly don't paste (readonly help and log view etc.)
+               if v.Type.readonly == false {
+                       if !PreActionCall("Paste", v) {
+                               break
+                       }
 
-               v.paste(e.Text())
+                       for _, c := range v.Buf.cursors {
+                               v.Cursor = c
+                               v.paste(e.Text())
+
+                       }
+                       v.Cursor = &v.Buf.Cursor
 
-               PostActionCall("Paste", v)
+                       PostActionCall("Paste", v)
+               }
        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
                relocate = false
 
                button := e.Buttons()
 
-               switch button {
-               case tcell.Button1:
-                       // Left click
-                       if v.mouseReleased {
-                               v.MoveToMouseClick(x, y)
-                               if time.Since(v.lastClickTime)/time.Millisecond < doubleClickThreshold {
-                                       if v.doubleClick {
-                                               // Triple click
-                                               v.lastClickTime = time.Now()
-
-                                               v.tripleClick = true
-                                               v.doubleClick = false
-
-                                               v.Cursor.SelectLine()
-                                               v.Cursor.CopySelection("primary")
-                                       } else {
-                                               // Double click
-                                               v.lastClickTime = time.Now()
-
-                                               v.doubleClick = true
-                                               v.tripleClick = false
-
-                                               v.Cursor.SelectWord()
-                                               v.Cursor.CopySelection("primary")
-                                       }
-                               } else {
-                                       v.doubleClick = false
-                                       v.tripleClick = false
-                                       v.lastClickTime = time.Now()
-
-                                       v.Cursor.OrigSelection[0] = v.Cursor.Loc
-                                       v.Cursor.CurSelection[0] = v.Cursor.Loc
-                                       v.Cursor.CurSelection[1] = v.Cursor.Loc
+               for key, actions := range bindings {
+                       if button == key.buttons && e.Modifiers() == key.modifiers {
+                               for _, c := range v.Buf.cursors {
+                                       v.Cursor = c
+                                       relocate = v.ExecuteActions(actions) || relocate
                                }
-                               v.mouseReleased = false
-                       } else if !v.mouseReleased {
-                               v.MoveToMouseClick(x, y)
-                               if v.tripleClick {
-                                       v.Cursor.AddLineToSelection()
-                               } else if v.doubleClick {
-                                       v.Cursor.AddWordToSelection()
-                               } else {
-                                       v.Cursor.SetSelectionEnd(v.Cursor.Loc)
-                                       v.Cursor.CopySelection("primary")
+                               v.Cursor = &v.Buf.Cursor
+                       }
+               }
+
+               for key, actions := range mouseBindings {
+                       if button == key.buttons && e.Modifiers() == key.modifiers {
+                               for _, action := range actions {
+                                       action(v, true, e)
                                }
                        }
-               case tcell.Button2:
-                       // Middle mouse button was clicked,
-                       // We should paste primary
-                       v.PastePrimary(true)
+               }
+
+               switch button {
                case tcell.ButtonNone:
                        // Mouse event with no click
                        if !v.mouseReleased {
                                // 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
                                // be in the right place from the last mouse event
                                // However, if we are running in a terminal that doesn't support mouse motion
@@ -590,14 +600,6 @@ func (v *View) HandleEvent(event tcell.Event) {
                                }
                                v.mouseReleased = true
                        }
-               case tcell.WheelUp:
-                       // Scroll up
-                       scrollspeed := int(v.Buf.Settings["scrollspeed"].(float64))
-                       v.ScrollUp(scrollspeed)
-               case tcell.WheelDown:
-                       // Scroll down
-                       scrollspeed := int(v.Buf.Settings["scrollspeed"].(float64))
-                       v.ScrollDown(scrollspeed)
                }
        }
 
@@ -648,7 +650,7 @@ func (v *View) openHelp(helpPage string) {
        if data, err := FindRuntimeFile(RTHelp, helpPage).Data(); err != nil {
                TermMessage("Unable to load help text", helpPage, "\n", err)
        } else {
-               helpBuffer := NewBuffer(strings.NewReader(string(data)), helpPage+".md")
+               helpBuffer := NewBufferFromString(string(data), helpPage+".md")
                helpBuffer.name = "Help"
 
                if v.Type == vtHelp {
@@ -661,6 +663,10 @@ func (v *View) openHelp(helpPage string) {
 }
 
 func (v *View) DisplayView() {
+       if v.Buf.Settings["softwrap"].(bool) && v.leftCol != 0 {
+               v.leftCol = 0
+       }
+
        if v.Type == vtLog {
                // Log views should always follow the cursor...
                v.Relocate()
@@ -688,9 +694,11 @@ func (v *View) DisplayView() {
                v.lineNumOffset += 2
        }
 
+       divider := 0
        if v.x != 0 {
                // One space for the extra split divider
                v.lineNumOffset++
+               divider = 1
        }
 
        xOffset := v.x + v.lineNumOffset
@@ -723,6 +731,14 @@ func (v *View) DisplayView() {
                        realLineN++
                }
 
+               colorcolumn := int(v.Buf.Settings["colorcolumn"].(float64))
+               if colorcolumn != 0 {
+                       style := GetColor("color-column")
+                       fg, _, _ := style.Decompose()
+                       st := defStyle.Background(fg)
+                       screen.SetContent(xOffset+colorcolumn, yOffset+visualLineN, ' ', nil, st)
+               }
+
                screenX = v.x
 
                // If there are gutter messages we need to display the '>>' symbol here
@@ -788,25 +804,25 @@ func (v *View) DisplayView() {
 
                        // Write the spaces before the line number if necessary
                        for i := 0; i < maxLineNumLength-len(lineNum); i++ {
-                               screen.SetContent(screenX, yOffset+visualLineN, ' ', nil, lineNumStyle)
+                               screen.SetContent(screenX+divider, yOffset+visualLineN, ' ', nil, lineNumStyle)
                                screenX++
                        }
                        if softwrapped && visualLineN != 0 {
                                // Pad without the line number because it was written on the visual line before
                                for range lineNum {
-                                       screen.SetContent(screenX, yOffset+visualLineN, ' ', nil, lineNumStyle)
+                                       screen.SetContent(screenX+divider, yOffset+visualLineN, ' ', nil, lineNumStyle)
                                        screenX++
                                }
                        } else {
                                // Write the actual line number
                                for _, ch := range lineNum {
-                                       screen.SetContent(screenX, yOffset+visualLineN, ch, nil, lineNumStyle)
+                                       screen.SetContent(screenX+divider, yOffset+visualLineN, ch, nil, lineNumStyle)
                                        screenX++
                                }
                        }
 
                        // Write the extra space
-                       screen.SetContent(screenX, yOffset+visualLineN, ' ', nil, lineNumStyle)
+                       screen.SetContent(screenX+divider, yOffset+visualLineN, ' ', nil, lineNumStyle)
                        screenX++
                }
 
@@ -816,23 +832,28 @@ func (v *View) DisplayView() {
                        if char != nil {
                                lineStyle := char.style
 
-                               charLoc := char.realLoc
-                               if v.Cursor.HasSelection() &&
-                                       (charLoc.GreaterEqual(v.Cursor.CurSelection[0]) && charLoc.LessThan(v.Cursor.CurSelection[1]) ||
-                                               charLoc.LessThan(v.Cursor.CurSelection[0]) && charLoc.GreaterEqual(v.Cursor.CurSelection[1])) {
-                                       // The current character is selected
-                                       lineStyle = defStyle.Reverse(true)
-
-                                       if style, ok := colorscheme["selection"]; ok {
-                                               lineStyle = style
-                                       }
+                               colorcolumn := int(v.Buf.Settings["colorcolumn"].(float64))
+                               if colorcolumn != 0 && char.visualLoc.X == colorcolumn {
+                                       style := GetColor("color-column")
+                                       fg, _, _ := style.Decompose()
+                                       lineStyle = lineStyle.Background(fg)
                                }
 
-                               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
+                               charLoc := char.realLoc
+                               for _, c := range v.Buf.cursors {
+                                       v.Cursor = c
+                                       if v.Cursor.HasSelection() &&
+                                               (charLoc.GreaterEqual(v.Cursor.CurSelection[0]) && charLoc.LessThan(v.Cursor.CurSelection[1]) ||
+                                                       charLoc.LessThan(v.Cursor.CurSelection[0]) && charLoc.GreaterEqual(v.Cursor.CurSelection[1])) {
+                                               // The current character is selected
+                                               lineStyle = defStyle.Reverse(true)
+
+                                               if style, ok := colorscheme["selection"]; ok {
+                                                       lineStyle = style
+                                               }
+                                       }
                                }
+                               v.Cursor = &v.Buf.Cursor
 
                                if v.Buf.Settings["cursorline"].(bool) && tabs[curTab].CurView == v.Num &&
                                        !v.Cursor.HasSelection() && v.Cursor.Y == realLineN {
@@ -843,6 +864,16 @@ func (v *View) DisplayView() {
 
                                screen.SetContent(xOffset+char.visualLoc.X, yOffset+char.visualLoc.Y, char.drawChar, nil, lineStyle)
 
+                               for i, 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 || i != 0) {
+                                               ShowMultiCursor(xOffset+char.visualLoc.X, yOffset+char.visualLoc.Y, i)
+                                               cursorSet = true
+                                       }
+                               }
+                               v.Cursor = &v.Buf.Cursor
+
                                lastChar = char
                        }
                }
@@ -850,19 +881,30 @@ func (v *View) DisplayView() {
                lastX := 0
                var realLoc Loc
                var visualLoc Loc
+               var cx, cy int
                if lastChar != nil {
                        lastX = xOffset + lastChar.visualLoc.X + lastChar.width
-                       if tabs[curTab].CurView == v.Num && !v.Cursor.HasSelection() &&
-                               v.Cursor.Y == lastChar.realLoc.Y && v.Cursor.X == lastChar.realLoc.X+1 {
-                               screen.ShowCursor(lastX, yOffset+lastChar.visualLoc.Y)
+                       for i, c := range v.Buf.cursors {
+                               v.Cursor = c
+                               if tabs[curTab].CurView == v.Num && !v.Cursor.HasSelection() &&
+                                       v.Cursor.Y == lastChar.realLoc.Y && v.Cursor.X == lastChar.realLoc.X+1 {
+                                       ShowMultiCursor(lastX, yOffset+lastChar.visualLoc.Y, i)
+                                       cx, cy = lastX, yOffset+lastChar.visualLoc.Y
+                               }
                        }
-                       realLoc = Loc{lastChar.realLoc.X, realLineN}
+                       v.Cursor = &v.Buf.Cursor
+                       realLoc = Loc{lastChar.realLoc.X + 1, realLineN}
                        visualLoc = Loc{lastX - xOffset, lastChar.visualLoc.Y}
                } else if len(line) == 0 {
-                       if tabs[curTab].CurView == v.Num && !v.Cursor.HasSelection() &&
-                               v.Cursor.Y == realLineN {
-                               screen.ShowCursor(xOffset, yOffset+visualLineN)
+                       for i, c := range v.Buf.cursors {
+                               v.Cursor = c
+                               if tabs[curTab].CurView == v.Num && !v.Cursor.HasSelection() &&
+                                       v.Cursor.Y == realLineN {
+                                       ShowMultiCursor(xOffset, yOffset+visualLineN, i)
+                                       cx, cy = xOffset, yOffset+visualLineN
+                               }
                        }
+                       v.Cursor = &v.Buf.Cursor
                        lastX = xOffset
                        realLoc = Loc{0, realLineN}
                        visualLoc = Loc{0, visualLineN}
@@ -886,33 +928,44 @@ func (v *View) DisplayView() {
                                style := GetColor("cursor-line")
                                fg, _, _ := style.Decompose()
                                style = style.Background(fg)
-                               screen.SetContent(i, yOffset+visualLineN, ' ', nil, style)
+                               if !(tabs[curTab].CurView == v.Num && !v.Cursor.HasSelection() && i == cx && yOffset+visualLineN == cy) {
+                                       screen.SetContent(i, yOffset+visualLineN, ' ', nil, style)
+                               }
                        }
                }
        }
 
-       if v.x != 0 && visualLineN < v.Height {
+       if divider != 0 {
                dividerStyle := defStyle
                if style, ok := colorscheme["divider"]; ok {
                        dividerStyle = style
                }
-               for i := visualLineN + 1; i < v.Height; i++ {
+               for i := 0; i < v.Height; i++ {
                        screen.SetContent(v.x, yOffset+i, '|', nil, dividerStyle.Reverse(true))
                }
        }
 }
 
-// DisplayCursor draws the current buffer's cursor to the screen
-func (v *View) DisplayCursor(x, y int) {
-       // screen.ShowCursor(v.x+v.Cursor.GetVisualX()+v.lineNumOffset-v.leftCol, y)
-       screen.ShowCursor(x, y)
+// ShowMultiCursor will display a cursor at a location
+// If i == 0 then the terminal cursor will be used
+// Otherwise a fake cursor will be drawn at the position
+func ShowMultiCursor(x, y, i int) {
+       if i == 0 {
+               screen.ShowCursor(x, y)
+       } else {
+               r, _, _, _ := screen.GetContent(x, y)
+               screen.SetContent(x, y, r, nil, defStyle.Reverse(true))
+       }
 }
 
 // Display renders the view, the cursor, and statusline
 func (v *View) Display() {
+       if globalSettings["termtitle"].(bool) {
+               screen.SetTitle("micro: " + v.Buf.GetName())
+       }
        v.DisplayView()
        // Don't draw the cursor if it is out of the viewport or if it has a selection
-       if (v.Cursor.Y-v.Topline < 0 || v.Cursor.Y-v.Topline > v.Height-1) || v.Cursor.HasSelection() {
+       if v.Num == tabs[curTab].CurView && (v.Cursor.Y-v.Topline < 0 || v.Cursor.Y-v.Topline > v.Height-1 || v.Cursor.HasSelection()) {
                screen.HideCursor()
        }
        _, screenH := screen.Size()