Commit 0939238

mo khan <mo@mokhan.ca>
2021-07-26 03:31:48
finish chapter 4
1 parent 5da2b92
Changed files (1)
kilo.c
@@ -1,17 +1,26 @@
 /*** includes ***/
 
+#define _DEFAULT_SOURCE
+#define _BSD_SOURCE
+#define _GNU_SOURCE
+
 #include <ctype.h>
 #include <errno.h>
 #include <stdio.h>
+#include <stdarg.h>
 #include <stdlib.h>
 #include <string.h>
 #include <sys/ioctl.h>
+#include <sys/types.h>
 #include <termios.h>
+#include <time.h>
 #include <unistd.h>
 
 /*** defines ***/
 
 #define KILO_VERSION "0.0.1"
+#define KILO_TAB_STOP 8
+
 #define CTRL_KEY(k) ((k) & 0x1f)
 
 enum editorKey {
@@ -28,10 +37,25 @@ enum editorKey {
 
 /*** data ***/
 
+typedef struct erow {
+  int size;
+  int rsize;
+  char *chars;
+  char *render;
+} erow;
+
 struct editor_config {
   int cx, cy;
+  int rx;
+  int rowoff;
+  int coloff;
   int screenrows;
   int screencols;
+  int numrows;
+  erow *row;
+  char *filename;
+  char statusmsg[80];
+  time_t statusmsg_time;
   struct termios orig_termios;
 };
 
@@ -149,6 +173,78 @@ int get_window_size(int *rows, int *cols) {
   }
 }
 
+/*** row operations ***/
+
+int editor_row_cx_to_rx(erow *row, int cx) {
+  int rx = 0;
+  int j;
+  for (j = 0; j < cx; j++) {
+    if (row->chars[j] == '\t')
+      rx += (KILO_TAB_STOP - 1) - (rx % KILO_TAB_STOP);
+    rx++;
+  }
+  return rx;
+}
+
+void editor_update_row(erow *row) {
+  int tabs = 0;
+  int j;
+  for (j = 0; j < row->size; j++)
+    if (row->chars[j] == '\t') tabs++;
+
+  free(row->render);
+  row->render = malloc(row->size + tabs*(KILO_TAB_STOP - 1) + 1);
+
+  int idx = 0;
+  for (j = 0; j < row->size; j++) {
+    if (row->chars[j] == '\t') {
+      row->render[idx++] = ' ';
+      while (idx % KILO_TAB_STOP != 0) row->render[idx++] = ' ';
+    } else {
+      row->render[idx++] = row->chars[j];
+    }
+  }
+  row->render[idx] = '\0';
+  row->rsize = idx;
+}
+
+void editor_append_row(char *s, size_t len) {
+  E.row = realloc(E.row, sizeof(erow) * (E.numrows + 1));
+
+  int at = E.numrows;
+  E.row[at].size = len;
+  E.row[at].chars = malloc(len + 1);
+  memcpy(E.row[at].chars, s, len);
+  E.row[at].chars[len] = '\0';
+
+  E.row[at].rsize = 0;
+  E.row[at].render = NULL;
+  editor_update_row(&E.row[at]);
+
+  E.numrows++;
+}
+
+/*** file i/o ***/
+
+void editor_open(char *filename) {
+  free(E.filename);
+  E.filename = strdup(filename);
+
+  FILE *fp = fopen(filename, "r");
+  if (!fp) die("fopen");
+
+  char *line = NULL;
+  size_t linecap = 0;
+  ssize_t linelen;
+  while ((linelen = getline(&line, &linecap, fp)) != -1) {
+    while (linelen > 0 && (line[linelen - 1] == '\n' || line[linelen - 1] == '\r'))
+      linelen--;
+    editor_append_row(line, linelen);
+  }
+  free(line);
+  fclose(fp);
+}
+
 /*** append buffer ***/
 
 struct abuf {
@@ -173,40 +269,99 @@ void ab_free(struct abuf *ab) {
 
 /*** output ***/
 
+void editor_scroll() {
+  E.rx = 0;
+  if (E.cy < E.numrows) {
+    E.rx = editor_row_cx_to_rx(&E.row[E.cy], E.cx);
+  }
+
+  if (E.cy < E.rowoff) {
+    E.rowoff = E.cy;
+  }
+  if (E.cy >= E.rowoff + E.screenrows) {
+    E.rowoff = E.cy - E.screenrows + 1;
+  }
+  if (E.rx < E.coloff) {
+    E.coloff = E.rx;
+  }
+  if (E.rx >= E.coloff + E.screencols) {
+    E.coloff = E.rx - E.screencols + 1;
+  }
+}
+
 void editor_draw_rows(struct abuf *ab) {
   for (int i = 0; i < E.screenrows; i++) {
-    if (i == E.screenrows / 3) {
-      char welcome[80];
-      int length = snprintf(welcome, sizeof(welcome), "Kilo editor -- version %s", KILO_VERSION);
-      if (length > E.screencols) length = E.screencols;
-      int padding = (E.screencols - length) / 2;
-      if (padding) {
+    int filerow = i + E.rowoff;
+    if (filerow >= E.numrows) {
+      if (E.numrows == 0 && i == E.screenrows / 3) {
+        char welcome[80];
+        int length = snprintf(welcome, sizeof(welcome),
+            "Kilo editor -- version %s", KILO_VERSION);
+        if (length > E.screencols) length = E.screencols;
+        int padding = (E.screencols - length) / 2;
+        if (padding) {
+          ab_append(ab, "~", 1);
+          padding--;
+        }
+        while (padding--) ab_append(ab, " ", 1);
+        ab_append(ab, welcome, length);
+      } else {
         ab_append(ab, "~", 1);
-        padding--;
       }
-      while (padding--) ab_append(ab, " ", 1);
-      ab_append(ab, welcome, length);
     } else {
-      ab_append(ab, "~", 1);
+      int len = E.row[filerow].rsize - E.coloff;
+      if (len < 0) len = 0;
+      if (len > E.screencols) len = E.screencols;
+      ab_append(ab, &E.row[filerow].render[E.coloff], len);
     }
 
     ab_append(ab, "\x1b[K", 3);
-    if (i < E.screenrows - 1) {
-      ab_append(ab, "\r\n", 2);
+    ab_append(ab, "\r\n", 2);
+  }
+}
+
+void editor_draw_status_bar(struct abuf *ab) {
+  ab_append(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 rlen = snprintf(rstatus, sizeof(rstatus), "%d/%d", E.cy + 1, E.numrows);
+  if (len > E.screencols) len = E.screencols;
+  ab_append(ab, status, len);
+  while (len < E.screencols) {
+    if (E.screencols - len == rlen) {
+      ab_append(ab, rstatus, rlen);
+      break;
+    } else {
+      ab_append(ab, " ", 1);
+      len++;
     }
   }
+  ab_append(ab, "\x1b[m", 3);
+  ab_append(ab, "\r\n", 2);
+}
+
+void editor_draw_message_bar(struct abuf *ab) {
+  ab_append(ab, "\x1b[K", 3);
+  int msglen = strlen(E.statusmsg);
+  if (msglen > E.screencols) msglen = E.screencols;
+  if (msglen && time(NULL) - E.statusmsg_time < 5)
+    ab_append(ab, E.statusmsg, msglen);
 }
 
 void editor_refresh_screen() {
+  editor_scroll();
+
   struct abuf ab = ABUF_INIT;
 
   ab_append(&ab, "\x1b[?25l", 6);
   ab_append(&ab, "\x1b[H", 3); // https://vt100.net/docs/vt100-ug/chapter3.html#CUP
 
   editor_draw_rows(&ab);
+  editor_draw_status_bar(&ab);
+  editor_draw_message_bar(&ab);
 
   char buf[32];
-  snprintf(buf, sizeof(buf), "\x1b[%d;%dH", E.cy + 1, E.cx + 1);
+  snprintf(buf, sizeof(buf), "\x1b[%d;%dH", (E.cy - E.rowoff) + 1, (E.rx - E.coloff) + 1);
   ab_append(&ab, buf, strlen(buf));
 
   ab_append(&ab, "\x1b[?25h", 6);
@@ -215,18 +370,34 @@ void editor_refresh_screen() {
   ab_free(&ab);
 }
 
+void editor_set_status_message(const char *fmt, ...) {
+  va_list ap;
+  va_start(ap, fmt);
+  vsnprintf(E.statusmsg, sizeof(E.statusmsg), fmt, ap);
+  va_end(ap);
+  E.statusmsg_time = time(NULL);
+}
+
 /*** input ***/
 
 void editor_move_cursor(int key) {
+  erow *row = (E.cy >= E.numrows) ? NULL : &E.row[E.cy];
+
   switch(key) {
     case ARROW_LEFT:
       if (E.cx != 0) {
         E.cx--;
+      } else if (E.cy > 0) {
+        E.cy--;
+        E.cx = E.row[E.cy].size;
       }
       break;
     case ARROW_RIGHT:
-      if (E.cx != E.screencols - 1) {
+      if (row && E.cx < row->size) {
         E.cx++;
+      } else if (row && E.cx == row->size) {
+        E.cy++;
+        E.cx = 0;
       }
       break;
     case ARROW_UP:
@@ -235,11 +406,17 @@ void editor_move_cursor(int key) {
       }
       break;
     case ARROW_DOWN:
-      if (E.cy != E.screenrows - 1) {
+      if (E.cy < E.numrows) {
         E.cy++;
       }
       break;
   }
+
+  row = (E.cy >= E.numrows) ? NULL : &E.row[E.cy];
+  int rowlen = row ? row->size : 0;
+  if (E.cx > rowlen) {
+    E.cx = rowlen;
+  }
 }
 
 void editor_process_keypress() {
@@ -257,12 +434,20 @@ void editor_process_keypress() {
       break;
 
     case END_KEY:
-      E.cx = E.screencols - 1;
+      if (E.cy < E.numrows)
+        E.cx = E.row[E.cy].size;
       break;
 
     case PAGE_UP:
     case PAGE_DOWN:
       {
+        if (c == PAGE_UP) {
+          E.cy = E.rowoff;
+        } else if (c == PAGE_DOWN) {
+          E.cy = E.rowoff + E.screenrows - 1;
+          if (E.cy > E.numrows) E.cy = E.numrows;
+        }
+
         int times = E.screenrows;
         while (times--)
           editor_move_cursor(c == PAGE_UP ? ARROW_UP : ARROW_DOWN);
@@ -290,14 +475,28 @@ void editor_process_keypress() {
 void init_editor() {
   E.cx = 0;
   E.cy = 0;
+  E.rx = 0;
+  E.rowoff = 0;
+  E.coloff = 0;
+  E.numrows = 0;
+  E.row = NULL;
+  E.filename = NULL;
+  E.statusmsg[0] = '\0';
+  E.statusmsg_time = 0;
 
   if (get_window_size(&E.screenrows, &E.screencols) == -1)
     die("get_window_size");
+  E.screenrows -= 2;
 }
 
-int main() {
+int main(int argc, char *argv[]) {
   enable_raw_mode();
   init_editor();
+  if (argc >= 2) {
+    editor_open(argv[1]);
+  }
+
+  editor_set_status_message("HELP: Ctrl-Q = quit");
 
   while (1) {
     editor_refresh_screen();