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
|
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)
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
27
editor/fn.go
27
editor/fn.go
@ -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
|
|
||||||
}
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
2
gilo.go
2
gilo.go
@ -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
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 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
|
||||||
|
Loading…
Reference in New Issue
Block a user