add support for reloading the current file

This commit is contained in:
2025-11-25 21:20:13 -08:00
parent 1a184b1a08
commit 75b19042b9
3 changed files with 646 additions and 353 deletions

View File

@@ -26,3 +26,8 @@ clean:
.PHONY: test.txt .PHONY: test.txt
test.txt: test.txt:
cp test.txt.bak $@ cp test.txt.bak $@
.PHONY: gdb
gdb:
@test -f $(TARGET).pid || (echo "error: $(TARGET).pid not found" >&2; exit 1)
@gdb -p $$(cat $(TARGET).pid | tr -d ' \t\n\r') ./$(TARGET)

9
ke.1
View File

@@ -40,12 +40,19 @@ Edit a new file. Also C-k C-e.
Incremental find. Incremental find.
.It C-k g .It C-k g
Go to a specific line. Go to a specific line.
.It C-k j
Jump to the mark.
.It C-k l .It C-k l
List the number of lines of code in a saved file. List the number of lines of code in a saved file.
.It C-k m .It C-k m
Run make(1). Run make(1).
.It C-k q .It C-k q
exit the editor. Also C-k C-q. Exit the editor. If the file has unsaved changes,
a warning will be printed; a second C-k q will exit.
.It C-k C-q
Immediately exit the editor.
.It C-k C-r
Reload the current buffer from disk.
.It C-k s .It C-k s
save the file, prompting for a filename if needed. Also C-k C-s. save the file, prompting for a filename if needed. Also C-k C-s.
.It C-k x .It C-k x

373
main.c
View File

