4 Commits

Author SHA1 Message Date
c2a6a593ef full split complete 2025-11-29 14:56:51 -08:00
74637c8e83 cleaning up code 2025-11-29 13:44:08 -08:00
3b401e770e working on undo 2025-11-29 13:05:12 -08:00
c47287e96e stash undo3 2025-11-29 12:36:11 -08:00
20 changed files with 3035 additions and 2784 deletions

View File

@@ -4,7 +4,7 @@ project(ke C) # Specify C language explicitly
set(CMAKE_C_STANDARD 99) set(CMAKE_C_STANDARD 99)
set(KE_VERSION "2.1.1") set(KE_VERSION "2.1.1")
set(CMAKE_C_FLAGS "-Wall -Wextra -pedantic -Wshadow -Werror -std=c99 -g -Werror=stringop-truncation") set(CMAKE_C_FLAGS "-Wall -Wextra -pedantic -Wshadow -Werror -std=c99 -g")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -D_DEFAULT_SOURCE -D_XOPEN_SOURCE") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -D_DEFAULT_SOURCE -D_XOPEN_SOURCE")
# Optionally enable AddressSanitizer (ASan) # Optionally enable AddressSanitizer (ASan)
@@ -19,16 +19,34 @@ endif()
include(GNUInstallDirs) include(GNUInstallDirs)
# Add executable set(SOURCES
add_executable(ke
abuf.c abuf.c
core.c
term.c term.c
buffer.c buffer.c
editor.c editor.c
core.c editing.c
core.h killring.c
process.c
undo.c
main.c main.c
) )
set(HEADERS
abuf.h
core.h
term.h
buffer.h
editor.h
editing.h
killring.h
process.h
undo.h
)
# Add executable
add_executable(ke ${SOURCES} ${HEADERS})
target_compile_definitions(ke PRIVATE KE_VERSION="ke version ${KE_VERSION}") target_compile_definitions(ke PRIVATE KE_VERSION="ke version ${KE_VERSION}")
install(TARGETS ke RUNTIME DESTINATION bin) install(TARGETS ke RUNTIME DESTINATION bin)
install(FILES ke.1 TYPE MAN) install(FILES ke.1 TYPE MAN)

View File

@@ -11,8 +11,10 @@ LDFLAGS := -fsanitize=address
all: $(TARGET) test.txt all: $(TARGET) test.txt
SRCS := main.c abuf.c term.c buffer.c editor.c core.c SRCS := main.c abuf.c core.c term.c buffer.c editor.c editing.c killring.c \
HDRS := abuf.h term.h buffer.h editor.h core.h process.c undo.c
HDRS := abuf.h core.h term.h buffer.h editor.h editing.c killring.h \
process.h undo.h
$(TARGET): $(SRCS) $(TARGET): $(SRCS)
$(CC) $(CFLAGS) -o $(TARGET) $(SRCS) $(LDFLAGS) $(CC) $(CFLAGS) -o $(TARGET) $(SRCS) $(LDFLAGS)

24
abuf.c
View File

