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
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(terminal.ClearLine)
ab.append("\r\n")
ab.appendLn(terminal.ClearLine)
}
}
@ -86,7 +85,7 @@ func (e *editor) drawPlaceholderRow(y int, ab *buffer) {
if e.document.rowCount() == 0 && y == e.screen.Rows/3 {
welcome := fmt.Sprintf("Gilo editor -- version %s", KiloVersion)
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
@ -119,7 +118,7 @@ func (e *editor) drawStatusBar(ab *buffer) {
leftStatus := fmt.Sprintf("%.20s - %d lines", fileName, e.document.rowCount())
length := len(leftStatus)
if length > cols {
leftStatus = truncateString(leftStatus, cols)
leftStatus = truncate(leftStatus, cols)
ab.append(leftStatus)
ab.append(terminal.ResetColor)
@ -150,7 +149,6 @@ func (e *editor) drawStatusBar(ab *buffer) {
padding := strings.Repeat(" ", cols-length)
ab.append(padding)
ab.append(terminal.ResetColor)
}
@ -158,7 +156,7 @@ func (e *editor) drawMessageBar(ab *buffer) {
ab.append("\r\n")
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 {
ab.append(msg)
}

View File

@ -3,6 +3,7 @@ package editor
import (
"fmt"
"time"
"timshome.page/gilo/key"
"timshome.page/gilo/terminal"
)
@ -65,32 +66,34 @@ func (e *editor) SetStatusMessage(template string, a ...interface{}) {
}
func (e *editor) ProcessKeypress() bool {
var str string
ch, _ := terminal.ReadKey()
if isCtrl(ch) {
switch ch {
case ctrl('q'):
case key.Ctrl('q'):
// Clean up on exit
terminal.Write(terminal.ClearScreen + terminal.ResetCursor)
return false
}
}
if ch == '\x1b' {
str = parseEscapeSequence()
} else {
str = string(ch)
}
case key.Esc:
// Modifier keys that return ANSI escape sequences
str := parseEscapeSequence()
switch str {
case keyUp, keyDown, keyLeft, keyRight, keyPageUp, keyPageDown, keyHome, keyEnd:
case keyUp,
keyDown,
keyLeft,
keyRight,
keyPageUp,
keyPageDown,
keyHome,
keyEnd:
e.moveCursor(str)
return true
}
default:
// Do something later
terminal.Write("Code: %v", str)
e.insertChar(ch)
}
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
func parseEscapeSequence() string {
var runes []rune

View File

@ -4,7 +4,7 @@ package editor
import "strings"
// Truncate a string to a length
func truncateString(s string, length int) string {
func truncate(s string, length int) string {
if length < 1 {
return ""
}
@ -27,28 +27,3 @@ func truncateString(s string, length int) 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"
)
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) {
firstString := "abcdefghijklmnopqrstuvwxyz"
truncated := truncateString(firstString, 13)
truncated := truncate(firstString, 13)
got := len(truncated)
want := 13
@ -75,7 +17,7 @@ func TestTruncateString(t *testing.T) {
}
func TestTruncateStringNegative(t *testing.T) {
got := truncateString("fdlkjf", -5)
got := truncate("fdlkjf", -5)
want := ""
if got != want {
@ -86,11 +28,10 @@ func TestTruncateStringNegative(t *testing.T) {
func TestTruncateShorterString(t *testing.T) {
str := "abcdefg"
got := truncateString(str, 13)
got := truncate(str, 13)
want := str
if got != want {
t.Errorf("Truncated value: %q, expected value: %q", got, want)
}
}

View File

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

View File

@ -1,6 +1,8 @@
package editor
import "strings"
import (
"strings"
)
type row struct {
chars []rune
@ -27,6 +29,31 @@ func (r *row) rSize() int {
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() {
r.render = r.render[:0]
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 {
renderX := 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")
}
}
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.SetStatusMessage("HELP: Ctrl-Q = quit");
e.SetStatusMessage("HELP: Ctrl-Q = quit")
// The input loop
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 (
// Clears the line after the escape sequence
ClearLine = EscPrefix + "K"
@ -47,7 +46,6 @@ func Code (s string, a ...interface{}) string {
return EscPrefix + str
}
// Generate the escape sequence to move the terminal cursor to the 0-based coordinate
func MoveCursor(x int, y int) string {
// Allow 0-based indexing, the terminal code is 1-based