fixed horizontal scrolling and tabs

master
Adam Veldhousen 2020-04-12 23:27:46 -05:00
parent 3ed0418f72
commit c89d698b60
Signed by: adam
GPG Key ID: 6DB29003C6DD1E4B
3 changed files with 172 additions and 61 deletions

View File

@ -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
View File

@ -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
View File

@ -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
}