kilo

Kilo: minimal text editor written in C
git clone git://git.dimitrijedobrota.com/kilo.git
Log | Files | Refs

commit fb8f78d58b8f580820bf6ffbbde0942217572e06
parent 156653a2d53a2abe82c5383810000db0127243bb
Author: Dimitrije Dobrota <mail@dimitrijedobrota.com>
Date:   Sun,  9 Apr 2023 11:38:01 +0200

A text editor

Diffstat:
Mkilo.c | 215++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
1 file changed, 207 insertions(+), 8 deletions(-)

diff --git a/kilo.c b/kilo.c @@ -6,6 +6,7 @@ #include <ctype.h> #include <errno.h> +#include <fcntl.h> #include <stdarg.h> #include <stdio.h> #include <stdlib.h> @@ -31,12 +32,14 @@ /*** defines ***/ -#define KILO_VERSION "0.0.1" -#define KILO_TAB_STOP 8 +#define KILO_VERSION "0.0.1" +#define KILO_TAB_STOP 8 +#define KILO_QUIT_TIMES 3 #define CTRL_KEY(k) ((k)&0x1f) enum editorKey { + BACKSPACE = 127, ARROW_LEFT = 1000, ARROW_RIGHT, ARROW_UP, @@ -66,6 +69,7 @@ struct editorConfig { int screencols; int numrows; erow *row; + int dirty; char *filename; char statusmsg[80]; time_t statusmsg_time; @@ -74,6 +78,12 @@ struct editorConfig { struct editorConfig E; +/*** prototypes ***/ + +void editorSetStatusMessage(const char *fmt, ...); +void editorRefreshScreen(); +char *editorPrompt(char *prompt); + /*** terminal ***/ void die(const char *s) { @@ -218,10 +228,12 @@ void editorUpdateRow(erow *row) { row->rsize = idx; } -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); @@ -232,10 +244,105 @@ void editorAppendRow(char *s, size_t len) { 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 operations ***/ + +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]; // reassign in case realloc changes it + 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; + for (int j = 0; j < E.numrows; j++) totlen += E.row[j].size + 1; + *buflen = totlen; + + char *buf = malloc(totlen), *p = buf; + for (int 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 editorOpen(char *filename) { free(E.filename); E.filename = strdup(filename); @@ -251,10 +358,40 @@ void editorOpen(char *filename) { (line[linelen - 1] == '\n' || line[linelen - 1] == '\r')) linelen--; - editorAppendRow(line, linelen); + editorInsertRow(E.numrows, line, linelen); } free(line); fclose(fp); + E.dirty = 0; +} + +void editorSave() { + if (E.filename == NULL) { + E.filename = editorPrompt("Save as: %s"); + if (E.filename == NULL) { + editorSetStatusMessage("Save aborted"); + 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); + editorSetStatusMessage("Can't save! I/O error: %s", strerror(errno)); } /*** append buffer ***/ @@ -280,6 +417,39 @@ void abFree(struct abuf *ab) { free(ab->b); } /*** input ***/ +char *editorPrompt(char *prompt) { + size_t bufsize = 128, buflen; + char *buf = malloc(bufsize); + buf[buflen = 0] = '\0'; + + while (1) { + editorSetStatusMessage(prompt, buf); + editorRefreshScreen(); + + int c = editorReadKey(); + + if (c == DEL_KEY || c == CTRL_KEY('h') || c == BACKSPACE) { + if (buflen != 0) buf[--buflen] = '\0'; + } else if (c == '\x1b') { + editorSetStatusMessage(""); + free(buf); + return NULL; + } else 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'; + } + } +} + void editorMoveCursor(int key) { erow *row = (E.cy >= E.numrows) ? NULL : &E.row[E.cy]; @@ -314,21 +484,41 @@ void editorMoveCursor(int key) { } void editorProcessKeypress() { + static int quit_times = KILO_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; break; case END_KEY: if (E.cy < E.numrows) E.cx = E.row[E.cy].size; 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) { @@ -347,7 +537,14 @@ void editorProcessKeypress() { case ARROW_DOWN: case ARROW_LEFT: case ARROW_RIGHT: editorMoveCursor(c); break; + + case CTRL_KEY('l'): + case '\x1b': break; + + default: editorInsertChar(c); break; } + + quit_times = KILO_QUIT_TIMES; } /*** output ***/ @@ -393,8 +590,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); CLAMP_RIGHT(len, E.screencols); abAppend(ab, status, len); @@ -460,6 +658,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; @@ -473,7 +672,7 @@ int main(int argc, char *argv[]) { initEditor(); if (argc >= 2) { editorOpen(argv[1]); } - editorSetStatusMessage("HELP: Ctrl-Q = quit"); + editorSetStatusMessage("HELP: Ctrl-S = save | Ctrl-Q = quit"); while (1) { editorRefreshScreen();