]> git.lizzy.rs Git - micro.git/blobdiff - cmd/micro/view.go
Code optimisation (#1117)
[micro.git] / cmd / micro / view.go
index ff7bb666e328c7704969b50fe6a1a05335df0bb9..d6d9470f2dac66d2f4b91cd230125857cdec6155 100644 (file)
@@ -1,24 +1,29 @@
 package main
 
 import (
-       "os"
+       "fmt"
+       "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 {
-       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 (
-       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}
+       vtRaw     = ViewType{4, true, true}
+       vtTerm    = ViewType{5, true, true}
 )
 
 // The View struct stores information about a view into a buffer.
@@ -60,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
@@ -68,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.
@@ -86,9 +94,16 @@ type View struct {
        // Same here, just to keep track for mouse move events
        tripleClick bool
 
+       // The cellview used for displaying and syntax highlighting
        cellview *CellView
 
        splitNode *LeafNode
+
+       // The scrollbar
+       scrollbar *ScrollBar
+
+       // Virtual terminal
+       term *Terminal
 }
 
 // NewView returns a new fullscreen view
@@ -114,7 +129,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,
        }
 
@@ -122,6 +141,8 @@ func NewViewWidthHeight(buf *Buffer, w, h int) *View {
                v.Height--
        }
 
+       v.term = new(Terminal)
+
        for pl := range loadedPlugins {
                _, err := Call(pl+".onViewOpen", v)
                if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") {
@@ -142,6 +163,24 @@ func (v *View) ToggleStatusLine() {
        }
 }
 