@@ -71,6 +71,18 @@ ab_append(abuf *buf, const char *s, size_t len)
} }
void
ab_append_ab(abuf *buf, const abuf *other)
{
assert(buf != NULL);
if (other == NULL) {
return;
}
ab_append(buf, other->b, other->size);
}
void void
ab_prependch(abuf *buf, const char c) ab_prependch(abuf *buf, const char c)
{ {
@@ -97,6 +109,18 @@ ab_prepend(abuf *buf, const char *s, const size_t len)
} }
void
ab_prepend_ab(abuf *buf, const abuf *other)
{
assert(buf != NULL);
if (other == NULL) {
return;
}
ab_prepend(buf, other->b, other->size);
}
void void
ab_free(abuf *buf) ab_free(abuf *buf)
{ {

2
abuf.h
View File

@@ -22,8 +22,10 @@ void ab_init_cap(abuf *buf, size_t cap);
void ab_resize(abuf *buf, size_t cap); void ab_resize(abuf *buf, size_t cap);
void ab_appendch(abuf *buf, char c); void ab_appendch(abuf *buf, char c);
void ab_append(abuf *buf, const char *s, size_t len); void ab_append(abuf *buf, const char *s, size_t len);
void ab_append_ab(abuf *buf, const abuf *other);
void ab_prependch(abuf *buf, const char c); void ab_prependch(abuf *buf, const char c);
void ab_prepend(abuf *buf, const char *s, const size_t len); void ab_prepend(abuf *buf, const char *s, const size_t len);
void ab_prepend_ab(abuf *buf, const abuf *other);
void ab_free(abuf *buf); void ab_free(abuf *buf);

123
buffer.c
View File

@@ -222,12 +222,12 @@ buffer_name(buffer *b)
void void
buffers_init(void) buffers_init(void)
{ {
int idx = 0; int idx = 0;
editor.buffers = NULL; editor.buffers = NULL;
editor.bufcount = 0; editor.bufcount = 0;
editor.curbuf = 0; editor.curbuf = 0;
editor.bufcap = 0; editor.bufcap = 0;
idx = buffer_add_empty(); idx = buffer_add_empty();
buffer_switch(idx); buffer_switch(idx);
@@ -237,28 +237,29 @@ buffers_init(void)
static void static void
buffer_list_resize(void) buffer_list_resize(void)
{ {
buffer **newlist = NULL; buffer **newlist = NULL;
if (editor.bufcount == editor.bufcap) { if (editor.bufcount == editor.bufcap) {
editor.bufcap = (size_t)cap_growth((int)editor.bufcap, (int)editor.bufcount + 1); editor.bufcap = (size_t)cap_growth((int)editor.bufcap,
(int)editor.bufcount + 1);
newlist = realloc(editor.buffers, sizeof(buffer *) * editor.bufcap); newlist = realloc(editor.buffers, sizeof(buffer *) * editor.bufcap);
assert(newlist != NULL); assert(newlist != NULL);
editor.buffers = newlist; editor.buffers = newlist;
} }
} }
int int
buffer_add_empty(void) buffer_add_empty(void)
{ {
buffer *buf = NULL; buffer *buf = NULL;
int idx = 0; int idx = 0;
buffer_list_resize(); buffer_list_resize();
buf = calloc(1, sizeof(buffer)); buf = calloc(1, sizeof(buffer));
assert(buf != NULL); assert(buf != NULL);
buf->curx = 0; buf->curx = 0;
buf->cury = 0; buf->cury = 0;
@@ -273,28 +274,23 @@ buffer_add_empty(void)
buf->mark_curx = 0; buf->mark_curx = 0;
buf->mark_cury = 0; buf->mark_cury = 0;
editor.buffers[editor.bufcount] = buf; undo_tree_init(&buf->undo);
idx = (int)editor.bufcount;
editor.bufcount++;
return idx;
}
editor.buffers[editor.bufcount] = buf;
void idx = (int)editor.bufcount;
buffer_save_current(void) editor.bufcount++;
{ return idx;
/* No-op: editor no longer mirrors per-buffer fields */
(void)editor;
} }
buffer * buffer *
buffer_current(void) buffer_current(void)
{ {
if (editor.bufcount == 0 || editor.curbuf >= editor.bufcount) { if (editor.bufcount == 0 || editor.curbuf >= editor.bufcount) {
return NULL; return NULL;
} }
return editor.buffers[editor.curbuf];
return editor.buffers[editor.curbuf];
} }
@@ -328,58 +324,58 @@ buffer_is_unnamed_and_empty(const buffer *b)
void void
buffer_switch(const int idx) buffer_switch(const int idx)
{ {
buffer *b = NULL; buffer *b = NULL;
if (idx < 0 || (size_t)idx >= editor.bufcount) { if (idx < 0 || (size_t)idx >= editor.bufcount) {
return; return;
} }
if (editor.curbuf == (size_t)idx) { if (editor.curbuf == (size_t)idx) {
return; return;
} }
b = editor.buffers[idx]; b = editor.buffers[idx];
editor.curbuf = (size_t)idx; editor.curbuf = (size_t)idx;
editor.dirtyex = 1; editor.dirtyex = 1;
editor_set_status("Switched to buffer %d: %s", editor.curbuf, buffer_name(b)); editor_set_status("Switched to buffer %d: %s", editor.curbuf, buffer_name(b));
} }
void void
buffer_next(void) buffer_next(void)
{ {
size_t idx = 0; size_t idx = 0;
if (editor.bufcount <= 1) { if (editor.bufcount <= 1) {
return; return;
} }
idx = (editor.curbuf + 1) % editor.bufcount; idx = (editor.curbuf + 1) % editor.bufcount;
buffer_switch((int)idx); buffer_switch((int)idx);
} }
void void
buffer_prev(void) buffer_prev(void)
{ {
size_t idx = 0; size_t idx = 0;
if (editor.bufcount <= 1) { if (editor.bufcount <= 1) {
return; return;
} }
idx = (editor.curbuf == 0) ? (editor.bufcount - 1) : (editor.curbuf - 1); idx = (editor.curbuf == 0) ? (editor.bufcount - 1) : (editor.curbuf - 1);
buffer_switch((int)idx); buffer_switch((int)idx);
} }
void void
buffer_close_current(void) buffer_close_current(void)
{ {
buffer *b = NULL; buffer *b = NULL;
size_t closing = 0; size_t closing = 0;
int target = 0; int target = 0;
int nb = 0; int nb = 0;
/* sanity check */ /* sanity check */
if (editor.bufcount == 0 || editor.curbuf >= editor.bufcount) { if (editor.bufcount == 0 || editor.curbuf >= editor.bufcount) {
@@ -464,3 +460,4 @@ buffer_switch_by_name(void)
free(name); free(name);
} }

View File

@@ -2,23 +2,24 @@
#define KE_BUFFER_H #define KE_BUFFER_H
#include "abuf.h" #include "abuf.h"
#include "undo.h"
typedef struct buffer { typedef struct buffer {
size_t curx, cury; size_t curx, cury;
size_t rx; size_t rx;
size_t nrows; size_t nrows;
size_t rowoffs, coloffs; size_t rowoffs, coloffs;
abuf *row; abuf *row;
char *filename; char *filename;
int dirty; int dirty;
int mark_set; int mark_set;
size_t mark_curx, mark_cury; size_t mark_curx, mark_cury;
undo_tree undo;
} buffer; } buffer;
/* Access current buffer and convenient aliases for file-specific fields */
buffer *buffer_current(void);
buffer *buffer_current(void);
#define CURBUF (buffer_current()) #define CURBUF (buffer_current())
#define EROW (CURBUF->row) #define EROW (CURBUF->row)
#define ENROWS (CURBUF->nrows) #define ENROWS (CURBUF->nrows)

38
core.c
View File

@@ -1,3 +1,4 @@
#include <sys/stat.h>
#include <assert.h> #include <assert.h>
#include <stddef.h> #include <stddef.h>
#include <stdio.h> #include <stdio.h>
@@ -7,7 +8,9 @@
#include "core.h" #include "core.h"
#ifdef INCLUDE_STRNSTR #ifdef INCLUDE_STRNSTR
/* /*
* Find the first occurrence of find in s, where the search is limited to the * Find the first occurrence of find in s, where the search is limited to the
* first slen characters of s. * first slen characters of s.
@@ -35,6 +38,40 @@ strnstr(const char *s, const char *find, size_t slen)
#endif #endif
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;
}
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;
}
void void
swap_size_t(size_t *first, size_t *second) swap_size_t(size_t *first, size_t *second)
{ {
@@ -111,3 +148,4 @@ die(const char* s)
perror(s); perror(s);
exit(1); exit(1);
} }

17
core.h
View File

@@ -4,7 +4,18 @@
#include <stddef.h> #include <stddef.h>
#define INITIAL_CAPACITY 8 #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 TAB_STOP 8
#define INITIAL_CAPACITY 8
typedef enum key_press { typedef enum key_press {
@@ -28,6 +39,8 @@ char *strnstr(const char *s, const char *find, size_t slen);
#define INCLUDE_STRNSTR #define INCLUDE_STRNSTR
#endif #endif
int path_is_dir(const char *path);
size_t str_lcp2(const char *a, const char *b);
void swap_size_t(size_t *first, size_t *second); void swap_size_t(size_t *first, size_t *second);
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);
@@ -36,4 +49,4 @@ void kwrite(int fd, const char *buf, int len);
void die(const char *s); void die(const char *s);
#endif #endif

1574
editing.c Normal file

File diff suppressed because it is too large Load Diff

50
editing.h Normal file
View File

@@ -0,0 +1,50 @@
#ifndef KE_EDITING_H
#define KE_EDITING_H
#include <stdint.h>
#include "abuf.h"
/* miscellaneous */
void file_open_prompt_cb(char *buf, int16_t key);
int erow_render_to_cursor(const abuf *row, int cx);
int erow_cursor_to_render(abuf *row, int rx);
int erow_init(abuf *row, int len);
void erow_insert(int at, char *s, int len);
void jump_to_position(size_t col, size_t 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(size_t at);
void row_append_row(abuf *row, const char *s, int len);
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);
char *editor_prompt(const char*, void (*cb)(char*, int16_t));
void editor_find_callback(char *query, int16_t c);
void editor_find(void);
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);
char *get_cloc_code_lines(const char *filename);
int dump_pidfile(void);
#endif

View File

@@ -16,7 +16,7 @@
* Global editor instance * Global editor instance
*/ */
struct editor editor = { struct editor editor = {
.cols = 0, .cols = 0,
.rows = 0, .rows = 0,
.mode = 0, .mode = 0,
.killring = NULL, .killring = NULL,
@@ -79,7 +79,6 @@ init_editor(void)
void void
reset_editor(void) reset_editor(void)
{ {
/* Reset the current buffer's contents/state. */
buffer *b = buffer_current(); buffer *b = buffer_current();
if (b == NULL) { if (b == NULL) {
return; return;
@@ -91,6 +90,7 @@ reset_editor(void)
} }
free(b->row); free(b->row);
} }
b->row = NULL; b->row = NULL;
b->nrows = 0; b->nrows = 0;
b->rowoffs = 0; b->rowoffs = 0;
@@ -107,3 +107,4 @@ reset_editor(void)
b->mark_curx = 0; b->mark_curx = 0;
b->mark_cury = 0; b->mark_cury = 0;
} }

356
killring.c Normal file
View File

@@ -0,0 +1,356 @@
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include "abuf.h"
#include "core.h"
#include "editing.h"
#include "editor.h"
#include "killring.h"
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)
{
size_t count = 0;
size_t curx = ECURX;
size_t cury = ECURY;
size_t markx = EMARK_CURX;
size_t marky = EMARK_CURY;
if (!cursor_after_mark()) {
swap_size_t(&curx, &markx);
swap_size_t(&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)
{
size_t curx = ECURX;
size_t cury = ECURY;
size_t markx = EMARK_CURX;
size_t marky = EMARK_CURY;
if (!EMARK_SET) {
return;
}
/* kill the current killring first */
killring_flush();
if (!cursor_after_mark()) {
swap_size_t(&curx, &markx);
swap_size_t(&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)
{
size_t start_row = 0;
size_t end_row = 0;
size_t 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 (end_row >= ENROWS) {
end_row = ENROWS - 1;
}
if (start_row >= ENROWS) {
return;
}
for (i = start_row; i <= end_row; i++) {
row_insert_ch(&EROW[i], 0, '\t');
}
ECURX = 0;
EDIRTY++;
}
void
unindent_region(void)
{
size_t start_row = 0;
size_t end_row = 0;
size_t i = 0;
size_t 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 >= ENROWS) {
end_row = ENROWS - 1;
}
for (i = start_row; i <= end_row; i++) {
row = &EROW[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 < row->size &&
row->b[del] == ' ') {
del++;
}
if (del > 0) {
/* +1 for NUL */
memmove(row->b, row->b + del,
row->size - del + 1);
row->size -= del;
}
}
}
ECURX = 0;
ECURY = start_row;
EDIRTY++;
editor_set_status("Region unindented");
}
void
delete_region(void)
{
size_t count = count_chars_from_cursor_to_mark();
size_t killed = 0;
size_t curx = ECURX;
size_t cury = ECURY;
size_t markx = EMARK_CURX;
size_t marky = EMARK_CURY;
if (!EMARK_SET) {
return;
}
if (!cursor_after_mark()) {
swap_size_t(&curx, &markx);
swap_size_t(&cury, &marky);
}
jump_to_position(markx, marky);
while (killed < count) {
move_cursor(ARROW_RIGHT, 0);
deletech(KILLRING_NO_OP);
killed++;
}
while (ECURX != markx && ECURY != marky) {
deletech(KILLRING_NO_OP);
}
editor.kill = 1;
editor_set_status("Region killed.");
}

26
killring.h Normal file
View File

@@ -0,0 +1,26 @@
#ifndef KE_KILLRING_H
#define KE_KILLRING_H
#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 */
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 unindent_region(void);
void delete_region(void);
#endif

2681
main.c

File diff suppressed because it is too large Load Diff

438
process.c Normal file
View File

@@ -0,0 +1,438 @@
#include <errno.h>
#include <ctype.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <_string.h>
#include "editing.h"
#include "buffer.h"
#include "core.h"
#include "editor.h"
#include "killring.h"
#include "term.h"
#include "process.h"
void
process_kcommand(const int16_t c)
{
char *buf = NULL;
size_t len = 0;
int jumpx = 0;
int jumpy = 0;
int reps = 0;
switch (c) {
case BACKSPACE:
while (ECURX > 0) {
process_normal(BACKSPACE);
}
break;
case '=':
if (EMARK_SET) {
indent_region();
} else {
editor_set_status("Mark not set.");
}
break;
case '-':
if (EMARK_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 = EMARK_CURX;
jumpy = EMARK_CURY;
EMARK_CURX = ECURX;
EMARK_CURY = ECURY;
jump_to_position(jumpx, jumpy);
editor_set_status("Jumped to mark");
break;
case 'c':
buffer_close_current();
break;
case 'd':
if (ECURX == 0 && cursor_at_eol()) {
delete_row(ECURY);
return;
}
reps = uarg_get();
while (reps--) {
while ((EROW[ECURY].size - ECURX) > 0) {
process_normal(DEL_KEY);
}
if (reps) {
newline();
}
}
break;
case DEL_KEY:
case CTRL_KEY('d'):
reps = uarg_get();
while (reps--) {
delete_row(ECURY);
}
break;
case 'e':
case CTRL_KEY('e'):
if (EDIRTY && 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':
if (editor.killring == NULL || editor.killring->size == 0) {
editor_set_status("The kill ring is empty.");
break;
}
len = editor.killring ? editor.killring->size : 0;
killring_flush();
editor_set_status("Kill ring cleared (%lu 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 (!EMARK_SET) {
editor_set_status("Mark not set.");
break;
}
jumpx = EMARK_CURX;
jumpy = EMARK_CURY;
EMARK_CURX = ECURX;
EMARK_CURY = ECURY;
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(EFILENAME);
editor_set_status("Lines of code: %s", buf);
free(buf);
break;
case 'm':
/* todo: fix the process failed: success issue */
if (system("make") != 0) {
editor_set_status(
"process failed: %s",
strerror(errno));
} else {
editor_set_status("make: ok");
}
break;
case 'q':
if (EDIRTY && 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 (EDIRTY && editor.dirtyex) {
editor_set_status("File not saved - C-k C-r again to reload.");
editor.dirtyex = 0;
return;
}
jumpx = ECURX;
jumpy = ECURY;
buf = strdup(EFILENAME);
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)
{
size_t cols = 0;
size_t 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 '>':
ECURY = ENROWS;
ECURX = 0;
break;
case '<':
ECURY = 0;
ECURX = 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 (!EMARK_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 from escape-mode the movie */
default:
editor_set_status("unknown ESC key: %04x", c);
}
uarg_clear();
}
int
process_keypress(void)
{
const int16_t c = get_keypress();
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;
}

22
process.h Normal file
View File

@@ -0,0 +1,22 @@
#ifndef KE_PROCESS_H
#define KE_PROCESS_H
#include <stdint.h>
/*
* 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
void process_kcommand(int16_t c);
void process_normal(int16_t c);
void process_escape(int16_t c);
int process_keypress(void);
#endif

228
term.c
View File

@@ -1,13 +1,18 @@
#include <sys/ioctl.h>
#include <assert.h> #include <assert.h>
#include <errno.h> #include <errno.h>
#include <stdlib.h> #include <stdlib.h>
#include <stdio.h>
#include <string.h> #include <string.h>
#include <termios.h> #include <termios.h>
#include <unistd.h> #include <unistd.h>
#include <sys/ioctl.h>
#include "abuf.h" #include "abuf.h"
#include "core.h" #include "core.h"
#include "buffer.h"
#include "editing.h"
#include "editor.h"
#include "process.h"
#include "term.h" #include "term.h"
#define ESCSEQ "\x1b[" #define ESCSEQ "\x1b["
@@ -84,3 +89,224 @@ get_winsz(size_t *rows, size_t *cols)
return 0; return 0;
} }
void
draw_rows(abuf *ab)
{
abuf *row = NULL;
char buf[editor.cols];
char c = 0;
size_t j = 0;
size_t filerow = 0;
size_t y = 0;
size_t len = 0;
size_t padding = 0;
size_t printed = 0;
size_t rx = 0;
for (y = 0; y < editor.rows; y++) {
filerow = y + EROWOFFS;
if (filerow >= ENROWS) {
if ((ENROWS == 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 < ECOLOFFS) {
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];
size_t len = 0;
size_t rlen = 0;
len = snprintf(status,
sizeof(status),
"%c%cke: %.20s - %lu lines",
status_mode_char(),
EDIRTY ? '!' : '-',
EFILENAME ? EFILENAME : "[no file]",
ENROWS);
if (EMARK_SET) {
snprintf(mstatus,
sizeof(mstatus),
" | M: %lu, %lu ",
EMARK_CURX + 1,
EMARK_CURY + 1);
} else {
snprintf(mstatus, sizeof(mstatus), " | M:clear ");
}
rlen = snprintf(rstatus,
sizeof(rstatus),
"L%lu/%lu C%lu %s",
ECURY + 1,
ENROWS,
ECURX + 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)
{
size_t len = 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;
ERX = 0;
if (ECURY < ENROWS) {
row = &EROW[ECURY];
ERX = erow_render_to_cursor(row, ECURX);
}
if (ECURY < EROWOFFS) {
EROWOFFS = ECURY;
}
if (ECURY >= EROWOFFS + editor.rows) {
EROWOFFS = ECURY - editor.rows + 1;
}
if (ERX < ECOLOFFS) {
ECOLOFFS = ERX;
}
if (ERX >= ECOLOFFS + editor.cols) {
ECOLOFFS = ERX - 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 "%lu;%luH",
(ECURY - EROWOFFS) + 1,
(ERX - ECOLOFFS) + 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);
}

16
term.h
View File

@@ -3,11 +3,18 @@
#include "abuf.h" #include "abuf.h"
/* Terminal control/setup API */ /* Terminal control/setup API */
void enable_termraw(void); void enable_termraw(void);
void disable_termraw(void); void disable_termraw(void);
void setup_terminal(void); void setup_terminal(void);
void display_clear(abuf *ab); void display_clear(abuf *ab);
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);
/* /*
* get_winsz uses the TIOCGWINSZ to get the window size. * get_winsz uses the TIOCGWINSZ to get the window size.
@@ -19,4 +26,5 @@ void display_clear(abuf *ab);
*/ */
int get_winsz(size_t *rows, size_t *cols); int get_winsz(size_t *rows, size_t *cols);
#endif /* KE_TERM_H */ #endif /* KE_TERM_H */

146
undo.c
View File

@@ -1,6 +1,12 @@
/*
* undo.c: ke's undo system
*/
#include <assert.h> #include <assert.h>
#include <stdlib.h> #include <stdlib.h>
#include "abuf.h" #include "abuf.h"
#include "buffer.h"
#include "undo.h" #include "undo.h"
@@ -18,7 +24,7 @@ undo_node_new(undo_kind kind)
ab_init(&node->text); ab_init(&node->text);
node->next = NULL; node->next = NULL;
node->parent = NULL; node->next = NULL;
return node; return node;
} }
@@ -27,8 +33,6 @@ undo_node_new(undo_kind kind)
void void
undo_node_free(undo_node *node) undo_node_free(undo_node *node)
{ {
undo_node *next = NULL;
if (node == NULL) { if (node == NULL) {
return; return;
} }
@@ -100,15 +104,137 @@ undo_begin(undo_tree *tree, undo_kind kind)
void void
undo_prepend(undo_tree *tree, abuf *buf) undo_prepend(undo_tree *tree, abuf *buf)
{ {
assert(tree != NULL);
assert(tree->pending != NULL);
ab_prepend_ab(&tree->pending->text, buf);
} }
void undo_append(undo_tree *tree, abuf *buf); void
void undo_prependch(undo_tree *tree, char c); undo_append(undo_tree *tree, abuf *buf)
void undo_appendch(undo_tree *tree, char c); {
void undo_commit(undo_tree *tree); assert(tree != NULL);
void undo_apply(struct editor *editor); assert(tree->pending != NULL);
void editor_undo(undo_tree *tree);
void editor_redo(undo_tree *tree); ab_append_ab(&tree->pending->text, buf);
}
void
undo_prependch(undo_tree *tree, char c)
{
assert(tree != NULL);
assert(tree->pending != NULL);
ab_prependch(&tree->pending->text, c);
}
void
undo_appendch(undo_tree *tree, char c)
{
assert(tree != NULL);
assert(tree->pending != NULL);
ab_appendch(&tree->pending->text, c);
}
void
undo_commit(undo_tree *tree)
{
assert(tree != NULL);
if (tree->pending == NULL) {
return;
}
if (tree->root == NULL) {
assert(tree->current == NULL);
tree->root = tree->pending;
tree->current = tree->pending;
tree->pending = NULL;
return;
}
assert(tree->current != NULL);
if (tree->current->next != NULL) {
undo_node_free_all(tree->current->next);
}
tree->pending->prev = tree->current;
tree->current->next = tree->pending;
tree->current = tree->pending;
tree->pending = NULL;
}
static int
undo_apply_undo(struct buffer *buf)
{
undo_node *node = buf->undo.current;
switch (node->kind) {
case UNDO_INSERT:
break;
default:
return 0;
}
return 0;
}
int
undo_apply(struct buffer *buf, int direction)
{
(void)direction;
if (buf == NULL) {
return 0;
}
undo_commit(&buf->undo);
if (buf->undo.current == NULL) {
return 0;
}
if (direction == UNDO_DIR_UNDO) {
return undo_apply_undo(buf);
} else if (direction == UNDO_DIR_UNDO) {
return 0;
} else {
return 0;
}
return 0;
}
int
editor_undo(struct buffer *buf)
{
if (buf == NULL) {
return 0;
}
undo_commit(&buf->undo);
return undo_apply(buf, UNDO_DIR_UNDO);
}
int
editor_redo(struct buffer *buf)
{
if (buf == NULL) {
return 0;
}
undo_commit(&buf->undo);
return undo_apply(buf, UNDO_DIR_REDO);
}

16
undo.h
View File

@@ -1,13 +1,19 @@
#include <stddef.h> #include <stddef.h>
#include "abuf.h" #include "abuf.h"
#include "editor.h"
#ifndef KE_UNDO_H #ifndef KE_UNDO_H
#define KE_UNDO_H #define KE_UNDO_H
struct buffer;
#define UNDO_DIR_UNDO -1
#define UNDO_DIR_REDO 1
typedef enum undo_kind { typedef enum undo_kind {
UNDO_INSERT = 1 << 0, UNDO_INSERT = 1 << 0,
UNDO_UNKNOWN = 1 << 1, UNDO_UNKNOWN = 1 << 1,
@@ -20,7 +26,7 @@ typedef struct undo_node {
abuf text; abuf text;
struct undo_node *next; struct undo_node *next;
struct undo_node *parent; struct undo_node *prev;
} undo_node; } undo_node;
@@ -42,9 +48,9 @@ void undo_append(undo_tree *tree, abuf *buf);
void undo_prependch(undo_tree *tree, char c); void undo_prependch(undo_tree *tree, char c);
void undo_appendch(undo_tree *tree, char c); void undo_appendch(undo_tree *tree, char c);
void undo_commit(undo_tree *tree); void undo_commit(undo_tree *tree);
void undo_apply(struct editor *editor); int undo_apply(struct buffer *buf, int direction);
void editor_undo(undo_tree *tree); int editor_undo(struct buffer *buf);
void editor_redo(undo_tree *tree); int editor_redo(struct buffer *buf);
#endif #endif