X-Git-Url: https://git.lizzy.rs/?a=blobdiff_plain;f=cmd%2Fmicro%2Fview.go;h=d6d9470f2dac66d2f4b91cd230125857cdec6155;hb=71af765b4e4f368c4bbbcb3947f3497e17271b62;hp=e6d3833edbaf9aab10b1183a7ebd9734aad36d7f;hpb=2e6cbcb362a4eba98a0019538d8fd6d924745f16;p=micro.git diff --git a/cmd/micro/view.go b/cmd/micro/view.go index e6d3833e..d6d9470f 100644 --- a/cmd/micro/view.go +++ b/cmd/micro/view.go @@ -1,25 +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. @@ -61,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 @@ -69,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. @@ -87,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 @@ -115,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, } @@ -123,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") { @@ -143,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 { @@ -160,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() @@ -168,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") } @@ -197,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. @@ -236,30 +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) - 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 = NewBuffer(strings.NewReader(""), filename) - } else { - buf = NewBuffer(file, filename) + messenger.Error(err) + return } v.OpenBuffer(buf) } @@ -283,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) @@ -292,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) @@ -353,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 @@ -405,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 } @@ -424,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) { @@ -439,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)) } @@ -448,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 @@ -457,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())) + } + + 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.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 @@ -590,14 +721,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) } } @@ -612,6 +735,10 @@ func (v *View) HandleEvent(event tcell.Event) { } } +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-- @@ -648,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 { @@ -660,9 +787,19 @@ func (v *View) openHelp(helpPage string) { } } +// DisplayView draws the view to the screen func (v *View) DisplayView() { - if v.Type == vtLog { - // Log views should always follow the cursor... + 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() } @@ -688,9 +825,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 +862,14 @@ func (v *View) DisplayView() { 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 @@ -788,25 +935,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 +963,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.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 { @@ -843,6 +995,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.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 } } @@ -850,19 +1012,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.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 + } } - realLoc = Loc{lastChar.realLoc.X, realLineN} + v.SetCursor(&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.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} @@ -882,40 +1055,56 @@ 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) - 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() + + 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 {