X-Git-Url: https://git.lizzy.rs/?a=blobdiff_plain;f=cmd%2Fmicro%2Fview.go;h=8a009b6504903588095febb95be640c35d1b68ef;hb=21840d3ffeee2ef4f3e3888c9ee72caac298f56e;hp=1f13acc71bfa6fff5f042ebadabfc12448b23967;hpb=c1db99a5a5f241584978f9fec89ee097bf2f2b5d;p=micro.git diff --git a/cmd/micro/view.go b/cmd/micro/view.go index 1f13acc7..8a009b65 100644 --- a/cmd/micro/view.go +++ b/cmd/micro/view.go @@ -6,17 +6,21 @@ import ( "strings" "time" - "github.com/mattn/go-runewidth" "github.com/mitchellh/go-homedir" "github.com/zyedidia/tcell" ) -type ViewType int +type ViewType struct { + kind int + readonly bool // The file cannot be edited + scratch bool // The file cannot be saved +} -const ( - vtDefault ViewType = iota - vtHelp - vtLog +var ( + vtDefault = ViewType{0, false, false} + vtHelp = ViewType{1, true, true} + vtLog = ViewType{2, true, true} + vtScratch = ViewType{3, false, true} ) // The View struct stores information about a view into a buffer. @@ -84,8 +88,7 @@ type View struct { // Same here, just to keep track for mouse move events tripleClick bool - // Syntax highlighting matches - matches SyntaxMatches + cellview *CellView splitNode *LeafNode } @@ -105,6 +108,7 @@ func NewViewWidthHeight(buf *Buffer, w, h int) *View { v.Width = w v.Height = h + v.cellview = new(CellView) v.ToggleTabbar() @@ -165,7 +169,7 @@ func (v *View) paste(clip string) { } clip = strings.Replace(clip, "\n", "\n"+leadingWS, -1) v.Buf.Insert(v.Cursor.Loc, clip) - v.Cursor.Loc = v.Cursor.Loc.Move(Count(clip), v.Buf) + // v.Cursor.Loc = v.Cursor.Loc.Move(Count(clip), v.Buf) v.freshClip = false messenger.Message("Pasted clipboard") } @@ -183,9 +187,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++ } } @@ -195,18 +199,19 @@ func (v *View) ScrollDown(n int) { // causing them to lose the unsaved changes func (v *View) CanClose() bool { if v.Type == vtDefault && v.Buf.IsModified { - var char rune + var choice bool var canceled bool if v.Buf.Settings["autosave"].(bool) { - char = 'y' + choice = true } else { - char, canceled = messenger.LetterPrompt("Save changes to "+v.Buf.GetName()+" before closing? (y,n,esc) ", 'y', 'n') + choice, canceled = messenger.YesNoPrompt("Save changes to " + v.Buf.GetName() + " before closing? (y,n,esc) ") } if !canceled { - if char == 'y' { + //if char == 'y' { + if choice { v.Save(true) return true - } else if char == 'n' { + } else { return true } } @@ -230,8 +235,6 @@ func (v *View) OpenBuffer(buf *Buffer) { v.Center(false) v.messages = make(map[string][]GutterMessage) - v.matches = Match(v) - // Set mouseReleased to true because we assume the mouse is not being pressed when // the editor is opened v.mouseReleased = true @@ -243,15 +246,22 @@ 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 if err != nil { messenger.Message(err.Error()) // File does not exist -- create an empty buffer with that name - buf = NewBuffer(strings.NewReader(""), filename) + buf = NewBufferFromString("", filename) } else { - buf = NewBuffer(file, filename) + buf = NewBuffer(file, FSize(file), filename) } v.OpenBuffer(buf) } @@ -269,7 +279,6 @@ func (v *View) ReOpen() { screen.Clear() v.Buf.ReOpen() v.Relocate() - v.matches = Match(v) } } @@ -441,6 +450,35 @@ func (v *View) MoveToMouseClick(x, y int) { v.Cursor.LastVisualX = v.Cursor.GetVisualX() } +func (v *View) ExecuteActions(actions []func(*View, bool) bool) bool { + relocate := false + readonlyBindingsList := []string{"Delete", "Insert", "Backspace", "Cut", "Play", "Paste", "Move", "Add", "DuplicateLine", "Macro"} + for _, action := range actions { + readonlyBindingsResult := false + funcName := ShortFuncName(action) + if v.Type.readonly == true { + // check for readonly and if true only let key bindings get called if they do not change the contents. + for _, readonlyBindings := range readonlyBindingsList { + if strings.Contains(funcName, readonlyBindings) { + readonlyBindingsResult = true + } + } + } + if !readonlyBindingsResult { + // call the key binding + relocate = action(v, true) || relocate + // Macro + if funcName != "ToggleMacro" && funcName != "PlayMacro" { + if recordingMacro { + curMacro = append(curMacro, action) + } + } + } + } + + return relocate +} + // HandleEvent handles an event passed by the main loop func (v *View) HandleEvent(event tcell.Event) { // This bool determines whether the view is relocated at the end of the function @@ -450,129 +488,105 @@ func (v *View) HandleEvent(event tcell.Event) { v.Buf.CheckModTime() switch e := event.(type) { - case *tcell.EventResize: - // Window resized - tabs[v.TabNum].Resize() case *tcell.EventKey: // Check first if input is a key binding, if it is we 'eat' the input and don't insert a rune isBinding := false - if e.Key() != tcell.KeyRune || e.Modifiers() != 0 { - for key, actions := range bindings { - if e.Key() == key.keyCode { - if e.Key() == tcell.KeyRune { - if e.Rune() != key.r { - continue - } + for key, actions := range bindings { + if e.Key() == key.keyCode { + if e.Key() == tcell.KeyRune { + if e.Rune() != key.r { + continue } - if e.Modifiers() == key.modifiers { + } + if e.Modifiers() == key.modifiers { + for _, c := range v.Buf.cursors { + v.Cursor = c relocate = false isBinding = true - for _, action := range actions { - relocate = action(v, true) || relocate - funcName := FuncName(action) - if funcName != "main.(*View).ToggleMacro" && funcName != "main.(*View).PlayMacro" { - if recordingMacro { - curMacro = append(curMacro, action) - } - } - } - break + relocate = v.ExecuteActions(actions) || relocate } + v.Cursor = &v.Buf.Cursor + break } } } if !isBinding && e.Key() == tcell.KeyRune { - // Insert a character - if v.Cursor.HasSelection() { - v.Cursor.DeleteSelection() - v.Cursor.ResetSelection() - } - v.Buf.Insert(v.Cursor.Loc, string(e.Rune())) - v.Cursor.Right() + // Check viewtype if readonly don't insert a rune (readonly help and log view etc.) + if v.Type.readonly == false { + for _, c := range v.Buf.cursors { + v.Cursor = c + + // Insert a character + if v.Cursor.HasSelection() { + v.Cursor.DeleteSelection() + v.Cursor.ResetSelection() + } + v.Buf.Insert(v.Cursor.Loc, string(e.Rune())) - for pl := range loadedPlugins { - _, err := Call(pl+".onRune", string(e.Rune()), v) - if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") { - TermMessage(err) - } - } + for pl := range loadedPlugins { + _, err := Call(pl+".onRune", string(e.Rune()), v) + if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") { + TermMessage(err) + } + } - if recordingMacro { - curMacro = append(curMacro, e.Rune()) + if recordingMacro { + curMacro = append(curMacro, e.Rune()) + } + } + v.Cursor = &v.Buf.Cursor } } case *tcell.EventPaste: - if !PreActionCall("Paste", v) { - break - } + // Check viewtype if readonly don't paste (readonly help and log view etc.) + if v.Type.readonly == false { + if !PreActionCall("Paste", v) { + break + } + + for _, c := range v.Buf.cursors { + v.Cursor = c + v.paste(e.Text()) - v.paste(e.Text()) + } + v.Cursor = &v.Buf.Cursor - PostActionCall("Paste", v) + PostActionCall("Paste", v) + } case *tcell.EventMouse: - x, y := e.Position() - x -= v.lineNumOffset - v.leftCol + v.x - y += v.Topline - v.y // Don't relocate for mouse events relocate = false button := e.Buttons() - switch button { - case tcell.Button1: - // Left click - if v.mouseReleased { - v.MoveToMouseClick(x, y) - if time.Since(v.lastClickTime)/time.Millisecond < doubleClickThreshold { - if v.doubleClick { - // Triple click - v.lastClickTime = time.Now() - - v.tripleClick = true - v.doubleClick = false - - v.Cursor.SelectLine() - v.Cursor.CopySelection("primary") - } else { - // Double click - v.lastClickTime = time.Now() - - v.doubleClick = true - v.tripleClick = false - - v.Cursor.SelectWord() - v.Cursor.CopySelection("primary") - } - } else { - v.doubleClick = false - v.tripleClick = false - v.lastClickTime = time.Now() - - v.Cursor.OrigSelection[0] = v.Cursor.Loc - v.Cursor.CurSelection[0] = v.Cursor.Loc - v.Cursor.CurSelection[1] = v.Cursor.Loc + for key, actions := range bindings { + if button == key.buttons && e.Modifiers() == key.modifiers { + for _, c := range v.Buf.cursors { + v.Cursor = c + relocate = v.ExecuteActions(actions) || relocate } - v.mouseReleased = false - } else if !v.mouseReleased { - v.MoveToMouseClick(x, y) - if v.tripleClick { - v.Cursor.AddLineToSelection() - } else if v.doubleClick { - v.Cursor.AddWordToSelection() - } else { - v.Cursor.SetSelectionEnd(v.Cursor.Loc) - v.Cursor.CopySelection("primary") + v.Cursor = &v.Buf.Cursor + } + } + + for key, actions := range mouseBindings { + if button == key.buttons && e.Modifiers() == key.modifiers { + for _, action := range actions { + action(v, true, e) } } - case tcell.Button2: - // Middle mouse button was clicked, - // We should paste primary - v.PastePrimary(true) + } + + switch button { case tcell.ButtonNone: // Mouse event with no click if !v.mouseReleased { // Mouse was just released + x, y := e.Position() + x -= v.lineNumOffset - v.leftCol + v.x + y += v.Topline - v.y + // Relocating here isn't really necessary because the cursor will // be in the right place from the last mouse event // However, if we are running in a terminal that doesn't support mouse motion @@ -586,19 +600,17 @@ 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() } } @@ -638,7 +650,7 @@ func (v *View) openHelp(helpPage string) { if data, err := FindRuntimeFile(RTHelp, helpPage).Data(); err != nil { TermMessage("Unable to load help text", helpPage, "\n", err) } else { - helpBuffer := NewBuffer(strings.NewReader(string(data)), helpPage+".md") + helpBuffer := NewBufferFromString(string(data), helpPage+".md") helpBuffer.name = "Help" if v.Type == vtHelp { @@ -650,34 +662,23 @@ 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) +func (v *View) DisplayView() { + if v.Buf.Settings["softwrap"].(bool) && v.leftCol != 0 { + v.leftCol = 0 } -} -// 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)) + // 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 = maxLineLength + 1 + v.lineNumOffset = maxLineNumLength + 1 } else { v.lineNumOffset = 0 } @@ -693,44 +694,52 @@ func (v *View) DisplayView() { v.lineNumOffset += 2 } + divider := 0 if v.x != 0 { // One space for the extra split divider v.lineNumOffset++ + divider = 1 } - // 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 + xOffset := v.x + v.lineNumOffset + yOffset := v.y - // This is the current line number of the buffer that we are drawing - curLineN = viewLine + v.Topline + height := v.Height + width := v.Width + left := v.leftCol + top := v.Topline - if screenY-v.y >= v.Height { - break - } + v.cellview.Draw(v.Buf, top, height, left, width-v.lineNumOffset) - if v.x != 0 { - // Draw the split divider - v.drawCell(screenX, screenY, '|', nil, defStyle.Reverse(true)) - screenX++ + 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] } - // 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) + var softwrapped bool + if firstChar != nil { + if firstChar.realLoc.Y == realLineN { + softwrapped = true } + realLineN = firstChar.realLoc.Y + } else { + realLineN++ + } - continue + colorcolumn := int(v.Buf.Settings["colorcolumn"].(float64)) + if colorcolumn != 0 { + style := GetColor("color-column") + fg, _, _ := style.Decompose() + st := defStyle.Background(fg) + screen.SetContent(xOffset+colorcolumn, yOffset+visualLineN, ' ', nil, st) } - line := v.Buf.Line(curLineN) + + screenX = v.x // If there are gutter messages we need to display the '>>' symbol here if hasGutterMessages { @@ -738,7 +747,7 @@ func (v *View) DisplayView() { msgOnLine := false for k := range v.messages { for _, msg := range v.messages[k] { - if msg.lineNum == curLineN { + if msg.lineNum == realLineN { msgOnLine = true gutterStyle := defStyle switch msg.kind { @@ -755,11 +764,11 @@ func (v *View) DisplayView() { gutterStyle = style } } - v.drawCell(screenX, screenY, '>', nil, gutterStyle) + screen.SetContent(screenX, yOffset+visualLineN, '>', nil, gutterStyle) screenX++ - v.drawCell(screenX, screenY, '>', nil, gutterStyle) + screen.SetContent(screenX, yOffset+visualLineN, '>', nil, gutterStyle) screenX++ - if v.Cursor.Y == curLineN && !messenger.hasPrompt { + if v.Cursor.Y == realLineN && !messenger.hasPrompt { messenger.Message(msg.msg) messenger.gutterMessage = true } @@ -768,11 +777,11 @@ func (v *View) DisplayView() { } // If there is no message on this line we just display an empty offset if !msgOnLine { - v.drawCell(screenX, screenY, ' ', nil, defStyle) + screen.SetContent(screenX, yOffset+visualLineN, ' ', nil, defStyle) screenX++ - v.drawCell(screenX, screenY, ' ', nil, defStyle) + screen.SetContent(screenX, yOffset+visualLineN, ' ', nil, defStyle) screenX++ - if v.Cursor.Y == curLineN && messenger.gutterMessage { + if v.Cursor.Y == realLineN && messenger.gutterMessage { messenger.Reset() messenger.gutterMessage = false } @@ -786,202 +795,177 @@ func (v *View) DisplayView() { lineNumStyle = style } if style, ok := colorscheme["current-line-number"]; ok { - if curLineN == v.Cursor.Y && tabs[curTab].CurView == v.Num && !v.Cursor.HasSelection() { + if realLineN == v.Cursor.Y && tabs[curTab].CurView == v.Num && !v.Cursor.HasSelection() { lineNumStyle = style } } - lineNum := strconv.Itoa(curLineN + 1) + lineNum := strconv.Itoa(realLineN + 1) // Write the spaces before the line number if necessary - for i := 0; i < maxLineLength-len(lineNum); i++ { - v.drawCell(screenX, screenY, ' ', nil, lineNumStyle) + for i := 0; i < maxLineNumLength-len(lineNum); i++ { + screen.SetContent(screenX+divider, yOffset+visualLineN, ' ', nil, lineNumStyle) screenX++ } - // Write the actual line number - for _, ch := range lineNum { - v.drawCell(screenX, screenY, ch, 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 - v.drawCell(screenX, screenY, ' ', nil, lineNumStyle) + screen.SetContent(screenX+divider, yOffset+visualLineN, ' ', 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] - } + var lastChar *Char + cursorSet := false + for _, char := range line { + if char != nil { + lineStyle := char.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])) { - // The current character is selected - lineStyle = defStyle.Reverse(true) + 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 style, ok := colorscheme["selection"]; ok { - lineStyle = style + charLoc := char.realLoc + for _, c := range v.Buf.cursors { + v.Cursor = c + if v.Cursor.HasSelection() && + (charLoc.GreaterEqual(v.Cursor.CurSelection[0]) && charLoc.LessThan(v.Cursor.CurSelection[1]) || + charLoc.LessThan(v.Cursor.CurSelection[0]) && charLoc.GreaterEqual(v.Cursor.CurSelection[1])) { + // The current character is selected + lineStyle = defStyle.Reverse(true) + + if style, ok := colorscheme["selection"]; ok { + lineStyle = style + } + } } - } else { - lineStyle = highlightStyle - } + v.Cursor = &v.Buf.Cursor - // 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 { + 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) } - } - if ch == '\t' { - // If the character we are displaying is a tab, we need to do a bunch of special things + screen.SetContent(xOffset+char.visualLoc.X, yOffset+char.visualLoc.Y, char.drawChar, nil, lineStyle) - // 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 + for i, c := range v.Buf.cursors { + v.Cursor = c + if tabs[curTab].CurView == v.Num && !v.Cursor.HasSelection() && + v.Cursor.Y == char.realLoc.Y && v.Cursor.X == char.realLoc.X && (!cursorSet || i != 0) { + ShowMultiCursor(xOffset+char.visualLoc.X, yOffset+char.visualLoc.Y, i) + cursorSet = true + } } - 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])) { + v.Cursor = &v.Buf.Cursor - lineIndentStyle = defStyle.Reverse(true) + lastChar = char + } + } - if style, ok := colorscheme["selection"]; ok { - lineIndentStyle = style - } + 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.Cursor = c + if tabs[curTab].CurView == v.Num && !v.Cursor.HasSelection() && + v.Cursor.Y == lastChar.realLoc.Y && v.Cursor.X == lastChar.realLoc.X+1 { + ShowMultiCursor(lastX, yOffset+lastChar.visualLoc.Y, i) + cx, cy = lastX, yOffset+lastChar.visualLoc.Y } - 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) + } + v.Cursor = &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.Cursor = c + if tabs[curTab].CurView == v.Num && !v.Cursor.HasSelection() && + v.Cursor.Y == realLineN { + ShowMultiCursor(xOffset, yOffset+visualLineN, i) + cx, cy = xOffset, yOffset+visualLineN } - 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) + v.Cursor = &v.Buf.Cursor + lastX = xOffset + realLoc = Loc{0, realLineN} + visualLoc = Loc{0, visualLineN} } - // 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])) { - + (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 } - v.drawCell(screenX, screenY, ' ', nil, selectStyle) - screenX++ + screen.SetContent(xOffset+visualLoc.X, yOffset+visualLoc.Y, ' ', nil, selectStyle) } - 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) - } + 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++ { + 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) } - v.drawCell(screenX-v.leftCol+i, screenY, ' ', nil, lineStyle) } } } + + 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)) + } + } } -// 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()