commit fb8f78d58b8f580820bf6ffbbde0942217572e06
parent 156653a2d53a2abe82c5383810000db0127243bb
Author: Dimitrije Dobrota <mail@dimitrijedobrota.com>
Date: Sun, 9 Apr 2023 11:38:01 +0200
A text editor
Diffstat:
M | kilo.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();