Add editor packages
This commit is contained in:
@@ -0,0 +1,33 @@
|
||||
package canvas
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"log/slog"
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
"code.chimeric.al/dwrz/wen/pkg/terminal"
|
||||
)
|
||||
|
||||
type Canvas struct {
|
||||
log *slog.Logger
|
||||
out *os.File
|
||||
terminal *terminal.Terminal
|
||||
|
||||
mu sync.Mutex
|
||||
buf bytes.Buffer
|
||||
}
|
||||
|
||||
type Parameters struct {
|
||||
Log *slog.Logger
|
||||
Out *os.File
|
||||
Terminal *terminal.Terminal
|
||||
}
|
||||
|
||||
func New(p Parameters) *Canvas {
|
||||
return &Canvas{
|
||||
log: p.Log,
|
||||
out: p.Out,
|
||||
terminal: p.Terminal,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
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(' ')
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package canvas
|
||||
|
||||
import "code.chimeric.al/dwrz/wen/pkg/terminal"
|
||||
|
||||
func (c *Canvas) Reset() {
|
||||
c.out.WriteString(terminal.ClearScreen)
|
||||
c.out.WriteString(terminal.CursorTopLeft)
|
||||
}
|
||||
Reference in New Issue
Block a user