10 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
85e9f33613 Fix newline on darwin.
Some checks failed
Release / Bump Homebrew formula (push) Has been cancelled
2025-11-28 22:01:46 -08:00
a574df2ab7 bump version
Some checks failed
Release / Bump Homebrew formula (push) Has been cancelled
2025-11-28 15:20:11 -08:00
a9bcb0d36b Major codebase cleanup and overhaul.
+ editor removes per-buffer fields.
+ switching from internal use of 'int' to 'size_t'.
+ deleting old code
+ double checking relevancy of comments. A lot has changed in
  5 years, even more so in the past week.
+ fixing a few vestigal memory errors from the overhaul.
+ fixing search behavior
2025-11-28 15:19:47 -08:00
7b20e9ee37 Fix segfault. 2025-11-28 10:59:55 -08:00
5d581c1c2f fixups
Some checks failed
Release / Bump Homebrew formula (push) Has been cancelled
2025-11-28 03:20:33 -08:00
78e4f84f7b fix bug into goto_line
Some checks failed
Release / Bump Homebrew formula (push) Has been cancelled
2025-11-28 03:12:08 -08:00
22 changed files with 3247 additions and 2962 deletions

View File

@@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.15)
project(ke C) # Specify C language explicitly project(ke C) # Specify C language explicitly
set(CMAKE_C_STANDARD 99) set(CMAKE_C_STANDARD 99)
set(KE_VERSION "2.0.0") set(KE_VERSION "2.1.1")
set(CMAKE_C_FLAGS "-Wall -Wextra -pedantic -Wshadow -Werror -std=c99 -g") 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")
@@ -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)

74
abuf.c
View File

