You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

393 lines
7.5 KiB

package main
import (
"bufio"
"fmt"
"io"
"log"
"os"
"syscall"
"github.com/pkg/term/termios"
)
var (
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)
KEY_RIGHT_ARROW = (iota + 1000)
KEY_PAGEUP = (iota + 1000)
KEY_PAGEDOWN = (iota + 1000)
KEY_HOME = (iota + 1000)
KEY_END = (iota + 1000)
KEY_DELETE = (iota + 1000)
)
func editorKeyPresses() bool {
char := editorReadKey()
state.StatusLines[1] = ""
if char == ctrl_key('r') {
uiRefresh()
} 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)
}
} else if char == ctrl_key('k') {
for i := state.rowOffset + state.Cy; i < state.getCurrentRowLen()-1; i++ {
editorMoveCaret(KEY_DOWN_ARROW)
}
} 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)
} else {
editorMoveCaret(char)
}
return true
}
func editorRemoveChar(char rune) {
cx := state.Cx
cy := state.Cy
rc := len(state.rows)
lt := ""
rl := 0
if rc > 0 {
lt = state.rows[cy]
rl = state.getCurrentRowLen()
}
// file is empty
if rc == 0 && lt == "" {
return
}
/*
1. Backspace at beginning of a line
2. Backspace in middle of a line
3. Backspace at end of a line
4. Backspace when there are 0 characters in the file
*/
if char == KEY_BACKSPACE {
if cx == 0 && cy == 0 {
return
}
// backspace in the middle of a line
if cx > 0 && cx < rl {
state.rows[cy] = fmt.Sprintf("%s%s", lt[:cx-1], lt[cx:])
editorMoveCaret(KEY_LEFT_ARROW)
return
}
// backspace at start of line
// join lines together and remove
if cx == 0 && cy > 0 {
plt := state.rows[cy-1]
_, highlightedRowLen := renderLine(plt)
state.rows[cy-1] = fmt.Sprintf("%s%s", plt, lt)
editorMoveCaret(KEY_UP_ARROW)
for x := 0; x < highlightedRowLen; x++ {
editorMoveCaret(KEY_RIGHT_ARROW)
}
// if we're at the last row, make sure to only remove the last line
if cy == rc-1 {
state.rows = state.rows[:cy]
} else {
state.rows = append(state.rows[:cy], state.rows[cy+1:]...)
}
return
}
// backspace at end of line
if cx == rl {
state.rows[cy] = lt[:cx-1]
editorMoveCaret(KEY_LEFT_ARROW)
return
}
} else if char == KEY_DELETE {
// in middle of line
if cx < rl-1 {
state.rows[state.Cy] = fmt.Sprintf("%s%s", lt[:cx], lt[cx+1:])
}
}
}
func editorInsertChar(char rune) {
cx := state.Cx
cy := state.Cy
rc := len(state.rows)
lt := ""
rl := 0
if rc > 0 {
lt = state.rows[cy]
rl = state.getCurrentRowLen()
}
/*
1. Enter at beginning line
2. Enter at middle of line
3. Enter at end of line
*/
if char == KEY_ENTER {
// beginning of line
if cx == 0 {
if cy == rc {
state.rows = append(state.rows, lt)
} else if cy < rc {
state.rows = append(append(state.rows[:cy], lt), state.rows[cy:]...)
}
state.rows[cy] = ""
// end of a line
} else if cx >= rl {
if cy < rc {
state.rows = append(append(state.rows[:cy], ""), state.rows[cy:]...)
state.rows[cy] = lt
} else if cy == rc {
state.rows = append(state.rows[:cy], "")
}
// mid line
} else if cx > 0 {
state.rows = append(append(state.rows[:cy], lt[cx:]), state.rows[cy:]...)
state.rows[cy] = lt[:cx]
}
state.Cx = 0
editorMoveCaret(KEY_DOWN_ARROW)
} else {
state.StatusLines[1] = fmt.Sprintf("%c %d", char, char)
state.rows[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)
switch char {
case KEY_UP_ARROW:
if y != 0 {
y--
}
break
case KEY_DOWN_ARROW:
if state.rows != nil && y < len(state.rows)-1 {
y++
}
break
case KEY_LEFT_ARROW:
if x > 0 {
x--
} else if y != 0 {
y--
x = state.getRowLen(y) + 1
}
break
case KEY_RIGHT_ARROW:
if state.rows != nil && rx < state.getRowLen(y) {
x++
} else if y < len(state.rows)-1 {
y++
x = 0
}
break
case KEY_HOME:
x = 0
break
case KEY_END:
x = state.Cols - 1
break
default:
return
}
if char == ctrl_key(KEY_DOWN_ARROW) {
y = len(state.rows) - 1
}
if char == ctrl_key(KEY_UP_ARROW) {
y = 0
}
if x > state.getRowLen(y)+1 {
x = state.getRowLen(y) + 1
}
state.Cx = x
state.Cy = y
editorScroll()
}
func editorScroll() {
gutterWidth, _ := calcMaxGutterWidth()
displayWidth := state.Cols - gutterWidth - 2
rx := gutterWidth + editorCxoRx(state.rows[state.Cy], state.Cx)
if rx >= state.colOffset+displayWidth {
state.colOffset = rx - displayWidth
}
if rx < state.colOffset {
state.colOffset = rx
}
if state.Cx >= state.colOffset+displayWidth {
state.colOffset = state.Cx - displayWidth
}
if state.Cx < state.colOffset {
state.colOffset = state.Cx
}
if state.Cy >= state.rowOffset+(state.Rows-2) {
state.rowOffset = state.Cy - (state.Rows - 2)
}
if state.Cy < state.rowOffset {
state.rowOffset = state.Cy
}
}
func editorReadKey() rune {
in := '\000'
var err error
var read int
for read != 1 {
in, read, err = stdin.ReadRune()
if err != nil && err != io.EOF {
return KEY_ESC
//log.Fatal("FAILED TO READ:", err)
}
}
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] == '[' {
if seq[1] >= '0' && seq[1] <= '9' {
if seq[2], read, err = stdin.ReadRune(); err != nil || read != 1 {
return KEY_ESC
}
// handle page up/down and home keys
if seq[2] == '~' {
switch seq[1] {
case '1':
case '7':
return KEY_HOME
case 3:
return KEY_DELETE
case '4':
case '8':
return KEY_END
case '5':
return KEY_PAGEUP
case '6':
return KEY_PAGEDOWN
}
}
} else {
// handle arrow keys and home keys
switch seq[1] {
case 'A':
return KEY_UP_ARROW
case 'B':
return KEY_DOWN_ARROW
case 'C':
return KEY_RIGHT_ARROW
case 'D':
return KEY_LEFT_ARROW
case 'H':
return KEY_HOME
case 'F':
return KEY_END
}
}
} else if seq[0] == 'O' {
switch seq[1] {
case 'H':
return KEY_HOME
case 'F':
return KEY_END
}
}
return KEY_ESC
}
switch in {
case '\x7f': // backspace
return KEY_BACKSPACE
case '\x0d':
return KEY_ENTER
}
return in
}
func enableRawMode() {
if err := termios.Tcgetattr(uintptr(0), &state.orig); err != nil {
log.Fatal("Tcgetattr, ", err)
}
raw := state.orig
raw.Iflag &^= syscall.IXON | syscall.ICRNL | syscall.BRKINT | syscall.INPCK | syscall.ISTRIP
raw.Oflag &^= syscall.OPOST
raw.Cflag &^= syscall.CS8
raw.Lflag &^= syscall.ECHO | syscall.ICANON | syscall.ISIG | syscall.IEXTEN
raw.Cc[syscall.VMIN] = 0
raw.Cc[syscall.VTIME] = 1
if err := termios.Tcsetattr(uintptr(0), termios.TCSAFLUSH, &raw); err != nil {
log.Fatal("tcsetattr, ", err)
}
}
func disableRawMode() {
if err := termios.Tcsetattr(uintptr(0), termios.TCSAFLUSH, &state.orig); err != nil {
log.Fatal(err)
}
}
func ctrl_key(key rune) rune {
return rune(int(key) & 0x1f)
}