mirror of
https://github.com/go-sylixos/elvish.git
synced 2024-12-13 09:57:51 +08:00
edit: Factor buffer keeping & syncing to a struct writer
This commit is contained in:
parent
153d3890b7
commit
86b22ee87b
|
@ -1,9 +0,0 @@
|
|||
package edit
|
||||
|
||||
// cell is an indivisible unit on the screen. It is not necessarily 1 column
|
||||
// wide.
|
||||
type cell struct {
|
||||
rune
|
||||
width byte
|
||||
attr string
|
||||
}
|
108
edit/editor.go
108
edit/editor.go
|
@ -5,24 +5,16 @@ import (
|
|||
"os"
|
||||
"fmt"
|
||||
"bufio"
|
||||
"bytes"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
"./tty"
|
||||
"../parse"
|
||||
)
|
||||
|
||||
// buffer keeps the status of the screen that the line editor is concerned
|
||||
// with. It usually reflects the last n lines of the screen.
|
||||
type buffer [][]cell
|
||||
|
||||
// Editor keeps the status of the line editor.
|
||||
type Editor struct {
|
||||
savedTermios *tty.Termios
|
||||
file *os.File
|
||||
oldBuf, buf buffer
|
||||
line, col, width, indent int
|
||||
currentAttr string
|
||||
writer *writer
|
||||
}
|
||||
|
||||
// LineRead is the result of ReadLine. Exactly one member is non-zero, making
|
||||
|
@ -44,7 +36,7 @@ func Init(file *os.File) (*Editor, error) {
|
|||
editor := &Editor{
|
||||
savedTermios: term.Copy(),
|
||||
file: file,
|
||||
oldBuf: [][]cell{[]cell{}},
|
||||
writer: newWriter(),
|
||||
}
|
||||
|
||||
term.SetIcanon(false)
|
||||
|
@ -90,102 +82,8 @@ func (ed *Editor) clearTip() {
|
|||
fmt.Fprintf(ed.file, "\n\033[K\033[A")
|
||||
}
|
||||
|
||||
func (ed *Editor) startBuffer() {
|
||||
ed.line = 0
|
||||
ed.col = 0
|
||||
ed.width = int(tty.GetWinsize(int(ed.file.Fd())).Col)
|
||||
ed.buf = [][]cell{make([]cell, ed.width)}
|
||||
ed.currentAttr = ""
|
||||
}
|
||||
|
||||
func (ed *Editor) commitBuffer() error {
|
||||
bytesBuf := new(bytes.Buffer)
|
||||
|
||||
newlines := len(ed.oldBuf) - 1
|
||||
if newlines > 0 {
|
||||
fmt.Fprintf(bytesBuf, "\033[%dA", newlines)
|
||||
}
|
||||
bytesBuf.WriteString("\r\033[J")
|
||||
|
||||
attr := ""
|
||||
for _, line := range ed.buf {
|
||||
for _, c := range line {
|
||||
if c.width > 0 && c.attr != attr {
|
||||
fmt.Fprintf(bytesBuf, "\033[%sm", c.attr)
|
||||
attr = c.attr
|
||||
}
|
||||
bytesBuf.WriteString(string(c.rune))
|
||||
}
|
||||
}
|
||||
if attr != "" {
|
||||
bytesBuf.WriteString("\033[m")
|
||||
}
|
||||
_, err := ed.file.Write(bytesBuf.Bytes())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ed.oldBuf = ed.buf
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ed *Editor) appendToLine(c cell) {
|
||||
ed.buf[ed.line] = append(ed.buf[ed.line], c)
|
||||
ed.col += int(c.width)
|
||||
}
|
||||
|
||||
func (ed *Editor) newline() {
|
||||
ed.buf[ed.line] = append(ed.buf[ed.line], cell{rune: '\n'})
|
||||
ed.buf = append(ed.buf, make([]cell, ed.width))
|
||||
ed.line++
|
||||
ed.col = 0
|
||||
if ed.indent > 0 {
|
||||
for i := 0; i < ed.indent; i++ {
|
||||
ed.appendToLine(cell{rune: ' ', width: 1})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (ed *Editor) write(r rune) {
|
||||
wd := wcwidth(r)
|
||||
c := cell{r, byte(wd), ed.currentAttr}
|
||||
|
||||
if ed.col + wd > ed.width {
|
||||
ed.newline()
|
||||
ed.appendToLine(c)
|
||||
} else if ed.col + wd == ed.width {
|
||||
ed.appendToLine(c)
|
||||
ed.newline()
|
||||
} else {
|
||||
ed.appendToLine(c)
|
||||
}
|
||||
}
|
||||
|
||||
func (ed *Editor) refresh(prompt, text string) error {
|
||||
ed.startBuffer()
|
||||
|
||||
for _, r := range prompt {
|
||||
ed.write(r)
|
||||
}
|
||||
|
||||
if ed.col * 2 < ed.width {
|
||||
ed.indent = ed.col
|
||||
}
|
||||
|
||||
l := parse.Lex("<interactive code>", text)
|
||||
|
||||
for {
|
||||
token := l.NextItem()
|
||||
if token.Typ == parse.ItemEOF {
|
||||
break
|
||||
}
|
||||
ed.currentAttr = attrForType[token.Typ]
|
||||
for _, r := range token.Val {
|
||||
ed.write(r)
|
||||
}
|
||||
}
|
||||
|
||||
return ed.commitBuffer()
|
||||
return ed.writer.refresh(prompt, text, ed.file)
|
||||
}
|
||||
|
||||
// ReadLine reads a line interactively.
|
||||
|
|
132
edit/writer.go
Normal file
132
edit/writer.go
Normal file
|
@ -0,0 +1,132 @@
|
|||
package edit
|
||||
|
||||
import (
|
||||
"os"
|
||||
"fmt"
|
||||
"bytes"
|
||||
"./tty"
|
||||
"../parse"
|
||||
)
|
||||
|
||||
// cell is an indivisible unit on the screen. It is not necessarily 1 column
|
||||
// wide.
|
||||
type cell struct {
|
||||
rune
|
||||
width byte
|
||||
attr string
|
||||
}
|
||||
|
||||
// buffer keeps the status of the screen that the line editor is concerned
|
||||
// with. It usually reflects the last n lines of the screen.
|
||||
type buffer [][]cell
|
||||
|
||||
// writer is the part of an Editor responsible for keeping the status of and
|
||||
// updating the screen.
|
||||
type writer struct {
|
||||
oldBuf, buf buffer
|
||||
line, col, width, indent int
|
||||
currentAttr string
|
||||
}
|
||||
|
||||
func newWriter() *writer {
|
||||
writer := &writer{oldBuf: buffer{[]cell{}}}
|
||||
return writer
|
||||
}
|
||||
|
||||
func (w *writer) startBuffer(file *os.File) {
|
||||
w.line = 0
|
||||
w.col = 0
|
||||
w.width = int(tty.GetWinsize(int(file.Fd())).Col)
|
||||
w.buf = [][]cell{make([]cell, w.width)}
|
||||
w.currentAttr = ""
|
||||
}
|
||||
|
||||
func (w *writer) commitBuffer(file *os.File) error {
|
||||
bytesBuf := new(bytes.Buffer)
|
||||
|
||||
newlines := len(w.oldBuf) - 1
|
||||
if newlines > 0 {
|
||||
fmt.Fprintf(bytesBuf, "\033[%dA", newlines)
|
||||
}
|
||||
bytesBuf.WriteString("\r\033[J")
|
||||
|
||||
attr := ""
|
||||
for _, line := range w.buf {
|
||||
for _, c := range line {
|
||||
if c.width > 0 && c.attr != attr {
|
||||
fmt.Fprintf(bytesBuf, "\033[%sm", c.attr)
|
||||
attr = c.attr
|
||||
}
|
||||
bytesBuf.WriteString(string(c.rune))
|
||||
}
|
||||
}
|
||||
if attr != "" {
|
||||
bytesBuf.WriteString("\033[m")
|
||||
}
|
||||
_, err := file.Write(bytesBuf.Bytes())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
w.oldBuf = w.buf
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *writer) appendToLine(c cell) {
|
||||
w.buf[w.line] = append(w.buf[w.line], c)
|
||||
w.col += int(c.width)
|
||||
}
|
||||
|
||||
func (w *writer) newline() {
|
||||
w.buf[w.line] = append(w.buf[w.line], cell{rune: '\n'})
|
||||
w.buf = append(w.buf, make([]cell, w.width))
|
||||
w.line++
|
||||
w.col = 0
|
||||
if w.indent > 0 {
|
||||
for i := 0; i < w.indent; i++ {
|
||||
w.appendToLine(cell{rune: ' ', width: 1})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (w *writer) write(r rune) {
|
||||
wd := wcwidth(r)
|
||||
c := cell{r, byte(wd), w.currentAttr}
|
||||
|
||||
if w.col + wd > w.width {
|
||||
w.newline()
|
||||
w.appendToLine(c)
|
||||
} else if w.col + wd == w.width {
|
||||
w.appendToLine(c)
|
||||
w.newline()
|
||||
} else {
|
||||
w.appendToLine(c)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *writer) refresh(prompt, text string, file *os.File) error {
|
||||
w.startBuffer(file)
|
||||
|
||||
for _, r := range prompt {
|
||||
w.write(r)
|
||||
}
|
||||
|
||||
if w.col * 2 < w.width {
|
||||
w.indent = w.col
|
||||
}
|
||||
|
||||
l := parse.Lex("<interactive code>", text)
|
||||
|
||||
for {
|
||||
token := l.NextItem()
|
||||
if token.Typ == parse.ItemEOF {
|
||||
break
|
||||
}
|
||||
w.currentAttr = attrForType[token.Typ]
|
||||
for _, r := range token.Val {
|
||||
w.write(r)
|
||||
}
|
||||
}
|
||||
|
||||
return w.commitBuffer(file)
|
||||
}
|
Loading…
Reference in New Issue
Block a user