/* * ke - kyle's editor * * ke started off following along with the kilo walkthrough at * https://viewsourcecode.org/snaptoken/kilo/ * * It is inspired heavily by mg(1) and VDE. This is a single file and * can be built with * $(CC) -D_DEFAULT_SOURCE -D_XOPEN_SOURCE -Wall -Wextra -pedantic \ * -Wshadow -Werror -std=c99 -g -o ke main.c * * It builds and runs on Linux and Darwin. I can't confirm BSD compatibility. * * commit 59d3fa1dab68e8683d5f5a9341f5f42ef3308876 * Author: Kyle Isom * Date: Fri Feb 7 20:46:43 2020 -0800 * * Initial import, starting with kyle's editor. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "abuf.h" #include "buffer.h" #include "editor.h" #include "core.h" #include "term.h" #ifndef KE_VERSION #define KE_VERSION "ke dev build" #endif #define ESCSEQ "\x1b[" #define CTRL_KEY(key) ((key)&0x1f) #define TAB_STOP 8 #define MSG_TIMEO 3 /* * define the keyboard input modes * normal: no special mode * kcommand: ^k commands * escape: what happens when you hit escape? */ #define MODE_NORMAL 0 #define MODE_KCOMMAND 1 #define MODE_ESCAPE 2 #define TAB_STOP 8 #define KILLRING_NO_OP 0 /* don't touch the killring */ #define KILLRING_APPEND 1 /* append deleted chars */ #define KILLRING_PREPEND 2 /* prepend deleted chars */ #define KILLING_SET 3 /* set killring to deleted char */ #define KILLRING_FLUSH 4 /* clear the killring */ /* kill ring, marking, etc... */ void killring_flush(void); void killring_yank(void); void killring_start_with_char(unsigned char ch); void killring_append_char(unsigned char ch); void killring_prepend_char(unsigned char ch); void toggle_markset(void); int cursor_after_mark(void); int count_chars_from_cursor_to_mark(void); void kill_region(void); void indent_region(void); void delete_region(void); /* miscellaneous */ void jump_to_position(int col, int row); void goto_line(void); int cursor_at_eol(void); int iswordchar(unsigned char c); void find_next_word(void); void delete_next_word(void); void find_prev_word(void); void delete_prev_word(void); void delete_row(int at); void row_insert_ch(abuf *row, int at, int16_t c); void row_delete_ch(abuf *row, int at); void insertch(int16_t c); void deletech(uint8_t op); void open_file(const char *filename); char *rows_to_buffer(int *buflen); int save_file(void); uint16_t is_arrow_key(int16_t c); int16_t get_keypress(void); void editor_find_callback(char *query, int16_t c); void editor_find(void); char *editor_prompt(const char*, void (*cb)(char*, int16_t)); void editor_openfile(void); int first_nonwhitespace(abuf *row); void move_cursor_once(int16_t c, int interactive); void move_cursor(int16_t c, int interactive); void uarg_start(void); void uarg_digit(int d); void uarg_clear(void); int uarg_get(void); void newline(void); void process_kcommand(int16_t c); void process_normal(int16_t c); void process_escape(int16_t c); int process_keypress(void); char *get_cloc_code_lines(const char *filename); int dump_pidfile(void); void draw_rows(abuf *ab); char status_mode_char(void); void draw_status_bar(abuf *ab); void draw_message_line(abuf *ab); void scroll(void); void display_refresh(void); void loop(void); void enable_debugging(void); void deathknell(void); static void signal_handler(int sig); static void install_signal_handlers(void); static int path_is_dir(const char *path) { struct stat st; if (path == NULL) { return 0; } if (stat(path, &st) == 0) { return S_ISDIR(st.st_mode); } return 0; } static size_t str_lcp2(const char *a, const char *b) { size_t i = 0; if (!a || !b) { return 0; } while (a[i] && b[i] && a[i] == b[i]) { i++; } return i; } /* * TODO(kyle): not proud of this, but it does work. It needs to be * cleaned up and the number of buffers consolidated. */ static void file_open_prompt_cb(char *buf, const int16_t key) { DIR *dirp = NULL; const char *name = NULL; const char *names[128] = {0}; char ext[256] = {0}; char full[PATH_MAX] = {0}; char msg[80] = {0}; char newbuf[PATH_MAX] = {0}; int isdir[128] = {0}; struct dirent *de = NULL; const char *slash = NULL; char dirpath[PATH_MAX] = {0}; char base[256] = {0}; int n = 0; size_t cur = 0; size_t k = 0; size_t lcp = 0; size_t to_copy = 0; size_t dlen = 0; size_t plen = 0; if (key != TAB_KEY) { return; } slash = strrchr(buf, '/'); if (slash) { dlen = (size_t) (slash - buf); if (dlen == 0) { /* path like "/foo" -> dir is "/" */ strcpy(dirpath, "/"); } else { if (dlen >= sizeof(dirpath)) { dlen = sizeof(dirpath) - 1; } memcpy(dirpath, buf, dlen); dirpath[dlen] = '\0'; } strncpy(base, slash + 1, sizeof(base) - 1); base[sizeof(base) - 1] = '\0'; } else { strcpy(dirpath, "."); strncpy(base, buf, sizeof(base) - 1); base[sizeof(base) - 1] = '\0'; } dirp = opendir(dirpath); if (!dirp) { editor_set_status("No such dir: %s", dirpath); return; } plen = strlen(base); while ((de = readdir(dirp)) != NULL) { name = de->d_name; /* Skip . and .. */ if (strcmp(name, ".") == 0 || strcmp(name, "..") == 0) { continue; } if (plen == 0 || strncmp(name, base, plen) == 0) { if (n < 128) { names[n] = strdup(name); /* Build full path to test dir */ if (snprintf(full, sizeof(full), "%s/%s", dirpath, name) >= 0) { isdir[n] = path_is_dir(full); } else { isdir[n] = 0; } n++; } } } closedir(dirp); if (n == 0) { editor_set_status("No file matches '%s' in %s", base, dirpath); return; } lcp = strlen(names[0]); for (int i = 1; i < n; i++) { k = str_lcp2(names[0], names[i]); if (k < lcp) { lcp = k; } if (lcp == 0) { break; } } newbuf[0] = '\0'; if (slash) { dlen = (size_t) (slash - buf); if (dlen >= sizeof(newbuf)) { dlen = sizeof(newbuf) - 1; } memcpy(newbuf, buf, dlen); newbuf[dlen] = '\0'; strncat(newbuf, "/", sizeof(newbuf) - strlen(newbuf) - 1); } /* appending: if unique -> full name (+ '/' if dir), else current * base extended to LCP */ if (n == 1) { strncat(newbuf, names[0], sizeof(newbuf) - strlen(newbuf) - 1); if (isdir[0]) { strncat(newbuf, "/", sizeof(newbuf) - strlen(newbuf) - 1); } /* copy to input buffer (max 127 chars + NUL) avoiding truncation warnings */ do { const char *src__ = newbuf[0] ? newbuf : names[0]; size_t cap__ = 128u; size_t len__ = strnlen(src__, cap__ - 1); memcpy(buf, src__, len__); buf[len__] = '\0'; } while (0); editor_set_status("Unique match: %s%s", names[0], isdir[0] ? "/" : ""); } else { cur = strlen(base); if (lcp > cur) { to_copy = lcp - cur; if (to_copy >= sizeof(ext)) { to_copy = sizeof(ext) - 1; } memcpy(ext, names[0] + cur, to_copy); ext[to_copy] = '\0'; strncat(newbuf, base, sizeof(newbuf) - strlen(newbuf) - 1); strncat(newbuf, ext, sizeof(newbuf) - strlen(newbuf) - 1); } else { strncat(newbuf, base, sizeof(newbuf) - strlen(newbuf) - 1); } /* copy to input buffer (max 127 chars + NUL) avoiding truncation warnings */ do { const char *src__ = newbuf; size_t cap__ = 128u; size_t len__ = strnlen(src__, cap__ - 1); memcpy(buf, src__, len__); buf[len__] = '\0'; } while (0); size_t used = 0; used += snprintf(msg + used, sizeof(msg) - used, "%d matches: ", n); for (int i = 0; i < n && used < sizeof(msg) - 1; i++) { used += snprintf(msg + used, sizeof(msg) - used, "%s%s%s", (i ? ", " : ""), names[i], isdir[i] ? "/" : ""); } editor_set_status("%s", msg); } /* Free duplicated names */ for (int i = 0; i < n; i++) { free((void *) names[i]); } } int erow_render_to_cursor(const abuf *row, const int cx) { int rx = 0; size_t j = 0; wchar_t wc; mbstate_t st; memset(&st, 0, sizeof(st)); while (j < (size_t)cx && j < (size_t)row->size) { unsigned char b = (unsigned char)row->b[j]; if (b == '\t') { rx += (TAB_STOP - 1) - (rx % TAB_STOP); rx++; j++; continue; } if (b < 0x20) { /* render as \xx -> width 3 */ rx += 3; j++; continue; } if (b < 0x80) { rx++; j++; continue; } size_t rem = (size_t)row->size - j; size_t n = mbrtowc(&wc, &row->b[j], rem, &st); if (n == (size_t)-2) { /* incomplete sequence at end; treat one byte */ rx += 1; j += 1; memset(&st, 0, sizeof(st)); } else if (n == (size_t)-1) { /* invalid byte; consume one and reset state */ rx += 1; j += 1; memset(&st, 0, sizeof(st)); } else if (n == 0) { /* null character */ rx += 0; j += 1; } else { int w = wcwidth(wc); if (w < 0) w = 1; /* non-printable -> treat as width 1 */ rx += w; j += n; } } return rx; } int erow_cursor_to_render(abuf *row, int rx) { int cur_rx = 0; size_t j = 0; wchar_t wc; mbstate_t st; memset(&st, 0, sizeof(st)); while (j < (size_t)row->size) { int w = 0; size_t adv = 1; unsigned char b = (unsigned char)row->b[j]; if (b == '\t') { int add = (TAB_STOP - 1) - (cur_rx % TAB_STOP); w = add + 1; adv = 1; /* tabs are single byte */ } else if (b < 0x20) { w = 3; /* "\\xx" */ adv = 1; } else if (b < 0x80) { w = 1; adv = 1; } else { size_t rem = (size_t)row->size - j; size_t n = mbrtowc(&wc, &row->b[j], rem, &st); if (n == (size_t)-2 || n == (size_t)-1) { /* invalid/incomplete */ w = 1; adv = 1; memset(&st, 0, sizeof(st)); } else if (n == 0) { w = 0; adv = 1; } else { int ww = wcwidth(wc); if (ww < 0) ww = 1; w = ww; adv = n; } } if (cur_rx + w > rx) { break; } cur_rx += w; j += adv; } return (int)j; } int erow_init(abuf *row, int len) { ab_init_cap(row, len); return 0; } void erow_insert(int at, char *s, int len) { abuf *row = realloc(EROW, sizeof(abuf) * (ENROWS + 1)); assert(row != NULL); EROW = row; if (at < ENROWS) { memmove(&EROW[at + 1], &EROW[at], sizeof(abuf) * (ENROWS - at)); } ab_init(&EROW[at]); ab_append(&EROW[at], s, len); ENROWS++; } void killring_flush(void) { if (editor.killring != NULL) { ab_free(editor.killring); free(editor.killring); editor.killring = NULL; } } void killring_yank(void) { if (editor.killring == NULL) { return; } /* * Insert killring contents at the cursor without clearing the ring. * Interpret '\n' as an actual newline() rather than inserting a raw 0x0A * byte, so yanked content preserves lines correctly. */ for (int i = 0; i < (int)editor.killring->size; i++) { unsigned char ch = (unsigned char)editor.killring->b[i]; if (ch == '\n') { newline(); } else { insertch(ch); } } } void killring_start_with_char(unsigned char ch) { abuf *row = NULL; if (editor.killring != NULL) { ab_free(editor.killring); free(editor.killring); editor.killring = NULL; } editor.killring = malloc(sizeof(abuf)); assert(editor.killring != NULL); assert(erow_init(editor.killring, 0) == 0); /* append one char to empty killring without affecting editor.dirty */ row = editor.killring; row->b = realloc(row->b, row->size + 2); assert(row->b != NULL); row->b[row->size] = ch; row->size++; row->b[row->size] = '\0'; } void killring_append_char(unsigned char ch) { abuf *row = NULL; if (editor.killring == NULL) { killring_start_with_char(ch); return; } row = editor.killring; row->b = realloc(row->b, row->size + 2); assert(row->b != NULL); row->b[row->size] = ch; row->size++; row->b[row->size] = '\0'; } void killring_prepend_char(unsigned char ch) { abuf *row = NULL; if (editor.killring == NULL) { killring_start_with_char(ch); return; } row = editor.killring; row->b = realloc(row->b, row->size + 2); assert(row->b != NULL); memmove(&row->b[1], &row->b[0], row->size + 1); row->b[0] = ch; row->size++; } void toggle_markset(void) { if (EMARK_SET) { EMARK_SET = 0; editor_set_status("Mark cleared."); return; } EMARK_SET = 1; EMARK_CURX = ECURX; EMARK_CURY = ECURY; editor_set_status("Mark set."); } int cursor_after_mark(void) { if (EMARK_CURY < ECURY) { return 1; } if (EMARK_CURY > ECURY) { return 0; } return ECURX >= EMARK_CURX; } int count_chars_from_cursor_to_mark(void) { int count = 0; int curx = ECURX; int cury = ECURY; int markx = EMARK_CURX; int marky = EMARK_CURY; if (!cursor_after_mark()) { swap_int(&curx, &markx); swap_int(&curx, &marky); } ECURX = markx; ECURY = marky; while (ECURY != cury) { while (!cursor_at_eol()) { move_cursor(ARROW_RIGHT, 1); count++; } move_cursor(ARROW_RIGHT, 1); count++; } while (ECURX != curx) { count++; move_cursor(ARROW_RIGHT, 1); } return count; } void kill_region(void) { int curx = ECURX; int cury = ECURY; int markx = EMARK_CURX; int marky = EMARK_CURY; if (!EMARK_SET) { return; } /* kill the current killring first */ killring_flush(); if (!cursor_after_mark()) { swap_int(&curx, &markx); swap_int(&cury, &marky); } ECURX = markx; ECURY = marky; while (ECURY != cury) { while (!cursor_at_eol()) { killring_append_char(EROW[ECURY].b[ECURX]); move_cursor(ARROW_RIGHT, 0); } killring_append_char('\n'); move_cursor(ARROW_RIGHT, 0); } while (ECURX != curx) { killring_append_char(EROW[ECURY].b[ECURX]); move_cursor(ARROW_RIGHT, 0); } editor_set_status("Region killed."); /* clearing the mark needs to be done outside this function; * * when deleting the region, the mark needs to be set too. */ } void indent_region(void) { int start_row = 0; int end_row = 0; int i = 0; if (!EMARK_SET) { return; } if (EMARK_CURY < ECURY) { start_row = EMARK_CURY; end_row = ECURY; } else if (EMARK_CURY > ECURY) { start_row = ECURY; end_row = EMARK_CURY; } else { start_row = end_row = ECURY; } /* Ensure bounds are valid */ if (start_row < 0) { start_row = 0; } if (end_row >= ENROWS) { end_row = ENROWS - 1; } if (start_row >= ENROWS || end_row < 0) { return; } for (i = start_row; i <= end_row; i++) { row_insert_ch(&EROW[i], 0, '\t'); } ECURX = 0; EDIRTY++; } void unindent_region(void) { int start_row = 0; int end_row = 0; int i = 0; int del = 0; abuf *row = NULL; if (!EMARK_SET) { editor_set_status("Mark not set."); return; } if (EMARK_CURY < ECURY || (EMARK_CURY == ECURY && EMARK_CURX < ECURX)) { start_row = EMARK_CURY; end_row = ECURY; } else { start_row = ECURY; end_row = EMARK_CURY; } if (start_row >= ENROWS) { return; } if (end_row >= editor.nrows) { end_row = editor.nrows - 1; } /* actually unindent every line in the region */ for (i = start_row; i <= end_row; i++) { row = &editor.row[i]; if (row->size == 0) { continue; } if (row->b[0] == '\t') { row_delete_ch(row, 0); } else if (row->b[0] == ' ') { del = 0; while (del < TAB_STOP && del < (int) row->size && row->b[del] == ' ') { del++; } if (del > 0) { memmove(row->b, row->b + del, row->size - del + 1); /* +1 for NUL */ row->size -= del; } } } editor.curx = 0; editor.cury = start_row; editor.dirty++; editor_set_status("Region unindented"); } /* call after kill_region */ void delete_region(void) { int count = count_chars_from_cursor_to_mark(); int killed = 0; int curx = editor.curx; int cury = editor.cury; int markx = editor.mark_curx; int marky = editor.mark_cury; if (!editor.mark_set) { return; } if (!cursor_after_mark()) { swap_int(&curx, &markx); swap_int(&cury, &marky); } jump_to_position(markx, marky); while (killed < count) { move_cursor(ARROW_RIGHT, 0); deletech(KILLRING_NO_OP); killed++; } while (editor.curx != markx && editor.cury != marky) { deletech(KILLRING_NO_OP); } editor.kill = 1; editor_set_status("Region killed."); } void jump_to_position(int col, int row) { if (ENROWS <= 0) { ECURX = 0; ECURY = 0; editor.curx = ECURX; editor.cury = ECURY; display_refresh(); return; } if (row < 0) { row = 0; } else if (row >= ENROWS) { row = ENROWS - 1; } if (col < 0) { col = 0; } else if (col > (int) EROW[row].size) { col = (int) EROW[row].size; } ECURX = col; ECURY = row; editor.curx = ECURX; editor.cury = ECURY; display_refresh(); } void goto_line(void) { int lineno = 0; char *query = editor_prompt("Line: %s", NULL); if (query == NULL) { return; } lineno = atoi(query); if (lineno < 1 || lineno > ENROWS) { editor_set_status("Line number must be between 1 and %d.", ENROWS); free(query); return; } jump_to_position(0, lineno - 1); free(query); } int cursor_at_eol(void) { assert(ECURX >= 0); assert(ECURY >= 0); assert(ECURY <= ENROWS); assert(ECURX <= (int)EROW[ECURY].size); return ECURX == (int)EROW[ECURY].size; } int iswordchar(const unsigned char c) { return isalnum(c) || c == '_' || strchr("/!@#$%^&*+-=~", c) != NULL; } void find_next_word(void) { while (cursor_at_eol()) { move_cursor(ARROW_RIGHT, 1); } if (iswordchar(EROW[ECURY].b[ECURX])) { while (!isspace(EROW[ECURY].b[ECURX]) && ! cursor_at_eol()) { move_cursor(ARROW_RIGHT, 1); } return; } if (isspace(EROW[ECURY].b[ECURX])) { while (isspace(EROW[ECURY].b[ECURX])) { move_cursor(ARROW_RIGHT, 1); } find_next_word(); } } void delete_next_word(void) { while (cursor_at_eol()) { move_cursor(ARROW_RIGHT, 1); deletech(KILLRING_APPEND); } if (iswordchar(EROW[ECURY].b[ECURX])) { while (!isspace(EROW[ECURY].b[ECURX]) && ! cursor_at_eol()) { move_cursor(ARROW_RIGHT, 1); deletech(KILLRING_APPEND); } return; } if (isspace(EROW[ECURY].b[ECURX])) { while (isspace(EROW[ECURY].b[ECURX])) { move_cursor(ARROW_RIGHT, 1); deletech(KILLRING_APPEND); } delete_next_word(); } } void find_prev_word(void) { if (editor.cury == 0 && editor.curx == 0) { return; } move_cursor(ARROW_LEFT, 1); while (cursor_at_eol() || isspace( editor.row[editor.cury].b[editor.curx])) { if (editor.cury == 0 && editor.curx == 0) { return; } move_cursor(ARROW_LEFT, 1); } while (editor.curx > 0 && !isspace( editor.row[editor.cury].b[editor.curx - 1])) { move_cursor(ARROW_LEFT, 1); } } void delete_prev_word(void) { if (editor.cury == 0 && editor.curx == 0) { return; } deletech(KILLRING_PREPEND); while (editor.cury > 0 || editor.curx > 0) { if (editor.curx == 0) { deletech(KILLRING_PREPEND); continue; } if (!isspace(editor.row[editor.cury].b[editor.curx - 1])) { break; } deletech(KILLRING_PREPEND); } while (editor.curx > 0) { if (isspace(editor.row[editor.cury].b[editor.curx - 1])) { break; } deletech(KILLRING_PREPEND); } } void delete_row(const int at) { abuf *row = NULL; if (at < 0 || at >= editor.nrows) { return; } /* * Update killring with the deleted row's contents followed by a newline * unless this deletion is an internal merge triggered by deletech at * start-of-line. In that case, deletech will account for the single * newline itself and we must NOT also push the entire row here. */ if (!editor.no_kill) { row = &editor.row[at]; /* Start or continue the kill sequence based on editor.killing */ if (row->size > 0) { /* push raw bytes of the line */ if (!editor.kill) { killring_start_with_char( (unsigned char)row->b[0]); for (int i = 1; i < (int)row->size; i++) { killring_append_char( (unsigned char)row->b[i]); } } else { for (int i = 0; i < (int)row->size; i++) { killring_append_char( (unsigned char)row->b[i]); } } killring_append_char('\n'); editor.kill = 1; } else { if (!editor.kill) { killring_start_with_char('\n'); } else { killring_append_char('\n'); } editor.kill = 1; } } ab_free(&editor.row[at]); memmove(&editor.row[at], &editor.row[at + 1], sizeof(abuf) * (editor.nrows - at - 1)); editor.nrows--; editor.dirty++; } void row_append_row(abuf *row, const char *s, const int len) { ab_append(row, s, len); editor.dirty++; } void row_insert_ch(abuf *row, int at, const int16_t c) { /* * row_insert_ch just concerns itself with how to update a row. */ if (at < 0 || at > (int)row->size) { at = (int)row->size; } assert(c > 0); ab_resize(row, row->size + 2); memmove(&row->b[at + 1], &row->b[at], row->size - at + 1); row->b[at] = c & 0xff; row->size++; row->b[row->size] = 0; } void row_delete_ch(abuf *row, const int at) { if (at < 0 || at >= (int)row->size) { return; } memmove(&row->b[at], &row->b[at + 1], row->size - at); row->size--; row->b[row->size] = 0; editor.dirty++; } void insertch(const int16_t c) { /* * insert_ch doesn't need to worry about how to update a * a row; it can just figure out where the cursor is * at and what to do. */ if (editor.cury == editor.nrows) { erow_insert(editor.nrows, "", 0); } /* Inserting ends kill ring chaining. */ editor.kill = 0; row_insert_ch(&editor.row[editor.cury], editor.curx, (int16_t)(c & 0xff)); editor.curx++; editor.dirty++; } void deletech(uint8_t op) { abuf *row = NULL; unsigned char dch = 0; int prev = 0; if (editor.cury >= editor.nrows) { return; } if (editor.cury == 0 && editor.curx == 0) { return; } row = &editor.row[editor.cury]; if (editor.curx > 0) { dch = (unsigned char)row->b[editor.curx - 1]; } else { dch = '\n'; } if (editor.curx > 0) { row_delete_ch(row, editor.curx - 1); editor.curx--; } else { editor.curx = editor.row[editor.cury - 1].size; row_append_row(&editor.row[editor.cury - 1], row->b, row->size); prev = editor.no_kill; editor.no_kill = 1; delete_row(editor.cury); editor.no_kill = prev; editor.cury--; } if (op == KILLRING_FLUSH) { killring_flush(); editor.kill = 0; return; } if (op == KILLRING_NO_OP) { return; } if (!editor.kill) { killring_start_with_char(dch); editor.kill = 1; return; } if (op == KILLING_SET) { killring_start_with_char(dch); editor.kill = 1; } else if (op == KILLRING_APPEND) { killring_append_char(dch); } else if (op == KILLRING_PREPEND) { killring_prepend_char(dch); } } void open_file(const char *filename) { char *line = NULL; size_t linecap = 0; ssize_t linelen = 0; FILE *fp = NULL; buffer *cur = NULL; /* Ensure we have a current buffer to load into */ cur = buffer_current(); if (cur == NULL) { return; } /* Clear editor working set and the current buffer’s contents so we load * fresh data instead of appending to any previous rows. */ reset_editor(); if (EROW != NULL && ENROWS > 0) { for (int i = 0; i < ENROWS; i++) { ab_free(&EROW[i]); } free(EROW); EROW = NULL; ENROWS = 0; } /* Reset cursor/scroll positions for the buffer */ ECURX = ECURY = 0; ERX = 0; EROWOFFS = ECOLOFFS = 0; if (filename == NULL) { return; } EFILENAME = strdup(filename); assert(EFILENAME != NULL); EDIRTY = 0; fp = fopen(EFILENAME, "r"); if (fp == NULL) { if (errno == ENOENT) { editor_set_status("[new file]"); return; } die("fopen"); } while ((linelen = getline(&line, &linecap, fp)) != -1) { if (linelen != -1) { while (linelen > 0 && (line[linelen - 1] == '\r' || line[linelen - 1] == '\n')) { linelen--; } erow_insert(ENROWS, line, (int)linelen); } } free(line); line = NULL; fclose(fp); /* sync changes back */ editor.row = EROW; editor.nrows = ENROWS; editor.filename = EFILENAME; editor.dirty = EDIRTY; } /* * convert our rows to a buffer; caller must free it. */ char *rows_to_buffer(int *buflen) { int len = 0; int j = 0; char *buf = NULL; char *p = NULL; for (j = 0; j < ENROWS; j++) { /* extra byte for newline */ len += EROW[j].size + 1; } if (len == 0) { return NULL; } *buflen = len; buf = malloc(len); assert(buf != NULL); p = buf; for (j = 0; j < ENROWS; j++) { memcpy(p, EROW[j].b, EROW[j].size); p += EROW[j].size; *p++ = '\n'; } return buf; } int save_file(void) { int fd = -1; int len = 0; int status = 1; char *buf = NULL; if (!editor.dirty) { editor_set_status("No changes to save."); return 0; } if (editor.filename == NULL) { editor.filename = editor_prompt("Filename: %s", NULL); if (editor.filename == NULL) { editor_set_status("Save aborted."); return 0; } } buf = rows_to_buffer(&len); fd = open(editor.filename, O_RDWR | O_CREAT, 0644); if (fd == -1) { goto save_exit; } if (-1 == ftruncate(fd, len)) { goto save_exit; } if (len == 0) { status = 0; goto save_exit; } if ((ssize_t)len != write(fd, buf, len)) { goto save_exit; } status = 0; save_exit: if (fd) { close(fd); } if (buf) { free(buf); buf = NULL; } if (status != 0) { buf = strerror(errno); editor_set_status("Error writing %s: %s", editor.filename, buf); } else { editor_set_status("Wrote %d bytes to %s.", len, editor.filename); editor.dirty = 0; } return status; } uint16_t is_arrow_key(const int16_t c) { switch (c) { case ARROW_DOWN: case ARROW_LEFT: case ARROW_RIGHT: case ARROW_UP: case CTRL_KEY('p'): case CTRL_KEY('n'): case CTRL_KEY('f'): case CTRL_KEY('b'): case CTRL_KEY('a'): case CTRL_KEY('e'): case END_KEY: case HOME_KEY: case PG_DN: case PG_UP: return 1; default: return 0; } return 0; } int16_t get_keypress(void) { char seq[3] = {0}; unsigned char uc = 0; int16_t c = 0; if (read(STDIN_FILENO, &uc, 1) == -1) { die("get_keypress:read"); } c = (int16_t)uc; if (c == 0x1b) { if (read(STDIN_FILENO, &seq[0], 1) != 1) { return c; } if (read(STDIN_FILENO, &seq[1], 1) != 1) { return c; } if (seq[0] == '[') { if (seq[1] < 'A') { if (read(STDIN_FILENO, &seq[2], 1) != 1) { return c; } if (seq[2] == '~') { switch (seq[1]) { case '1': return HOME_KEY; case '3': return DEL_KEY; case '4': return END_KEY; case '5': return PG_UP; case '6': return PG_DN; case '7': return HOME_KEY; case '8': return END_KEY; default: break; } } } else { switch (seq[1]) { case 'A': return ARROW_UP; case 'B': return ARROW_DOWN; case 'C': return ARROW_RIGHT; case 'D': return ARROW_LEFT; case 'F': return END_KEY; case 'H': return HOME_KEY; default: break; } } } else if (seq[0] == 'O') { switch (seq[1]) { case 'F': return END_KEY; case 'H': return HOME_KEY; default: break; } } return 0x1b; } return c; } char *editor_prompt(const char *prompt, void (*cb)(char*, int16_t)) { size_t bufsz = 128; char *buf = malloc(bufsz); size_t buflen = 0; int16_t c; if (buf == NULL) { return NULL; } buf[0] = '\0'; while (1) { editor_set_status(prompt, buf); display_refresh(); while ((c = get_keypress()) <= 0); if (c == DEL_KEY || c == CTRL_KEY('h') || c == BACKSPACE) { if (buflen != 0) { buf[--buflen] = '\0'; } } else if (c == ESC_KEY || c == CTRL_KEY('g')) { editor_set_status(""); if (cb) { cb(buf, c); } free(buf); return NULL; } else if (c == '\r') { if (buflen != 0) { editor_set_status(""); if (cb) { cb(buf, c); } return buf; } } else if (c == TAB_KEY) { /* invoke completion callback without inserting a TAB */ if (cb) { cb(buf, c); } /* keep buflen in sync in case callback edited buf */ buflen = strlen(buf); } else if (c >= 0x20 && c < 0x7f) { if (buflen == bufsz - 1) { bufsz *= 2; buf = realloc(buf, bufsz); assert(buf != NULL); } buf[buflen++] = (char) (c & 0xff); buf[buflen] = '\0'; } if (cb) { cb(buf, c); /* keep buflen in sync with any changes the callback made */ buflen = strlen(buf); } } free(buf); return NULL; } void editor_find_callback(char* query, int16_t c) { static int last_match = -1; /* row index of last match */ static int direction = 1; /* 1 = forward, -1 = back */ static char last_query[128] = {0}; /* last successful query */ abuf *row = NULL; const int saved_cx = editor.curx; const int saved_cy = editor.cury; const size_t qlen = strlen(query); const char *match = NULL; const char *search_start = NULL; int i = 0; int skip = 0; int start_row = 0; int start_col = 0; if (c == '\r' || c == ESC_KEY || c == CTRL_KEY('g')) { last_match = -1; direction = 1; last_query[0] = '\0'; return; } if (c == CTRL_KEY('s') || c == ARROW_DOWN || c == ARROW_RIGHT) { direction = 1; } else if (c == CTRL_KEY('r') || c == ARROW_UP || c == ARROW_LEFT) { direction = -1; } if (qlen > 0 && (qlen != strlen(last_query) || strcmp(query, last_query) != 0)) { last_match = -1; strcpy(last_query, query); } start_row = editor.cury; start_col = editor.curx; if (last_match == -1) { if (direction == 1) { start_col += 1; } last_match = editor.cury; } int current = last_match - direction; int wrapped = 0; for (i = 0; i < editor.nrows; i++) { current += direction; if (current >= editor.nrows) { current = 0; if (wrapped++) { break; } } if (current < 0) { current = editor.nrows - 1; if (wrapped++) { break; } } row = &editor.row[current]; search_start = row->b; if (current == start_row && direction == 1 && wrapped == 0) { skip = start_col; if (skip > (int)row->size) { skip = (int)row->size; } search_start += skip; } match = strnstr(search_start, query, row->size - (search_start - row->b)); if (match) { last_match = current; editor.cury = current; editor.curx = erow_cursor_to_render(row, match - row->b); if (current == start_row && direction == 1 && last_match == -1) { editor.curx += start_col; /* adjust if we skipped prefix */ } scroll(); display_refresh(); return; } } /* No match found */ if (qlen > 0) { editor_set_status("Failing search: %s", query); } editor.curx = saved_cx; editor.cury = saved_cy; display_refresh(); } void editor_find(void) { /* TODO(kyle): consider making this an abuf */ char *query; int scx = ECURX; int scy = ECURY; int sco = ECOLOFFS; int sro = EROWOFFS; query = editor_prompt("Search (ESC to cancel): %s", editor_find_callback); if (query) { free(query); query = NULL; } else { ECURX = scx; ECURY = scy; ECOLOFFS = sco; EROWOFFS = sro; } display_refresh(); } void editor_openfile(void) { char *filename = NULL; buffer *cur = NULL; int nb = NULL; filename = editor_prompt("Load file: %s", file_open_prompt_cb); if (filename == NULL) { return; } cur = buffer_current(); if (editor.bufcount == 1 && buffer_is_unnamed_and_empty(cur)) { open_file(filename); buffer_save_current(); } else { nb = buffer_add_empty(); buffer_switch(nb); open_file(filename); buffer_save_current(); } free(filename); } int first_nonwhitespace(abuf *row) { int pos; wchar_t wc; mbstate_t state; size_t len; if (row == NULL) { return 0; } memset(&state, 0, sizeof(state)); pos = editor.curx; if (pos > (int)row->size) { pos = row->size; } while (pos < (int)row->size) { if ((unsigned char)row->b[pos] < 0x80) { if (!isspace((unsigned char)row->b[pos])) { return pos; } pos++; continue; } len = mbrtowc(&wc, &row->b[pos], row->size - pos, &state); if (len == (size_t)-1 || len == (size_t)-2) { break; } if (len == 0) { break; } if (!iswspace(wc)) { break; } pos += len; } return pos; } void move_cursor_once(const int16_t c, int interactive) { abuf *row = NULL; int reps = 0; row = editor.cury >= editor.nrows ? NULL : &editor.row[editor.cury]; switch (c) { case ARROW_UP: case CTRL_KEY('p'): if (editor.cury > 0) { editor.cury--; row = (editor.cury >= editor.nrows) ? NULL : &editor.row[editor.cury]; if (interactive) { editor.curx = first_nonwhitespace(row); } } break; case ARROW_DOWN: case CTRL_KEY('n'): if (editor.cury < editor.nrows - 1) { editor.cury++; row = editor.cury >= editor.nrows ? NULL : &editor.row[editor.cury]; if (interactive) { editor.curx = first_nonwhitespace(row); } } break; case ARROW_RIGHT: case CTRL_KEY('f'): if (!row) { break; } if (editor.curx < (int)row->size) { editor.curx++; /* skip over UTF-8 continuation bytes */ while (editor.curx < (int)row->size && ((unsigned char)row->b[editor.curx] & 0xC0) == 0x80) { editor.curx++; } } else if (editor.curx == (int)row->size && editor.cury < editor.nrows - 1) { editor.cury++; editor.curx = 0; } break; case ARROW_LEFT: case CTRL_KEY('b'): if (editor.curx > 0) { editor.curx--; while (editor.curx > 0 && ((unsigned char)row->b[editor.curx] & 0xC0) == 0x80) { editor.curx--; } } else if (editor.cury > 0) { editor.cury--; editor.curx = (int)editor.row[editor.cury].size; row = &editor.row[editor.cury]; while (editor.curx > 0 && ((unsigned char)row->b[editor.curx] & 0xC0) == 0x80) { editor.curx--; } } break; case PG_UP: case PG_DN: if (c == PG_UP) { editor.cury = editor.rowoffs; } else if (c == PG_DN) { editor.cury = editor.rowoffs + editor.rows - 1; if (editor.cury > editor.nrows) { editor.cury = editor.nrows; } } reps = editor.rows; while (--reps) { move_cursor(c == PG_UP ? ARROW_UP : ARROW_DOWN, 1); } break; case HOME_KEY: case CTRL_KEY('a'): editor.curx = 0; break; case END_KEY: case CTRL_KEY('e'): if (editor.cury >= editor.nrows) { break; } editor.curx = (int)editor.row[editor.cury].size; break; default: break; } } void move_cursor(const int16_t c, const int interactive) { int n = uarg_get(); while (n-- > 0) { move_cursor_once(c, interactive); } } void newline(void) { abuf *row = NULL; if (editor.cury >= editor.nrows) { erow_insert(editor.cury, "", 0); editor.cury++; editor.curx = 0; } else if (editor.curx == 0) { erow_insert(editor.cury, "", 0); editor.cury++; editor.curx = 0; } else { row = &editor.row[editor.cury]; erow_insert(editor.cury + 1, &row->b[editor.curx], row->size - editor.curx); row = &editor.row[editor.cury]; row->size = editor.curx; row->b[row->size] = '\0'; editor.cury++; editor.curx = 0; } /* BREAK THE KILL CHAIN \m/ */ editor.kill = 0; } void uarg_start(void) { if (editor.uarg == 0) { editor.ucount = 0; } else { if (editor.ucount == 0) { editor.ucount = 1; } editor.ucount *= 4; } editor.uarg = 1; editor_set_status("C-u %d", editor.ucount); } void uarg_digit(int d) { if (editor.uarg == 0) { editor.uarg = 1; editor.ucount = 0; } editor.ucount = editor.ucount * 10 + d; editor_set_status("C-u %d", editor.ucount); } void uarg_clear(void) { editor.uarg = 0; editor.ucount = 0; } int uarg_get(void) { int n = editor.ucount > 0 ? editor.ucount : 1; uarg_clear(); return n; } void process_kcommand(const int16_t c) { char *buf = NULL; int len = 0; int jumpx = 0; int jumpy = 0; int reps = 0; switch (c) { case BACKSPACE: while (editor.curx > 0) { process_normal(BACKSPACE); } break; case '=': if (editor.mark_set) { indent_region(); } else { editor_set_status("Mark not set."); } break; case '-': if (editor.mark_set) { unindent_region(); } else { editor_set_status("Mark not set."); } break; case CTRL_KEY('\\'): /* sometimes it's nice to dump core */ disable_termraw(); abort(); case '@': if (!dump_pidfile()) { break; } /* FALLTHRU */ case '!': /* useful for debugging */ editor_set_status("PID: %ld", (long)getpid()); break; case ' ': toggle_markset(); break; case CTRL_KEY(' '): jumpx = editor.mark_curx; jumpy = editor.mark_cury; editor.mark_curx = editor.curx; editor.mark_cury = editor.cury; jump_to_position(jumpx, jumpy); editor_set_status("Jumped to mark"); break; case 'c': /* Close current buffer (was kill ring clear; that moved to C-k f) */ buffer_close_current(); break; case 'd': if (editor.curx == 0 && cursor_at_eol()) { delete_row(editor.cury); return; } reps = uarg_get(); while (reps--) { while ((EROW[ECURY].size - editor.curx) > 0) { process_normal(DEL_KEY); } if (reps) { newline(); } } break; case DEL_KEY: case CTRL_KEY('d'): reps = uarg_get(); while (reps--) { delete_row(editor.cury); } break; case 'e': case CTRL_KEY('e'): if (editor.dirty && editor.dirtyex) { editor_set_status( "File not saved - C-k e again to open a new file anyways."); editor.dirtyex = 0; return; } editor_openfile(); break; case 'f': { /* Rebound: clear kill ring on C-k f */ len = editor.killring ? (int)editor.killring->size : 0; killring_flush(); editor_set_status("Kill ring cleared (%d characters)", len); break; } case 'n': buffer_next(); break; case 'p': buffer_prev(); break; case 'b': buffer_switch_by_name(); break; case 'g': goto_line(); break; case 'j': if (!editor.mark_set) { editor_set_status("Mark not set."); break; } jumpx = editor.mark_curx; jumpy = editor.mark_cury; editor.mark_curx = editor.curx; editor.mark_cury = editor.cury; jump_to_position(jumpx, jumpy); editor_set_status("Jumped to mark; mark is now the previous location."); break; case 'l': buf = get_cloc_code_lines(editor.filename); editor_set_status("Lines of code: %s", buf); free(buf); break; case 'm': if (system("make") != 0) { editor_set_status( "process failed: %s", strerror(errno)); } else { editor_set_status("make: ok"); } break; case 'q': if (editor.dirty && editor.dirtyex) { editor_set_status( "File not saved - C-k q again to quit."); editor.dirtyex = 0; return; } exit(0); case CTRL_KEY('q'): exit(0); case CTRL_KEY('r'): if (editor.dirty && editor.dirtyex) { editor_set_status("File not saved - C-k C-r again to reload."); editor.dirtyex = 0; return; } jumpx = editor.curx; jumpy = editor.cury; buf = strdup(editor.filename); reset_editor(); open_file(buf); display_refresh(); free(buf); jump_to_position(jumpx, jumpy); editor_set_status("file reloaded"); break; case CTRL_KEY('s'): case 's': save_file(); break; case CTRL_KEY('x'): case 'x': exit(save_file()); case 'u': reps = uarg_get(); while (reps--) {} editor_set_status("Undo not implemented."); break; case 'U': reps = uarg_get(); while (reps--) {} editor_set_status("Redo not implemented."); break; case 'y': reps = uarg_get(); while (reps--) { killring_yank(); } break; case ESC_KEY: case CTRL_KEY('g'): break; default: if (isprint(c)) { editor_set_status("unknown kcommand '%c'", c); break; } editor_set_status("unknown kcommand: %04x", c); return; } editor.dirtyex = 1; } void process_normal(int16_t c) { int cols = 0; int rows = 0; int reps = 0; /* C-u handling – must be the very first thing */ if (c == CTRL_KEY('u')) { uarg_start(); return; } /* digits after a C-u are part of the argument */ if (editor.uarg && c >= '0' && c <= '9') { uarg_digit(c - '0'); return; } if (is_arrow_key(c)) { /* moving the cursor breaks a delete sequence */ editor.kill = 0; move_cursor(c, 1); return; } switch (c) { case '\r': newline(); break; case CTRL_KEY('k'): editor.mode = MODE_KCOMMAND; return; case BACKSPACE: case CTRL_KEY('h'): case CTRL_KEY('d'): case DEL_KEY: if (c == DEL_KEY || c == CTRL_KEY('d')) { reps = uarg_get(); while (reps-- > 0) { move_cursor(ARROW_RIGHT, 1); deletech(KILLRING_APPEND); } } else { reps = uarg_get(); while (reps-- > 0) { deletech(KILLRING_PREPEND); } } break; case CTRL_KEY('a'): /* beginning of line */ case HOME_KEY: move_cursor(CTRL_KEY('a'), 1); break; case CTRL_KEY('e'): /* end of line */ case END_KEY: move_cursor(CTRL_KEY('e'), 1); break; case CTRL_KEY('g'): break; case CTRL_KEY('l'): if (get_winsz(&rows, &cols) == 0) { editor.rows = rows; editor.cols = cols; } else { editor_set_status("Couldn't update window size."); } display_refresh(); break; case CTRL_KEY('s'): editor_find(); break; case CTRL_KEY('w'): kill_region(); delete_region(); toggle_markset(); break; case CTRL_KEY('y'): reps = uarg_get(); while (reps-- > 0) { killring_yank(); } break; case ESC_KEY: editor.mode = MODE_ESCAPE; break; default: if (c == TAB_KEY) { reps = uarg_get(); while (reps-- > 0) { insertch(c); } } else if (c >= 0x20 && c != 0x7f) { reps = uarg_get(); while (reps-- > 0) { insertch(c); } } break; } editor.dirtyex = 1; } void process_escape(const int16_t c) { int reps = 0; editor_set_status("hi"); switch (c) { case '>': editor.cury = editor.nrows; editor.curx = 0; break; case '<': editor.cury = 0; editor.curx = 0; break; case 'b': reps = uarg_get(); while (reps--) { find_prev_word(); } break; case 'd': reps = uarg_get(); while (reps--) { delete_next_word(); } break; case 'f': reps = uarg_get(); while (reps--) { find_next_word(); } break; case 'm': toggle_markset(); break; case 'w': if (!editor.mark_set) { editor_set_status("mark isn't set"); break; } kill_region(); toggle_markset(); break; case BACKSPACE: reps = uarg_get(); while (reps--) { delete_prev_word(); } break; case ESC_KEY: case CTRL_KEY('g'): break; /* escape out of escape-mode */ default: editor_set_status("unknown ESC key: %04x", c); } uarg_clear(); } int process_keypress(void) { const int16_t c = get_keypress(); /* we didn't actually read a key */ if (c <= 0) { return 0; } switch (editor.mode) { case MODE_KCOMMAND: process_kcommand(c); editor.mode = MODE_NORMAL; break; case MODE_NORMAL: process_normal(c); break; case MODE_ESCAPE: process_escape(c); editor.mode = MODE_NORMAL; break; default: editor_set_status("we're in the %d-D space now cap'n", editor.mode); editor.mode = MODE_NORMAL; } return 1; } char *get_cloc_code_lines(const char *filename) { char command[512] = {0}; char outbuf[256] = {0}; char *result = NULL; FILE *pipe = NULL; size_t len = 0; if (editor.filename == NULL) { snprintf(command, sizeof(command), "buffer has no associated file."); result = malloc((kstrnlen(command, sizeof(command))) + 1); assert(result != NULL); strcpy(result, command); return result; } if (editor.dirty) { snprintf(command, sizeof(command), "buffer must be saved first."); result = malloc((kstrnlen(command, sizeof(command))) + 1); assert(result != NULL); strcpy(result, command); return result; } snprintf(command, sizeof(command), "cloc --quiet %s | tail -2 | head -1 | awk '{print $5}'", filename); pipe = popen(command, "r"); if (!pipe) { snprintf(command, sizeof(command), "Error getting LOC: %s", strerror(errno)); result = (char*)malloc(sizeof(outbuf) + 1); return NULL; } if (fgets(outbuf, sizeof(outbuf), pipe) != NULL) { len = strlen(outbuf); if (len > 0 && outbuf[len - 1] == '\n') { outbuf[len - 1] = '\0'; } result = malloc(strlen(outbuf) + 1); assert(result != NULL); if (result) { strcpy(result, outbuf); pclose(pipe); return result; } } pclose(pipe); char *zero = malloc(2); if (zero) { strcpy(zero, "0"); return zero; } return NULL; } int dump_pidfile(void) { FILE* pid_file = NULL; if ((pid_file = fopen("ke.pid", "w")) == NULL) { editor_set_status("Failed to dump PID file: %s", strerror(errno)); return 0; } fprintf(pid_file, "%ld", (long)getpid()); fclose(pid_file); return 1; } void draw_rows(abuf *ab) { assert(editor.cols >= 0); abuf *row = NULL; char buf[editor.cols]; char c = 0; size_t j = 0; int len = 0; int filerow = 0; int padding = 0; int printed = 0; int rx = 0; int y = 0; for (y = 0; y < editor.rows; y++) { filerow = y + editor.rowoffs; if (filerow >= editor.nrows) { if ((editor.nrows == 0) && (y == editor.rows / 3)) { len = snprintf(buf, sizeof(buf), "%s", KE_VERSION); padding = (editor.rows - len) / 2; if (padding) { ab_append(ab, "|", 1); padding--; } while (padding--) ab_append(ab, " ", 1); ab_append(ab, buf, len); } else { ab_append(ab, "|", 1); } } else { row = &EROW[filerow]; j = 0; rx = printed = 0; while (j < row->size && printed < editor.cols) { c = row->b[j]; if (rx < editor.coloffs) { if (c == '\t') rx += (TAB_STOP - (rx % TAB_STOP)); else if (c < 0x20) rx += 3; else rx++; j++; continue; } if (c == '\t') { int sp = TAB_STOP - (rx % TAB_STOP); for (int k = 0; k < sp && printed < editor.cols; k++) { ab_appendch(ab, ' '); printed++; rx++; } } else if (c < 0x20) { char seq[4]; snprintf(seq, sizeof(seq), "\\%02x", c); ab_append(ab, seq, 3); printed += 3; rx += 3; } else { ab_appendch(ab, c); printed++; rx++; } j++; } len = printed; } ab_append(ab, ESCSEQ "K", 3); ab_append(ab, "\r\n", 2); } } char status_mode_char(void) { switch (editor.mode) { case MODE_NORMAL: return 'N'; case MODE_KCOMMAND: return 'K'; case MODE_ESCAPE: return 'E'; default: return '?'; } } void draw_status_bar(abuf *ab) { char status[editor.cols]; char rstatus[editor.cols]; char mstatus[editor.cols]; int len = 0; int rlen = 0; len = snprintf(status, sizeof(status), "%c%cke: %.20s - %d lines", status_mode_char(), editor.dirty ? '!' : '-', editor.filename ? editor.filename : "[no file]", editor.nrows); if (editor.mark_set) { snprintf(mstatus, sizeof(mstatus), " | M: %d, %d ", editor.mark_curx + 1, editor.mark_cury + 1); } else { snprintf(mstatus, sizeof(mstatus), " | M:clear "); } rlen = snprintf(rstatus, sizeof(rstatus), "L%d/%d C%d %s", editor.cury + 1, editor.nrows, editor.curx + 1, mstatus); ab_append(ab, ESCSEQ "7m", 4); ab_append(ab, status, len); while (len < editor.cols) { if ((editor.cols - len) == rlen) { ab_append(ab, rstatus, rlen); break; } ab_append(ab, " ", 1); len++; } ab_append(ab, ESCSEQ "m", 3); ab_append(ab, "\r\n", 2); } void draw_message_line(abuf *ab) { int len = (int)strlen(editor.msg); ab_append(ab, ESCSEQ "K", 3); if (len > editor.cols) { len = editor.cols; } if (len && time(NULL) - editor.msgtm < MSG_TIMEO) { ab_append(ab, editor.msg, len); } } void scroll(void) { const abuf *row = NULL; editor.rx = 0; if (editor.cury < editor.nrows) { row = &editor.row[editor.cury]; editor.rx = erow_render_to_cursor(row, editor.curx); } if (editor.cury < editor.rowoffs) { editor.rowoffs = editor.cury; } if (editor.cury >= editor.rowoffs + editor.rows) { editor.rowoffs = editor.cury - editor.rows + 1; } if (editor.rx < editor.coloffs) { editor.coloffs = editor.rx; } if (editor.rx >= editor.coloffs + editor.cols) { editor.coloffs = editor.rx - editor.cols + 1; } } void display_refresh(void) { char buf[32] = {0}; abuf ab = ABUF_INIT; scroll(); ab_append(&ab, ESCSEQ "?25l", 6); ab_append(&ab, ESCSEQ "H", 3); display_clear(&ab); draw_rows(&ab); draw_status_bar(&ab); draw_message_line(&ab); snprintf(buf, sizeof(buf), ESCSEQ "%d;%dH", (editor.cury - editor.rowoffs) + 1, (editor.rx - editor.coloffs) + 1); ab_append(&ab, buf, kstrnlen(buf, 32)); /* ab_append(&ab, ESCSEQ "1;2H", 7); */ ab_append(&ab, ESCSEQ "?25h", 6); kwrite(STDOUT_FILENO, ab.b, (int)ab.size); ab_free(&ab); } int kbhit(void) { int bytes_waiting = 0; ioctl(STDIN_FILENO, FIONREAD, &bytes_waiting); if (bytes_waiting < 0) { editor_set_status("kbhit: FIONREAD failed: %s", strerror(errno)); /* if FIONREAD fails, we need to assume we should read. this * will default to a much slower input sequence, but it'll work. */ return 1; } return bytes_waiting > 0; } void loop(void) { int up = 1; /* update on the first runthrough */ while (1) { if (up) { display_refresh(); } /* * ke should only refresh the display if it has received keyboard * input; if it has, drain all the inputs. This is useful for * handling pastes without massive screen flicker. * */ up = process_keypress(); if (up != 0) { while (kbhit()) { process_keypress(); } } } } void enable_debugging(void) { dump_pidfile(); } void deathknell(void) { fflush(stderr); if (editor.killring != NULL) { ab_free(editor.killring); free(editor.killring); editor.killring = NULL; } reset_editor(); disable_termraw(); } static void signal_handler(int sig) { signal(sig, SIG_DFL); fprintf(stderr, "caught signal %d\n", sig); deathknell(); raise(sig); _exit(127 + sig); } static void install_signal_handlers(void) { /* Block all these signals while inside any of them */ const int fatal_signals[] = { SIGABRT, SIGFPE, SIGILL, SIGSEGV, #ifdef SIGBUS SIGBUS, #endif #ifdef SIGQUIT SIGQUIT, #endif #ifdef SIGSYS SIGSYS, #endif -1 /* sentinel */ }; int i = 0; for (i = 0; fatal_signals[i] != -1; i++) { signal(fatal_signals[i], signal_handler); } atexit(deathknell); } int main(int argc, char *argv[]) { const char *arg = NULL; const char *path = NULL; int i = 0; int v = 0; int nb = 0; int opt = 0; int debug = 0; int pending_line = 0; /* line number for the next filename */ int first_loaded = 0; /* has a filed been loaded already? */ install_signal_handlers(); while ((opt = getopt(argc, argv, "df:")) != -1) { if (opt == 'd') { debug = 1; } else { fprintf(stderr, "Usage: ke [-d] [-f logfile] [ +N ] [file ...]\n"); exit(EXIT_FAILURE); } } argc -= optind; argv += optind; setlocale(LC_ALL, ""); if (debug) { enable_debugging(); } setup_terminal(); init_editor(); /* start processing file names. if an arg starts with a '+', * interpret it as the line to jump to. */ for (i = 0; i < argc; i++) { arg = argv[i]; if (arg[0] == '+') { path = arg + 1; v = 0; if (*path != '\0') { v = atoi(path); if (v < 1) v = 0; } pending_line = v; continue; } if (!first_loaded) { open_file(arg); if (pending_line > 0) { jump_to_position(0, pending_line - 1); pending_line = 0; } buffer_save_current(); first_loaded = 1; } else { nb = buffer_add_empty(); buffer_switch(nb); open_file(arg); if (pending_line > 0) { jump_to_position(0, pending_line - 1); pending_line = 0; } buffer_save_current(); } } editor_set_status("C-k q to exit / C-k d to dump core"); display_clear(NULL); loop(); return 0; }