package main import ( "github.com/mattn/go-runewidth" "github.com/zyedidia/tcell" ) func min(a, b int) int { if a <= b { return a } return b } func visualToCharPos(visualIndex int, lineN int, str string, buf *Buffer, tabsize int) (int, int, *tcell.Style) { charPos := 0 var lastWidth int var style *tcell.Style for i := range str { width := StringWidth(str[:i], tabsize) if group, ok := buf.Match(lineN)[charPos]; ok { s := GetColor(group) style = &s } if width >= visualIndex { return charPos, visualIndex - lastWidth, style } if i != 0 { charPos++ } lastWidth = width } return -1, -1, style } type Char struct { visualLoc Loc realLoc Loc char rune // The actual character that is drawn // This is only different from char if it's for example hidden character drawChar rune style tcell.Style } type CellView struct { lines [][]*Char } func (c *CellView) Draw(buf *Buffer, top, height, left, width int) { tabsize := int(buf.Settings["tabsize"].(float64)) softwrap := buf.Settings["softwrap"].(bool) indentchar := []rune(buf.Settings["indentchar"].(string))[0] start := buf.Cursor.Y if buf.Settings["syntax"].(bool) { if start > 0 && buf.lines[start-1].rehighlight { buf.highlighter.ReHighlightLine(buf, start-1) buf.lines[start-1].rehighlight = false } buf.highlighter.ReHighlight(buf, start) } c.lines = make([][]*Char, 0) viewLine := 0 lineN := top curStyle := defStyle for viewLine < height { if lineN >= len(buf.lines) { break } lineStr := buf.Line(lineN) line := []rune(lineStr) colN, startOffset, startStyle := visualToCharPos(left, lineN, lineStr, buf, tabsize) if colN < 0 { colN = len(line) } viewCol := -startOffset if startStyle != nil { curStyle = *startStyle } // We'll either draw the length of the line, or the width of the screen // whichever is smaller lineLength := min(StringWidth(lineStr, tabsize), width) c.lines = append(c.lines, make([]*Char, lineLength)) wrap := false // We only need to wrap if the length of the line is greater than the width of the terminal screen if softwrap && StringWidth(lineStr, tabsize) > width { wrap = true // We're going to draw the entire line now lineLength = StringWidth(lineStr, tabsize) } for viewCol < lineLength { if colN >= len(line) { break } if group, ok := buf.Match(lineN)[colN]; ok { curStyle = GetColor(group) } char := line[colN] if viewCol >= 0 { c.lines[viewLine][viewCol] = &Char{Loc{viewCol, viewLine}, Loc{colN, lineN}, char, char, curStyle} } if char == '\t' { if viewCol >= 0 { c.lines[viewLine][viewCol].drawChar = indentchar } viewCol += tabsize - (viewCol+left)%tabsize } else if runewidth.RuneWidth(char) > 1 { viewCol += runewidth.RuneWidth(char) } else { viewCol++ } colN++ if wrap && viewCol >= width { viewLine++ // If we go too far soft wrapping we have to cut off if viewLine >= height { break } nextLine := line[colN:] lineLength := min(StringWidth(string(nextLine), tabsize), width) c.lines = append(c.lines, make([]*Char, lineLength)) viewCol = 0 } } if group, ok := buf.Match(lineN)[len(line)]; ok { curStyle = GetColor(group) } // newline viewLine++ lineN++ } }