Add arrow keys
This commit is contained in:
@@ -16,8 +16,8 @@ const (
|
||||
)
|
||||
|
||||
type Editor struct {
|
||||
scr *Screen
|
||||
kb *Keyboard
|
||||
scr *screen
|
||||
kb *keyboard
|
||||
col, row int
|
||||
x, y int
|
||||
lines []string
|
||||
@@ -29,8 +29,8 @@ type Editor struct {
|
||||
func Init() *Editor {
|
||||
console.Raw()
|
||||
|
||||
scr := NewScreen()
|
||||
kb := NewKeyboard()
|
||||
scr := newScreen()
|
||||
kb := newKeyboard()
|
||||
|
||||
return &Editor{
|
||||
scr: &scr,
|
||||
@@ -58,42 +58,85 @@ func (ed *Editor) runeCount() int {
|
||||
return utf8.RuneCountInString(ed.lines[ed.row])
|
||||
}
|
||||
|
||||
func (ed *Editor) lineHeight(line string) int {
|
||||
w, _ := ed.scr.size()
|
||||
rc := utf8.RuneCountInString(line)
|
||||
width := util.StringWidth(line, rc)
|
||||
return 1 + max(width-1, 0)/w
|
||||
}
|
||||
|
||||
func (ed *Editor) drawBuffer() {
|
||||
_, h := ed.scr.size()
|
||||
|
||||
y := 0
|
||||
for i := 0; i < len(ed.lines); i++ {
|
||||
console.MoveCursor(0, i)
|
||||
var line string
|
||||
if ed.mode == modeInsert && i == ed.row {
|
||||
console.Print(ed.head)
|
||||
console.Print(ed.insert.String())
|
||||
console.Print(ed.tail)
|
||||
line = ed.head + ed.insert.String() + ed.tail
|
||||
} else {
|
||||
console.Print(ed.lines[i])
|
||||
line = ed.lines[i]
|
||||
}
|
||||
|
||||
console.MoveCursor(0, y)
|
||||
console.Print(line)
|
||||
|
||||
y += ed.lineHeight(line)
|
||||
if y >= h-1 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
for ; y < h-1; y++ {
|
||||
console.MoveCursor(0, y)
|
||||
console.Print("~")
|
||||
}
|
||||
}
|
||||
|
||||
func (ed *Editor) drawStatus() {
|
||||
_, h := console.Size()
|
||||
_, h := ed.scr.size()
|
||||
|
||||
console.MoveCursor(0, h-2)
|
||||
console.MoveCursor(0, h-1)
|
||||
switch ed.mode {
|
||||
case modeCommand:
|
||||
console.Print("-- [command] q: quit, i: insert, a: insert after --")
|
||||
console.Print("c")
|
||||
case modeInsert:
|
||||
console.Print("-- [insert] Esc: command mode --")
|
||||
console.Print("i")
|
||||
}
|
||||
}
|
||||
|
||||
func (ed *Editor) updateCursor() {
|
||||
w, _ := ed.scr.size()
|
||||
|
||||
var dy int
|
||||
switch ed.mode {
|
||||
case modeCommand:
|
||||
ed.row = min(max(ed.row, 0), max(len(ed.lines)-1, 0))
|
||||
len := ed.runeCount()
|
||||
ed.col = min(ed.col, max(len-1, 0))
|
||||
ed.x = util.StringWidth(ed.lines[ed.row], ed.col)
|
||||
|
||||
// XXX approximation
|
||||
width := util.StringWidth(ed.lines[ed.row], ed.col)
|
||||
ed.x = width % w
|
||||
dy = width / w
|
||||
case modeInsert:
|
||||
ed.x = util.StringWidth(ed.head+ed.insert.String(), ed.col)
|
||||
// XXX approximation
|
||||
width := util.StringWidth(ed.head+ed.insert.String(), ed.col)
|
||||
ed.x = width % w
|
||||
dy = width / w
|
||||
}
|
||||
ed.y = ed.row
|
||||
|
||||
y := 0
|
||||
for i := 0; i < ed.row; i++ {
|
||||
var line string
|
||||
if ed.mode == modeInsert && i == ed.row {
|
||||
line = ed.head + ed.insert.String() + ed.tail
|
||||
} else {
|
||||
line = ed.lines[i]
|
||||
}
|
||||
|
||||
y += ed.lineHeight(line)
|
||||
}
|
||||
ed.y = y + dy
|
||||
}
|
||||
|
||||
func (ed *Editor) repaint() {
|
||||
@@ -120,11 +163,18 @@ func (ed *Editor) exitInsert() {
|
||||
}
|
||||
|
||||
func (ed *Editor) insertNewline() {
|
||||
ed.lines[ed.row] = ed.head + ed.insert.String()
|
||||
ed.lines = append(ed.lines, "")
|
||||
copy(ed.lines[ed.row+1:], ed.lines[ed.row:])
|
||||
lines := make([]string, 0, len(ed.lines)+1)
|
||||
lines = append(lines, ed.lines[:ed.row+1]...)
|
||||
lines = append(lines, "")
|
||||
if ed.row+1 < len(ed.lines) {
|
||||
lines = append(lines, ed.lines[ed.row+1:]...)
|
||||
}
|
||||
|
||||
lines[ed.row] = ed.head + ed.insert.String()
|
||||
lines[ed.row+1] = ed.tail
|
||||
ed.lines = lines
|
||||
ed.row++
|
||||
ed.lines[ed.row] = ed.tail
|
||||
|
||||
ed.col = 0
|
||||
ed.head = ""
|
||||
ed.insert = new(strings.Builder)
|
||||
@@ -139,33 +189,63 @@ func (ed *Editor) Main() {
|
||||
for {
|
||||
ed.repaint()
|
||||
|
||||
r := ed.kb.ReadRune()
|
||||
k, r := ed.kb.readKey()
|
||||
switch ed.mode {
|
||||
case modeCommand:
|
||||
switch r {
|
||||
case 'q':
|
||||
return
|
||||
case 'i':
|
||||
ed.enterInsert()
|
||||
case 'a':
|
||||
ed.enterInsertAfter()
|
||||
case 'h':
|
||||
ed.moveLeft(1)
|
||||
case 'l':
|
||||
ed.moveRight(1)
|
||||
case 'j':
|
||||
ed.moveDown(1)
|
||||
case 'k':
|
||||
switch k {
|
||||
case keyNormal:
|
||||
switch r {
|
||||
case 'q':
|
||||
return
|
||||
case 'i':
|
||||
ed.enterInsert()
|
||||
case 'a':
|
||||
ed.enterInsertAfter()
|
||||
case 'h':
|
||||
ed.moveLeft(1)
|
||||
case 'l':
|
||||
ed.moveRight(1)
|
||||
case 'j':
|
||||
ed.moveDown(1)
|
||||
case 'k':
|
||||
ed.moveUp(1)
|
||||
}
|
||||
case keyUp:
|
||||
ed.moveUp(1)
|
||||
case keyDown:
|
||||
ed.moveDown(1)
|
||||
case keyRight:
|
||||
ed.moveRight(1)
|
||||
case keyLeft:
|
||||
ed.moveLeft(1)
|
||||
default:
|
||||
// TODO ring
|
||||
}
|
||||
case modeInsert:
|
||||
switch r {
|
||||
case Escape:
|
||||
switch k {
|
||||
case keyNormal:
|
||||
switch r {
|
||||
case runeEscape:
|
||||
ed.exitInsert()
|
||||
case runeEnter:
|
||||
ed.insertNewline()
|
||||
default:
|
||||
ed.insertRune(r)
|
||||
}
|
||||
case keyUp:
|
||||
ed.exitInsert()
|
||||
case Enter:
|
||||
ed.insertNewline()
|
||||
ed.moveUp(1)
|
||||
case keyDown:
|
||||
ed.exitInsert()
|
||||
ed.moveDown(1)
|
||||
case keyRight:
|
||||
ed.exitInsert()
|
||||
ed.moveRight(1)
|
||||
case keyLeft:
|
||||
ed.exitInsert()
|
||||
ed.moveLeft(1)
|
||||
default:
|
||||
ed.insertRune(r)
|
||||
// TODO ring
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,15 +4,56 @@ import (
|
||||
"tea.kareha.org/lab/levi/internal/console"
|
||||
)
|
||||
|
||||
const Escape rune = 0x1b
|
||||
const Enter rune = '\r'
|
||||
const runeEscape rune = 0x1b
|
||||
const runeEnter rune = '\r'
|
||||
|
||||
type Keyboard struct{}
|
||||
type key int
|
||||
|
||||
func NewKeyboard() Keyboard {
|
||||
return Keyboard{}
|
||||
const (
|
||||
keyNormal = iota
|
||||
keyUp
|
||||
keyDown
|
||||
keyRight
|
||||
keyLeft
|
||||
)
|
||||
|
||||
type keyboard struct {
|
||||
buf []rune
|
||||
}
|
||||
|
||||
func (kb *Keyboard) ReadRune() rune {
|
||||
return console.ReadRune()
|
||||
func newKeyboard() keyboard {
|
||||
return keyboard{
|
||||
buf: make([]rune, 0),
|
||||
}
|
||||
}
|
||||
|
||||
func (kb *keyboard) readKey() (key, rune) {
|
||||
if len(kb.buf) > 0 {
|
||||
r := kb.buf[0]
|
||||
kb.buf = kb.buf[1:]
|
||||
return keyNormal, r
|
||||
}
|
||||
r := console.ReadRune()
|
||||
if r != runeEscape {
|
||||
return keyNormal, r
|
||||
}
|
||||
r2 := console.ReadRune()
|
||||
if r2 != '[' {
|
||||
kb.buf = append(kb.buf, r2)
|
||||
return keyNormal, r
|
||||
}
|
||||
r3 := console.ReadRune()
|
||||
switch r3 {
|
||||
case 'A':
|
||||
return keyUp, 0
|
||||
case 'B':
|
||||
return keyDown, 0
|
||||
case 'C':
|
||||
return keyRight, 0
|
||||
case 'D':
|
||||
return keyLeft, 0
|
||||
}
|
||||
kb.buf = append(kb.buf, r2)
|
||||
kb.buf = append(kb.buf, r3)
|
||||
return keyNormal, r
|
||||
}
|
||||
|
||||
@@ -4,18 +4,18 @@ import (
|
||||
"tea.kareha.org/lab/levi/internal/console"
|
||||
)
|
||||
|
||||
type Screen struct {
|
||||
type screen struct {
|
||||
w, h int
|
||||
}
|
||||
|
||||
func NewScreen() Screen {
|
||||
func newScreen() screen {
|
||||
w, h := console.Size()
|
||||
return Screen{
|
||||
return screen{
|
||||
w: w,
|
||||
h: h,
|
||||
}
|
||||
}
|
||||
|
||||
func (scr *Screen) Size() (int, int) {
|
||||
func (scr *screen) size() (int, int) {
|
||||
return scr.w, scr.h
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user