]> git.lizzy.rs Git - micro.git/blobdiff - cmd/micro/view.go
Code optimisation (#1117)
[micro.git] / cmd / micro / view.go
index 37acf3f517dade3c5ef876f913520d0003a831dc..d6d9470f2dac66d2f4b91cd230125857cdec6155 100644 (file)
@@ -1,7 +1,8 @@
 package main
 
 import (
-       "os"
+       "fmt"
+       "reflect"
        "strconv"
        "strings"
        "time"
@@ -11,9 +12,9 @@ import (
 
 // 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 +22,8 @@ var (
        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.
@@ -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,6 +73,8 @@ 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
@@ -89,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
@@ -117,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,
        }
 
@@ -125,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") {
@@ -145,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 {
@@ -162,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()
@@ -236,29 +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) {
-       filename = ReplaceHome(filename)
-       file, err := os.Open(filename)
-       fileInfo, _ := os.Stat(filename)
-
-       if err == nil && fileInfo.IsDir() {
-               messenger.Error(filename, " is a directory")
-               return
-       }
-
-       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 = NewBufferFromString("", filename)
-       } else {
-               buf = NewBuffer(file, FSize(file), filename)
+               messenger.Error(err)
+               return
        }
        v.OpenBuffer(buf)
 }
@@ -352,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
@@ -404,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
        }
@@ -423,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) {
@@ -438,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))
        }
@@ -454,7 +513,8 @@ func (v *View) ExecuteActions(actions []func(*View, bool) bool) bool {
        for _, action := range actions {
                readonlyBindingsResult := false
                funcName := ShortFuncName(action)
-               if v.Type.readonly == true {
+               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) {
@@ -464,7 +524,7 @@ func (v *View) ExecuteActions(actions []func(*View, bool) bool) bool {
                }
                if !readonlyBindingsResult {
                        // call the key binding
-                       relocate = action(v, true) || relocate
+                       relocate = action(curv, true) || relocate
                        // Macro
                        if funcName != "ToggleMacro" && funcName != "PlayMacro" {
                                if recordingMacro {
@@ -490,6 +550,25 @@ func (v *View) SetCursor(c *Cursor) bool {
 
 // 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
@@ -500,7 +579,7 @@ func (v *View) HandleEvent(event tcell.Event) {
        case *tcell.EventRaw:
                for key, actions := range bindings {
                        if key.keyCode == -1 {
-                               if e.EscapeCode() == key.escape {
+                               if e.EscSeq() == key.escape {
                                        for _, c := range v.Buf.cursors {
                                                ok := v.SetCursor(c)
                                                if !ok {
@@ -541,9 +620,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)
 
@@ -552,7 +632,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)
@@ -570,7 +657,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
                        }
@@ -702,12 +789,17 @@ func (v *View) openHelp(helpPage string) {
 
 // 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 {
-               // 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()
        }
 
@@ -771,11 +863,11 @@ func (v *View) DisplayView() {
                }
 
                colorcolumn := int(v.Buf.Settings["colorcolumn"].(float64))
-               if colorcolumn != 0 {
+               if colorcolumn != 0 && xOffset+colorcolumn-v.leftCol < v.Width {
                        style := GetColor("color-column")
                        fg, _, _ := style.Decompose()
                        st := defStyle.Background(fg)
-                       screen.SetContent(xOffset+colorcolumn, yOffset+visualLineN, ' ', nil, st)
+                       screen.SetContent(xOffset+colorcolumn-v.leftCol, yOffset+visualLineN, ' ', nil, st)
                }
 
                screenX = v.x
@@ -1008,6 +1100,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 {