working on backspace, delete and enter key behavior

master
Adam Veldhousen 4 years ago
parent c89d698b60
commit cab3304e5b
Signed by: adam
GPG Key ID: 6DB29003C6DD1E4B

@ -1,3 +1,15 @@
# Pound
a text editor - following along with this: https://viewsourcecode.org/snaptoken/kilo/03.rawInputAndOutput.html
A text editor - following along with this: https://viewsourcecode.org/snaptoken/kilo/03.rawInputAndOutput.html
## Tips
Want to add a binding (e.g. "PageUp") but are unsure what the X sequence
(e.g. "\x1b[5~") is? Open another terminal (like xterm) without tmux,
then run `showkey -a` to get the sequence associated to a key combination.
## License
GPL3

@ -2,23 +2,59 @@ package main
import (
"bufio"
"fmt"
"log"
"os"
)
func fileOpen(fname string) {
f, err := os.Open(fname)
if err != nil && os.IsNotExist(err) {
// create file if it doesn't exist
if f, err = os.Create(fname); err != nil {
log.Fatal(err)
}
state.rows = []string{""}
state.Cy = 0
state.Cx = 0
} else if err != nil {
log.Fatal(err)
} else {
fread := bufio.NewScanner(f)
fread.Split(bufio.ScanLines)
state.rows = nil
for fread.Scan() {
state.rows = append(state.rows, fread.Text())
}
// if len(state.rows) == 0 || (len(state.rows) == 1 && state.rows[0] == "") {
// state.rows = []string{" "}
// }
}
f.Close()
state.file = fname
}
func fileSave() {
f, err := os.Create(state.file)
if err != nil {
log.Fatal(err)
}
defer f.Close()
fread := bufio.NewScanner(f)
fread.Split(bufio.ScanLines)
state.rows = nil
for fread.Scan() {
state.rows = append(state.rows, fread.Text())
bytesWritten := 0
for _, row := range state.rows {
bw, err := fmt.Fprintf(f, "%s\n", row)
if err != nil {
log.Fatalf("COULD NOT SAVE FILE: %v", err)
}
bytesWritten += bw
}
state.file = fname
state.StatusLine = fmt.Sprintf("'%s' wrote %d bytes.", state.file, bytesWritten)
}

@ -12,12 +12,14 @@ import (
)
var (
stdin = bufio.NewReader(os.Stdin)
stdout = bufio.NewWriter(os.Stdout)
stdin = bufio.NewReader(os.Stdin)
)
const (
KEY_ESC = rune('\x1b')
KEY_BACKSPACE = rune('\x08')
KEY_ENTER = rune('\x0d')
KEY_TAB = rune('\x09')
KEY_UP_ARROW = (iota + 1000)
KEY_DOWN_ARROW = (iota + 1000)
KEY_LEFT_ARROW = (iota + 1000)
@ -31,33 +33,102 @@ const (
func editorKeyPresses() bool {
char := editorReadKey()
state.StatusLine = ""
editorMoveCaret(char)
if char == ctrl_key('r') {
uiRefresh()
}
if char == ctrl_key('w') {
} else if char == ctrl_key('s') {
fileSave()
} else if char == ctrl_key('j') {
for i := state.rowOffset + state.Cy; i > 0; i-- {
editorMoveCaret(KEY_UP_ARROW)
}
}
if char == ctrl_key('s') {
} else if char == ctrl_key('k') {
for i := state.rowOffset + state.Cy; i < state.getCurrentRowLen()-1; i++ {
editorMoveCaret(KEY_DOWN_ARROW)
}
}
if char == ctrl_key('q') || char == 'q' {
} else if char == ctrl_key('q') {
return false
} else if char == KEY_BACKSPACE || char == KEY_DELETE {
editorRemoveChar(char)
} else if char >= 32 && char <= 126 || char == KEY_ENTER || char == KEY_TAB {
editorInsertChar(char)
}
return true
}
func editorRemoveChar(char rune) {
lineText := state.rows[state.Cy]
rl := state.getCurrentRowLen()
cx := state.Cx
if char == KEY_BACKSPACE {
if cx == 0 && state.Cy > 0 {
plt := state.rows[state.Cy-1]
state.rows[state.Cy-1] = fmt.Sprintf("%s%s", plt, lineText)
if state.Cy < len(state.rows)-1 {
state.rows = append(state.rows[:state.Cy-1], state.rows[state.Cy:]...)
editorMoveCaret(KEY_UP_ARROW)
} else {
state.rows = state.rows[:state.Cy-1]
state.Cy--
}
state.Cx = 0
} else if cx == 0 {
// do nothing because there are no characters
return
} else {
state.StatusLine = fmt.Sprintf("D: %d - %s%s", cx, lineText[:cx-1], lineText[cx:])
state.rows[state.Cy] = fmt.Sprintf("%s%s", lineText[:cx-1], lineText[cx:])
editorMoveCaret(KEY_LEFT_ARROW)
}
} else if char == KEY_DELETE {
if cx < rl-1 {
state.rows[state.Cy] = fmt.Sprintf("%s%s", lineText[:cx], lineText[cx+1:])
}
} else {
return
}
}
func editorInsertChar(char rune) {
lt := ""
cx := state.Cx
if len(state.rows) > 0 {
lt = state.rows[state.Cy]
} else {
state.rows = []string{fmt.Sprintf("%c", char)}
return
}
if char == KEY_ENTER {
oldL := lt[:cx]
newL := lt[cx:]
state.rows[state.Cy] = oldL
if state.Cy == len(state.rows)-1 {
state.rows[state.Cy] = oldL
state.rows = append(state.rows, newL)
} else {
state.rows = append(append(state.rows[:state.Cy], newL), state.rows[state.Cy:]...)
}
state.Cx = 0
editorMoveCaret(KEY_DOWN_ARROW)
} else {
state.rows[state.Cy] = fmt.Sprintf("%s%c%s", lt[:cx], char, lt[cx:])
editorMoveCaret(KEY_RIGHT_ARROW)
}
}
func editorMoveCaret(char rune) {
x, y := state.Cx, state.Cy
if len(state.rows) == 0 {
return
}
rx := editorCxoRx(state.rows[y], x)
@ -96,7 +167,6 @@ func editorMoveCaret(char rune) {
break
}
if char == ctrl_key(KEY_DOWN_ARROW) {
y = len(state.rows) - 1
}
@ -129,13 +199,13 @@ func editorScroll() {
state.colOffset = rx
}
// if state.Cx >= state.colOffset+displayWidth {
// state.colOffset = state.Cx - displayWidth
// }
if state.Cx >= state.colOffset+displayWidth {
state.colOffset = state.Cx - displayWidth
}
// if state.Cx < state.colOffset {
// state.colOffset = state.Cx
// }
if state.Cx < state.colOffset {
state.colOffset = state.Cx
}
if state.Cy >= state.rowOffset+(state.Rows-2) {
state.rowOffset = state.Cy - (state.Rows - 2)
@ -154,30 +224,27 @@ func editorReadKey() rune {
for read != 1 {
in, read, err = stdin.ReadRune()
if err != nil && err != io.EOF {
log.Fatal("FAILED TO READ:", err)
return KEY_ESC
//log.Fatal("FAILED TO READ:", err)
}
}
// fmt.Printf("%v == %v ? %v", rune(in[0]), KEY_ESC, rune(in[0]) == KEY_ESC)
if in == KEY_ESC {
seq := make([]rune, 3)
if seq[0], read, err = stdin.ReadRune(); err != nil || read != 1 {
return KEY_ESC
}
if seq[1], read, err = stdin.ReadRune(); err != nil || read != 1 {
return KEY_ESC
}
if seq[0] == '[' {
// fmt.Printf("%c %c %c", seq[0], seq[1], seq[2])
if seq[1] >= '0' && seq[1] <= '9' {
if seq[2], read, err = stdin.ReadRune(); err != nil || read != 1 {
return KEY_ESC
}
state.StatusLine = fmt.Sprintf("Last Read = '%c''%c''%c' ", seq[0], seq[1], seq[2])
// handle page up/down and home keys
if seq[2] == '~' {
switch seq[1] {
@ -224,6 +291,13 @@ func editorReadKey() rune {
return KEY_ESC
}
switch in {
case '\x7f': // backspace
return KEY_BACKSPACE
case '\x0d':
return KEY_ENTER
}
return in
}

@ -2,11 +2,14 @@ package main
import (
"bytes"
"fmt"
"log"
"os"
"syscall"
)
var Version = "0.0.0-dev"
var state editorState
type editorState struct {
@ -17,6 +20,7 @@ type editorState struct {
Rows int
Cols int
StatusLine string
DebugLine string
rows []string
rowOffset int
colOffset int
@ -39,10 +43,9 @@ func (es editorState) getRowLen(lineNumber int) int {
}
func main() {
defer cleanup()
enableRawMode()
initEditor()
defer disableRawMode()
defer uiClearScreen()
if len(os.Args) > 1 {
fileOpen(os.Args[1])
}
@ -56,11 +59,22 @@ func main() {
func initEditor() {
var err error
if state.Rows, state.Cols, err = getWindowSize(); err != nil {
log.Fatal(err)
log.Fatalf("COULD NOT GET WINDOW SIZE: %v", err)
}
state.Cx = 5
state.Cx = 0
state.Buffer = &bytes.Buffer{}
state.rows = []string{"Pound Ed -- version 0.0.0"}
state.rows = []string{fmt.Sprintf("Pound Ed %s", Version)}
state.file = ""
}
func cleanup() {
disableRawMode()
uiClearScreen()
return
if r := recover(); r != nil {
fmt.Fprintf(os.Stderr, "Unrecoverable error:\n\t%+v\n", r)
os.Exit(-1)
}
}

@ -28,8 +28,8 @@ $(LINTBIN):
@GO111MODULE=off go get github.com/golangci/golangci-lint/cmd/golangci-lint
lint: $(LINTBIN)
go mod tidy -v
@go mod tidy -v
$(LINTBIN) run -p bugs -p format -p performance -p unused
test: lint
test:
go test -v -cover $(PKGS)

57
ui.go

@ -16,26 +16,30 @@ import (
const (
CLEAR = "\x1b[K"
TAB_SIZE = 8
EMPTY_LINE_GUTTER = " ~ "
EMPTY_LINE_GUTTER = " x~ "
LINE_GUTTER = " "
)
func uiDrawRows() {
buf := state.Buffer
_, highlightedRowLen := renderLine(state.rows[state.Cy])
rx := editorCxoRx(state.rows[state.Cy], state.Cx)
currentRow := ""
if len(state.rows) > 0 {
currentRow = state.rows[state.Cy]
}
_, highlightedRowLen := renderLine(currentRow)
rx := editorCxoRx(currentRow, state.Cx)
gutterWidth, gutterFmtStr := calcMaxGutterWidth()
state.StatusLine = fmt.Sprintf("offset: {x:%d, y:%d}, loc {x:%d:y:%d}",
state.colOffset,
state.rowOffset,
state.Cx,
state.Cy)
file := state.file
if file == "" {
file = "NO FILE"
}
statusLine := fmt.Sprintf(
"%s\x1b[1;7m '%s' \x1b[m L:%d/%d C:%d/%d | H:%d W: %d | %s",
CLEAR,
state.file,
file,
state.Cy+1,
len(state.rows),
rx+1,
@ -105,11 +109,13 @@ func uiRefresh() {
gutterWidth, _ := calcMaxGutterWidth()
// x := gutterWidth + (state.Cx - state.colOffset)
y := state.Cy - state.rowOffset
rx := gutterWidth + editorCxoRx(state.rows[y], state.Cx) - state.colOffset
currentRow := ""
if len(state.rows) > 0 {
currentRow = state.rows[y]
}
rx := gutterWidth + editorCxoRx(currentRow, state.Cx) - state.colOffset
setCursorPosition(rx, y)
@ -119,20 +125,12 @@ func uiRefresh() {
buf.Reset()
}
type window struct {
Row uint16
Col uint16
Xpixel uint16
Ypixel uint16
}
func uiClearScreen() {
fmt.Fprintf(os.Stdout, "\x1b[2J")
fmt.Fprintf(os.Stdout, "\x1b[H")
}
func setCursorPosition(x, y int) {
fmt.Fprintf(state.Buffer, "\x1b[%d;%dH", y+1, x+1)
}
@ -165,6 +163,13 @@ func getCursorPosition() (int, int, error) {
return rows, cols, nil
}
type window struct {
Row uint16
Col uint16
Xpixel uint16
Ypixel uint16
}
func getWindowSize() (int, int, error) {
w := new(window)
_, _, err := syscall.Syscall(syscall.SYS_IOCTL,
@ -184,10 +189,6 @@ func getWindowSize() (int, int, error) {
return int(w.Row), int(w.Col), nil
}
func iscntrl(c int) bool {
return c > 0x00 && c < 0x1f || c == 0x7f
}
func calcMaxGutterWidth() (int, string) {
lineCount := len(state.rows)
maxDigits := len([]rune(fmt.Sprintf("%d", lineCount)))
@ -199,7 +200,7 @@ func calcMaxGutterWidth() (int, string) {
func editorCxoRx(row string, cx int) int {
rx := 0
for idx, c := range []rune(row) {
for idx, c := range row {
if idx < cx {
if c == '\t' {
// rx += (TAB_SIZE - 1) - (rx % TAB_SIZE)
@ -211,15 +212,15 @@ func editorCxoRx(row string, cx int) int {
break
}
return rx
return rx
}
func renderLine(line string) (string, int) {
rl := ""
tabCount := 0
for _, char := range []rune(line) {
for _, char := range line {
if char == '\t' {
tabCount += 1
tabCount++
for i := 0; i < TAB_SIZE; i++ {
rl += " "
}

Loading…
Cancel
Save