src

Go monorepo.
Log | Files | Refs

commit dbbdfdb26227884c1cd95f892716bccfca3040de
parent 67080321d318b0a7c24d36c91bde2bcc8197678f
Author: dwrz <dwrz@dwrz.net>
Date:   Thu,  8 Dec 2022 22:15:26 +0000

Refactor editor to use canvas pkg

Diffstat:
Apkg/editor/canvas/canvas.go | 26++++++++++++++++++++++++++
Apkg/editor/canvas/render.go | 115+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Apkg/editor/canvas/reset.go | 8++++++++
Mpkg/editor/editor.go | 9++++++---
Mpkg/editor/quit.go | 7+------
Dpkg/editor/render.go | 121-------------------------------------------------------------------------------
Mpkg/editor/run.go | 7+++----
7 files changed, 159 insertions(+), 134 deletions(-)

diff --git a/pkg/editor/canvas/canvas.go b/pkg/editor/canvas/canvas.go @@ -0,0 +1,26 @@ +package canvas + +import ( + "bytes" + "os" + + "code.dwrz.net/src/pkg/terminal" +) + +type Canvas struct { + buf bytes.Buffer + out *os.File + terminal *terminal.Terminal +} + +type Parameters struct { + Out *os.File + Terminal *terminal.Terminal +} + +func New(p Parameters) *Canvas { + return &Canvas{ + out: p.Out, + terminal: p.Terminal, + } +} diff --git a/pkg/editor/canvas/render.go b/pkg/editor/canvas/render.go @@ -0,0 +1,115 @@ +package canvas + +import ( + "fmt" + "path" + + "code.dwrz.net/src/pkg/color" + "code.dwrz.net/src/pkg/editor/buffer" + "code.dwrz.net/src/pkg/editor/message" + "code.dwrz.net/src/pkg/terminal" + "github.com/mattn/go-runewidth" +) + +func (c *Canvas) Render(b *buffer.Buffer, msg *message.Message) error { + size, err := c.terminal.Size() + if err != nil { + return fmt.Errorf("failed to get terminal size: %w", err) + } + + var ( + cursor = b.Cursor() + bars = 2 + height = int(size.Rows) - bars + width = int(size.Columns) + output = b.Render(height, width) + ) + + // Move the cursor to the top left. + c.buf.Write([]byte(terminal.CursorHide)) + c.buf.Write([]byte(terminal.CursorTopLeft)) + + // Print each line. + for _, line := range output.Lines { + c.buf.Write([]byte(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(msg, width) + + // Set the cursor. + c.buf.Write([]byte(fmt.Sprintf( + terminal.SetCursorFmt, cursor.Line(), cursor.Glyph(), + ))) + c.buf.Write([]byte(terminal.CursorShow)) + + c.out.Write(c.buf.Bytes()) + + return nil +} + +// TODO: show a character cursor, not the terminal cursor. +func (c *Canvas) statusBar(b *buffer.Buffer, width, y, x int) { + c.buf.Write([]byte(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(msg *message.Message, width int) { + c.buf.Write([]byte(terminal.EraseLine)) + switch { + case msg == nil: + c.buf.WriteString(fmt.Sprintf("%*s", width, " ")) + case len(msg.Text) > width: + c.buf.WriteString(fmt.Sprintf("%*s", width, msg.Text)) + default: + c.buf.WriteString(fmt.Sprintf( + "%s %*s", msg.Text, width-len(msg.Text), " ", + )) + } +} diff --git a/pkg/editor/canvas/reset.go b/pkg/editor/canvas/reset.go @@ -0,0 +1,8 @@ +package canvas + +import "code.dwrz.net/src/pkg/terminal" + +func (c *Canvas) Reset() { + c.out.Write([]byte(terminal.ClearScreen)) + c.out.Write([]byte(terminal.CursorTopLeft)) +} diff --git a/pkg/editor/editor.go b/pkg/editor/editor.go @@ -6,17 +6,18 @@ import ( "sync" "code.dwrz.net/src/pkg/editor/buffer" + "code.dwrz.net/src/pkg/editor/canvas" "code.dwrz.net/src/pkg/editor/message" "code.dwrz.net/src/pkg/log" "code.dwrz.net/src/pkg/terminal" ) type Editor struct { + canvas *canvas.Canvas errs chan error in *os.File input chan input log *log.Logger - out *os.File messages chan *message.Message terminal *terminal.Terminal tmpdir string @@ -36,12 +37,14 @@ type Parameters struct { func New(p Parameters) (*Editor, error) { var editor = &Editor{ - buffers: map[string]*buffer.Buffer{}, + buffers: map[string]*buffer.Buffer{}, + canvas: canvas.New(canvas.Parameters{ + Out: p.Out, Terminal: p.Terminal, + }), errs: make(chan error), in: p.In, input: make(chan input), log: p.Log, - out: p.Out, messages: make(chan *message.Message), terminal: p.Terminal, tmpdir: p.TempDir, diff --git a/pkg/editor/quit.go b/pkg/editor/quit.go @@ -1,12 +1,7 @@ package editor -import ( - "code.dwrz.net/src/pkg/terminal" -) - func (e *Editor) quit() { - e.out.Write([]byte(terminal.ClearScreen)) - e.out.Write([]byte(terminal.CursorTopLeft)) + e.canvas.Reset() if err := e.terminal.Reset(); err != nil { e.log.Error.Printf( diff --git a/pkg/editor/render.go b/pkg/editor/render.go @@ -1,121 +0,0 @@ -package editor - -import ( - "bytes" - "fmt" - "path" - "strings" - - "github.com/mattn/go-runewidth" - - "code.dwrz.net/src/pkg/color" - "code.dwrz.net/src/pkg/terminal" -) - -func (e *Editor) render(msg *Message) error { - size, err := e.terminal.Size() - if err != nil { - return fmt.Errorf("failed to get terminal size: %w", err) - } - - var ( - buf bytes.Buffer - cursor = e.active.Cursor() - bars = 2 - height = int(size.Rows) - bars - width = int(size.Columns) - output = e.active.Render(height, width) - ) - - // Move the cursor to the top left. - buf.Write([]byte(terminal.CursorHide)) - buf.Write([]byte(terminal.CursorTopLeft)) - - // Print each line. - for _, line := range output.Lines { - buf.Write([]byte(terminal.EraseLine)) - buf.WriteString(line) - buf.WriteString("\r\n") - } - - // Draw the status bar. - buf.Write([]byte(terminal.EraseLine)) - buf.WriteString(e.statusBar(width, cursor.Line(), cursor.Glyph())) - buf.WriteString("\r\n") - - // Draw the message bar. - buf.Write([]byte(terminal.EraseLine)) - buf.WriteString(e.messageBar(msg, width)) - - // Set the cursor. - buf.Write([]byte( - fmt.Sprintf(terminal.SetCursorFmt, output.Line, output.Glyph)), - ) - buf.Write([]byte(terminal.CursorShow)) - - e.out.Write(buf.Bytes()) - - return nil -} - -// TODO: show a character cursor, not the terminal cursor. -func (e *Editor) statusBar(width, y, x int) string { - var bar strings.Builder - - bar.WriteString(color.Inverse) - - // Icon - icon := "文 " - bar.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(e.active.Name()) - nw := runewidth.StringWidth(name) - if nw <= width { - bar.WriteString(name) - width -= nw - } else { - for _, r := range name { - rw := runewidth.RuneWidth(r) - if width-rw >= 0 { - bar.WriteRune(r) - width -= rw - } else { - break - } - } - } - - // Add empty spaces to the end of the line. - for i := width; i >= 0; i-- { - bar.WriteRune(' ') - } - - // Cursor - bar.WriteString(cursor) - - bar.WriteString(color.Reset) - - return bar.String() -} - -// TODO: handle messages that are too long for one line. -// TODO: should status bar render independently? -func (e *Editor) messageBar(msg *Message, width int) string { - switch { - case msg == nil: - return fmt.Sprintf("%*s", width, " ") - case len(msg.Text) > width: - return fmt.Sprintf("%*s", width, msg.Text) - default: - return fmt.Sprintf("%s %*s", msg.Text, width-len(msg.Text), " ") - } - -} diff --git a/pkg/editor/run.go b/pkg/editor/run.go @@ -5,13 +5,12 @@ import ( "time" "code.dwrz.net/src/pkg/build" - "code.dwrz.net/src/pkg/terminal" "code.dwrz.net/src/pkg/editor/message" ) func (e *Editor) Run(files []string) error { e.terminal.SetRaw() - e.out.Write([]byte(terminal.ClearScreen)) + e.canvas.Reset() // Log build info. e.log.Debug.Printf( @@ -42,7 +41,7 @@ func (e *Editor) Run(files []string) error { case msg := <-e.messages: e.log.Debug.Printf("%s", msg.Text) - if err := e.render(&msg); err != nil { + if err := e.canvas.Render(e.active, msg); err != nil { return fmt.Errorf("failed to render: %w", err) } @@ -55,7 +54,7 @@ func (e *Editor) Run(files []string) error { "failed to process input: %w", err, ) } - if err := e.render(nil); err != nil { + if err := e.canvas.Render(e.active, nil); err != nil { return fmt.Errorf("failed to render: %w", err) } }