+// StartTerminal execs a command in this view
+func (v *View) StartTerminal(execCmd []string, wait bool, getOutput bool, luaCallback string) error {
+       err := v.term.Start(execCmd, v, getOutput)
+       v.term.wait = wait
+       v.term.callback = luaCallback
+       if err == nil {
+               v.term.Resize(v.Width, v.Height)
+               v.Type = vtTerm
+       }
+       return err
+}
+
+// CloseTerminal shuts down the tty running in this view
+// and returns it to the default view type
+func (v *View) CloseTerminal() {
+       v.term.Stop()
+}
+
 // ToggleTabbar creates an extra row for the tabbar if necessary
 func (v *View) ToggleTabbar() {
        if len(tabs) > 1 {
@@ -159,7 +198,10 @@ func (v *View) ToggleTabbar() {
 }
 
 func (v *View) paste(clip string) {
-       leadingWS := GetLeadingWhitespace(v.Buf.Line(v.Cursor.Y))
+       leadingWS := ""
+       if v.Cursor.X > 0 {
+               leadingWS = GetLeadingWhitespace(v.Buf.Line(v.Cursor.Y))
+       }
 
        if v.Cursor.HasSelection() {
                v.Cursor.DeleteSelection()
@@ -167,7 +209,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")
 }
@@ -185,9 +227,9 @@ func (v *View) ScrollUp(n int) {
 // ScrollDown scrolls the view down n lines (if possible)
 func (v *View) ScrollDown(n int) {
        // Try to scroll by n but if it would overflow, scroll by 1
-       if v.Topline+n <= v.Buf.NumLines-v.Height {
+       if v.Topline+n <= v.Buf.NumLines {
                v.Topline += n
-       } else if v.Topline < v.Buf.NumLines-v.Height {
+       } else if v.Topline < v.Buf.NumLines-1 {
                v.Topline++
        }
 }
@@ -196,26 +238,24 @@ 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 {
-               var char rune
+       if v.Type == vtDefault && v.Buf.Modified() {
+               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' {
-                               return true
                        }
+               } else {
+                       return false
                }
-       } else {
-               return true
        }
-       return false
+       return true
 }
 
 // OpenBuffer opens a new buffer in this view.
@@ -235,23 +275,21 @@ 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{}
+
+       GlobalPluginCall("onBufferOpen", v.Buf)
+       GlobalPluginCall("onViewOpen", v)
 }
 
 // Open opens the given file in the view
-func (v *View) Open(filename string) {
-       home, _ := homedir.Dir()
-       filename = strings.Replace(filename, "~", home, 1)
-       file, err := os.Open(filename)
-       defer file.Close()
-
-       var buf *Buffer
+func (v *View) Open(path string) {
+       buf, err := NewBufferFromFile(path)
        if err != nil {
-               messenger.Message(err.Error())
-               // File does not exist -- create an empty buffer with that name
-               buf = NewBuffer(strings.NewReader(""), filename)
-       } else {
-               buf = NewBuffer(file, filename)
+               messenger.Error(err)
+               return
        }
        v.OpenBuffer(buf)
 }
@@ -275,7 +313,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)
@@ -284,7 +322,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)
@@ -345,6 +383,10 @@ func (v *View) GetSoftWrapLocation(vx, vy int) (int, int) {
        return 0, 0
 }
 
+// Bottomline returns the line number of the lowest line in the view
+// You might think that this is obviously just v.Topline + v.Height
+// but if softwrap is enabled things get complicated since one buffer
+// line can take up multiple lines in the view
 func (v *View) Bottomline() int {
        if !v.Buf.Settings["softwrap"].(bool) {
                return v.Topline + v.Height
@@ -397,7 +439,7 @@ func (v *View) Relocate() bool {
        if cy > v.Topline+height-1-scrollmargin && cy < v.Buf.NumLines-scrollmargin {
                v.Topline = cy - height + 1 + scrollmargin
                ret = true
-       } else if cy >= v.Buf.NumLines-scrollmargin && cy > height {
+       } else if cy >= v.Buf.NumLines-scrollmargin && cy >= height {
                v.Topline = v.Buf.NumLines - height
                ret = true
        }
@@ -416,6 +458,31 @@ func (v *View) Relocate() bool {
        return ret
 }
 
+// GetMouseClickLocation gets the location in the buffer from a mouse click
+// on the screen
+func (v *View) GetMouseClickLocation(x, y int) (int, int) {
+       x -= v.lineNumOffset - v.leftCol + v.x
+       y += v.Topline - v.y
+
+       if y-v.Topline > v.Height-1 {
+               v.ScrollDown(1)
+               y = v.Height + v.Topline - 1
+       }
+       if y < 0 {
+               y = 0
+       }
+       if x < 0 {
+               x = 0
+       }
+
+       newX, newY := v.GetSoftWrapLocation(x, y)
+       if newX > Count(v.Buf.Line(newY)) {
+               newX = Count(v.Buf.Line(newY))
+       }
+
+       return newX, newY
+}
+
 // MoveToMouseClick moves the cursor to location x, y assuming x, y were given
 // by a mouse click
 func (v *View) MoveToMouseClick(x, y int) {
@@ -431,7 +498,6 @@ func (v *View) MoveToMouseClick(x, y int) {
        }
 
        x, y = v.GetSoftWrapLocation(x, y)
-       // x = v.Cursor.GetCharPosInLine(y, x)
        if x > Count(v.Buf.Line(y)) {
                x = Count(v.Buf.Line(y))
        }
@@ -440,8 +506,69 @@ 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)
+               curv := CurView()
+               if curv.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(curv, true) || relocate
+                       // Macro
+                       if funcName != "ToggleMacro" && funcName != "PlayMacro" {
+                               if recordingMacro {
+                                       curMacro = append(curMacro, action)
+                               }
+                       }
+               }
+       }
+
+       return relocate
+}
+
+// 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 == vtTerm {
+               v.term.HandleEvent(event)
+               return
+       }
+
+       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
@@ -449,126 +576,138 @@ 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
-               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 {
+                                               ok := v.SetCursor(c)
+                                               if !ok {
+                                                       break
+                                               }
                                                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.SetCursor(&v.Buf.Cursor)
+                                       v.Buf.MergeCursors()
+                                       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.SetCursor(c)
+
+                                       // Insert a character
+                                       if v.Cursor.HasSelection() {
+                                               v.Cursor.DeleteSelection()
+                                               v.Cursor.ResetSelection()
+                                       }
 
-                       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 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()))
+                                       }
 
