Add editor packages
This commit is contained in:
@@ -0,0 +1,218 @@
|
||||
package buffer
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"unicode/utf8"
|
||||
|
||||
"code.chimeric.al/dwrz/wen/internal/editor/buffer/glyph"
|
||||
)
|
||||
|
||||
// Cursor tracks the position in a Buffer.
|
||||
// glyph is the 0-indexed terminal display column (tabs are 8 columns, wide
|
||||
// runes are 2 columns).
|
||||
// index is the 0-indexed byte offset in the current line's UTF-8 string.
|
||||
// line is the 0-indexed row in Buffer.lines.
|
||||
type Cursor struct {
|
||||
glyph int
|
||||
index int
|
||||
line int
|
||||
}
|
||||
|
||||
func (b *Buffer) Cursor() Cursor {
|
||||
return *b.cursor
|
||||
}
|
||||
|
||||
func (c Cursor) Glyph() int {
|
||||
return c.glyph
|
||||
}
|
||||
|
||||
func (c Cursor) Index() int {
|
||||
return c.index
|
||||
}
|
||||
|
||||
func (c Cursor) Line() int {
|
||||
return c.line
|
||||
}
|
||||
|
||||
func (b *Buffer) CursorDown() {
|
||||
b.log.Debug("↓ before", slog.Any("cursor", b.cursor))
|
||||
defer b.log.Debug("↓ after", slog.Any("cursor", b.cursor))
|
||||
|
||||
// If we're on the last line, don't move down.
|
||||
if b.cursor.line >= len(b.lines)-1 {
|
||||
return
|
||||
}
|
||||
|
||||
// Move to the next line.
|
||||
b.cursor.line++
|
||||
|
||||
// Adjust the column.
|
||||
var (
|
||||
line = b.CursorLine()
|
||||
length = line.Length()
|
||||
width = line.Width()
|
||||
)
|
||||
switch {
|
||||
// If we've moved to a shorter line, snap to its end.
|
||||
case b.cursor.glyph > width:
|
||||
b.cursor.index = length
|
||||
b.cursor.glyph = width
|
||||
|
||||
default:
|
||||
b.cursor.index, b.cursor.glyph = line.FindGlyphIndex(
|
||||
b.cursor.glyph,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Buffer) CursorLeft() {
|
||||
b.log.Debug("← before", slog.Any("cursor", b.cursor))
|
||||
defer b.log.Debug("← after", slog.Any("cursor", b.cursor))
|
||||
|
||||
switch {
|
||||
// If we're at the beginning of the line, move to the line above;
|
||||
// unless we're at the start of the buffer.
|
||||
case b.cursor.index == 0 && b.cursor.line > 0:
|
||||
b.cursor.line--
|
||||
|
||||
line := b.CursorLine()
|
||||
b.cursor.index = line.Length()
|
||||
b.cursor.glyph = line.Width()
|
||||
|
||||
// Move left by one rune.
|
||||
case b.cursor.index > 0:
|
||||
var (
|
||||
line = b.CursorLine()
|
||||
r rune
|
||||
size int
|
||||
)
|
||||
// Reverse until we hit the start of a rune.
|
||||
for i := b.cursor.index - 1; i >= 0; i-- {
|
||||
r, size = line.DecodeRune(i)
|
||||
if r != utf8.RuneError {
|
||||
b.cursor.index -= size
|
||||
b.cursor.glyph -= glyph.Width(r)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Buffer) CursorRight() {
|
||||
b.log.Debug("→ before", slog.Any("cursor", b.cursor))
|
||||
defer b.log.Debug("→ after", slog.Any("cursor", b.cursor))
|
||||
|
||||
var (
|
||||
line = b.CursorLine()
|
||||
length = line.Length()
|
||||
)
|
||||
|
||||
switch {
|
||||
// If we're at the end of the line, move to the line below;
|
||||
// unless we're at the end of the buffer.
|
||||
case b.cursor.index == length && b.cursor.line < len(b.lines)-1:
|
||||
b.cursor.line++
|
||||
b.cursor.index = 0
|
||||
b.cursor.glyph = 0
|
||||
|
||||
// Move the index right by one rune.
|
||||
case b.cursor.index < length:
|
||||
r, size := line.DecodeRune(b.cursor.index)
|
||||
if r == utf8.RuneError {
|
||||
b.cursor.index++
|
||||
b.cursor.glyph += glyph.Width(r)
|
||||
} else {
|
||||
b.cursor.index += size
|
||||
b.cursor.glyph += glyph.Width(r)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Buffer) CursorUp() {
|
||||
b.log.Debug("↑ before", slog.Any("cursor", b.cursor))
|
||||
defer b.log.Debug("↑ after", slog.Any("cursor", b.cursor))
|
||||
|
||||
// If we're on the first line, don't move up.
|
||||
if b.cursor.line == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
b.cursor.line--
|
||||
|
||||
// Adjust the column.
|
||||
var (
|
||||
line = b.CursorLine()
|
||||
length = line.Length()
|
||||
width = line.Width()
|
||||
)
|
||||
switch {
|
||||
case b.cursor.glyph > width:
|
||||
b.cursor.index = length
|
||||
b.cursor.glyph = width
|
||||
|
||||
default:
|
||||
b.cursor.index, b.cursor.glyph = line.FindGlyphIndex(
|
||||
b.cursor.glyph,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Buffer) PageDown(height int) {
|
||||
if b.cursor.line+height >= len(b.lines) {
|
||||
b.cursor.line = len(b.lines) - 1
|
||||
} else {
|
||||
b.cursor.line += height
|
||||
}
|
||||
|
||||
var (
|
||||
line = b.CursorLine()
|
||||
length = line.Length()
|
||||
width = line.Width()
|
||||
)
|
||||
switch {
|
||||
case b.cursor.glyph > width:
|
||||
b.cursor.index = length
|
||||
b.cursor.glyph = width
|
||||
|
||||
default:
|
||||
b.cursor.index, b.cursor.glyph = line.FindGlyphIndex(
|
||||
b.cursor.glyph,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Buffer) PageUp(height int) {
|
||||
if b.cursor.line-height > 0 {
|
||||
b.cursor.line -= height
|
||||
} else {
|
||||
b.cursor.line = 0
|
||||
}
|
||||
|
||||
var (
|
||||
line = b.CursorLine()
|
||||
length = line.Length()
|
||||
width = line.Width()
|
||||
)
|
||||
switch {
|
||||
case b.cursor.glyph > width:
|
||||
b.cursor.index = length
|
||||
b.cursor.glyph = width
|
||||
|
||||
default:
|
||||
b.cursor.index, b.cursor.glyph = line.FindGlyphIndex(
|
||||
b.cursor.glyph,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Buffer) CursorHome() {
|
||||
b.cursor.glyph = 0
|
||||
b.cursor.index = 0
|
||||
}
|
||||
|
||||
func (b *Buffer) CursorEnd() {
|
||||
var line = b.CursorLine()
|
||||
|
||||
b.cursor.index = line.Length()
|
||||
b.cursor.glyph = line.Width()
|
||||
}
|
||||
Reference in New Issue
Block a user