This commit is contained in:
parent
bd9a07feed
commit
41d1aa6553
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
|
27
editor/fn.go
27
editor/fn.go
@ -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
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
// ----------------------------------------------------------------------------
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
2
gilo.go
2
gilo.go
@ -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
34
key/key.go
Normal 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
61
key/key_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user