@@ -6,6 +6,15 @@
#include "core.h" #include "core.h"
static void
abuf_grow(abuf *buf, size_t delta)
{
if (buf->cap - buf->size < delta) {
ab_resize(buf, buf->cap + delta);
}
}
void void
ab_init(abuf *buf) ab_init(abuf *buf)
{ {
@@ -19,25 +28,31 @@ ab_init(abuf *buf)
void void
ab_init_cap(abuf *buf, const size_t cap) ab_init_cap(abuf *buf, const size_t cap)
{ {
buf->b = calloc(cap, 1); ab_init(buf);
buf->size = 0;
buf->cap = cap; if (cap > 0) {
ab_resize(buf, cap);
}
} }
void void
ab_resize(abuf *buf, size_t cap) ab_resize(abuf *buf, size_t cap)
{ {
cap = cap_growth(buf->cap, cap); char *newbuf = NULL;
buf->b = realloc(buf->b, cap);
assert(buf->b != NULL); cap = cap_growth(buf->cap, cap) + 1;
newbuf = realloc(buf->b, cap);
assert(newbuf != NULL);
buf->cap = cap; buf->cap = cap;
buf->b = newbuf;
} }
void void
ab_appendch(abuf *buf, char c) ab_appendch(abuf *buf, char c)
{ {
abuf_grow(buf, 1);
ab_append(buf, &c, 1); ab_append(buf, &c, 1);
} }
@@ -45,20 +60,10 @@ ab_appendch(abuf *buf, char c)
void void
ab_append(abuf *buf, const char *s, size_t len) ab_append(abuf *buf, const char *s, size_t len)
{ {
char *nc = buf->b; char *nc = NULL;
size_t sz = buf->size + len;
if (sz >= buf->cap) { abuf_grow(buf, len);
while (sz > buf->cap) { nc = buf->b;
if (buf->cap == 0) {
buf->cap = 1;
} else {
buf->cap *= 2;
}
}
nc = realloc(nc, buf->cap);
assert(nc != NULL);
}
memcpy(&nc[buf->size], s, len); memcpy(&nc[buf->size], s, len);
buf->b = nc; buf->b = nc;
@@ -66,9 +71,23 @@ 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)
{ {
abuf_grow(buf, 1);
ab_prepend(buf, &c, 1); ab_prepend(buf, &c, 1);
} }
@@ -76,7 +95,10 @@ ab_prependch(abuf *buf, const char c)
void void
ab_prepend(abuf *buf, const char *s, const size_t len) ab_prepend(abuf *buf, const char *s, const size_t len)
{ {
char *nc = realloc(buf->b, buf->size + len); char *nc = NULL;
abuf_grow(buf, len);
nc = buf->b;
assert(nc != NULL); assert(nc != NULL);
memmove(nc + len, nc, buf->size); memmove(nc + len, nc, buf->size);
@@ -87,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)
{ {

6
abuf.h
View File

@@ -1,8 +1,8 @@
/* /*
* abuf.h - append/prepend buffer utilities * abuf.h - append/prepend buffer utilities
*/ */
#ifndef ABUF_H #ifndef KE_ABUF_H
#define ABUF_H #define KE_ABUF_H
#include <stddef.h> #include <stddef.h>
@@ -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);

145
buffer.c
View File

@@ -35,12 +35,13 @@ static int
buffer_find_exact_by_name(const char *name) buffer_find_exact_by_name(const char *name)
{ {
buffer *b = NULL; buffer *b = NULL;
size_t i = 0;
if (name == NULL) { if (name == NULL) {
return -1; return -1;
} }
for (int i = 0; i < editor.bufcount; i++) { for (i = 0; i < editor.bufcount; i++) {
b = editor.buffers[i]; b = editor.buffers[i];
const char *full = b->filename; const char *full = b->filename;
const char *base = buf_basename(full); const char *base = buf_basename(full);
@@ -66,11 +67,13 @@ static int
buffer_collect_prefix_matches(const char *prefix, int *out_idx, const int max_out) buffer_collect_prefix_matches(const char *prefix, int *out_idx, const int max_out)
{ {
buffer *b = NULL; buffer *b = NULL;
int count = 0; int count = 0;
int matched = 0; int matched = 0;
size_t i = 0;
size_t plen = (prefix ? strlen(prefix) : 0); size_t plen = (prefix ? strlen(prefix) : 0);
for (int i = 0; i < editor.bufcount; i++) { for (i = 0; i < editor.bufcount; i++) {
matched = 0;
b = editor.buffers[i]; b = editor.buffers[i];
const char *cand1 = b->filename; const char *cand1 = b->filename;
@@ -205,49 +208,6 @@ buffer_switch_prompt_cb(char *buf, const int16_t key)
} }
static void
buffer_bind_to_editor(const buffer *b)
{
if (b == NULL) {
return;
}
editor.curx = b->curx;
editor.cury = b->cury;
editor.rx = b->rx;
editor.nrows = b->nrows;
editor.rowoffs = b->rowoffs;
editor.coloffs = b->coloffs;
editor.row = b->row;
editor.filename = b->filename;
editor.dirty = b->dirty;
editor.mark_set = b->mark_set;
editor.mark_curx = b->mark_curx;
editor.mark_cury = b->mark_cury;
}
static void
buffer_extract_from_editor(buffer *b)
{
if (b == NULL) {
return;
}
b->curx = editor.curx;
b->cury = editor.cury;
b->rx = editor.rx;
b->nrows = editor.nrows;
b->rowoffs = editor.rowoffs;
b->coloffs = editor.coloffs;
b->row = editor.row;
b->filename = editor.filename;
b->dirty = editor.dirty;
b->mark_set = editor.mark_set;
b->mark_curx = editor.mark_curx;
b->mark_cury = editor.mark_cury;
}
const char * const char *
buffer_name(buffer *b) buffer_name(buffer *b)
{ {
@@ -266,22 +226,37 @@ buffers_init(void)
editor.buffers = NULL; editor.buffers = NULL;
editor.bufcount = 0; editor.bufcount = 0;
editor.curbuf = -1; editor.curbuf = 0;
editor.bufcap = 0;
idx = buffer_add_empty(); idx = buffer_add_empty();
buffer_switch(idx); buffer_switch(idx);
} }
static void
buffer_list_resize(void)
{
buffer **newlist = NULL;
if (editor.bufcount == editor.bufcap) {
editor.bufcap = (size_t)cap_growth((int)editor.bufcap,
(int)editor.bufcount + 1);
newlist = realloc(editor.buffers, sizeof(buffer *) * editor.bufcap);
assert(newlist != NULL);
editor.buffers = newlist;
}
}
int int
buffer_add_empty(void) buffer_add_empty(void)
{ {
buffer *buf = NULL; buffer *buf = NULL;
buffer **newlist = realloc(editor.buffers, sizeof(buffer *) * (editor.bufcount + 1));
int idx = 0; int idx = 0;
assert(newlist != NULL); buffer_list_resize();
editor.buffers = newlist;
buf = calloc(1, sizeof(buffer)); buf = calloc(1, sizeof(buffer));
assert(buf != NULL); assert(buf != NULL);
@@ -299,33 +274,22 @@ buffer_add_empty(void)
buf->mark_curx = 0; buf->mark_curx = 0;
buf->mark_cury = 0; buf->mark_cury = 0;
undo_tree_init(&buf->undo);
editor.buffers[editor.bufcount] = buf; editor.buffers[editor.bufcount] = buf;
idx = editor.bufcount; idx = (int)editor.bufcount;
editor.bufcount++; editor.bufcount++;
return idx; return idx;
} }
void
buffer_save_current(void)
{
buffer *b = NULL;
if (editor.curbuf < 0 || editor.curbuf >= editor.bufcount) {
return;
}
b = editor.buffers[editor.curbuf];
buffer_extract_from_editor(b);
}
buffer * buffer *
buffer_current(void) buffer_current(void)
{ {
if (editor.curbuf < 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];
} }
@@ -362,21 +326,16 @@ buffer_switch(const int idx)
{ {
buffer *b = NULL; buffer *b = NULL;
if (idx < 0 || idx >= editor.bufcount) { if (idx < 0 || (size_t)idx >= editor.bufcount) {
return; return;
} }
if (editor.curbuf == idx) { if (editor.curbuf == (size_t)idx) {
return; return;
} }
if (editor.curbuf >= 0) {
buffer_save_current();
}
b = editor.buffers[idx]; b = editor.buffers[idx];
buffer_bind_to_editor(b); editor.curbuf = (size_t)idx;
editor.curbuf = 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));
} }
@@ -385,28 +344,28 @@ buffer_switch(const int idx)
void void
buffer_next(void) buffer_next(void)
{ {
int 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(idx); buffer_switch((int)idx);
} }
void void
buffer_prev(void) buffer_prev(void)
{ {
int idx = 0; size_t idx = 0;
if (editor.bufcount <= 1) { if (editor.bufcount <= 1) {
return; return;
} }
idx = (editor.curbuf - 1 + editor.bufcount) % editor.bufcount; idx = (editor.curbuf == 0) ? (editor.bufcount - 1) : (editor.curbuf - 1);
buffer_switch(idx); buffer_switch((int)idx);
} }
@@ -414,21 +373,20 @@ void
buffer_close_current(void) buffer_close_current(void)
{ {
buffer *b = NULL; buffer *b = NULL;
int closing = 0; size_t closing = 0;
int target = 0; int target = 0;
int nb = 0; int nb = 0;
/* sanity check */ /* sanity check */
if (editor.curbuf < 0 || editor.curbuf >= editor.bufcount) { if (editor.bufcount == 0 || editor.curbuf >= editor.bufcount) {
editor_set_status("No buffer to close."); editor_set_status("No buffer to close.");
return; return;
} }
closing = editor.curbuf; closing = editor.curbuf;
target = -1;
if (editor.bufcount > 1) { if (editor.bufcount > 1) {
target = (closing - 1 >= 0) ? (closing - 1) : (closing + 1); target = (closing > 0) ? (int) (closing - 1) : (int) (closing + 1);
buffer_switch(target); buffer_switch(target);
} else { } else {
nb = buffer_add_empty(); nb = buffer_add_empty();
@@ -438,7 +396,7 @@ buffer_close_current(void)
b = editor.buffers[closing]; b = editor.buffers[closing];
if (b) { if (b) {
if (b->row) { if (b->row) {
for (int i = 0; i < b->nrows; i++) { for (size_t i = 0; i < b->nrows; i++) {
ab_free(&b->row[i]); ab_free(&b->row[i]);
} }
free(b->row); free(b->row);
@@ -451,11 +409,11 @@ buffer_close_current(void)
} }
memmove(&editor.buffers[closing], &editor.buffers[closing + 1], memmove(&editor.buffers[closing], &editor.buffers[closing + 1],
sizeof(buffer *) * (editor.bufcount - closing - 1)); sizeof(buffer *) * (editor.bufcount - closing - 1));
editor.bufcount--; editor.bufcount--;
if (editor.bufcount == 0) { if (editor.bufcount == 0) {
editor.curbuf = -1; editor.curbuf = 0;
} else { } else {
if (editor.curbuf > closing) { if (editor.curbuf > closing) {
editor.curbuf--; editor.curbuf--;
@@ -464,7 +422,7 @@ buffer_close_current(void)
editor.dirtyex = 1; editor.dirtyex = 1;
editor_set_status("Closed buffer. Now on %s", editor_set_status("Closed buffer. Now on %s",
buffer_name(editor.buffers[editor.curbuf])); buffer_name(editor.buffers[editor.curbuf]));
} }
@@ -497,8 +455,9 @@ buffer_switch_by_name(void)
if (idx >= 0) { if (idx >= 0) {
buffer_switch(idx); buffer_switch(idx);
} else { } else {
editor_set_status("No such buffer: %s", name); editor_set_status("No open buffer: %s", name);
} }
free(name); free(name);
} }

View File

@@ -1,24 +1,25 @@
#ifndef BUFFER_H #ifndef KE_BUFFER_H
#define BUFFER_H #define KE_BUFFER_H
#include "abuf.h" #include "abuf.h"
#include "undo.h"
typedef struct buffer { typedef struct buffer {
int curx, cury; size_t curx, cury;
int rx; size_t rx;
int nrows; size_t nrows;
int rowoffs, coloffs; size_t rowoffs, coloffs;
abuf *row; abuf *row;
char *filename; char *filename;
int dirty; int dirty;
int mark_set; int mark_set;
int 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)

41
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,19 +38,42 @@ strnstr(const char *s, const char *find, size_t slen)
#endif #endif
char int
nibble_to_hex(char c) path_is_dir(const char *path)
{ {
c &= 0xf; struct stat st;
if (c < 10) {
return (char)('0' + c); if (path == NULL) {
return 0;
} }
return (char)('A' + (c - 10));
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_int(int *first, int *second) swap_size_t(size_t *first, size_t *second)
{ {
*first ^= *second; *first ^= *second;
*second ^= *first; *second ^= *first;
@@ -122,3 +148,4 @@ die(const char* s)
perror(s); perror(s);
exit(1); exit(1);
} }

25
core.h
View File

@@ -4,8 +4,18 @@
#include <stddef.h> #include <stddef.h>
#define calloc1(sz) calloc(1, sz) #ifndef KE_VERSION
#define INITIAL_CAPACITY 8 #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 {
@@ -29,13 +39,14 @@ char *strnstr(const char *s, const char *find, size_t slen);
#define INCLUDE_STRNSTR #define INCLUDE_STRNSTR
#endif #endif
char nibble_to_hex(char c); int path_is_dir(const char *path);
void swap_int(int *first, int *second); size_t str_lcp2(const char *a, const char *b);
int next_power_of_2(int n); void swap_size_t(size_t *first, size_t *second);
int cap_growth(int cap, int sz); int next_power_of_2(int n);
int cap_growth(int cap, int sz);
size_t kstrnlen(const char *buf, size_t max); size_t kstrnlen(const char *buf, size_t max);
void kwrite(int fd, const char *buf, int len); 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

@@ -17,30 +17,19 @@
*/ */
struct editor editor = { struct editor editor = {
.cols = 0, .cols = 0,
.rows = 0, .rows = 0,
.curx = 0, .mode = 0,
.cury = 0,
.rx = 0,
.mode = 0,
.nrows = 0,
.rowoffs = 0,
.coloffs = 0,
.row = NULL,
.killring = NULL, .killring = NULL,
.kill = 0, .kill = 0,
.no_kill = 0, .no_kill = 0,
.filename = NULL, .dirtyex = 0,
.dirty = 0, .uarg = 0,
.dirtyex = 0, .ucount = 0,
.mark_set = 0, .msgtm = 0,
.mark_curx = 0, .buffers = NULL,
.mark_cury = 0,
.uarg = 0,
.ucount = 0,
.msgtm = 0,
.buffers = NULL,
.bufcount = 0, .bufcount = 0,
.curbuf = -1, .curbuf = 0,
.bufcap = 0,
}; };
@@ -69,29 +58,19 @@ init_editor(void)
editor.rows--; /* status bar */ editor.rows--; /* status bar */
editor.rows--; /* message line */ editor.rows--; /* message line */
editor.curx = editor.cury = 0;
editor.rx = 0;
editor.nrows = 0;
editor.rowoffs = editor.coloffs = 0;
editor.row = NULL;
/* don't clear out the kill ring: /* don't clear out the kill ring:
* killing / yanking across files is helpful, and killring * killing / yanking across files is helpful, and killring
* is initialized to NULL at program start. * is initialized to NULL at program start.
*/ */
editor.kill = 0; editor.kill = 0;
editor.no_kill = 0; editor.no_kill = 0;
editor.msg[0] = '\0'; editor.msg[0] = '\0';
editor.msgtm = 0; editor.msgtm = 0;
editor.dirty = 0;
editor.mark_set = 0;
editor.mark_cury = editor.mark_curx = 0;
/* initialize buffer system on first init */ /* initialize buffer system on first init */
if (editor.buffers == NULL && editor.bufcount == 0) { if (editor.buffers == NULL && editor.bufcount == 0) {
editor.bufcap = 0;
buffers_init(); buffers_init();
} }
} }
@@ -100,25 +79,32 @@ init_editor(void)
void void
reset_editor(void) reset_editor(void)
{ {
/* Clear current working set. Notably, does not reset terminal buffer *b = buffer_current();
* or buffers list. */ if (b == NULL) {
for (int i = 0; i < editor.nrows; i++) { return;
ab_free(&editor.row[i]);
}
free(editor.row);
editor.row = NULL;
editor.nrows = 0;
editor.rowoffs = editor.coloffs = 0;
editor.curx = editor.cury = 0;
editor.rx = 0;
if (editor.filename != NULL) {
free(editor.filename);
editor.filename = NULL;
} }
editor.dirty = 0; if (b->row) {
editor.mark_set = 0; for (size_t i = 0; i < b->nrows; i++) {
editor.mark_cury = editor.mark_curx = 0; ab_free(&b->row[i]);
}
free(b->row);
}
b->row = NULL;
b->nrows = 0;
b->rowoffs = 0;
b->coloffs = 0;
b->rx = 0;
b->curx = 0;
b->cury = 0;
if (b->filename) {
free(b->filename);
b->filename = NULL;
}
b->dirty = 0;
b->mark_set = 0;
b->mark_curx = 0;
b->mark_cury = 0;
} }

View File

@@ -1,5 +1,5 @@
#ifndef EDITOR_H #ifndef KE_EDITOR_H
#define EDITOR_H #define KE_EDITOR_H
#include <termios.h> #include <termios.h>
#include <time.h> #include <time.h>
@@ -9,32 +9,20 @@
#include "buffer.h" #include "buffer.h"
/* TODO(kyle): remove the "per-buffer" fields completely from the editor. */
struct editor { struct editor {
int rows, cols; size_t rows, cols;
int curx, cury; /* per-buffer */
int rx; /* per-buffer */
int mode; int mode;
int nrows; /* per-buffer */
int rowoffs, coloffs; /* per-buffer */
abuf *row; /* per-buffer */
abuf *killring; abuf *killring;
int kill; /* KILL CHAIN (\m/) */ int kill; /* KILL CHAIN (\m/) */
int no_kill; /* don't kill in delete_row */ int no_kill; /* don't kill in delete_row */
char *filename; /* per-buffer */
int dirty; /* per-buffer */
int dirtyex; int dirtyex;
char msg[80]; char msg[80];
int mark_set; /* per-buffer */
int mark_curx, mark_cury; /* per-buffer */
int uarg, ucount; /* C-u support */ int uarg, ucount; /* C-u support */
time_t msgtm; time_t msgtm;
struct buffer **buffers; /* array of buffers */
/* Multi-buffer support */ size_t bufcount; /* number of buffers */
struct buffer **buffers; /* array of buffers */ size_t curbuf; /* current buffer index */
int bufcount; /* number of buffers */ size_t bufcap; /* current buffer capacity */
int curbuf; /* current buffer index */
}; };
@@ -44,4 +32,4 @@ void init_editor(void);
void reset_editor(void); void reset_editor(void);
#endif /* EDITOR_H */ #endif /* KE_EDITOR_H */

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

2639
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

156
scratch.c
View File

@@ -2,15 +2,20 @@
* scratch.c - ideas in progress * scratch.c - ideas in progress
*/ */
#include "buffer.h"
#include "abuf.h"
#include <ctype.h>
#include <string.h>
#define REFLOW_MARGIN 72 #define REFLOW_MARGIN 72
void void
reflow_region(void) reflow_region(void)
{ {
int start_row, end_row, i, col, wlen, this_len; int start_row, end_row, i, col, wlen, this_len;
struct erow *row; abuf *row;
struct abuf buf = ABUF_INIT; struct abuf buf = ABUF_INIT;
struct abuf out = ABUF_INIT; struct abuf out = ABUF_INIT;
int in_paragraph = 0; int in_paragraph = 0;
int indent_len = 0; int indent_len = 0;
char indent[REFLOW_MARGIN + 1]; char indent[REFLOW_MARGIN + 1];
@@ -19,38 +24,38 @@ reflow_region(void)
char *p = NULL; char *p = NULL;
char *s = NULL; char *s = NULL;
if (editor.mark_set) { if (EMARK_SET) {
if (editor.mark_cury < editor.cury || if (EMARK_CURY < ECURY ||
(editor.mark_cury == editor.cury && (EMARK_CURY == ECURY &&
editor.mark_curx < editor.curx)) { EMARK_CURX < ECURX)) {
start_row = editor.mark_cury; start_row = EMARK_CURY;
end_row = editor.cury; end_row = ECURY;
} else { } else {
start_row = editor.cury; start_row = ECURY;
end_row = editor.mark_cury; end_row = EMARK_CURY;
} }
} else { } else {
start_row = end_row = editor.cury; start_row = end_row = ECURY;
while (start_row > 0 && editor.row[start_row - 1].size > 0) { while (start_row > 0 && EROW[start_row - 1].size > 0) {
start_row--; start_row--;
} }
while (end_row < editor.nrows - 1 && while (end_row < ENROWS - 1 &&
editor.row[end_row + 1].size > 0) { EROW[end_row + 1].size > 0) {
end_row++; end_row++;
} }
} }
if (start_row >= editor.nrows) { if (start_row >= ENROWS) {
return; return;
} }
if (end_row >= editor.nrows) { if (end_row >= ENROWS) {
end_row = editor.nrows - 1; end_row = ENROWS - 1;
} }
for (i = start_row; i <= end_row; i++) { for (i = start_row; i <= end_row; i++) {
row = &editor.row[i]; row = &EROW[i];
if (row->size == 0) { if (row->size == 0) {
if (in_paragraph) { if (in_paragraph) {
@@ -62,21 +67,21 @@ reflow_region(void)
continue; continue;
} }
if (!in_paragraph) { if (!in_paragraph) {
indent_len = 0; indent_len = 0;
while (indent_len < row->size && while (indent_len < (int)row->size &&
(row->line[indent_len] == ' ' || (row->b[indent_len] == ' ' ||
row->line[indent_len] == '\t')) { row->b[indent_len] == '\t')) {
indent[indent_len] = row->line[indent_len], indent_len++; indent[indent_len] = row->b[indent_len], indent_len++;
} }
indent[indent_len] = '\0'; indent[indent_len] = '\0';
in_paragraph = 1; in_paragraph = 1;
} }
ab_append(&buf, row->line + indent_len, row->size - indent_len); ab_append(&buf, row->b + indent_len, row->size - indent_len);
ab_append(&buf, " ", 1); ab_append(&buf, " ", 1);
} }
if (in_paragraph) { if (in_paragraph) {
ab_append(&buf, "\n", 1); ab_append(&buf, "\n", 1);
@@ -145,18 +150,57 @@ reflow_region(void)
ab_free(&out); ab_free(&out);
for (i = end_row; i >= start_row; i--) { for (i = end_row; i >= start_row; i--) {
delete_row(i); delete_row(i);
} }
s = buf.b; s = buf.b;
while ((e = strchr(s, '\n'))) { while ((e = strchr(s, '\n'))) {
erow_insert(start_row++, s, e - s); erow_insert(start_row++, s, e - s);
s = e + 1; s = e + 1;
} }
ab_free(&buf); ab_free(&buf);
editor.dirty++; EDIRTY++;
editor_set_status("Region reflowed to %d columns", REFLOW_MARGIN); editor_set_status("Region reflowed to %d columns", REFLOW_MARGIN);
} }
static inline
void clamp_curx_to_row(void)
{
abuf *row = NULL;
int maxx = 0;
if (ECURY >= ENROWS) {
return;
}
row = &EROW[ECURY];
if (ECURX < 0) {
ECURX = 0;
}
maxx = (int) row->size;
if (ECURX > maxx) {
ECURX = maxx;
}
}
static inline
void set_cursor(int col, int row)
{
if (row < 0) {
row = 0;
}
if (row > ENROWS) {
row = ENROWS;
}
ECURY = row;
ECURX = col;
clamp_curx_to_row();
}

242
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["
@@ -71,16 +76,237 @@ setup_terminal(void)
int int
get_winsz(int *rows, int *cols) get_winsz(size_t *rows, size_t *cols)
{ {
struct winsize ws; struct winsize ws;
if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == -1 || ws.ws_col == 0) { if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == -1 || ws.ws_col == 0) {
return -1; return -1;
}
*cols = (size_t)ws.ws_col;
*rows = (size_t)ws.ws_row;
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 ");
} }
*cols = ws.ws_col; rlen = snprintf(rstatus,
*rows = ws.ws_row; sizeof(rstatus),
"L%lu/%lu C%lu %s",
ECURY + 1,
ENROWS,
ECURX + 1,
mstatus);
return 0; 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);
} }

24
term.h
View File

@@ -1,13 +1,20 @@
#ifndef TERM_H #ifndef KE_TERM_H
#define TERM_H #define KE_TERM_H
#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.
@@ -17,6 +24,7 @@ void display_clear(abuf *ab);
* on this for now because it's bloaty and this works on OpenBSD and * on this for now because it's bloaty and this works on OpenBSD and
* Linux, at least. * Linux, at least.
*/ */
int get_winsz(int *rows, int *cols); int get_winsz(size_t *rows, size_t *cols);
#endif /* TERM_H */
#endif /* KE_TERM_H */

174
undo.c
View File

@@ -1,8 +1,16 @@
/*
* undo.c: ke's undo system
*/
#include <assert.h>
#include <stdlib.h>
#include "abuf.h" #include "abuf.h"
#include "buffer.h"
#include "undo.h" #include "undo.h"
undo_node undo_node *
undo_node_new(undo_kind kind) undo_node_new(undo_kind kind)
{ {
undo_node *node = NULL; undo_node *node = NULL;
@@ -13,24 +21,23 @@ undo_node_new(undo_kind kind)
node->kind = kind; node->kind = kind;
node->row = node->col = 0; node->row = node->col = 0;
abuf_init(node->text); ab_init(&node->text);
node->next = NULL; node->next = NULL;
node->parent = NULL; node->next = NULL;
return node;
} }
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 NULL; return;
} }
abuf_free(node-text); ab_free(&node->text);
next = node->next;
} }
@@ -44,9 +51,10 @@ undo_node_free_all(undo_node *node)
} }
while (node != NULL) { while (node != NULL) {
next = node->next;
undo_node_free(node); undo_node_free(node);
free(node); free(node);
node = node->next; node = next;
} }
} }
@@ -65,7 +73,7 @@ undo_tree_init(undo_tree *tree)
void void
undo_tree_free(undo_tree *tree) undo_tree_free(undo_tree *tree)
{ {
assert(tree == NULL); assert(tree != NULL);
undo_node_free(tree->pending); undo_node_free(tree->pending);
undo_node_free_all(tree->root); undo_node_free_all(tree->root);
@@ -86,19 +94,147 @@ undo_begin(undo_tree *tree, undo_kind kind)
undo_commit(tree); undo_commit(tree);
} }
pending = undo_new_new(kind); pending = undo_node_new(kind);
assert(pending != NULL); assert(pending != NULL);
tree->pending = pending; tree->pending = pending;
} }
void undo_prepend(abuf *buf); void
void undo_append(buf *buf); undo_prepend(undo_tree *tree, abuf *buf)
void undo_prependch(char c); {
void undo_appendch(char c); assert(tree != NULL);
void undo_commit(void); assert(tree->pending != NULL);
void undo_apply(undo_node *node);
void editor_undo(void); ab_prepend_ab(&tree->pending->text, buf);
void editor_redo(void); }
void
undo_append(undo_tree *tree, abuf *buf)
{
assert(tree != NULL);
assert(tree->pending != NULL);
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);
}

34
undo.h
View File

@@ -1,5 +1,17 @@
#ifndef KE_UNDO #include <stddef.h>
#define KE_UNDO
#include "abuf.h"
#ifndef 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 {
@@ -9,12 +21,12 @@ typedef enum undo_kind {
typedef struct undo_node { typedef struct undo_node {
undo_kind op; undo_kind kind;
size_t row, col; size_t row, col;
abuf text; abuf text;
struct undo_node *next; struct undo_node *next;
struct undo_node *parent; struct undo_node *prev;
} undo_node; } undo_node;
@@ -31,14 +43,14 @@ void undo_node_free(undo_node *node);
void undo_tree_init(undo_tree *tree); void undo_tree_init(undo_tree *tree);
void undo_tree_free(undo_tree *tree); void undo_tree_free(undo_tree *tree);
void undo_begin(undo_tree *tree, undo_kind kind); void undo_begin(undo_tree *tree, undo_kind kind);
void undo_prepend(abuf *buf); void undo_prepend(undo_tree *tree, abuf *buf);
void undo_append(buf *buf); void undo_append(undo_tree *tree, abuf *buf);
void undo_prependch(char c); void undo_prependch(undo_tree *tree, char c);
void undo_appendch(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(undo_node *node); int undo_apply(struct buffer *buf, int direction);
void editor_undo(void); int editor_undo(struct buffer *buf);
void editor_redo(void); int editor_redo(struct buffer *buf);
#endif #endif