fixed horizontal scrolling and tabs
parent
3ed0418f72
commit
c89d698b60
85
input.go
85
input.go
|
|
@ -17,7 +17,7 @@ var (
|
|||
)
|
||||
|
||||
const (
|
||||
KEY_ESCAPE = rune('\x1b')
|
||||
KEY_ESC = rune('\x1b')
|
||||
KEY_UP_ARROW = (iota + 1000)
|
||||
KEY_DOWN_ARROW = (iota + 1000)
|
||||
KEY_LEFT_ARROW = (iota + 1000)
|
||||
|
|
@ -44,7 +44,7 @@ func editorKeyPresses() bool {
|
|||
}
|
||||
}
|
||||
if char == ctrl_key('s') {
|
||||
for i := state.rowOffset + state.Cy; i < len(state.rows)-1; i++ {
|
||||
for i := state.rowOffset + state.Cy; i < state.getCurrentRowLen()-1; i++ {
|
||||
editorMoveCaret(KEY_DOWN_ARROW)
|
||||
}
|
||||
}
|
||||
|
|
@ -58,7 +58,8 @@ func editorKeyPresses() bool {
|
|||
|
||||
func editorMoveCaret(char rune) {
|
||||
x, y := state.Cx, state.Cy
|
||||
gutterWidth, _ := calcMaxGutterWidth(len(state.rows))
|
||||
|
||||
rx := editorCxoRx(state.rows[y], x)
|
||||
|
||||
switch char {
|
||||
case KEY_UP_ARROW:
|
||||
|
|
@ -72,52 +73,77 @@ func editorMoveCaret(char rune) {
|
|||
}
|
||||
break
|
||||
case KEY_LEFT_ARROW:
|
||||
if x > gutterWidth {
|
||||
if x > 0 {
|
||||
x--
|
||||
} else if y != 0 {
|
||||
y--
|
||||
x = len(state.rows[y]) + gutterWidth
|
||||
x = state.getRowLen(y) + 1
|
||||
}
|
||||
break
|
||||
case KEY_RIGHT_ARROW:
|
||||
if state.rows != nil && (x-gutterWidth) < len(state.rows[y]) {
|
||||
if state.rows != nil && rx < state.getRowLen(y) {
|
||||
x++
|
||||
} else if y < len(state.rows)-1 {
|
||||
y++
|
||||
x = gutterWidth
|
||||
x = 0
|
||||
}
|
||||
break
|
||||
case KEY_HOME:
|
||||
x = gutterWidth
|
||||
x = 0
|
||||
break
|
||||
case KEY_END:
|
||||
x = state.Cols - 1
|
||||
break
|
||||
}
|
||||
|
||||
if (x - gutterWidth) > len(state.rows[y]) {
|
||||
x = len(state.rows[y]) + gutterWidth
|
||||
|
||||
if char == ctrl_key(KEY_DOWN_ARROW) {
|
||||
y = len(state.rows) - 1
|
||||
}
|
||||
|
||||
if x >= state.colOffset+(state.Cols-gutterWidth) {
|
||||
state.colOffset = x - (state.Cols - gutterWidth)
|
||||
if char == ctrl_key(KEY_UP_ARROW) {
|
||||
y = 0
|
||||
}
|
||||
|
||||
if x < (state.colOffset + gutterWidth) {
|
||||
state.colOffset = x
|
||||
}
|
||||
|
||||
if y >= state.rowOffset+(state.Rows-2) {
|
||||
state.rowOffset = y - (state.Rows - 2)
|
||||
}
|
||||
|
||||
if y < state.rowOffset {
|
||||
state.rowOffset = y
|
||||
if x > state.getRowLen(y)+1 {
|
||||
x = state.getRowLen(y) + 1
|
||||
}
|
||||
|
||||
state.Cx = x
|
||||
state.Cy = y
|
||||
state.StatusLine = fmt.Sprintf("offset: %d:%d, loc %d:%d", state.colOffset, state.rowOffset, x, 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 {
|
||||
|
|
@ -127,20 +153,19 @@ func editorReadKey() rune {
|
|||
|
||||
for read != 1 {
|
||||
in, read, err = stdin.ReadRune()
|
||||
// read, err = os.Stdin.Read(in)
|
||||
if err != nil && err != io.EOF {
|
||||
log.Fatal("FAILED TO READ:", err)
|
||||
}
|
||||
}
|
||||
|
||||
// fmt.Printf("%v == %v ? %v", rune(in[0]), KEY_ESCAPE, rune(in[0]) == KEY_ESCAPE)
|
||||
if in == KEY_ESCAPE {
|
||||
// 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_ESCAPE
|
||||
return KEY_ESC
|
||||
}
|
||||
if seq[1], read, err = stdin.ReadRune(); err != nil || read != 1 {
|
||||
return KEY_ESCAPE
|
||||
return KEY_ESC
|
||||
}
|
||||
|
||||
if seq[0] == '[' {
|
||||
|
|
@ -148,7 +173,7 @@ func editorReadKey() rune {
|
|||
// 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_ESCAPE
|
||||
return KEY_ESC
|
||||
}
|
||||
|
||||
state.StatusLine = fmt.Sprintf("Last Read = '%c''%c''%c' ", seq[0], seq[1], seq[2])
|
||||
|
|
@ -196,7 +221,7 @@ func editorReadKey() rune {
|
|||
}
|
||||
}
|
||||
|
||||
return KEY_ESCAPE
|
||||
return KEY_ESC
|
||||
}
|
||||
|
||||
return in
|
||||
|
|
|
|||
15
main.go
15
main.go
|
|
@ -23,6 +23,21 @@ type editorState struct {
|
|||
file string
|
||||
}
|
||||
|
||||
func (es editorState) getCurrentRowLen() int {
|
||||
return es.getRowLen(es.Cy)
|
||||
}
|
||||
|
||||
func (es editorState) getRowLen(lineNumber int) int {
|
||||
if lineNumber >= len(es.rows) {
|
||||
return 0
|
||||
}
|
||||
|
||||
row := es.rows[lineNumber]
|
||||
_, l := renderLine(row)
|
||||
|
||||
return l
|
||||
}
|
||||
|
||||
func main() {
|
||||
enableRawMode()
|
||||
initEditor()
|
||||
|
|
|
|||
133
ui.go
133
ui.go
|
|
@ -9,23 +9,39 @@ import (
|
|||
"os"
|
||||
"runtime"
|
||||
"syscall"
|
||||
"unicode/utf8"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const (
|
||||
CLEAR = "\x1b[K"
|
||||
TAB_SIZE = 8
|
||||
EMPTY_LINE_GUTTER = " ~ "
|
||||
LINE_GUTTER = " "
|
||||
)
|
||||
|
||||
func uiDrawRows() {
|
||||
buf := state.Buffer
|
||||
rowLen := 0
|
||||
gutterWidth, gutterFmtStr := calcMaxGutterWidth(len(state.rows))
|
||||
_, highlightedRowLen := renderLine(state.rows[state.Cy])
|
||||
rx := editorCxoRx(state.rows[state.Cy], 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)
|
||||
|
||||
statusLine := fmt.Sprintf(
|
||||
"'%s' | L: %v/%v/%v | C: %v/%v/%v | %s",
|
||||
"%s\x1b[1;7m '%s' \x1b[m L:%d/%d C:%d/%d | H:%d W: %d | %s",
|
||||
CLEAR,
|
||||
state.file,
|
||||
state.Cy+1,
|
||||
len(state.rows),
|
||||
state.Rows,
|
||||
state.Cx-gutterWidth,
|
||||
rowLen,
|
||||
state.Cols,
|
||||
rx+1,
|
||||
highlightedRowLen,
|
||||
state.Rows-1,
|
||||
state.Cols-gutterWidth-1,
|
||||
state.StatusLine)
|
||||
|
||||
for y := 0; y < state.Rows; y++ {
|
||||
|
|
@ -38,28 +54,37 @@ func uiDrawRows() {
|
|||
} else if y < state.Rows && state.Cy < len(state.rows) {
|
||||
rowIdx := y + state.rowOffset
|
||||
|
||||
// wrie the gutter w/ line numbers
|
||||
fmt.Fprintf(buf, gutterFmtStr, rowIdx+1)
|
||||
if rowIdx < len(state.rows) {
|
||||
// wrie the gutter w/ line numbers
|
||||
fmt.Fprintf(buf, gutterFmtStr, rowIdx+1)
|
||||
|
||||
// calc how much room left for text
|
||||
editorCols := state.Cols - gutterWidth
|
||||
// calc how much room left for text
|
||||
editorCols := (state.Cols - gutterWidth) - 1
|
||||
|
||||
// get the row which is the y on screen + offset
|
||||
row := state.rows[rowIdx]
|
||||
rowLen := len(row)
|
||||
// get length and content for the current row
|
||||
row, rowLen := renderLine(state.rows[rowIdx])
|
||||
|
||||
if rowLen > editorCols {
|
||||
rowLen = rowLen - editorCols
|
||||
}
|
||||
// calculate offset from the left
|
||||
renderLen := rowLen
|
||||
rowEnd := editorCols + state.colOffset
|
||||
if renderLen > editorCols && rowEnd <= rowLen {
|
||||
renderLen = rowEnd
|
||||
}
|
||||
|
||||
if state.colOffset < rowLen {
|
||||
buf.WriteString(row[state.colOffset:rowLen])
|
||||
if state.colOffset < renderLen {
|
||||
buf.WriteString(row[state.colOffset:renderLen])
|
||||
}
|
||||
|
||||
if rowLen > rowEnd {
|
||||
buf.WriteByte('>')
|
||||
}
|
||||
}
|
||||
} else {
|
||||
buf.WriteString(" ~")
|
||||
buf.WriteString(EMPTY_LINE_GUTTER)
|
||||
}
|
||||
|
||||
buf.WriteString("\x1b[K")
|
||||
buf.WriteString(CLEAR)
|
||||
|
||||
if y < state.Rows-1 {
|
||||
buf.WriteString("\r\n")
|
||||
}
|
||||
|
|
@ -68,8 +93,8 @@ func uiDrawRows() {
|
|||
|
||||
func uiRefresh() {
|
||||
buf := state.Buffer
|
||||
buf.WriteString("\x1b[?25l")
|
||||
buf.WriteString("\x1b[H")
|
||||
fmt.Fprintf(buf, "\x1b[?251")
|
||||
fmt.Fprintf(buf, "\x1b[H")
|
||||
|
||||
var err error
|
||||
if state.Rows, state.Cols, err = getWindowSize(); err != nil {
|
||||
|
|
@ -78,9 +103,17 @@ func uiRefresh() {
|
|||
|
||||
uiDrawRows()
|
||||
|
||||
setCursorPosition(state.Cx-state.colOffset, state.Cy-state.rowOffset)
|
||||
gutterWidth, _ := calcMaxGutterWidth()
|
||||
|
||||
buf.WriteString("\x1b[?25h")
|
||||
|
||||
// x := gutterWidth + (state.Cx - state.colOffset)
|
||||
y := state.Cy - state.rowOffset
|
||||
rx := gutterWidth + editorCxoRx(state.rows[y], state.Cx) - state.colOffset
|
||||
|
||||
|
||||
setCursorPosition(rx, y)
|
||||
|
||||
fmt.Fprintf(buf, "\x1b[?25h")
|
||||
|
||||
os.Stdout.Write(buf.Bytes())
|
||||
buf.Reset()
|
||||
|
|
@ -94,11 +127,12 @@ type window struct {
|
|||
}
|
||||
|
||||
func uiClearScreen() {
|
||||
os.Stdout.Write([]byte("\x1b[2J"))
|
||||
os.Stdout.Write([]byte("\x1b[H"))
|
||||
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)
|
||||
}
|
||||
|
||||
|
|
@ -119,7 +153,7 @@ func getCursorPosition() (int, int, error) {
|
|||
}
|
||||
}
|
||||
|
||||
if buf[0] != '\x1b' || buf[1] != '[' {
|
||||
if buf[0] != byte(KEY_ESC) || buf[1] != '[' {
|
||||
return 0, 0, errors.New("can't read pos")
|
||||
}
|
||||
|
||||
|
|
@ -154,9 +188,46 @@ func iscntrl(c int) bool {
|
|||
return c > 0x00 && c < 0x1f || c == 0x7f
|
||||
}
|
||||
|
||||
func calcMaxGutterWidth(lineCount int) (int, string) {
|
||||
maxDigits := len(fmt.Sprintf("%d", lineCount))
|
||||
func calcMaxGutterWidth() (int, string) {
|
||||
lineCount := len(state.rows)
|
||||
maxDigits := len([]rune(fmt.Sprintf("%d", lineCount)))
|
||||
gutterStr := fmt.Sprintf("%s%s%%%dd ", CLEAR, LINE_GUTTER, maxDigits)
|
||||
|
||||
// 3 accounts for ' ~ '
|
||||
return 3 + maxDigits, fmt.Sprintf("%%%dd ~ ", maxDigits)
|
||||
return len([]rune(LINE_GUTTER)) + maxDigits + 1, gutterStr
|
||||
}
|
||||
|
||||
func editorCxoRx(row string, cx int) int {
|
||||
rx := 0
|
||||
for idx, c := range []rune(row) {
|
||||
if idx < cx {
|
||||
if c == '\t' {
|
||||
// rx += (TAB_SIZE - 1) - (rx % TAB_SIZE)
|
||||
rx += (TAB_SIZE - 1)
|
||||
}
|
||||
rx++
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
return rx
|
||||
}
|
||||
|
||||
func renderLine(line string) (string, int) {
|
||||
rl := ""
|
||||
tabCount := 0
|
||||
for _, char := range []rune(line) {
|
||||
if char == '\t' {
|
||||
tabCount += 1
|
||||
for i := 0; i < TAB_SIZE; i++ {
|
||||
rl += " "
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
rl = rl + string(char)
|
||||
}
|
||||
|
||||
return rl, utf8.RuneCountInString(rl) //+ (TAB_SIZE * tabCount) - tabCount
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue