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:
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)
}
}