-                       if recordingMacro {
-                               curMacro = append(curMacro, 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)
+                                               }
+                                       }
+
+                                       if recordingMacro {
+                                               curMacro = append(curMacro, e.Rune())
+                                       }
+                               }
+                               v.SetCursor(&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.SetCursor(c)
+                               v.paste(e.Text())
+                       }
+                       v.SetCursor(&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")
+               for key, actions := range bindings {
+                       if button == key.buttons && e.Modifiers() == key.modifiers {
+                               for _, c := range v.Buf.cursors {
+                                       ok := v.SetCursor(c)
+                                       if !ok {
+                                               break
                                        }
-                               } 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
+                                       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.SetCursor(&v.Buf.Cursor)
+                               v.Buf.MergeCursors()
+                       }
+               }
+
+               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
@@ -582,22 +721,24 @@ 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)
                }
        }
 
        if relocate {
                v.Relocate()
+               // We run relocate again because there's a bug with relocating with softwrap
+               // when for example you jump to the bottom of the buffer and it tries to
+               // calculate where to put the topline so that the bottom line is at the bottom
+               // of the terminal and it runs into problems with visual lines vs real lines.
+               // This is (hopefully) a temporary solution
+               v.Relocate()
        }
 }
 
+func (v *View) mainCursor() bool {
+       return v.Buf.curCursor == len(v.Buf.cursors)-1
+}
+
 // GutterMessage creates a message in this view's gutter
 func (v *View) GutterMessage(section string, lineN int, msg string, kind int) {
        lineN--
@@ -634,7 +775,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 {
@@ -646,341 +787,324 @@ func (v *View) openHelp(helpPage string) {
        }
 }
 
-func (v *View) drawCell(x, y int, ch rune, combc []rune, style tcell.Style) {
-       if x >= v.x && x < v.x+v.Width && y >= v.y && y < v.y+v.Height {
-               screen.SetContent(x, y, ch, combc, style)
-       }
-}
-
-// DisplayView renders the view to the screen
-// func (v *View) DisplayView() {
-//     if v.Type == vtLog {
-//             // Log views should always follow the cursor...
-//             v.Relocate()
-//     }
-//
-//     if v.Buf.Settings["syntax"].(bool) {
-//             v.matches = Match(v)
-//     }
-//
-//     // The charNum we are currently displaying
-//     // starts at the start of the viewport
-//     charNum := Loc{0, v.Topline}
-//
-//     // Convert the length of buffer to a string, and get the length of the string
-//     // We are going to have to offset by that amount
-//     maxLineLength := len(strconv.Itoa(v.Buf.NumLines))
-//
-//     if v.Buf.Settings["ruler"] == true {
-//             // + 1 for the little space after the line number
-//             v.lineNumOffset = maxLineLength + 1
-//     } else {
-//             v.lineNumOffset = 0
-//     }
-//
-//     // We need to add to the line offset if there are gutter messages
-//     var hasGutterMessages bool
-//     for _, v := range v.messages {
-//             if len(v) > 0 {
-//                     hasGutterMessages = true
-//             }
-//     }
-//     if hasGutterMessages {
-//             v.lineNumOffset += 2
-//     }
-//
-//     if v.x != 0 {
-//             // One space for the extra split divider
-//             v.lineNumOffset++
-//     }
-//
-//     // These represent the current screen coordinates
-//     screenX, screenY := v.x, v.y-1
-//
-//     highlightStyle := defStyle
-//     curLineN := 0
-//
-//     // ViewLine is the current line from the top of the viewport
-//     for viewLine := 0; viewLine < v.Height; viewLine++ {
-//             screenY++
-//             screenX = v.x
-//
-//             // This is the current line number of the buffer that we are drawing
-//             curLineN = viewLine + v.Topline
-//
-//             if screenY-v.y >= v.Height {
-//                     break
-//             }
-//
-//             if v.x != 0 {
-//                     // Draw the split divider
-//                     v.drawCell(screenX, screenY, '|', nil, defStyle.Reverse(true))
-//                     screenX++
-//             }
-//
-//             // If the buffer is smaller than the view height we have to clear all this space
-//             if curLineN >= v.Buf.NumLines {
-//                     for i := screenX; i < v.x+v.Width; i++ {
-//                             v.drawCell(i, screenY, ' ', nil, defStyle)
-//                     }
-//
-//                     continue
-//             }
-//             line := v.Buf.Line(curLineN)
-//
-//             // If there are gutter messages we need to display the '>>' symbol here
-//             if hasGutterMessages {
-//                     // msgOnLine stores whether or not there is a gutter message on this line in particular
-//                     msgOnLine := false
-//                     for k := range v.messages {
-//                             for _, msg := range v.messages[k] {
-//                                     if msg.lineNum == curLineN {
-//                                             msgOnLine = true
-//                                             gutterStyle := defStyle
-//                                             switch msg.kind {
-//                                             case GutterInfo:
-//                                                     if style, ok := colorscheme["gutter-info"]; ok {
-//                                                             gutterStyle = style
-//                                                     }
-//                                             case GutterWarning:
-//                                                     if style, ok := colorscheme["gutter-warning"]; ok {
-//                                                             gutterStyle = style
-//                                                     }
-//                                             case GutterError:
-//                                                     if style, ok := colorscheme["gutter-error"]; ok {
-//                                                             gutterStyle = style
-//                                                     }
-//                                             }
-//                                             v.drawCell(screenX, screenY, '>', nil, gutterStyle)
-//                                             screenX++
-//                                             v.drawCell(screenX, screenY, '>', nil, gutterStyle)
-//                                             screenX++
-//                                             if v.Cursor.Y == curLineN && !messenger.hasPrompt {
-//                                                     messenger.Message(msg.msg)
-//                                                     messenger.gutterMessage = true
-//                                             }
-//                                     }
-//                             }
-//                     }
-//                     // If there is no message on this line we just display an empty offset
-//                     if !msgOnLine {
-//                             v.drawCell(screenX, screenY, ' ', nil, defStyle)
-//                             screenX++
-//                             v.drawCell(screenX, screenY, ' ', nil, defStyle)
-//                             screenX++
-//                             if v.Cursor.Y == curLineN && messenger.gutterMessage {
-//                                     messenger.Reset()
-//                                     messenger.gutterMessage = false
-//                             }
-//                     }
-//             }
-//
-//             lineNumStyle := defStyle
-//             if v.Buf.Settings["ruler"] == true {
-//                     // Write the line number
-//                     if style, ok := colorscheme["line-number"]; ok {
-//                             lineNumStyle = style
-//                     }
-//                     if style, ok := colorscheme["current-line-number"]; ok {
-//                             if curLineN == v.Cursor.Y && tabs[curTab].CurView == v.Num && !v.Cursor.HasSelection() {
-//                                     lineNumStyle = style
-//                             }
-//                     }
-//
-//                     lineNum := strconv.Itoa(curLineN + 1)
-//
-//                     // Write the spaces before the line number if necessary
-//                     for i := 0; i < maxLineLength-len(lineNum); i++ {
-//                             v.drawCell(screenX, screenY, ' ', nil, lineNumStyle)
-//                             screenX++
-//                     }
-//                     // Write the actual line number
-//                     for _, ch := range lineNum {
-//                             v.drawCell(screenX, screenY, ch, nil, lineNumStyle)
-//                             screenX++
-//                     }
-//
-//                     // Write the extra space
-//                     v.drawCell(screenX, screenY, ' ', nil, lineNumStyle)
-//                     screenX++
-//             }
-//
-//             // Now we actually draw the line
-//             colN := 0
-//             strWidth := 0
-//             tabSize := int(v.Buf.Settings["tabsize"].(float64))
-//             for _, ch := range line {
-//                     if v.Buf.Settings["softwrap"].(bool) {
-//                             if screenX-v.x >= v.Width {
-//                                     screenY++
-//
-//                                     x := 0
-//                                     if hasGutterMessages {
-//                                             v.drawCell(v.x+x, screenY, ' ', nil, defStyle)
-//                                             x++
-//                                             v.drawCell(v.x+x, screenY, ' ', nil, defStyle)
-//                                             x++
-//                                     }
-//                                     for i := 0; i < v.lineNumOffset; i++ {
-//                                             screen.SetContent(v.x+i+x, screenY, ' ', nil, lineNumStyle)
-//                                     }
-//                                     screenX = v.x + v.lineNumOffset
-//                             }
-//                     }
-//
-//                     if tabs[curTab].CurView == v.Num && !v.Cursor.HasSelection() && v.Cursor.Y == curLineN && colN == v.Cursor.X {
-//                             v.DisplayCursor(screenX-v.leftCol, screenY)
-//                     }
-//
-//                     lineStyle := defStyle
-//
-//                     if v.Buf.Settings["syntax"].(bool) {
-//                             // Syntax highlighting is enabled
-//                             highlightStyle = v.matches[viewLine][colN]
-//                     }
-//
-//                     if v.Cursor.HasSelection() &&
-//                             (charNum.GreaterEqual(v.Cursor.CurSelection[0]) && charNum.LessThan(v.Cursor.CurSelection[1]) ||
-//                                     charNum.LessThan(v.Cursor.CurSelection[0]) && charNum.GreaterEqual(v.Cursor.CurSelection[1])) {
-//                             // The current character is selected
-//                             lineStyle = defStyle.Reverse(true)
-//
-//                             if style, ok := colorscheme["selection"]; ok {
-//                                     lineStyle = style
-//                             }
-//                     } else {
-//                             lineStyle = highlightStyle
-//                     }
-//
-//                     // We need to display the background of the linestyle with the correct color if cursorline is enabled
-//                     // and this is the current view and there is no selection on this line and the cursor is on this line
-//                     if v.Buf.Settings["cursorline"].(bool) && tabs[curTab].CurView == v.Num && !v.Cursor.HasSelection() && v.Cursor.Y == curLineN {
-//                             if style, ok := colorscheme["cursor-line"]; ok {
-//                                     fg, _, _ := style.Decompose()
-//                                     lineStyle = lineStyle.Background(fg)
-//                             }
-//                     }
-//
-//                     if ch == '\t' {
-//                             // If the character we are displaying is a tab, we need to do a bunch of special things
-//
-//                             // First the user may have configured an `indent-char` to be displayed to show that this
-//                             // is a tab character
-//                             lineIndentStyle := defStyle
-//                             if style, ok := colorscheme["indent-char"]; ok && v.Buf.Settings["indentchar"].(string) != " " {
-//                                     lineIndentStyle = style
-//                             }
-//                             if v.Cursor.HasSelection() &&
-//                                     (charNum.GreaterEqual(v.Cursor.CurSelection[0]) && charNum.LessThan(v.Cursor.CurSelection[1]) ||
-//                                             charNum.LessThan(v.Cursor.CurSelection[0]) && charNum.GreaterEqual(v.Cursor.CurSelection[1])) {
-//
-//                                     lineIndentStyle = defStyle.Reverse(true)
-//
-//                                     if style, ok := colorscheme["selection"]; ok {
-//                                             lineIndentStyle = style
-//                                     }
-//                             }
-//                             if v.Buf.Settings["cursorline"].(bool) && tabs[curTab].CurView == v.Num && !v.Cursor.HasSelection() && v.Cursor.Y == curLineN {
-//                                     if style, ok := colorscheme["cursor-line"]; ok {
-//                                             fg, _, _ := style.Decompose()
-//                                             lineIndentStyle = lineIndentStyle.Background(fg)
-//                                     }
-//                             }
-//                             // Here we get the indent char
-//                             indentChar := []rune(v.Buf.Settings["indentchar"].(string))
-//                             if screenX-v.x-v.leftCol >= v.lineNumOffset {
-//                                     v.drawCell(screenX-v.leftCol, screenY, indentChar[0], nil, lineIndentStyle)
-//                             }
-//                             // Now the tab has to be displayed as a bunch of spaces
-//                             visLoc := strWidth
-//                             remainder := tabSize - (visLoc % tabSize)
-//                             for i := 0; i < remainder-1; i++ {
-//                                     screenX++
-//                                     if screenX-v.x-v.leftCol >= v.lineNumOffset {
-//                                             v.drawCell(screenX-v.leftCol, screenY, ' ', nil, lineStyle)
-//                                     }
-//                             }
-//                             strWidth += remainder
-//                     } else if runewidth.RuneWidth(ch) > 1 {
-//                             if screenX-v.x-v.leftCol >= v.lineNumOffset {
-//                                     v.drawCell(screenX, screenY, ch, nil, lineStyle)
-//                             }
-//                             for i := 0; i < runewidth.RuneWidth(ch)-1; i++ {
-//                                     screenX++
-//                                     if screenX-v.x-v.leftCol >= v.lineNumOffset {
-//                                             v.drawCell(screenX-v.leftCol, screenY, '<', nil, lineStyle)
-//                                     }
-//                             }
-//                             strWidth += StringWidth(string(ch), tabSize)
-//                     } else {
-//                             if screenX-v.x-v.leftCol >= v.lineNumOffset {
-//                                     v.drawCell(screenX-v.leftCol, screenY, ch, nil, lineStyle)
-//                             }
-//                             strWidth += StringWidth(string(ch), tabSize)
-//                     }
-//                     charNum = charNum.Move(1, v.Buf)
-//                     screenX++
-//                     colN++
-//             }
-//             // Here we are at a newline
-//
-//             if tabs[curTab].CurView == v.Num && !v.Cursor.HasSelection() && v.Cursor.Y == curLineN && colN == v.Cursor.X {
-//                     v.DisplayCursor(screenX-v.leftCol, screenY)
-//             }
-//
-//             // The newline may be selected, in which case we should draw the selection style
-//             // with a space to represent it
-//             if v.Cursor.HasSelection() &&
-//                     (charNum.GreaterEqual(v.Cursor.CurSelection[0]) && charNum.LessThan(v.Cursor.CurSelection[1]) ||
-//                             charNum.LessThan(v.Cursor.CurSelection[0]) && charNum.GreaterEqual(v.Cursor.CurSelection[1])) {
-//
-//                     selectStyle := defStyle.Reverse(true)
-//
-//                     if style, ok := colorscheme["selection"]; ok {
-//                             selectStyle = style
-//                     }
-//                     v.drawCell(screenX, screenY, ' ', nil, selectStyle)
-//                     screenX++
-//             }
-//
-//             charNum = charNum.Move(1, v.Buf)
-//
-//             for i := 0; i < v.Width; i++ {
-//                     lineStyle := defStyle
-//                     if v.Buf.Settings["cursorline"].(bool) && tabs[curTab].CurView == v.Num && !v.Cursor.HasSelection() && v.Cursor.Y == curLineN {
-//                             if style, ok := colorscheme["cursor-line"]; ok {
-//                                     fg, _, _ := style.Decompose()
-//                                     lineStyle = lineStyle.Background(fg)
-//                             }
-//                     }
-//                     if screenX-v.x-v.leftCol+i >= v.lineNumOffset {
-//                             colorcolumn := int(v.Buf.Settings["colorcolumn"].(float64))
-//                             if colorcolumn != 0 && screenX-v.lineNumOffset+i == colorcolumn-1 {
-//                                     if style, ok := colorscheme["color-column"]; ok {
-//                                             fg, _, _ := style.Decompose()
-//                                             lineStyle = lineStyle.Background(fg)
-//                                     }
-//                             }
-//                             v.drawCell(screenX-v.leftCol+i, screenY, ' ', nil, lineStyle)
-//                     }
-//             }
-//     }
-// }
-
-// 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)
+// DisplayView draws the view to the screen
+func (v *View) DisplayView() {
+       if v.Type == vtTerm {
+               v.term.Display()
+               return
+       }
+
+       if v.Buf.Settings["softwrap"].(bool) && v.leftCol != 0 {
+               v.leftCol = 0
+       }
+
+       if v.Type == vtLog || v.Type == vtRaw {
+               // Log or raw views should always follow the cursor...
+               v.Relocate()
+       }
+
+       // We need to know the string length of the largest line number
+       // so we can pad appropriately when displaying line numbers
+       maxLineNumLength := len(strconv.Itoa(v.Buf.NumLines))
+
+       if v.Buf.Settings["ruler"] == true {
+               // + 1 for the little space after the line number
+               v.lineNumOffset = maxLineNumLength + 1
+       } else {
+               v.lineNumOffset = 0
+       }
+
+       // We need to add to the line offset if there are gutter messages
+       var hasGutterMessages bool
+       for _, v := range v.messages {
+               if len(v) > 0 {
+                       hasGutterMessages = true
+               }
+       }
+       if hasGutterMessages {
+               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
+       yOffset := v.y
+
+       height := v.Height
+       width := v.Width
+       left := v.leftCol
+       top := v.Topline
+
+       v.cellview.Draw(v.Buf, top, height, left, width-v.lineNumOffset)
+
+       screenX := v.x
+       realLineN := top - 1
+       visualLineN := 0
+       var line []*Char
+       for visualLineN, line = range v.cellview.lines {
+               var firstChar *Char
+               if len(line) > 0 {
+                       firstChar = line[0]
+               }
+
+               var softwrapped bool
+               if firstChar != nil {
+                       if firstChar.realLoc.Y == realLineN {
+                               softwrapped = true
+                       }
+                       realLineN = firstChar.realLoc.Y
+               } else {
+                       realLineN++
+               }
+
+               colorcolumn := int(v.Buf.Settings["colorcolumn"].(float64))
+               if colorcolumn != 0 && xOffset+colorcolumn-v.leftCol < v.Width {
+                       style := GetColor("color-column")
+                       fg, _, _ := style.Decompose()
+                       st := defStyle.Background(fg)
+                       screen.SetContent(xOffset+colorcolumn-v.leftCol, yOffset+visualLineN, ' ', nil, st)
+               }
+
+               screenX = v.x
+
+               // If there are gutter messages we need to display the '>>' symbol here
+               if hasGutterMessages {
+                       // msgOnLine stores whether or not there is a gutter message on this line in particular
+                       msgOnLine := false
+                       for k := range v.messages {
+                               for _, msg := range v.messages[k] {
+                                       if msg.lineNum == realLineN {
+                                               msgOnLine = true
+                                               gutterStyle := defStyle
+                                               switch msg.kind {
+                                               case GutterInfo:
+                                                       if style, ok := colorscheme["gutter-info"]; ok {
+                                                               gutterStyle = style
+                                                       }
+                                               case GutterWarning:
+                                                       if style, ok := colorscheme["gutter-warning"]; ok {
+                                                               gutterStyle = style
+                                                       }
+                                               case GutterError:
+                                                       if style, ok := colorscheme["gutter-error"]; ok {
+                                                               gutterStyle = style
+                                                       }
+                                               }
+                                               screen.SetContent(screenX, yOffset+visualLineN, '>', nil, gutterStyle)
+                                               screenX++
+                                               screen.SetContent(screenX, yOffset+visualLineN, '>', nil, gutterStyle)
+                                               screenX++
+                                               if v.Cursor.Y == realLineN && !messenger.hasPrompt {
+                                                       messenger.Message(msg.msg)
+                                                       messenger.gutterMessage = true
+                                               }
+                                       }
+                               }
+                       }
+                       // If there is no message on this line we just display an empty offset
+                       if !msgOnLine {
+                               screen.SetContent(screenX, yOffset+visualLineN, ' ', nil, defStyle)
+                               screenX++
+                               screen.SetContent(screenX, yOffset+visualLineN, ' ', nil, defStyle)
+                               screenX++
+                               if v.Cursor.Y == realLineN && messenger.gutterMessage {
+                                       messenger.Reset()
+                                       messenger.gutterMessage = false
+                               }
+                       }
+               }
+
+               lineNumStyle := defStyle
+               if v.Buf.Settings["ruler"] == true {
+                       // Write the line number
+                       if style, ok := colorscheme["line-number"]; ok {
+                               lineNumStyle = style
+                       }
+                       if style, ok := colorscheme["current-line-number"]; ok {
+                               if realLineN == v.Cursor.Y && tabs[curTab].CurView == v.Num && !v.Cursor.HasSelection() {
+                                       lineNumStyle = style
+                               }
+                       }
+
+                       lineNum := strconv.Itoa(realLineN + 1)
+
+                       // Write the spaces before the line number if necessary
+                       for i := 0; i < maxLineNumLength-len(lineNum); i++ {
+                               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+divider, yOffset+visualLineN, ' ', nil, lineNumStyle)
+                                       screenX++
+                               }
+                       } else {
+                               // Write the actual line number
+                               for _, ch := range lineNum {
+                                       screen.SetContent(screenX+divider, yOffset+visualLineN, ch, nil, lineNumStyle)
+                                       screenX++
+                               }
+                       }
+
+                       // Write the extra space
+                       screen.SetContent(screenX+divider, yOffset+visualLineN, ' ', nil, lineNumStyle)
+                       screenX++
+               }
+
+               var lastChar *Char
+               cursorSet := false
+               for _, char := range line {
+                       if char != nil {
+                               lineStyle := char.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)
+                               }
+
+                               charLoc := char.realLoc
+                               for _, c := range v.Buf.cursors {
+                                       v.SetCursor(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.SetCursor(&v.Buf.Cursor)
+
+                               if v.Buf.Settings["cursorline"].(bool) && tabs[curTab].CurView == v.Num &&
+                                       !v.Cursor.HasSelection() && v.Cursor.Y == realLineN {
+                                       style := GetColor("cursor-line")
+                                       fg, _, _ := style.Decompose()
+                                       lineStyle = lineStyle.Background(fg)
+                               }
+
+                               screen.SetContent(xOffset+char.visualLoc.X, yOffset+char.visualLoc.Y, char.drawChar, nil, lineStyle)
+
+                               for i, c := range v.Buf.cursors {
+                                       v.SetCursor(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.SetCursor(&v.Buf.Cursor)
+
+                               lastChar = char
+                       }
+               }
+
+               lastX := 0
+               var realLoc Loc
+               var visualLoc Loc
+               var cx, cy int
+               if lastChar != nil {
+                       lastX = xOffset + lastChar.visualLoc.X + lastChar.width
+                       for i, c := range v.Buf.cursors {
+                               v.SetCursor(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
+                               }
+                       }
+                       v.SetCursor(&v.Buf.Cursor)
+                       realLoc = Loc{lastChar.realLoc.X + 1, realLineN}
+                       visualLoc = Loc{lastX - xOffset, lastChar.visualLoc.Y}
+               } else if len(line) == 0 {
+                       for i, c := range v.Buf.cursors {
+                               v.SetCursor(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.SetCursor(&v.Buf.Cursor)
+                       lastX = xOffset
+                       realLoc = Loc{0, realLineN}
+                       visualLoc = Loc{0, visualLineN}
+               }
+
+               if v.Cursor.HasSelection() &&
+                       (realLoc.GreaterEqual(v.Cursor.CurSelection[0]) && realLoc.LessThan(v.Cursor.CurSelection[1]) ||
+                               realLoc.LessThan(v.Cursor.CurSelection[0]) && realLoc.GreaterEqual(v.Cursor.CurSelection[1])) {
+                       // The current character is selected
+                       selectStyle := defStyle.Reverse(true)
+
+                       if style, ok := colorscheme["selection"]; ok {
+                               selectStyle = style
+                       }
+                       screen.SetContent(xOffset+visualLoc.X, yOffset+visualLoc.Y, ' ', nil, selectStyle)
+               }
+
+               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-v.lineNumOffset; i++ {
+                               style := GetColor("cursor-line")
+                               fg, _, _ := style.Decompose()
+                               style = style.Background(fg)
+                               if !(tabs[curTab].CurView == v.Num && !v.Cursor.HasSelection() && i == cx && yOffset+visualLineN == cy) {
+                                       screen.SetContent(i, yOffset+visualLineN, ' ', nil, style)
+                               }
+                       }
+               }
+       }
+
+       if divider != 0 {
+               dividerStyle := defStyle
+               if style, ok := colorscheme["divider"]; ok {
+                       dividerStyle = style
+               }
+               for i := 0; i < v.Height; i++ {
+                       screen.SetContent(v.x, yOffset+i, '|', nil, dividerStyle.Reverse(true))
+               }
+       }
+}
+
+// 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()
+
+       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 {