Add editor packages
This commit is contained in:
@@ -0,0 +1,165 @@
|
||||
package prompt
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
"code.chimeric.al/dwrz/wen/internal/editor/buffer/glyph"
|
||||
"code.chimeric.al/dwrz/wen/internal/editor/buffer/line"
|
||||
)
|
||||
|
||||
type Prompt struct {
|
||||
log *slog.Logger
|
||||
|
||||
err error
|
||||
cb CB
|
||||
cursor cursor
|
||||
line *line.Line
|
||||
text string
|
||||
}
|
||||
|
||||
type CB func(l line.Line) error
|
||||
|
||||
// cursor tracks the position in the prompt input line.
|
||||
// glyph is the 0-indexed terminal display column.
|
||||
// index is the 0-indexed byte offset in line's UTF-8 string.
|
||||
type cursor struct {
|
||||
glyph int
|
||||
index int
|
||||
}
|
||||
|
||||
type Parameters struct {
|
||||
Log *slog.Logger
|
||||
}
|
||||
|
||||
func New(p Parameters) *Prompt {
|
||||
return &Prompt{log: p.Log}
|
||||
}
|
||||
|
||||
// Complete runs the prompt callback with the current input line.
|
||||
// If the callback returns an error, the prompt stays active and displays the
|
||||
// error; otherwise the prompt state is cleared.
|
||||
func (p *Prompt) Complete() {
|
||||
if err := p.cb(*p.line); err != nil {
|
||||
p.log.Error("callback error", slog.Any("error", err))
|
||||
p.err = err
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// No error -- we should be done.
|
||||
p.cb = nil
|
||||
p.err = nil
|
||||
p.line = nil
|
||||
p.text = ""
|
||||
p.cursor = cursor{}
|
||||
}
|
||||
|
||||
func (p *Prompt) Cursor() int {
|
||||
return p.cursor.glyph
|
||||
}
|
||||
|
||||
func (p *Prompt) Escape() {
|
||||
p.cb = nil
|
||||
p.err = nil
|
||||
p.line = nil
|
||||
p.text = ""
|
||||
p.cursor = cursor{}
|
||||
}
|
||||
|
||||
// IsPrompt reports whether an interactive prompt is active (as opposed to a
|
||||
// passive status message).
|
||||
func (p *Prompt) IsPrompt() bool {
|
||||
return p.line != nil
|
||||
}
|
||||
|
||||
func (p *Prompt) Prompt(s string, cb CB) {
|
||||
p.text = s
|
||||
p.line = &line.Line{}
|
||||
p.cb = cb
|
||||
|
||||
p.log.Debug("new prompt", slog.Any("prompt", p))
|
||||
}
|
||||
|
||||
func (p *Prompt) SetText(s string) {
|
||||
p.text = s
|
||||
}
|
||||
|
||||
func (p *Prompt) Text() string {
|
||||
if p.err != nil {
|
||||
return fmt.Sprintf("[%v] %s", p.err, p.text)
|
||||
}
|
||||
|
||||
return p.text
|
||||
}
|
||||
|
||||
func (p *Prompt) Line(offset, width int) string {
|
||||
if p.line == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return p.line.Render(offset, width)
|
||||
}
|
||||
|
||||
func (p *Prompt) CursorLeft() {
|
||||
if p.cursor.index > 0 {
|
||||
var (
|
||||
r rune
|
||||
size int
|
||||
)
|
||||
// Reverse until we hit the start of a rune.
|
||||
for i := p.cursor.index - 1; i >= 0; i-- {
|
||||
r, size = p.line.DecodeRune(i)
|
||||
if r != utf8.RuneError {
|
||||
p.cursor.index -= size
|
||||
p.cursor.glyph -= glyph.Width(r)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Prompt) CursorRight() {
|
||||
if p.cursor.index < p.line.Length() {
|
||||
r, size := p.line.DecodeRune(p.cursor.index)
|
||||
if r == utf8.RuneError {
|
||||
p.cursor.index++
|
||||
p.cursor.glyph += glyph.Width(r)
|
||||
} else {
|
||||
p.cursor.index += size
|
||||
p.cursor.glyph += glyph.Width(r)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Prompt) CursorHome() {
|
||||
p.cursor.glyph = 0
|
||||
p.cursor.index = 0
|
||||
}
|
||||
|
||||
func (p *Prompt) CursorEnd() {
|
||||
p.cursor.index = p.line.Length()
|
||||
p.cursor.glyph = p.line.Width()
|
||||
}
|
||||
|
||||
func (p *Prompt) Backspace() {
|
||||
if p.cursor.index > 0 {
|
||||
p.CursorLeft()
|
||||
p.line.DeleteRune(p.cursor.index)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Prompt) Insert(r rune) {
|
||||
switch {
|
||||
// Ignore all other non-printable characters.
|
||||
case !unicode.IsPrint(r):
|
||||
return
|
||||
|
||||
default:
|
||||
p.line.Insert(p.cursor.index, r)
|
||||
p.cursor.index += utf8.RuneLen(r)
|
||||
p.cursor.glyph += glyph.Width(r)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user