// Package editor Editor methods involved in drawing to the console package editor import ( "fmt" "strings" "time" "timshome.page/gilo/editor/highlight" "timshome.page/gilo/internal/gilo" "timshome.page/gilo/terminal" ) // ---------------------------------------------------------------------------- // !Editor Methods // ---------------------------------------------------------------------------- func (e *Editor) RefreshScreen() { e.scroll() ab := gilo.NewBuffer() ab.Append(terminal.HideCursor) ab.Append(terminal.ResetCursor) e.drawRows(ab) e.drawStatusBar(ab) e.drawMessageBar(ab) ab.Append(terminal.MoveCursor(e.renderX-e.offset.X, e.cursor.Y-e.offset.Y)) ab.Append(terminal.ShowCursor) terminal.Write(ab.ToString()) } func (e *Editor) scroll() { e.renderX = 0 if e.cursor.Y < e.doc.RowCount() { e.renderX = e.doc.GetRow(e.cursor.Y).CursorXToRenderX(e.cursor.X) } if e.cursor.Y < e.offset.Y { e.offset.Y = e.cursor.Y } if e.cursor.Y >= e.offset.Y+e.screen.Rows { e.offset.Y = e.cursor.Y - e.screen.Rows + 1 } if e.renderX < e.offset.X { e.offset.X = e.renderX } if e.renderX >= e.offset.X+e.screen.Cols { e.offset.X = e.renderX - e.screen.Cols } } func (e *Editor) drawRows(ab *gilo.Buffer) { for y := 0; y < e.screen.Rows; y++ { fileRow := y + e.offset.Y if fileRow >= e.doc.RowCount() { e.drawPlaceholderRow(y, ab) } else { rawRow := e.doc.GetRow(fileRow) // If the column offset is greater than the length of the row, // just display an empty row if e.offset.X > rawRow.Size() { ab.Append("") continue } e.drawFileRow(fileRow, ab) } ab.AppendLn(terminal.ClearLine) } } func (e *Editor) drawFileRow(fileRow int, ab *gilo.Buffer) { currentColor := terminal.DefaultFGColor row := e.doc.GetRow(fileRow) // Because runes can be more than one byte, 🫵 render // this by runes so that multibyte-characters (like emoji) can // all be displayed for i, ch := range row.RenderRune(e.offset) { if row.Hl[i] == highlight.Normal { if currentColor != terminal.DefaultFGColor { ab.Append(terminal.DefaultFGColor) currentColor = terminal.DefaultFGColor } ab.AppendRune(ch) } else { color := highlight.SyntaxToColor(row.Hl[i]) if color != currentColor { currentColor = color ab.Append(color) } ab.AppendRune(ch) } } ab.Append(terminal.DefaultFGColor) } func (e *Editor) drawPlaceholderRow(y int, ab *gilo.Buffer) { if e.doc.RowCount() == 0 && y == e.screen.Rows/3 { welcome := fmt.Sprintf("Gilo editor -- version %s", gilo.Version) if len(welcome) > e.screen.Cols { welcome = gilo.Truncate(welcome, e.screen.Cols) } padding := (e.screen.Cols - len(welcome)) / 2 if padding > 0 { ab.AppendRune('~') padding-- } for padding > 0 { padding-- ab.AppendRune(' ') } ab.Append(welcome) } else { ab.AppendRune('~') } } func (e *Editor) drawStatusBar(ab *gilo.Buffer) { cols := e.screen.Cols ab.Append(terminal.InvertColor) fileName := "[No Name]" if e.doc.Filename != "" { fileName = e.doc.Filename } modified := "" if e.doc.IsDirty() { modified = "(modified)" } leftStatus := fmt.Sprintf("%.20s - %d lines %s", fileName, e.doc.RowCount(), modified) length := len(leftStatus) if length > cols { leftStatus = gilo.Truncate(leftStatus, cols) ab.Append(leftStatus) ab.Append(terminal.ResetColor) return } syntaxName := "no filetype" if e.doc.Syntax != nil { syntaxName = e.doc.Syntax.FileType } rightStatus := fmt.Sprintf("%s | %d/%d", syntaxName, e.cursor.Y+1, e.doc.RowCount()) rlength := len(rightStatus) statusLength := length + rlength if statusLength <= cols { paddingLength := cols - statusLength padding := strings.Repeat(" ", paddingLength) ab.Append(leftStatus) ab.Append(padding) ab.Append(rightStatus) ab.Append(terminal.ResetColor) return } ab.Append(leftStatus) // Pad the rest of the status line padding := strings.Repeat(" ", cols-length) ab.Append(padding) ab.Append(terminal.ResetColor) } func (e *Editor) drawMessageBar(ab *gilo.Buffer) { ab.Append("\r\n") ab.Append(terminal.ClearLine) msg := gilo.Truncate(e.status.message, e.screen.Cols) if len(msg) > 0 && time.Since(e.status.created).Seconds() < 5.0 { ab.Append(msg) } }