diff --git a/tted.c b/tted.c index 79c4946..8c22ae0 100644 --- a/tted.c +++ b/tted.c @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -17,10 +18,12 @@ /** defines */ #define TTED_VERSION "0.0.1" -#define CTRL_KEY(k) ((k) & 0x1f) #define TTED_TAB_STOP 8 +#define TTED_QUIT_TIMES 3 +#define CTRL_KEY(k) ((k) & 0x1f) enum editorKey { + BACKSPACE = 127, ARROW_LEFT = 1000, ARROW_RIGHT, ARROW_UP, @@ -50,6 +53,7 @@ struct editorConfig { int screencols; int numrows; erow *row; + int dirty; char *filename; char statusmsg[80]; time_t statusmsg_time; @@ -57,6 +61,10 @@ struct editorConfig { }; struct editorConfig E; +/** prototypes */ +void editorSetStatusMessage(const char *fmt, ...); +void editorRefreshScreen(); + /** terminal */ void die(const char *s) { write(STDOUT_FILENO, "\x1b[2J", 4); @@ -216,10 +224,11 @@ void editorUpdateRow(erow *row) { } -void editorAppendRow(char *s, size_t len) { +void editorInsertRow(int at, char *s, size_t len) { + if (at < 0 || at > E.numrows) return; E.row = realloc(E.row, sizeof(erow) * (E.numrows + 1)); + memmove(&E.row[at +1], &E.row[at], sizeof(erow) * (E.numrows-at)); - int at = E.numrows; E.row[at].size = len; E.row[at].chars = malloc(len + 1); memcpy(E.row[at].chars, s, len); @@ -228,9 +237,135 @@ void editorAppendRow(char *s, size_t len) { E.row[at].render = NULL; editorUpdateRow(&E.row[at]); E.numrows++; + E.dirty++; +} + +void editorFreeRow(erow *row) { + free(row->render); + free(row->chars); +} + +void editorDelRow(int at) { + if (at < 0 || at >= E.numrows) return; + + editorFreeRow(&E.row[at]); + memmove(&E.row[at], &E.row[at + 1], sizeof(erow) * (E.numrows - at - 1)); + E.numrows--; + E.dirty++; +} + +void editorRowInsertChar(erow *row, int at, int c) { + if (at < 0 || at > row->size) at = row->size; + row->chars = realloc(row->chars, row->size + 2); + memmove(&row->chars[at + 1], &row->chars[at], row->size - at + 1); + row->size++; + row->chars[at] = c; + editorUpdateRow(row); + E.dirty++; +} + +void editorRowAppendString(erow *row, char *s, size_t len) { + row->chars = realloc(row->chars, row->size + len + 1); + memcpy(&row->chars[row->size], s, len); + row->size += len; + row->chars[row->size] = '\0'; + editorUpdateRow(row); + E.dirty++; +} + +void editorRowDelChar(erow *row, int at) { + if (at < 0 || at >= row->size) return; + memmove(&row->chars[at], &row->chars[at + 1], row->size - at); + row->size--; + editorUpdateRow(row); + E.dirty++; +} + +/** Editor Operatons */ +void editorInsertChar(int c) { + if (E.cy == E.numrows) { + editorInsertRow(E.numrows, "", 0); + } + editorRowInsertChar(&E.row[E.cy], E.cx, c); + E.cx++; +} + +void editorInsertNewLine() { + if (E.cx == 0) { + editorInsertRow(E.cy, "", 0); + } else { + erow *row = &E.row[E.cy]; + editorInsertRow(E.cy+1, &row->chars[E.cx], row->size - E.cx); + row = &E.row[E.cy]; + row->size = E.cx; + row->chars[row->size] = '\0'; + editorUpdateRow(row); + } + E.cy++; + E.cx=0; +} + +void editorDelChar() { + if (E.cy == E.numrows) return; + if (E.cx == 0 && E.cy == 0) return; + + erow *row = &E.row[E.cy]; + if (E.cx > 0) { + editorRowDelChar(row, E.cx - 1); + E.cx--; + } else { + E.cx = E.row[E.cy - 1].size; + editorRowAppendString(&E.row[E.cy - 1], row->chars, row->size); + editorDelRow(E.cy); + E.cy--; + } } /** file i/o */ + +char *editorRowsToString(int *buflen) { + int totlen = 0; + int j; + for (j = 0; j < E.numrows; j++) { + totlen += E.row[j].size + 1; + } + *buflen = totlen; + + char *buf = malloc(totlen); + char *p = buf; + for (j = 0; j < E.numrows; j++) { + memcpy(p, E.row[j].chars, E.row[j].size); + p += E.row[j].size; + *p = '\n'; + p++; + } + + return buf; +} + +void editorSave() { + if (E.filename == NULL) return; + + int len; + char *buf = editorRowsToString(&len); + + int fd = open(E.filename, O_RDWR | O_CREAT, 0644); + if (fd != -1) { + if (ftruncate(fd, len) != -1) { + if (write(fd, buf, len) == len) { + close(fd); + free(buf); + E.dirty = 0; + editorSetStatusMessage("%d bytes written to disk", len); + return; + } + } + close(fd); + } + free(buf); +} + + void editorOpen(char *filename) { free(E.filename); E.filename = strdup(filename); @@ -244,10 +379,12 @@ void editorOpen(char *filename) { while (linelen > 0 && (line[linelen - 1] == '\n' || line[linelen - 1] == '\r')) linelen--; - editorAppendRow(line, linelen); + editorInsertRow(E.numrows, line, linelen); } free(line); fclose(fp); + E.dirty = 0; + } @@ -329,8 +466,9 @@ void editorDrawRows(struct abuf *ab) { void editorDrawStatusBar(struct abuf *ab) { abAppend(ab, "\x1b[7m", 4); char status[80], rstatus[80]; - int len = snprintf(status, sizeof(status), "%.20s - %d lines", - E.filename ? E.filename : "[No Name]", E.numrows); + int len = snprintf(status, sizeof(status), "%.20s - %d lines %s", + E.filename ? E.filename : "[No Name]", E.numrows, + E.dirty ? "(modified)" : ""); int rlen = snprintf(rstatus, sizeof(rstatus), "%d/%d", E.cy + 1, E.numrows); if (len > E.screencols) len = E.screencols; abAppend(ab, status, len); @@ -348,11 +486,11 @@ void editorDrawStatusBar(struct abuf *ab) { abAppend(ab, "\r\n", 2); } -void editorDrawMessageBar(struct abuf *ab){ +void editorDrawMessageBar(struct abuf *ab) { abAppend(ab, "\x1b[K", 3); int msglen = strlen(E.statusmsg); if (msglen > E.screencols) msglen = E.screencols; - if (msglen && time(NULL) - E.statusmsg_time < 5){ + if (msglen && time(NULL) - E.statusmsg_time < 5) { abAppend(ab, E.statusmsg, msglen); } } @@ -387,6 +525,34 @@ void editorSetStatusMessage(const char *fmt, ...) { /** input */ +char *editorPrompt(char *prompt){ + size_t bufsize = 128; + char *buf = malloc(bufsize); + + size_t buflen = 0; + buf[0] = '\0'; + + while(1){ + editorSetStatusMessage(prompt, buf); + editorRefreshScreen(); + int c = editorReadKey(); + if (c == '\r') { + if (buflen != 0){ + editorSetStatusMessage(""); + return buf; + } + } else if (!iscntrl(c) && c < 128){ + if(buflen==bufsize-1){ + bufsize *=2; + buf = realloc(buf, bufsize); + } + buf[buflen++] = c; + buf[buflen] = '\0'; + } + break; + } +} + void editorMoveCursor(int key) { erow *row = (E.cy >= E.numrows) ? NULL : &E.row[E.cy]; @@ -428,13 +594,28 @@ void editorMoveCursor(int key) { } void editorProcessKeypress() { + static int quit_times = TTED_QUIT_TIMES; + int c = editorReadKey(); switch (c) { + case '\r': + editorInsertNewLine(); + break; case CTRL_KEY('q'): + if (E.dirty && quit_times > 0) { + editorSetStatusMessage("WARNING!!! File has unsaved changes. " + "Press Ctrl-Q %d more times to quit.", quit_times); + quit_times--; + return; + } + write(STDOUT_FILENO, "\x1b[2J", 4); write(STDOUT_FILENO, "\x1b[H", 3); exit(0); break; + case CTRL_KEY('s'): + editorSave(); + break; case HOME_KEY: E.cx = 0; @@ -446,6 +627,13 @@ void editorProcessKeypress() { } break; + case BACKSPACE: + case CTRL_KEY('h'): + case DEL_KEY: + if (c == DEL_KEY) editorMoveCursor(ARROW_RIGHT); + editorDelChar(); + break; + case PAGE_UP: case PAGE_DOWN: { if (c == PAGE_UP) { @@ -467,7 +655,16 @@ void editorProcessKeypress() { case ARROW_RIGHT: editorMoveCursor(c); break; + case CTRL_KEY('l'): + case '\x1b': + break; + + default: + editorInsertChar(c); + break; } + + quit_times = TTED_QUIT_TIMES; } /** init */ @@ -479,6 +676,7 @@ void initEditor() { E.coloff = 0; E.numrows = 0; E.row = NULL; + E.dirty = 0; E.filename = NULL; E.statusmsg[0] = '\0'; E.statusmsg_time = 0; @@ -494,7 +692,7 @@ int main(int argc, char *argv[]) { editorOpen(argv[1]); } - editorSetStatusMessage("HELP: Ctrl-Q = quit"); + editorSetStatusMessage("HELP: Ctrl-Q = quit; Ctrl-S = save;"); while (1) { editorRefreshScreen();