Insert a char
All checks were successful
timw4mail/gilo/pipeline/head This commit looks good

This commit is contained in:
Timothy Warren 2021-04-01 16:17:13 -04:00
parent bd9a07feed
commit 41d1aa6553
19 changed files with 235 additions and 166 deletions

View File

@ -73,12 +73,11 @@ func (e *editor) drawRows(ab *buffer) {
} }
rowLen := e.document.rows[fileRow].rSize() - e.offset.x rowLen := e.document.rows[fileRow].rSize() - e.offset.x
outputRow := truncateString(string(e.document.rows[fileRow].render[e.offset.x:]), rowLen) outputRow := truncate(string(e.document.rows[fileRow].render[e.offset.x:]), rowLen)
ab.append(outputRow) ab.append(outputRow)
} }
ab.append(terminal.ClearLine) ab.appendLn(terminal.ClearLine)
ab.append("\r\n")
} }
} }
@ -86,7 +85,7 @@ func (e *editor) drawPlaceholderRow(y int, ab *buffer) {
if e.document.rowCount() == 0 && y == e.screen.Rows/3 { if e.document.rowCount() == 0 && y == e.screen.Rows/3 {
welcome := fmt.Sprintf("Gilo editor -- version %s", KiloVersion) welcome := fmt.Sprintf("Gilo editor -- version %s", KiloVersion)
if len(welcome) > e.screen.Cols { if len(welcome) > e.screen.Cols {
welcome = truncateString(welcome, e.screen.Cols) welcome = truncate(welcome, e.screen.Cols)
} }
padding := (e.screen.Cols - len(welcome)) / 2 padding := (e.screen.Cols - len(welcome)) / 2
@ -119,7 +118,7 @@ func (e *editor) drawStatusBar(ab *buffer) {
leftStatus := fmt.Sprintf("%.20s - %d lines", fileName, e.document.rowCount()) leftStatus := fmt.Sprintf("%.20s - %d lines", fileName, e.document.rowCount())
length := len(leftStatus) length := len(leftStatus)
if length > cols { if length > cols {
leftStatus = truncateString(leftStatus, cols) leftStatus = truncate(leftStatus, cols)
ab.append(leftStatus) ab.append(leftStatus)
ab.append(terminal.ResetColor) ab.append(terminal.ResetColor)
@ -150,7 +149,6 @@ func (e *editor) drawStatusBar(ab *buffer) {
padding := strings.Repeat(" ", cols-length) padding := strings.Repeat(" ", cols-length)
ab.append(padding) ab.append(padding)
ab.append(terminal.ResetColor) ab.append(terminal.ResetColor)
} }
@ -158,7 +156,7 @@ func (e *editor) drawMessageBar(ab *buffer) {
ab.append("\r\n") ab.append("\r\n")
ab.append(terminal.ClearLine) ab.append(terminal.ClearLine)
msg := truncateString(e.status.message, e.screen.Cols) msg := truncate(e.status.message, e.screen.Cols)
if len(msg) > 0 && time.Since(e.status.created).Seconds() < 5.0 { if len(msg) > 0 && time.Since(e.status.created).Seconds() < 5.0 {
ab.append(msg) ab.append(msg)
} }

View File

@ -3,6 +3,7 @@ package editor
import ( import (
"fmt" "fmt"
"time" "time"
"timshome.page/gilo/key"
"timshome.page/gilo/terminal" "timshome.page/gilo/terminal"
) )
@ -65,32 +66,34 @@ func (e *editor) SetStatusMessage(template string, a ...interface{}) {
} }
func (e *editor) ProcessKeypress() bool { func (e *editor) ProcessKeypress() bool {
var str string
ch, _ := terminal.ReadKey() ch, _ := terminal.ReadKey()
if isCtrl(ch) {
switch ch { switch ch {
case ctrl('q'): case key.Ctrl('q'):
// Clean up on exit // Clean up on exit
terminal.Write(terminal.ClearScreen + terminal.ResetCursor) terminal.Write(terminal.ClearScreen + terminal.ResetCursor)
return false return false
}
}
if ch == '\x1b' { case key.Esc:
str = parseEscapeSequence() // Modifier keys that return ANSI escape sequences
} else { str := parseEscapeSequence()
str = string(ch)
}
switch str { switch str {
case keyUp, keyDown, keyLeft, keyRight, keyPageUp, keyPageDown, keyHome, keyEnd: case keyUp,
keyDown,
keyLeft,
keyRight,
keyPageUp,
keyPageDown,
keyHome,
keyEnd:
e.moveCursor(str) e.moveCursor(str)
return true }
default: default:
// Do something later e.insertChar(ch)
terminal.Write("Code: %v", str)
} }
return true return true
@ -166,6 +169,15 @@ func (e *editor) moveCursor (key string) {
} }
} }
func (e *editor) insertChar(ch rune) {
if e.cursor.y == e.document.rowCount() {
e.document.appendRow("")
}
e.document.rows[e.cursor.y].insertRune(ch, e.cursor.x)
e.cursor.x += 1
}
// Convert the raw ANSI escape sequences to the type of key input // Convert the raw ANSI escape sequences to the type of key input
func parseEscapeSequence() string { func parseEscapeSequence() string {
var runes []rune var runes []rune

View File

@ -4,7 +4,7 @@ package editor
import "strings" import "strings"
// Truncate a string to a length // Truncate a string to a length
func truncateString(s string, length int) string { func truncate(s string, length int) string {
if length < 1 { if length < 1 {
return "" return ""
} }
@ -27,28 +27,3 @@ func truncateString(s string, length int) string {
return buf.String() return buf.String()
} }
// Is this an ASCII character?
func isAscii(char rune) bool {
return char < 0x80
}
// Is this an ASCII ctrl character?
func isCtrl(char rune) bool {
if !isAscii(char) {
return false
}
return char == 0x7f || char < 0x20
}
// Return the input code of a Ctrl-key chord.
func ctrl(char rune) rune {
if !isAscii(char) {
return 0
}
ch := char & 0x1f
return ch
}

View File

@ -4,67 +4,9 @@ import (
"testing" "testing"
) )
type isRune struct {
arg1 rune
expected bool
}
// (╯°□°)╯︵ ┻━┻
var isAsciiTest = []isRune {
{'┻', false},
{'$', true},
{'︵', false},
{0x7f, true},
}
func TestIsAscii(t *testing.T) {
for _, test := range isAsciiTest {
if output := isAscii(test.arg1); output != test.expected {
t.Errorf("Output %v not equal to expected %v for input %q", output, test.expected, test.arg1)
}
}
}
var isCtrlTest = []isRune {
{0x78, false},
{0x7f, true},
{0x02, true},
{0x98, false},
{'a', false},
}
func TestIsCtrl(t *testing.T) {
for _, test := range isCtrlTest {
if output := isCtrl(test.arg1); output != test.expected {
t.Errorf("Output %v not equal to expected %v for input %q", output, test.expected, test.arg1)
}
}
}
type ctrlTest struct {
arg1, expected rune
}
var ctrlTests = []ctrlTest {
{'A', '\x01'},
{'B', '\x02'},
{'Z', '\x1a'},
{'#', 3},
{'┻', 0},
{'😿', 0},
}
func TestCtrl(t *testing.T) {
for _, test := range ctrlTests {
if output := ctrl(test.arg1); output != test.expected {
t.Errorf("Output %v not equal to expected %v for input %q", output, test.expected, test.arg1)
}
}
}
func TestTruncateString(t *testing.T) { func TestTruncateString(t *testing.T) {
firstString := "abcdefghijklmnopqrstuvwxyz" firstString := "abcdefghijklmnopqrstuvwxyz"
truncated := truncateString(firstString, 13) truncated := truncate(firstString, 13)
got := len(truncated) got := len(truncated)
want := 13 want := 13
@ -75,7 +17,7 @@ func TestTruncateString(t *testing.T) {
} }
func TestTruncateStringNegative(t *testing.T) { func TestTruncateStringNegative(t *testing.T) {
got := truncateString("fdlkjf", -5) got := truncate("fdlkjf", -5)
want := "" want := ""
if got != want { if got != want {
@ -86,11 +28,10 @@ func TestTruncateStringNegative(t *testing.T) {
func TestTruncateShorterString(t *testing.T) { func TestTruncateShorterString(t *testing.T) {
str := "abcdefg" str := "abcdefg"
got := truncateString(str, 13) got := truncate(str, 13)
want := str want := str
if got != want { if got != want {
t.Errorf("Truncated value: %q, expected value: %q", got, want) t.Errorf("Truncated value: %q, expected value: %q", got, want)
} }
} }

View File

@ -1,18 +1,5 @@
package editor package editor
// ----------------------------------------------------------------------------
// !Terminal Input Escape Code Sequences
// ----------------------------------------------------------------------------
const(
KeyArrowUp = "A"
KeyArrowDown = "B"
KeyArrowRight = "C"
KeyArrowLeft = "D"
KeyPageUp = "5~"
KeyPageDown = "6~"
)
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// !Constants representing input keys // !Constants representing input keys
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------

View File

@ -1,6 +1,8 @@
package editor package editor
import "strings" import (
"strings"
)
type row struct { type row struct {
chars []rune chars []rune
@ -27,6 +29,31 @@ func (r *row) rSize() int {
return len(r.render) return len(r.render)
} }
func (r *row) insertRune(ch rune, at int) {
// If insertion index is invalid, just
// append the rune to the end of the array
if at < 0 || at >= r.size() {
r.chars = append(r.chars, ch)
r.update()
return
}
var newSlice []rune
// Split the character array at the insertion point
start := r.chars[0:at]
end := r.chars[at:r.size()]
// Splice it back together
newSlice = append(newSlice, start...)
newSlice = append(newSlice, ch)
newSlice = append(newSlice, end...)
r.chars = newSlice
r.update()
}
func (r *row) update() { func (r *row) update() {
r.render = r.render[:0] r.render = r.render[:0]
replacement := strings.Repeat(" ", KiloTabStop) replacement := strings.Repeat(" ", KiloTabStop)
@ -37,6 +64,10 @@ func (r *row) update() {
} }
} }
func (r *row) toString() string {
return string(r.chars)
}
func (r *row) cursorXToRenderX(cursorX int) int { func (r *row) cursorXToRenderX(cursorX int) int {
renderX := 0 renderX := 0
i := 0 i := 0

View File

@ -38,3 +38,35 @@ func TestRenderSize(t *testing.T) {
t.Errorf("Row rsize not equal to number of runes in original string") t.Errorf("Row rsize not equal to number of runes in original string")
} }
} }
type insertRune struct {
initial string
ch rune
at int
}
var insertRuneTests = []insertRune{
{"abde", 'c', 2},
{"bcde", 'a', 0},
{"abcd", 'e', 4},
{"abcd", 'e', 17},
{"abcd", 'e', -2},
}
func TestInsertRune(t *testing.T) {
for _, test := range insertRuneTests {
row := newRow(test.initial)
row.insertRune(test.ch, test.at)
if row.size() != 5 {
t.Errorf("Row size after inserting rune at index [%d] is %d, should be %d", test.at, row.size(), 5)
}
got := row.toString()
want := "abcde"
if got != want {
t.Errorf("Row after update is '%s', should be '%s'", got, want)
}
}
}

View File

@ -29,7 +29,7 @@ func main() {
e.Open(os.Args[1]) e.Open(os.Args[1])
} }
e.SetStatusMessage("HELP: Ctrl-Q = quit"); e.SetStatusMessage("HELP: Ctrl-Q = quit")
// The input loop // The input loop
for { for {

34
key/key.go Normal file
View File

@ -0,0 +1,34 @@
package key
// ----------------------------------------------------------------------------
// !Terminal Input Escape Code Sequences
// ----------------------------------------------------------------------------
const (
Esc = '\x1b'
)
// Is this an ASCII character?
func isAscii(char rune) bool {
return char < 0x80
}
// Is this an ASCII ctrl character?
func IsCtrl(char rune) bool {
if !isAscii(char) {
return false
}
return char == 0x7f || char < 0x20
}
// Return the input code of a Ctrl-key chord.
func Ctrl(char rune) rune {
if !isAscii(char) {
return 0
}
ch := char & 0x1f
return ch
}

61
key/key_test.go Normal file
View File

@ -0,0 +1,61 @@
package key
import "testing"
type isRune struct {
arg1 rune
expected bool
}
// (╯°□°)╯︵ ┻━┻
var isAsciiTest = []isRune{
{'┻', false},
{'$', true},
{'︵', false},
{0x7f, true},
}
func TestIsAscii(t *testing.T) {
for _, test := range isAsciiTest {
if output := isAscii(test.arg1); output != test.expected {
t.Errorf("Output %v not equal to expected %v for input %q", output, test.expected, test.arg1)
}
}
}
var isCtrlTest = []isRune{
{0x78, false},
{0x7f, true},
{0x02, true},
{0x98, false},
{'a', false},
}
func TestIsCtrl(t *testing.T) {
for _, test := range isCtrlTest {
if output := IsCtrl(test.arg1); output != test.expected {
t.Errorf("Output %v not equal to expected %v for input %q", output, test.expected, test.arg1)
}
}
}
type ctrlTest struct {
arg1, expected rune
}
var ctrlTests = []ctrlTest{
{'A', '\x01'},
{'B', '\x02'},
{'Z', '\x1a'},
{'#', 3},
{'┻', 0},
{'😿', 0},
}
func TestCtrl(t *testing.T) {
for _, test := range ctrlTests {
if output := Ctrl(test.arg1); output != test.expected {
t.Errorf("Output %v not equal to expected %v for input %q", output, test.expected, test.arg1)
}
}
}

View File

@ -9,7 +9,6 @@ import "fmt"
const EscPrefix = "\x1b[" const EscPrefix = "\x1b["
const ( const (
// Clears the line after the escape sequence // Clears the line after the escape sequence
ClearLine = EscPrefix + "K" ClearLine = EscPrefix + "K"
@ -47,7 +46,6 @@ func Code (s string, a ...interface{}) string {
return EscPrefix + str return EscPrefix + str
} }
// Generate the escape sequence to move the terminal cursor to the 0-based coordinate // Generate the escape sequence to move the terminal cursor to the 0-based coordinate
func MoveCursor(x int, y int) string { func MoveCursor(x int, y int) string {
// Allow 0-based indexing, the terminal code is 1-based // Allow 0-based indexing, the terminal code is 1-based