diff --git a/file.go b/file.go index 91d3124..271e517 100644 --- a/file.go +++ b/file.go @@ -29,9 +29,9 @@ func fileOpen(fname string) { state.rows = append(state.rows, fread.Text()) } - // if len(state.rows) == 0 || (len(state.rows) == 1 && state.rows[0] == "") { - // state.rows = []string{" "} - // } + if len(state.rows) == 0 || (len(state.rows) == 1 && state.rows[0] == "") { + state.rows = []string{""} + } } f.Close() @@ -47,14 +47,24 @@ func fileSave() { defer f.Close() bytesWritten := 0 + charsWritten := 0 + linesWritten := 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 + charsWritten += len([]rune(row)) + 1 + linesWritten++ } - state.StatusLine = fmt.Sprintf("'%s' wrote %d bytes.", state.file, bytesWritten) + state.StatusLines[1] = fmt.Sprintf( + "'%s' saved. %dL, %dC, %d bytes", + state.file, + linesWritten, + charsWritten, + bytesWritten, + ) } diff --git a/input.go b/input.go index 41f4596..51ddac0 100644 --- a/input.go +++ b/input.go @@ -33,9 +33,7 @@ const ( func editorKeyPresses() bool { char := editorReadKey() - state.StatusLine = "" - - editorMoveCaret(char) + state.StatusLines[1] = "" if char == ctrl_key('r') { uiRefresh() @@ -55,71 +53,133 @@ func editorKeyPresses() bool { 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) { - lineText := state.rows[state.Cy] - rl := state.getCurrentRowLen() - 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 && 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) + 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 = state.rows[:state.Cy-1] - state.Cy-- + state.rows = append(state.rows[:cy], state.rows[cy+1:]...) } - 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:]) + } + + // 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", lineText[:cx], lineText[cx+1:]) + state.rows[state.Cy] = fmt.Sprintf("%s%s", lt[:cx], lt[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 + 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 { - 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:]...) + // 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.rows[state.Cy] = fmt.Sprintf("%s%c%s", lt[:cx], char, lt[cx:]) + 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) } } @@ -165,6 +225,8 @@ func editorMoveCaret(char rune) { case KEY_END: x = state.Cols - 1 break + default: + return } if char == ctrl_key(KEY_DOWN_ARROW) { diff --git a/main.go b/main.go index 95e5785..4e6090d 100644 --- a/main.go +++ b/main.go @@ -19,7 +19,7 @@ type editorState struct { Cy int Rows int Cols int - StatusLine string + StatusLines [2]string DebugLine string rows []string rowOffset int @@ -66,6 +66,7 @@ func initEditor() { state.Buffer = &bytes.Buffer{} state.rows = []string{fmt.Sprintf("Pound Ed %s", Version)} state.file = "" + state.StatusLines = [2]string{} } func cleanup() { diff --git a/ui.go b/ui.go index 051d432..738d0a7 100644 --- a/ui.go +++ b/ui.go @@ -16,8 +16,11 @@ import ( const ( CLEAR = "\x1b[K" TAB_SIZE = 8 - EMPTY_LINE_GUTTER = " x~ " + EMPTY_LINE_GUTTER = " ~ " LINE_GUTTER = " " + STATUSLINEHEIGHT = 2 + WHITESPACE = "\xB8" + NEWLINE = '\xAC' ) func uiDrawRows() { @@ -36,26 +39,22 @@ func uiDrawRows() { file = "NO FILE" } - statusLine := fmt.Sprintf( - "%s\x1b[1;7m '%s' \x1b[m L:%d/%d C:%d/%d | H:%d W: %d | %s", + state.StatusLines[0] = fmt.Sprintf( + "%s\x1b[1;7m '%s' \x1b[m L:%d/%d C:%d/%d | H:%d W: %d", CLEAR, file, state.Cy+1, len(state.rows), rx+1, - highlightedRowLen, + highlightedRowLen+1, state.Rows-1, - state.Cols-gutterWidth-1, - state.StatusLine) + state.Cols-gutterWidth-1) + for y := 0; y < state.Rows; y++ { - if y == state.Rows-1 { - statusLen := len(statusLine) - if statusLen > state.Cols { - statusLen = state.Cols - } - buf.WriteString(statusLine[:statusLen]) - } else if y < state.Rows && state.Cy < len(state.rows) { + if y >= state.Rows-STATUSLINEHEIGHT { + buf.WriteString(drawStatusLine(y - (state.Rows - STATUSLINEHEIGHT))) + } else if state.Cy < len(state.rows) && y + state.rowOffset < len(state.rows) { rowIdx := y + state.rowOffset if rowIdx < len(state.rows) { @@ -75,8 +74,8 @@ func uiDrawRows() { renderLen = rowEnd } - if state.colOffset < renderLen { - buf.WriteString(row[state.colOffset:renderLen]) + if state.colOffset <= renderLen { + buf.WriteString(fmt.Sprintf("%s%c", row[state.colOffset:renderLen], NEWLINE)) } if rowLen > rowEnd { @@ -95,6 +94,16 @@ func uiDrawRows() { } } +func drawStatusLine(row int) string { + statusLine := state.StatusLines[row] + statusLen := len(statusLine) + if statusLen > state.Cols { + statusLen = state.Cols + } + return statusLine[:statusLen] +} + + func uiRefresh() { buf := state.Buffer fmt.Fprintf(buf, "\x1b[?251") @@ -222,7 +231,7 @@ func renderLine(line string) (string, int) { if char == '\t' { tabCount++ for i := 0; i < TAB_SIZE; i++ { - rl += " " + rl += WHITESPACE } continue }