@@ -1,8 +1,21 @@
/* /*
* kyle's editor * ke - kyle's editor
* *
* first version is a run-through of the kilo editor walkthrough as a * ke started off following along with the kilo walkthrough at
* set of guiderails. I've made a lot of changes. * 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 <kyle@imap.cc>
* Date: Fri Feb 7 20:46:43 2020 -0800
*
* Initial import, starting with kyle's editor.
*/ */
#include <sys/ioctl.h> #include <sys/ioctl.h>
@@ -10,17 +23,18 @@
#include <ctype.h> #include <ctype.h>
#include <errno.h> #include <errno.h>
#include <fcntl.h> #include <fcntl.h>
#include <locale.h>
#include <signal.h>
#include <stdarg.h> #include <stdarg.h>
#include <stdint.h> #include <stdint.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <locale.h>
#include <wchar.h>
#include <wctype.h>
#include <termios.h> #include <termios.h>
#include <time.h> #include <time.h>
#include <unistd.h> #include <unistd.h>
#include <wchar.h>
#include <wctype.h>
#include <sys/stat.h> #include <sys/stat.h>
@@ -57,10 +71,8 @@
#define KILLRING_FLUSH 4 /* clear the killring */ #define KILLRING_FLUSH 4 /* clear the killring */
static FILE* debug_log = NULL;
/*
* Function and struct declarations.
*/
/* append buffer */ /* append buffer */
struct abuf { struct abuf {
@@ -132,6 +144,7 @@ struct editor_t {
int next_power_of_2(int n); int next_power_of_2(int n);
int cap_growth(int cap, int sz); int cap_growth(int cap, int sz);
size_t kstrnlen(const char *buf, const size_t max);
void init_editor(void); void init_editor(void);
void reset_editor(void); void reset_editor(void);
void ab_append(struct abuf* buf, const char* s, int len); void ab_append(struct abuf* buf, const char* s, int len);
@@ -198,7 +211,38 @@ void scroll(void);
void display_refresh(void); void display_refresh(void);
void editor_set_status(const char* fmt, ...); void editor_set_status(const char* fmt, ...);
void loop(void); void loop(void);
void process_normal(int16_t c); void enable_debugging(const char* logfile);
void deathknell(void);
static void signal_handler(int sig);
static void install_signal_handlers(void);
#ifndef strnstr
/*
* Find the first occurrence of find in s, where the search is limited to the
* first slen characters of s.
*/
char*
strnstr(const char* s, const char* find, size_t slen)
{
char c, sc;
size_t len;
if ((c = *find++) != '\0') {
len = strlen(find);
do {
do {
if (slen-- < 1 || (sc = *s++) == '\0')
return (NULL);
} while (sc != c);
if (len > slen)
return (NULL);
} while (strncmp(s, find, len) != 0);
s--;
}
return ((char*)s);
}
#endif
int int
@@ -250,6 +294,17 @@ enum KeyPress {
}; };
size_t
kstrnlen(const char *buf, const size_t max)
{
if (buf == NULL) {
return 0;
}
return strnlen(buf, max);
}
/* /*
* init_editor should set up the global editor struct. * init_editor should set up the global editor struct.
*/ */
@@ -271,7 +326,12 @@ init_editor(void)
editor.nrows = 0; editor.nrows = 0;
editor.rowoffs = editor.coloffs = 0; editor.rowoffs = editor.coloffs = 0;
editor.row = NULL; editor.row = NULL;
editor.killring = NULL;
/* don't clear out the kill ring:
* killing / yanking across files is helpful, and killring
* is initialized to NULL at program start.
*/
/* editor.killring = NULL; */
editor.kill = 0; editor.kill = 0;
editor.no_kill = 0; editor.no_kill = 0;
@@ -300,11 +360,15 @@ reset_editor(void)
editor.filename = NULL; editor.filename = NULL;
} }
/* commenting this out, it's useful to be able
* yank across files. */
/*
if (editor.killring != NULL) { if (editor.killring != NULL) {
erow_free(editor.killring); erow_free(editor.killring);
free(editor.killring); free(editor.killring);
editor.killring = NULL; editor.killring = NULL;
} }
*/
init_editor(); init_editor();
} }
@@ -861,7 +925,7 @@ delete_region(void)
jump_to_position(markx, marky); jump_to_position(markx, marky);
while (killed < count) { while (killed < count) {
move_cursor(ARROW_RIGHT); // move_cursor(ARROW_RIGHT);
deletech(KILLRING_NO_OP); deletech(KILLRING_NO_OP);
killed++; killed++;
} }
@@ -925,14 +989,29 @@ get_winsz(int *rows, int *cols)
void void
jump_to_position(const int x, const int y) jump_to_position(int col, int row)
{ {
editor.curx = x; /* clamp position */
editor.cury = y; if (row < 0) {
row = 0;
editor.rowoffs = editor.cury - (editor.rows / 2); } else if (row > editor.nrows) {
row = editor.nrows - 1;
} }
if (col < 0) {
col = 0;
} else if (col > editor.row[row].size) {
col = editor.row[row].size;
}
editor.curx = col;
editor.cury = row;
scroll();
display_refresh();
}
void void
goto_line(void) goto_line(void)
{ {
@@ -1047,6 +1126,7 @@ find_prev_word(void)
} }
} }
void void
delete_prev_word(void) delete_prev_word(void)
{ {
@@ -1077,6 +1157,7 @@ delete_prev_word(void)
} }
} }
void void
delete_row(int at) delete_row(int at)
{ {
@@ -1216,6 +1297,12 @@ deletech(uint8_t op)
row = &editor.row[editor.cury]; row = &editor.row[editor.cury];
if (cursor_at_eol()) {
if (editor.cury >= editor.nrows - 1) {
return;
}
}
/* determine which character is being deleted for killring purposes */ /* determine which character is being deleted for killring purposes */
if (editor.curx > 0) { if (editor.curx > 0) {
dch = (unsigned char)row->line[editor.curx - 1]; dch = (unsigned char)row->line[editor.curx - 1];
@@ -1279,6 +1366,10 @@ open_file(const char *filename)
reset_editor(); reset_editor();
if (filename == NULL) {
return;
}
editor.filename = strdup(filename); editor.filename = strdup(filename);
assert(editor.filename != NULL); assert(editor.filename != NULL);
@@ -1560,6 +1651,7 @@ editor_prompt(char *prompt, void (*cb)(char *, int16_t))
} }
free(buf); free(buf);
return NULL;
} }
@@ -1569,7 +1661,7 @@ editor_find_callback(char *query, int16_t c)
static int lmatch = -1; static int lmatch = -1;
static int dir = -1; static int dir = -1;
int i, current, traversed = 0; int i, current, traversed = 0;
int qlen = strnlen(query, 128); int qlen = kstrnlen(query, 128);
char* match; char* match;
struct erow* row; struct erow* row;
@@ -1589,6 +1681,7 @@ editor_find_callback(char *query, int16_t c)
if (lmatch == -1) { if (lmatch == -1) {
dir = 1; dir = 1;
current = editor.cury;
} }
current = lmatch; current = lmatch;
@@ -1599,9 +1692,9 @@ editor_find_callback(char *query, int16_t c)
} }
current += dir; current += dir;
if (current == -1) { if (current < 0) {
current = editor.nrows - 1; current = editor.nrows - 1;
} else if (current == editor.nrows) { } else if (current >= editor.nrows) {
current = 0; current = 0;
} }
@@ -1635,6 +1728,7 @@ editor_find_callback(char *query, int16_t c)
void void
editor_find(void) editor_find(void)
{ {
/* TODO(kyle): consider making this an abuf */
char* query; char* query;
int scx = editor.curx; int scx = editor.curx;
int scy = editor.cury; int scy = editor.cury;
@@ -1645,6 +1739,7 @@ editor_find(void)
editor_find_callback); editor_find_callback);
if (query) { if (query) {
free(query); free(query);
query = NULL;
} else { } else {
editor.curx = scx; editor.curx = scx;
editor.cury = scy; editor.cury = scy;
@@ -1727,7 +1822,7 @@ move_cursor(int16_t c)
break; break;
case ARROW_DOWN: case ARROW_DOWN:
case CTRL_KEY('n'): case CTRL_KEY('n'):
if (editor.cury < editor.nrows) { if (editor.cury < editor.nrows - 1) {
editor.cury++; editor.cury++;
row = (editor.cury >= editor.nrows) row = (editor.cury >= editor.nrows)
? NULL ? NULL
@@ -1740,12 +1835,12 @@ move_cursor(int16_t c)
if (row && editor.curx < row->size) { if (row && editor.curx < row->size) {
editor.curx++; editor.curx++;
/* skip over UTF-8 continuation bytes */ /* skip over UTF-8 continuation bytes */
while (row && editor.curx < row->size && while (editor.curx < row->size &&
((unsigned char)row->line[editor.curx] & ((unsigned char)row->line[editor.curx] &
0xC0) == 0x80) { 0xC0) == 0x80) {
editor.curx++; editor.curx++;
} }
} else if (row && editor.curx == row->size) { } else if (row && editor.curx == row->size && editor.cury < editor.nrows - 1) {
editor.cury++; editor.cury++;
row = (editor.cury >= editor.nrows) row = (editor.cury >= editor.nrows)
? NULL ? NULL
@@ -1857,7 +1952,7 @@ get_cloc_code_lines(const char* filename)
if (editor.filename == NULL) { if (editor.filename == NULL) {
snprintf(command, sizeof(command), snprintf(command, sizeof(command),
"buffer has no associated file."); "buffer has no associated file.");
result = malloc((strnlen(command, sizeof(command))) + 1); result = malloc((kstrnlen(command, sizeof(command))) + 1);
assert(result != NULL); assert(result != NULL);
strcpy(result, command); strcpy(result, command);
return result; return result;
@@ -1866,7 +1961,7 @@ get_cloc_code_lines(const char* filename)
if (editor.dirty) { if (editor.dirty) {
snprintf(command, sizeof(command), snprintf(command, sizeof(command),
"buffer must be saved first."); "buffer must be saved first.");
result = malloc((strnlen(command, sizeof(command))) + 1); result = malloc((kstrnlen(command, sizeof(command))) + 1);
assert(result != NULL); assert(result != NULL);
strcpy(result, command); strcpy(result, command);
return result; return result;
@@ -1909,11 +2004,30 @@ get_cloc_code_lines(const char* filename)
} }
static 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 void
process_kcommand(int16_t c) process_kcommand(int16_t c)
{ {
char *buf = NULL; char *buf = NULL;
int len = 0; int len = 0;
int jumpx = 0;
int jumpy = 0;
switch (c) { switch (c) {
case BACKSPACE: case BACKSPACE:
@@ -1921,13 +2035,39 @@ process_kcommand(int16_t c)
process_normal(BACKSPACE); process_normal(BACKSPACE);
} }
break; break;
case TAB_KEY:
if (editor.mark_set) {
indent_region();
} else {
editor_set_status("Mark not set.");
}
break;
case CTRL_KEY('\\'): case CTRL_KEY('\\'):
/* sometimes it's nice to dump core */ /* sometimes it's nice to dump core */
disable_termraw(); disable_termraw();
abort(); abort();
case '@':
if (!dump_pidfile()) {
break;
}
/* FALLTHRU */
case '!':
/* useful for debugging */
editor_set_status("PID: %ld", (long)getpid());
break;
case ' ': case ' ':
toggle_markset(); toggle_markset();
break; 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': case 'c':
len = editor.killring->size; len = editor.killring->size;
killring_flush(); killring_flush();
@@ -1965,6 +2105,20 @@ process_kcommand(int16_t c)
case 'g': case 'g':
goto_line(); goto_line();
break; 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': case 'l':
buf = get_cloc_code_lines(editor.filename); buf = get_cloc_code_lines(editor.filename);
@@ -1976,13 +2130,11 @@ process_kcommand(int16_t c)
editor_set_status( editor_set_status(
"process failed: %s", "process failed: %s",
strerror(errno)); strerror(errno));
} } else {
else {
editor_set_status("make: ok"); editor_set_status("make: ok");
} }
break; break;
case 'q': case 'q':
case CTRL_KEY('q'):
if (editor.dirty && editor.dirtyex) { if (editor.dirty && editor.dirtyex) {
editor_set_status( editor_set_status(
"File not saved - C-k q again to quit."); "File not saved - C-k q again to quit.");
@@ -1990,6 +2142,25 @@ process_kcommand(int16_t c)
return; return;
} }
exit(0); 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();
jump_to_position(jumpx, jumpy);
break;
case CTRL_KEY('s'): case CTRL_KEY('s'):
case 's': case 's':
save_file(); save_file();
@@ -2069,11 +2240,7 @@ process_normal(int16_t c)
break; break;
default: default:
if (c == TAB_KEY) { if (c == TAB_KEY) {
if (editor.mark_set) {
indent_region();
} else {
insertch(c); insertch(c);
}
} else if (c >= 0x20 && c != 0x7f) { } else if (c >= 0x20 && c != 0x7f) {
insertch(c); insertch(c);
} }
@@ -2244,19 +2411,20 @@ draw_rows(struct abuf *ab)
{ {
assert(editor.cols >= 0); assert(editor.cols >= 0);
struct erow *row;
char buf[editor.cols]; char buf[editor.cols];
int buflen, filerow, padding; int len, filerow, padding;
int y; int y;
for (y = 0; y < editor.rows; y++) { for (y = 0; y < editor.rows; y++) {
filerow = y + editor.rowoffs; filerow = y + editor.rowoffs;
if (filerow >= editor.nrows) { if (filerow >= editor.nrows) {
if ((editor.nrows == 0) && (y == editor.rows / 3)) { if ((editor.nrows == 0) && (y == editor.rows / 3)) {
buflen = snprintf(buf, len = snprintf(buf,
sizeof(buf), sizeof(buf),
"%s", "%s",
KE_VERSION); KE_VERSION);
padding = (editor.rows - buflen) / 2; padding = (editor.rows - len) / 2;
if (padding) { if (padding) {
ab_append(ab, "|", 1); ab_append(ab, "|", 1);
@@ -2265,23 +2433,24 @@ draw_rows(struct abuf *ab)
while (padding--) while (padding--)
ab_append(ab, " ", 1); ab_append(ab, " ", 1);
ab_append(ab, buf, buflen); ab_append(ab, buf, len);
} else { } else {
ab_append(ab, "|", 1); ab_append(ab, "|", 1);
} }
} else { } else {
erow_update(&editor.row[filerow]); row = &editor.row[filerow];
buflen = editor.row[filerow].rsize - editor.coloffs; erow_update(row);
if (buflen < 0) { len = row->rsize - editor.coloffs;
buflen = 0; if (len < 0) {
len = 0;
} }
if (buflen > editor.cols) { if (len > editor.cols) {
buflen = editor.cols; len = editor.cols;
} }
ab_append(ab, ab_append(ab,
editor.row[filerow].render + editor.coloffs, editor.row[filerow].render + editor.coloffs,
buflen); len);
} }
ab_append(ab, ESCSEQ "K", 3); ab_append(ab, ESCSEQ "K", 3);
ab_append(ab, "\r\n", 2); ab_append(ab, "\r\n", 2);
@@ -2419,7 +2588,7 @@ display_refresh(void)
ESCSEQ "%d;%dH", ESCSEQ "%d;%dH",
(editor.cury - editor.rowoffs) + 1, (editor.cury - editor.rowoffs) + 1,
(editor.rx - editor.coloffs) + 1); (editor.rx - editor.coloffs) + 1);
ab_append(&ab, buf, strnlen(buf, 32)); ab_append(&ab, buf, kstrnlen(buf, 32));
/* ab_append(&ab, ESCSEQ "1;2H", 7); */ /* ab_append(&ab, ESCSEQ "1;2H", 7); */
ab_append(&ab, ESCSEQ "?25h", 6); ab_append(&ab, ESCSEQ "?25h", 6);
@@ -2463,16 +2632,128 @@ loop(void)
} }
void
enable_debugging(const char* logfile)
{
time_t now;
if (debug_log != NULL) {
fclose(debug_log);
}
if ((debug_log = fopen(logfile, "w")) == NULL) {
fprintf(stderr, "Failed to open error log!\n");
fprintf(stderr, "\t%s\n", strerror(errno));
}
dump_pidfile();
stderr = debug_log;
now = time(&now);
printf("time: %s\n", ctime(&now));
fprintf(stderr, "Debug log started %s\n", ctime(&now));
fflush(stderr);
}
void
deathknell(void)
{
if (debug_log != NULL) {
fflush(stderr);
fclose(debug_log);
debug_log = NULL;
}
if (editor.killring != NULL) {
erow_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 int
main(int argc, char* argv[]) main(int argc, char* argv[])
{ {
char* logfile = "debug-ke.log";
int opt;
int debug = 0;
install_signal_handlers();
while ((opt = getopt(argc, argv, "df:")) != -1) {
switch (opt) {
case 'd':
debug = 1;
break;
case 'f':
logfile = optarg;
break;
default:
fprintf(stderr, "Usage: ke [-d] [-f logfile] [path]\n");
exit(EXIT_FAILURE);
}
}
argc -= optind;
argv += optind;
setlocale(LC_ALL, ""); setlocale(LC_ALL, "");
if (debug) {
enable_debugging(logfile);
}
setup_terminal(); setup_terminal();
init_editor(); init_editor();
if (argc > 1) { if (argc > 0) {
open_file(argv[1]); open_file(argv[0]);
} }
editor_set_status("C-k q to exit / C-k d to dump core"); editor_set_status("C-k q to exit / C-k d to dump core");