]> git.lizzy.rs Git - micro.git/blobdiff - cmd/micro/view.go
Updating to make overwrite mode as an action
[micro.git] / cmd / micro / view.go
index fa628b18e7ac173c0d1c2484d7e281ce2e4528b0..e53c66180b7fec021d8a1d419bcea078c299cd7a 100644 (file)
@@ -1,19 +1,21 @@
 package main
 
 import (
+       "fmt"
        "os"
+       "reflect"
        "strconv"
        "strings"
        "time"
 
-       "github.com/mitchellh/go-homedir"
        "github.com/zyedidia/tcell"
 )
 
+// The ViewType defines what kind of view this is
 type ViewType struct {
-       kind     int
-       readonly bool // The file cannot be edited
-       scratch  bool // The file cannot be saved
+       Kind     int
+       Readonly bool // The file cannot be edited
+       Scratch  bool // The file cannot be saved
 }
 
 var (
@@ -21,6 +23,7 @@ var (
        vtHelp    = ViewType{1, true, true}
        vtLog     = ViewType{2, true, true}
        vtScratch = ViewType{3, false, true}
+       vtRaw     = ViewType{4, true, true}
 )
 
 // The View struct stores information about a view into a buffer.
@@ -62,7 +65,7 @@ type View struct {
        // The buffer
        Buf *Buffer
        // The statusline
-       sline Statusline
+       sline *Statusline
 
        // Since tcell doesn't differentiate between a mouse release event
        // and a mouse move event with no keys pressed, we need to keep
@@ -70,9 +73,12 @@ type View struct {
        // 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       Loc
 
        // lastCutTime stores when the last ctrl+k was issued.
        // It is used for clearing the clipboard to replace it with fresh cut lines.
@@ -91,6 +97,8 @@ type View struct {
        cellview *CellView
 
        splitNode *LeafNode
+
+       scrollbar *ScrollBar
 }
 
 // NewView returns a new fullscreen view
@@ -116,7 +124,11 @@ func NewViewWidthHeight(buf *Buffer, w, h int) *View {
 
        v.messages = make(map[string][]GutterMessage)
 
-       v.sline = Statusline{
+       v.sline = &Statusline{
+               view: v,
+       }
+
+       v.scrollbar = &ScrollBar{
                view: v,
        }
 
@@ -198,7 +210,7 @@ func (v *View) ScrollDown(n int) {
 // If there are unsaved changes, the user will be asked if the view can be closed
 // causing them to lose the unsaved changes
 func (v *View) CanClose() bool {
-       if v.Type == vtDefault && v.Buf.IsModified {
+       if v.Type == vtDefault && v.Buf.Modified() {
                var choice bool
                var canceled bool
                if v.Buf.Settings["autosave"].(bool) {
@@ -210,15 +222,12 @@ func (v *View) CanClose() bool {
                        //if char == 'y' {
                        if choice {
                                v.Save(true)
-                               return true
-                       } else {
-                               return true
                        }
+               } else {
+                       return false
                }
-       } else {
-               return true
        }
-       return false
+       return true
 }
 
 // OpenBuffer opens a new buffer in this view.
@@ -238,13 +247,15 @@ func (v *View) OpenBuffer(buf *Buffer) {
        // Set mouseReleased to true because we assume the mouse is not being pressed when
        // the editor is opened
        v.mouseReleased = true
+       // Set isOverwriteMode to false, because we assume we are in the default mode when editor
+       // is opened
+       v.isOverwriteMode = false
        v.lastClickTime = time.Time{}
 }
 
 // Open opens the given file in the view
 func (v *View) Open(filename string) {
-       home, _ := homedir.Dir()
-       filename = strings.Replace(filename, "~", home, 1)
+       filename = ReplaceHome(filename)
        file, err := os.Open(filename)
        fileInfo, _ := os.Stat(filename)
 
@@ -285,7 +296,7 @@ func (v *View) ReOpen() {
 // HSplit opens a horizontal split with the given buffer
 func (v *View) HSplit(buf *Buffer) {
        i := 0
-       if v.Buf.Settings["splitBottom"].(bool) {
+       if v.Buf.Settings["splitbottom"].(bool) {
                i = 1
        }
        v.splitNode.HSplit(buf, v.Num+i)
@@ -294,7 +305,7 @@ func (v *View) HSplit(buf *Buffer) {
 // VSplit opens a vertical split with the given buffer
 func (v *View) VSplit(buf *Buffer) {
        i := 0
-       if v.Buf.Settings["splitRight"].(bool) {
+       if v.Buf.Settings["splitright"].(bool) {
                i = 1
        }
        v.splitNode.VSplit(buf, v.Num+i)
@@ -450,13 +461,14 @@ func (v *View) MoveToMouseClick(x, y int) {
        v.Cursor.LastVisualX = v.Cursor.GetVisualX()
 }
 
+// Execute actions executes the supplied actions
 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 {
+               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) {
@@ -479,13 +491,33 @@ func (v *View) ExecuteActions(actions []func(*View, bool) bool) bool {
        return relocate
 }
 
-func (v *View) SetCursor(c *Cursor) {
+// SetCursor sets the view's and buffer's cursor
+func (v *View) SetCursor(c *Cursor) bool {
+       if c == nil {
+               return false
+       }
        v.Cursor = c
        v.Buf.curCursor = c.Num
+
+       return true
 }
 
 // HandleEvent handles an event passed by the main loop
 func (v *View) HandleEvent(event tcell.Event) {
+       if v.Type == vtRaw {
+               v.Buf.Insert(v.Cursor.Loc, reflect.TypeOf(event).String()[7:])
+               v.Buf.Insert(v.Cursor.Loc, fmt.Sprintf(": %q\n", event.EscSeq()))
+
+               switch e := event.(type) {
+               case *tcell.EventKey:
+                       if e.Key() == tcell.KeyCtrlQ {
+                               v.Quit(true)
+                       }
+               }
+
+               return
+       }
+
        // This bool determines whether the view is relocated at the end of the function
        // By default it's true because most events should cause a relocate
        relocate := true
@@ -493,6 +525,24 @@ func (v *View) HandleEvent(event tcell.Event) {
        v.Buf.CheckModTime()
 
        switch e := event.(type) {
+       case *tcell.EventRaw:
+               for key, actions := range bindings {
+                       if key.keyCode == -1 {
+                               if e.EscSeq() == key.escape {
+                                       for _, c := range v.Buf.cursors {
+                                               ok := v.SetCursor(c)
+                                               if !ok {
+                                                       break
+                                               }
+                                               relocate = false
+                                               relocate = v.ExecuteActions(actions) || relocate
+                                       }
+                                       v.SetCursor(&v.Buf.Cursor)
+                                       v.Buf.MergeCursors()
+                                       break
+                               }
+                       }
+               }
        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
@@ -505,7 +555,10 @@ func (v *View) HandleEvent(event tcell.Event) {
                                }
                                if e.Modifiers() == key.modifiers {
                                        for _, c := range v.Buf.cursors {
-                                               v.SetCursor(c)
+                                               ok := v.SetCursor(c)
+                                               if !ok {
+                                                       break
+                                               }
                                                relocate = false
                                                isBinding = true
                                                relocate = v.ExecuteActions(actions) || relocate
@@ -516,9 +569,10 @@ func (v *View) HandleEvent(event tcell.Event) {
                                }
                        }
                }
+
                if !isBinding && e.Key() == tcell.KeyRune {
                        // 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.SetCursor(c)
 
@@ -527,7 +581,14 @@ func (v *View) HandleEvent(event tcell.Event) {
                                                v.Cursor.DeleteSelection()
                                                v.Cursor.ResetSelection()
                                        }
-                                       v.Buf.Insert(v.Cursor.Loc, string(e.Rune()))
+
+                                       if v.isOverwriteMode {
+                                               next := v.Cursor.Loc
+                                               next.X++
+                                               v.Buf.Replace(v.Cursor.Loc, next, string(e.Rune()))
+                                       } else {
+                                               v.Buf.Insert(v.Cursor.Loc, string(e.Rune()))
+                                       }
 
                                        for pl := range loadedPlugins {
                                                _, err := Call(pl+".onRune", string(e.Rune()), v)
@@ -545,7 +606,7 @@ func (v *View) HandleEvent(event tcell.Event) {
                }
        case *tcell.EventPaste:
                // Check viewtype if readonly don't paste (readonly help and log view etc.)
-               if v.Type.readonly == false {
+               if v.Type.Readonly == false {
                        if !PreActionCall("Paste", v) {
                                break
                        }
@@ -553,7 +614,6 @@ func (v *View) HandleEvent(event tcell.Event) {
                        for _, c := range v.Buf.cursors {
                                v.SetCursor(c)
                                v.paste(e.Text())
-
                        }
                        v.SetCursor(&v.Buf.Cursor)
 
@@ -568,7 +628,10 @@ func (v *View) HandleEvent(event tcell.Event) {
                for key, actions := range bindings {
                        if button == key.buttons && e.Modifiers() == key.modifiers {
                                for _, c := range v.Buf.cursors {
-                                       v.SetCursor(c)
+                                       ok := v.SetCursor(c)
+                                       if !ok {
+                                               break
+                                       }
                                        relocate = v.ExecuteActions(actions) || relocate
                                }
                                v.SetCursor(&v.Buf.Cursor)
@@ -673,13 +736,14 @@ func (v *View) openHelp(helpPage string) {
        }
 }
 
+// DisplayView draws the view to the screen
 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...
+       if v.Type == vtLog || v.Type == vtRaw {
+               // Log or raw views should always follow the cursor...
                v.Relocate()
        }
 
@@ -935,7 +999,7 @@ func (v *View) DisplayView() {
 
                if v.Buf.Settings["cursorline"].(bool) && tabs[curTab].CurView == v.Num &&
                        !v.Cursor.HasSelection() && v.Cursor.Y == realLineN {
-                       for i := lastX; i < xOffset+v.Width; i++ {
+                       for i := lastX; i < xOffset+v.Width-v.lineNumOffset; i++ {
                                style := GetColor("cursor-line")
                                fg, _, _ := style.Decompose()
                                style = style.Background(fg)
@@ -980,6 +1044,11 @@ func (v *View) Display() {
                screen.HideCursor()
        }
        _, screenH := screen.Size()
+
+       if v.Buf.Settings["scrollbar"].(bool) {
+               v.scrollbar.Display()
+       }
+
        if v.Buf.Settings["statusline"].(bool) {
                v.sline.Display()
        } else if (v.y + v.Height) != screenH-1 {