// Marks in the gutter: a character beside a line saying something is wrong // with it. package editor import "rickub.com/turbo-editors/turbo-core/theme" // Severity is how serious a mark is. // // This is the editor's own type, not the language server's, and deliberately // so: this package colours and draws text, and knows nothing about protocols. // Whoever holds the diagnostics translates them into these on the way in. // // view.SetMarks(map[int]editor.Severity{41: editor.MarkError}) type Severity int // The severities a mark may have, in the order a line's worst one is chosen. const ( // MarkNone is the zero value: a line with nothing wrong with it. MarkNone Severity = iota MarkHint MarkInformation MarkWarning MarkError ) // markGlyphs are what each severity draws. They are single characters from the // range every terminal font has, because the gutter has exactly one column to // spare and a glyph that renders as a box is worse than a letter. var markGlyphs = map[Severity]rune{ MarkError: '×', MarkWarning: '!', MarkInformation: 'i', MarkHint: '·', } // markStyles are the theme keys each severity is drawn in. var markStyles = map[Severity]string{ MarkError: theme.KeyDiagnosticError, MarkWarning: theme.KeyDiagnosticWarning, MarkInformation: theme.KeyDiagnosticInfo, MarkHint: theme.KeyDiagnosticInfo, } // SetMarks says which lines have something wrong with them, keyed by // zero-based line number. // // Passing nil clears them, which is what a file with nothing wrong wants. // // view.SetMarks(map[int]editor.Severity{0: editor.MarkWarning, 12: editor.MarkError}) func (v *View) SetMarks(marks map[int]Severity) { v.marks = marks } // Marks returns what SetMarks was given. func (v *View) Marks() map[int]Severity { return v.marks } // markOf returns the severity to draw beside a line, and whether there is one. func (v *View) markOf(line int) (Severity, bool) { severity, marked := v.marks[line] if !marked || severity == MarkNone { return MarkNone, false } return severity, true } // drawMark paints the mark for a line, in the column that separates the line // numbers from the text. // // That column rather than a new one: the gutter is already `digits + 1` wide, // the +1 being the separator, so a mark costs no layout and nothing has to be // recomputed — not the text's left edge, not the cursor's screen column, not // the column a mouse click lands on. // // The consequence is that hiding the line numbers hides the marks. The // alternative — a one-column gutter appearing when the numbers are off — would // make the text jump sideways the moment a server reported anything. func (v *View) drawMark(p *Painter, th *theme.Theme, row, line int) { if !v.lineNumbers { return } severity, marked := v.markOf(line) if !marked { return } p.Text(v.gutterWidth()-1, row, string(markGlyphs[severity]), th.Style(markStyles[severity])) }