Files
wen/internal/editor/canvas/render.go
T

148 lines
3.0 KiB
Go
Raw Normal View History

2026-10-26 02:32:03 +00:00
package canvas
import (
"fmt"
"path"
"github.com/mattn/go-runewidth"
"code.chimeric.al/dwrz/wen/internal/editor/buffer"
"code.chimeric.al/dwrz/wen/internal/editor/prompt"
"code.chimeric.al/dwrz/wen/pkg/color"
"code.chimeric.al/dwrz/wen/pkg/terminal"
)
// Bars is the number of terminal rows reserved at the bottom of the screen
// for the status bar and message bar.
const Bars = 2
func (c *Canvas) Render(b *buffer.Buffer, prompt *prompt.Prompt) error {
c.mu.Lock()
defer c.mu.Unlock()
size, err := c.terminal.Size()
if err != nil {
return fmt.Errorf("failed to get terminal size: %w", err)
}
var (
cursor = b.Cursor()
height = int(size.Rows) - Bars
width = int(size.Columns)
output = b.Render(height, width)
)
// Move the cursor to the top left.
c.buf.WriteString(terminal.CursorHide)
c.buf.WriteString(terminal.CursorTopLeft)
// Print each line.
for _, line := range output.Lines {
c.buf.WriteString(terminal.EraseLine)
c.buf.WriteString(line)
c.buf.WriteString("\r\n")
}
// Draw the status bar.
c.statusBar(b, width, cursor.Line(), cursor.Glyph())
// Draw the message bar.
c.messageBar(prompt, width)
// Set the cursor.
if prompt.IsPrompt() {
c.buf.WriteString(fmt.Sprintf(
terminal.SetCursorFmt, size.Rows,
runewidth.StringWidth(prompt.Text())+prompt.Cursor()+2,
))
} else {
c.buf.WriteString(fmt.Sprintf(
terminal.SetCursorFmt, output.Line, output.Glyph,
))
}
c.buf.WriteString(terminal.CursorShow)
c.out.Write(c.buf.Bytes())
c.buf.Reset()
return nil
}
func (c *Canvas) statusBar(b *buffer.Buffer, width, y, x int) {
c.buf.WriteString(terminal.EraseLine)
c.buf.WriteString(color.Inverse)
// Icon
icon := "文 "
c.buf.WriteString(icon)
width -= runewidth.StringWidth(icon)
// Calculate the length of the cursor, so we can determine how much
// space we have left for the name of the buffer.
cursor := fmt.Sprintf(" %d:%d", y, x)
width -= runewidth.StringWidth(cursor)
// Filename.
// TODO: handle long filenames (shorten filepath).
name := path.Base(b.Name())
nw := runewidth.StringWidth(name)
if nw <= width {
c.buf.WriteString(name)
width -= nw
} else {
for _, r := range name {
rw := runewidth.RuneWidth(r)
if width-rw >= 0 {
c.buf.WriteRune(r)
width -= rw
} else {
break
}
}
}
// Add empty spaces to the end of the line.
for i := width; i > 0; i-- {
c.buf.WriteRune(' ')
}
// Cursor
c.buf.WriteString(cursor)
c.buf.WriteString(color.Reset)
c.buf.WriteString("\r\n")
}
// TODO: handle messages that are too long for one line.
func (c *Canvas) messageBar(p *prompt.Prompt, width int) {
c.buf.WriteString(terminal.EraseLine)
var (
text = p.Text()
line = p.Line(0, width) // TODO: handle offset.
output string
)
switch {
case text == "" && line == "":
output = ""
case line == "":
output = text
default:
output = fmt.Sprintf("%s %s", text, line)
}
for _, r := range output {
rw := runewidth.RuneWidth(r)
if width-rw < 0 {
break
}
c.buf.WriteRune(r)
width -= rw
}
for i := width; i > 0; i-- {
c.buf.WriteRune(' ')
}
}