Splitting into separate files.
This commit is contained in:
@@ -20,7 +20,14 @@ endif()
|
|||||||
include(GNUInstallDirs)
|
include(GNUInstallDirs)
|
||||||
|
|
||||||
# Add executable
|
# Add executable
|
||||||
add_executable(ke main.c)
|
add_executable(ke
|
||||||
|
main.c
|
||||||
|
abuf.c
|
||||||
|
term.c
|
||||||
|
buffer.c
|
||||||
|
core.c
|
||||||
|
core.h
|
||||||
|
)
|
||||||
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)
|
||||||
|
|||||||
6
Makefile
6
Makefile
@@ -11,8 +11,10 @@ LDFLAGS := -fsanitize=address
|
|||||||
|
|
||||||
all: $(TARGET) test.txt
|
all: $(TARGET) test.txt
|
||||||
|
|
||||||
$(TARGET): main.c
|
SRCS := main.c abuf.c term.c buffer.c
|
||||||
$(CC) $(CFLAGS) -o $(TARGET) $(LDFLAGS) main.c
|
|
||||||
|
$(TARGET): $(SRCS)
|
||||||
|
$(CC) $(CFLAGS) -o $(TARGET) $(SRCS) $(LDFLAGS)
|
||||||
|
|
||||||
.PHONY: install
|
.PHONY: install
|
||||||
#install: $(TARGET)
|
#install: $(TARGET)
|
||||||
|
|||||||
77
abuf.c
Normal file
77
abuf.c
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
#include <assert.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "abuf.h"
|
||||||
|
|
||||||
|
|
||||||
|
void
|
||||||
|
abuf_init(abuf *buf)
|
||||||
|
{
|
||||||
|
assert(buf != NULL);
|
||||||
|
|
||||||
|
buf->b = NULL;
|
||||||
|
buf->size = buf->cap = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void
|
||||||
|
ab_appendch(abuf *buf, char c)
|
||||||
|
{
|
||||||
|
ab_append(buf, &c, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void
|
||||||
|
ab_append(abuf *buf, const char *s, size_t len)
|
||||||
|
{
|
||||||
|
char *nc = buf->b;
|
||||||
|
size_t sz = buf->size + len;
|
||||||
|
|
||||||
|
if (sz >= buf->cap) {
|
||||||
|
while (sz > buf->cap) {
|
||||||
|
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);
|
||||||
|
buf->b = nc;
|
||||||
|
buf->size += len;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void
|
||||||
|
ab_prependch(abuf *buf, const char c)
|
||||||
|
{
|
||||||
|
ab_prepend(buf, &c, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void
|
||||||
|
ab_prepend(abuf *buf, const char *s, const size_t len)
|
||||||
|
{
|
||||||
|
char *nc = realloc(buf->b, buf->size + len);
|
||||||
|
assert(nc != NULL);
|
||||||
|
|
||||||
|
memmove(nc + len, nc, buf->size);
|
||||||
|
memcpy(nc, s, len);
|
||||||
|
|
||||||
|
buf->b = nc;
|
||||||
|
buf->size += len;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void
|
||||||
|
ab_free(abuf *buf)
|
||||||
|
{
|
||||||
|
free(buf->b);
|
||||||
|
buf->b = NULL;
|
||||||
|
buf->size = 0;
|
||||||
|
buf->cap = 0;
|
||||||
|
}
|
||||||
27
abuf.h
Normal file
27
abuf.h
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
/*
|
||||||
|
* abuf.h - append/prepend buffer utilities
|
||||||
|
*/
|
||||||
|
#ifndef ABUF_H
|
||||||
|
#define ABUF_H
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct abuf {
|
||||||
|
char *b;
|
||||||
|
size_t size;
|
||||||
|
size_t cap;
|
||||||
|
} abuf;
|
||||||
|
|
||||||
|
#define ABUF_INIT {NULL, 0, 0}
|
||||||
|
|
||||||
|
|
||||||
|
void ab_init(abuf *buf);
|
||||||
|
void ab_appendch(abuf *buf, char c);
|
||||||
|
void ab_append(abuf *buf, const char *s, size_t len);
|
||||||
|
void ab_prependch(abuf *buf, const char c);
|
||||||
|
void ab_prepend(abuf *buf, const char *s, const size_t len);
|
||||||
|
void ab_free(abuf *buf);
|
||||||
|
|
||||||
|
|
||||||
|
#endif
|
||||||
456
buffer.c
Normal file
456
buffer.c
Normal file
@@ -0,0 +1,456 @@
|
|||||||
|
/* buffer.c - buffer management implementation */
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "abuf.h"
|
||||||
|
#include "buffer.h"
|
||||||
|
#include "editor.h"
|
||||||
|
|
||||||
|
|
||||||
|
#define NO_NAME "[No Name]"
|
||||||
|
|
||||||
|
/* externs from other modules */
|
||||||
|
char *editor_prompt(char *, void (*cb)(char *, int16_t));
|
||||||
|
|
||||||
|
|
||||||
|
static const char *
|
||||||
|
buf_basename(const char *path)
|
||||||
|
{
|
||||||
|
if (path == NULL) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *slash = strrchr(path, '/');
|
||||||
|
return (slash != NULL) ? (slash + 1) : path;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
buffer_find_exact_by_name(const char *name)
|
||||||
|
{
|
||||||
|
buffer *b = NULL;
|
||||||
|
|
||||||
|
if (name == NULL) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < editor.bufcount; i++) {
|
||||||
|
b = editor.buffers[i];
|
||||||
|
const char *full = b->filename;
|
||||||
|
const char *base = buf_basename(full);
|
||||||
|
|
||||||
|
if (full && strcmp(full, name) == 0) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (base && strcmp(base, name) == 0) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (full == NULL && strcmp(name, NO_NAME) == 0) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static int
|
||||||
|
buffer_collect_prefix_matches(const char *prefix, int *out_idx, const int max_out)
|
||||||
|
{
|
||||||
|
buffer *b = NULL;
|
||||||
|
int count = 0;
|
||||||
|
int matched = 0;
|
||||||
|
size_t plen = (prefix ? strlen(prefix) : 0);
|
||||||
|
|
||||||
|
for (int i = 0; i < editor.bufcount; i++) {
|
||||||
|
b = editor.buffers[i];
|
||||||
|
|
||||||
|
const char *cand1 = b->filename;
|
||||||
|
const char *cand2 = buf_basename(cand1);
|
||||||
|
|
||||||
|
if (plen == 0) {
|
||||||
|
matched = 1; /* everything matches empty prefix */
|
||||||
|
} else {
|
||||||
|
if (cand2 && strncmp(cand2, prefix, plen) == 0) {
|
||||||
|
matched = 1;
|
||||||
|
} else if (cand1 && strncmp(cand1, prefix, plen) == 0) {
|
||||||
|
matched = 1;
|
||||||
|
} else if (!cand1 && strncmp(NO_NAME, prefix, plen) == 0) {
|
||||||
|
matched = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (matched) {
|
||||||
|
if (count < max_out) {
|
||||||
|
out_idx[count] = i;
|
||||||
|
}
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void
|
||||||
|
longest_common_prefix(char *buf, const size_t bufsz, const int *idxs, const int n)
|
||||||
|
{
|
||||||
|
const char *first = NULL;
|
||||||
|
const char *cand = NULL;
|
||||||
|
int k = 0;
|
||||||
|
size_t j = 0;
|
||||||
|
size_t cur = 0;
|
||||||
|
size_t lcp = 0;
|
||||||
|
size_t to_copy = 0;
|
||||||
|
|
||||||
|
if (n <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
first = buf_basename(editor.buffers[idxs[0]]->filename);
|
||||||
|
if (first == NULL) {
|
||||||
|
first = NO_NAME;
|
||||||
|
}
|
||||||
|
|
||||||
|
lcp = strnlen(first, FILENAME_MAX);
|
||||||
|
for (k = 1; k < n; k++) {
|
||||||
|
cand = buf_basename(editor.buffers[idxs[k]]->filename);
|
||||||
|
if (cand == NULL) {
|
||||||
|
cand = NO_NAME;
|
||||||
|
}
|
||||||
|
|
||||||
|
j = 0;
|
||||||
|
while (j < lcp && first[j] == cand[j]) {
|
||||||
|
j++;
|
||||||
|
}
|
||||||
|
|
||||||
|
lcp = j;
|
||||||
|
if (lcp == 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cur = strlen(buf);
|
||||||
|
if (lcp > cur) {
|
||||||
|
to_copy = lcp - cur;
|
||||||
|
if (cur + to_copy >= bufsz) {
|
||||||
|
to_copy = bufsz - cur - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
strncat(buf, first + cur, to_copy);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void
|
||||||
|
buffer_switch_prompt_cb(char *buf, const int16_t key)
|
||||||
|
{
|
||||||
|
char msg[80] = {0};
|
||||||
|
const char *name = NULL;
|
||||||
|
const char *nm = NULL;
|
||||||
|
int idxs[64] = {0};
|
||||||
|
int n = 0;
|
||||||
|
size_t need = 0;
|
||||||
|
size_t used = 0;
|
||||||
|
|
||||||
|
if (key != 9) { /* TODO(kyle): extract TAB_KEY */
|
||||||
|
return; /* TAB key */
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
n = buffer_collect_prefix_matches(buf, idxs, 64);
|
||||||
|
if (n <= 0) {
|
||||||
|
editor_set_status("No matches");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (n == 1) {
|
||||||
|
name = buf_basename(editor.buffers[idxs[0]]->filename);
|
||||||
|
if (name == NULL) {
|
||||||
|
name = NO_NAME;
|
||||||
|
}
|
||||||
|
|
||||||
|
need = strlen(name);
|
||||||
|
if (need < 128) {
|
||||||
|
memcpy(buf, name, need);
|
||||||
|
buf[need] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
editor_set_status("Unique match: %s", name);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
longest_common_prefix(buf, 128, idxs, n);
|
||||||
|
msg[0] = '\0';
|
||||||
|
used = 0;
|
||||||
|
used += snprintf(msg + used, sizeof(msg) - used, "%d matches: ", n);
|
||||||
|
for (int i = 0; i < n && used < sizeof(msg) - 1; i++) {
|
||||||
|
nm = buf_basename(editor.buffers[idxs[i]]->filename);
|
||||||
|
if (nm == NULL) {
|
||||||
|
nm = NO_NAME;
|
||||||
|
}
|
||||||
|
|
||||||
|
used += snprintf(msg + used, sizeof(msg) - used, "%s%s",
|
||||||
|
nm, (i == n - 1 ? "" : ", "));
|
||||||
|
}
|
||||||
|
|
||||||
|
editor_set_status("%s", msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void
|
||||||
|
buffer_bind_to_editor(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 *
|
||||||
|
buffer_name(buffer *b)
|
||||||
|
{
|
||||||
|
if (b && b->filename) {
|
||||||
|
return buf_basename(b->filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
return NO_NAME;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void
|
||||||
|
buffers_init(void)
|
||||||
|
{
|
||||||
|
int idx = 0;
|
||||||
|
|
||||||
|
editor.buffers = NULL;
|
||||||
|
editor.bufcount = 0;
|
||||||
|
editor.curbuf = -1;
|
||||||
|
|
||||||
|
idx = buffer_add_empty();
|
||||||
|
buffer_switch(idx);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int
|
||||||
|
buffer_add_empty(void)
|
||||||
|
{
|
||||||
|
buffer *buf = NULL;
|
||||||
|
buffer **newlist = realloc(editor.buffers, sizeof(buffer *) * (editor.bufcount + 1));
|
||||||
|
int idx = 0;
|
||||||
|
|
||||||
|
assert(newlist != NULL);
|
||||||
|
editor.buffers = newlist;
|
||||||
|
|
||||||
|
buf = calloc(1, sizeof(buffer));
|
||||||
|
assert(buf != NULL);
|
||||||
|
|
||||||
|
buf->curx = 0;
|
||||||
|
buf->cury = 0;
|
||||||
|
buf->rx = 0;
|
||||||
|
buf->nrows = 0;
|
||||||
|
buf->rowoffs = 0;
|
||||||
|
buf->coloffs = 0;
|
||||||
|
buf->row = NULL;
|
||||||
|
buf->filename = NULL;
|
||||||
|
buf->dirty = 0;
|
||||||
|
buf->mark_set = 0;
|
||||||
|
buf->mark_curx = 0;
|
||||||
|
buf->mark_cury = 0;
|
||||||
|
|
||||||
|
editor.buffers[editor.bufcount] = buf;
|
||||||
|
idx = editor.bufcount;
|
||||||
|
editor.bufcount++;
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void
|
||||||
|
buffer_switch(const int idx)
|
||||||
|
{
|
||||||
|
buffer *b = NULL;
|
||||||
|
|
||||||
|
if (idx < 0 || idx >= editor.bufcount) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (editor.curbuf == idx) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (editor.curbuf >= 0) {
|
||||||
|
buffer_save_current();
|
||||||
|
}
|
||||||
|
|
||||||
|
b = editor.buffers[idx];
|
||||||
|
buffer_bind_to_editor(b);
|
||||||
|
editor.curbuf = idx;
|
||||||
|
editor.dirtyex = 1;
|
||||||
|
editor_set_status("Switched to buffer %d: %s", editor.curbuf, buffer_name(b));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void
|
||||||
|
buffer_next(void)
|
||||||
|
{
|
||||||
|
int idx = 0;
|
||||||
|
|
||||||
|
if (editor.bufcount <= 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
idx = (editor.curbuf + 1) % editor.bufcount;
|
||||||
|
buffer_switch(idx);
|
||||||
|
}
|
||||||
|
|
||||||
|
void buffer_prev(void)
|
||||||
|
{
|
||||||
|
int idx = 0;
|
||||||
|
|
||||||
|
if (editor.bufcount <= 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
idx = (editor.curbuf - 1 + editor.bufcount) % editor.bufcount;
|
||||||
|
buffer_switch(idx);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void
|
||||||
|
buffer_close_current(void)
|
||||||
|
{
|
||||||
|
buffer *b = NULL;
|
||||||
|
int closing = 0;
|
||||||
|
int target = 0;
|
||||||
|
int nb = 0;
|
||||||
|
|
||||||
|
if (editor.curbuf < 0 || editor.curbuf >= editor.bufcount) {
|
||||||
|
editor_set_status("No buffer to close.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
closing = editor.curbuf;
|
||||||
|
|
||||||
|
target = -1;
|
||||||
|
if (editor.bufcount > 1) {
|
||||||
|
target = (closing - 1 >= 0) ? (closing - 1) : (closing + 1);
|
||||||
|
buffer_switch(target);
|
||||||
|
} else {
|
||||||
|
nb = buffer_add_empty();
|
||||||
|
buffer_switch(nb);
|
||||||
|
}
|
||||||
|
|
||||||
|
b = editor.buffers[closing];
|
||||||
|
if (b) {
|
||||||
|
if (b->row) {
|
||||||
|
for (int i = 0; i < b->nrows; i++) {
|
||||||
|
ab_free(&b->row[i]);
|
||||||
|
}
|
||||||
|
free(b->row);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (b->filename) {
|
||||||
|
free(b->filename);
|
||||||
|
}
|
||||||
|
free(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
memmove(&editor.buffers[closing], &editor.buffers[closing + 1],
|
||||||
|
sizeof(buffer *) * (editor.bufcount - closing - 1));
|
||||||
|
|
||||||
|
editor.bufcount--;
|
||||||
|
if (editor.bufcount == 0) {
|
||||||
|
editor.curbuf = -1;
|
||||||
|
} else {
|
||||||
|
if (editor.curbuf > closing) {
|
||||||
|
editor.curbuf--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
editor.dirtyex = 1;
|
||||||
|
editor_set_status("Closed buffer. Now on %s",
|
||||||
|
buffer_name(editor.buffers[editor.curbuf]));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void
|
||||||
|
buffer_switch_by_name(void)
|
||||||
|
{
|
||||||
|
int idxs[64] = {0};
|
||||||
|
char *name = NULL;
|
||||||
|
int idx = 0;
|
||||||
|
int n = 0;
|
||||||
|
|
||||||
|
name = editor_prompt("Switch buffer (name, TAB to complete): %s", buffer_switch_prompt_cb);
|
||||||
|
if (name == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
idx = buffer_find_exact_by_name(name);
|
||||||
|
if (idx < 0) {
|
||||||
|
n = buffer_collect_prefix_matches(name, idxs, 64);
|
||||||
|
if (n == 1) {
|
||||||
|
idx = idxs[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (idx >= 0) {
|
||||||
|
buffer_switch(idx);
|
||||||
|
} else {
|
||||||
|
editor_set_status("No such buffer: %s", name);
|
||||||
|
}
|
||||||
|
|
||||||
|
free(name);
|
||||||
|
}
|
||||||
31
buffer.h
Normal file
31
buffer.h
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
#ifndef BUFFER_H
|
||||||
|
#define BUFFER_H
|
||||||
|
|
||||||
|
#include "abuf.h"
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct buffer {
|
||||||
|
int curx, cury;
|
||||||
|
int rx;
|
||||||
|
int nrows;
|
||||||
|
int rowoffs, coloffs;
|
||||||
|
abuf *row;
|
||||||
|
char *filename;
|
||||||
|
int dirty;
|
||||||
|
int mark_set;
|
||||||
|
int mark_curx, mark_cury;
|
||||||
|
} buffer;
|
||||||
|
|
||||||
|
|
||||||
|
void buffers_init(void);
|
||||||
|
int buffer_add_empty(void);
|
||||||
|
void buffer_save_current(void);
|
||||||
|
void buffer_switch(int idx);
|
||||||
|
void buffer_next(void);
|
||||||
|
void buffer_prev(void);
|
||||||
|
void buffer_switch_by_name(void);
|
||||||
|
void buffer_close_current(void);
|
||||||
|
const char *buffer_name(buffer *b);
|
||||||
|
|
||||||
|
|
||||||
|
#endif
|
||||||
60
core.c
Normal file
60
core.c
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
#include <assert.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "core.h"
|
||||||
|
|
||||||
|
#ifdef INCLUDE_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
|
||||||
|
|
||||||
|
|
||||||
|
void
|
||||||
|
kwrite(const int fd, const char* buf, const int len)
|
||||||
|
{
|
||||||
|
int wlen = 0;
|
||||||
|
|
||||||
|
wlen = write(fd, buf, len);
|
||||||
|
assert(wlen != -1);
|
||||||
|
assert(wlen == len);
|
||||||
|
if (wlen == -1) {
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void
|
||||||
|
die(const char* s)
|
||||||
|
{
|
||||||
|
kwrite(STDOUT_FILENO, "\x1b[2J", 4);
|
||||||
|
kwrite(STDOUT_FILENO, "\x1b[H", 3);
|
||||||
|
|
||||||
|
perror(s);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
34
core.h
Normal file
34
core.h
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
#ifndef KE_CORE_H
|
||||||
|
#define KE_CORE_H
|
||||||
|
|
||||||
|
|
||||||
|
#define calloc1(sz) calloc(1, sz)
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
|
||||||
|
typedef enum key_press {
|
||||||
|
TAB_KEY = 9,
|
||||||
|
ESC_KEY = 27,
|
||||||
|
BACKSPACE = 127,
|
||||||
|
ARROW_LEFT = 1000,
|
||||||
|
ARROW_RIGHT = 1001,
|
||||||
|
ARROW_UP = 1002,
|
||||||
|
ARROW_DOWN = 1003,
|
||||||
|
DEL_KEY = 1004,
|
||||||
|
HOME_KEY = 1005,
|
||||||
|
END_KEY = 1006,
|
||||||
|
PG_UP = 1007,
|
||||||
|
PG_DN = 1008,
|
||||||
|
} key_press;
|
||||||
|
|
||||||
|
#ifndef strnstr
|
||||||
|
char *strnstr(const char *s, const char *find, size_t slen);
|
||||||
|
#define INCLUDE_STRNSTR
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
void kwrite(int fd, const char *buf, int len);
|
||||||
|
void die(const char *s);
|
||||||
|
|
||||||
|
|
||||||
|
#endif
|
||||||
564
main.c
564
main.c
@@ -36,6 +36,13 @@
|
|||||||
#include <wchar.h>
|
#include <wchar.h>
|
||||||
#include <wctype.h>
|
#include <wctype.h>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
|
#include <dirent.h>
|
||||||
|
#include <limits.h>
|
||||||
|
|
||||||
|
#include "abuf.h"
|
||||||
|
#include "buffer.h"
|
||||||
|
#include "core.h"
|
||||||
|
#include "term.h"
|
||||||
|
|
||||||
|
|
||||||
#ifndef KE_VERSION
|
#ifndef KE_VERSION
|
||||||
@@ -71,46 +78,8 @@
|
|||||||
#define KILLRING_FLUSH 4 /* clear the killring */
|
#define KILLRING_FLUSH 4 /* clear the killring */
|
||||||
|
|
||||||
|
|
||||||
#define calloc1(sz) calloc(1, sz)
|
|
||||||
|
|
||||||
|
|
||||||
/* append buffer */
|
|
||||||
typedef struct abuf {
|
|
||||||
char *b;
|
|
||||||
size_t size;
|
|
||||||
size_t cap;
|
|
||||||
} abuf;
|
|
||||||
|
|
||||||
#define ABUF_INIT {NULL, 0, 0}
|
|
||||||
|
|
||||||
|
|
||||||
typedef enum undo_kind {
|
|
||||||
UNDO_INSERT = 1 << 0,
|
|
||||||
UNDO_UNKNOWN = 1 << 1,
|
|
||||||
} undo_kind;
|
|
||||||
|
|
||||||
|
|
||||||
typedef struct undo_node {
|
|
||||||
undo_kind op;
|
|
||||||
size_t row, col;
|
|
||||||
abuf text;
|
|
||||||
|
|
||||||
struct undo_node *next;
|
|
||||||
struct undo_node *parent;
|
|
||||||
|
|
||||||
} undo_node;
|
|
||||||
|
|
||||||
|
|
||||||
typedef struct undo_tree {
|
|
||||||
undo_node *root; /* the start of the undo sequence */
|
|
||||||
undo_node *current; /* where we are currently at */
|
|
||||||
undo_node *pending; /* the current undo operations being built */
|
|
||||||
} undo_tree;
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* editor is the global editor state; it should be broken out
|
* editor is the global editor state.
|
||||||
* to buffers and screen state, probably.
|
|
||||||
*/
|
*/
|
||||||
struct editor {
|
struct editor {
|
||||||
struct termios entry_term;
|
struct termios entry_term;
|
||||||
@@ -132,6 +101,11 @@ struct editor {
|
|||||||
int mark_curx, mark_cury;
|
int mark_curx, mark_cury;
|
||||||
int uarg, ucount; /* C-u support */
|
int uarg, ucount; /* C-u support */
|
||||||
time_t msgtm;
|
time_t msgtm;
|
||||||
|
|
||||||
|
/* Multi-buffer support */
|
||||||
|
struct buffer **buffers; /* buffer list */
|
||||||
|
int bufcount; /* number of buffers */
|
||||||
|
int curbuf; /* current buffer index */
|
||||||
} editor = {
|
} editor = {
|
||||||
.cols = 0,
|
.cols = 0,
|
||||||
.rows = 0,
|
.rows = 0,
|
||||||
@@ -153,23 +127,23 @@ struct editor {
|
|||||||
.mark_cury = 0,
|
.mark_cury = 0,
|
||||||
.uarg = 0,
|
.uarg = 0,
|
||||||
.ucount = 0,
|
.ucount = 0,
|
||||||
|
.buffers = NULL,
|
||||||
|
.bufcount = 0,
|
||||||
|
.curbuf = -1,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
void init_editor(void);
|
void init_editor(void);
|
||||||
void reset_editor(void);
|
void reset_editor(void);
|
||||||
|
|
||||||
|
/* buffers now declared in buffer.h */
|
||||||
|
|
||||||
/* small tools, abufs, etc */
|
/* small tools, abufs, etc */
|
||||||
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, size_t max);
|
size_t kstrnlen(const char *buf, size_t max);
|
||||||
void ab_init(abuf *buf);
|
void ab_init(abuf *buf);
|
||||||
void ab_init_cap(abuf *buf, size_t cap);
|
void ab_init_cap(abuf *buf, size_t cap);
|
||||||
void ab_appendch(abuf *buf, char c);
|
|
||||||
void ab_append(abuf *buf, const char *s, size_t len);
|
|
||||||
void ab_prependch(abuf *buf, char c);
|
|
||||||
void ab_prepend(abuf *buf, const char *s, size_t len);
|
|
||||||
void ab_free(abuf *buf);
|
|
||||||
char nibble_to_hex(char c);
|
char nibble_to_hex(char c);
|
||||||
void swap_int(int *a, int *b);
|
void swap_int(int *a, int *b);
|
||||||
|
|
||||||
@@ -189,7 +163,7 @@ void delete_region(void);
|
|||||||
/* miscellaneous */
|
/* miscellaneous */
|
||||||
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);
|
||||||
int get_winsz(int *rows, int *cols);
|
/* get_winsz now provided by term.h */
|
||||||
void jump_to_position(int col, int row);
|
void jump_to_position(int col, int row);
|
||||||
void goto_line(void);
|
void goto_line(void);
|
||||||
int cursor_at_eol(void);
|
int cursor_at_eol(void);
|
||||||
@@ -226,10 +200,7 @@ void process_escape(int16_t c);
|
|||||||
int process_keypress(void);
|
int process_keypress(void);
|
||||||
char *get_cloc_code_lines(const char *filename);
|
char *get_cloc_code_lines(const char *filename);
|
||||||
int dump_pidfile(void);
|
int dump_pidfile(void);
|
||||||
void enable_termraw(void);
|
/* terminal functions declared in term.h */
|
||||||
void display_clear(abuf *ab);
|
|
||||||
void disable_termraw(void);
|
|
||||||
void setup_terminal(void);
|
|
||||||
void draw_rows(abuf *ab);
|
void draw_rows(abuf *ab);
|
||||||
char status_mode_char(void);
|
char status_mode_char(void);
|
||||||
void draw_status_bar(abuf *ab);
|
void draw_status_bar(abuf *ab);
|
||||||
@@ -244,34 +215,6 @@ static void signal_handler(int sig);
|
|||||||
static void install_signal_handlers(void);
|
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
|
||||||
next_power_of_2(int n)
|
next_power_of_2(int n)
|
||||||
{
|
{
|
||||||
@@ -305,22 +248,6 @@ cap_growth(int cap, int sz)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
enum KeyPress {
|
|
||||||
TAB_KEY = 9,
|
|
||||||
ESC_KEY = 27,
|
|
||||||
BACKSPACE = 127,
|
|
||||||
ARROW_LEFT = 1000,
|
|
||||||
ARROW_RIGHT,
|
|
||||||
ARROW_UP,
|
|
||||||
ARROW_DOWN,
|
|
||||||
DEL_KEY,
|
|
||||||
HOME_KEY,
|
|
||||||
END_KEY,
|
|
||||||
PG_UP,
|
|
||||||
PG_DN,
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
size_t
|
size_t
|
||||||
kstrnlen(const char *buf, const size_t max)
|
kstrnlen(const char *buf, const size_t max)
|
||||||
{
|
{
|
||||||
@@ -368,6 +295,11 @@ init_editor(void)
|
|||||||
editor.dirty = 0;
|
editor.dirty = 0;
|
||||||
editor.mark_set = 0;
|
editor.mark_set = 0;
|
||||||
editor.mark_cury = editor.mark_curx = 0;
|
editor.mark_cury = editor.mark_curx = 0;
|
||||||
|
|
||||||
|
/* initialize buffer system on first init */
|
||||||
|
if (editor.buffers == NULL && editor.bufcount == 0) {
|
||||||
|
buffers_init();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -377,18 +309,23 @@ init_editor(void)
|
|||||||
void
|
void
|
||||||
reset_editor(void)
|
reset_editor(void)
|
||||||
{
|
{
|
||||||
|
/* Clear current working set (does not reset terminal or buffers list) */
|
||||||
for (int i = 0; i < editor.nrows; i++) {
|
for (int i = 0; i < editor.nrows; i++) {
|
||||||
ab_free(&editor.row[i]);
|
ab_free(&editor.row[i]);
|
||||||
}
|
}
|
||||||
free(editor.row);
|
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) {
|
if (editor.filename != NULL) {
|
||||||
free(editor.filename);
|
free(editor.filename);
|
||||||
editor.filename = NULL;
|
editor.filename = NULL;
|
||||||
}
|
}
|
||||||
|
editor.dirty = 0;
|
||||||
|
editor.mark_set = 0;
|
||||||
init_editor();
|
editor.mark_cury = editor.mark_curx = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -420,66 +357,162 @@ ab_resize(abuf *buf, size_t cap)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void
|
/* Buffer management moved to buffer.c */
|
||||||
ab_appendch(abuf *buf, char c)
|
|
||||||
|
/* =========================
|
||||||
|
* File open: TAB completion callback
|
||||||
|
* ========================= */
|
||||||
|
static int path_is_dir(const char *path)
|
||||||
{
|
{
|
||||||
ab_append(buf, &c, 1);
|
struct stat st;
|
||||||
|
if (path == NULL) return 0;
|
||||||
|
if (stat(path, &st) == 0) {
|
||||||
|
return S_ISDIR(st.st_mode);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static size_t str_lcp2(const char *a, const char *b)
|
||||||
void
|
|
||||||
ab_append(abuf *buf, const char *s, size_t len)
|
|
||||||
{
|
{
|
||||||
char *nc = buf->b;
|
if (!a || !b) return 0;
|
||||||
size_t sz = buf->size + len;
|
size_t i = 0;
|
||||||
|
while (a[i] && b[i] && a[i] == b[i]) i++;
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
if (sz >= buf->cap) {
|
static void file_open_prompt_cb(char *buf, int16_t key)
|
||||||
while (sz > buf->cap) {
|
{
|
||||||
if (buf->cap == 0) {
|
if (key != TAB_KEY) return;
|
||||||
buf->cap = 1;
|
|
||||||
|
/* Determine directory and basename prefix */
|
||||||
|
char dirpath[PATH_MAX];
|
||||||
|
char base[256];
|
||||||
|
const char *slash = strrchr(buf, '/');
|
||||||
|
if (slash) {
|
||||||
|
size_t dlen = (size_t)(slash - buf);
|
||||||
|
if (dlen == 0) {
|
||||||
|
/* path like "/foo" -> dir is "/" */
|
||||||
|
strcpy(dirpath, "/");
|
||||||
} else {
|
} else {
|
||||||
buf->cap *= 2;
|
if (dlen >= sizeof(dirpath)) dlen = sizeof(dirpath) - 1;
|
||||||
|
memcpy(dirpath, buf, dlen);
|
||||||
|
dirpath[dlen] = '\0';
|
||||||
}
|
}
|
||||||
}
|
strncpy(base, slash + 1, sizeof(base) - 1);
|
||||||
nc = realloc(nc, buf->cap);
|
base[sizeof(base) - 1] = '\0';
|
||||||
assert(nc != NULL);
|
} else {
|
||||||
|
strcpy(dirpath, ".");
|
||||||
|
strncpy(base, buf, sizeof(base) - 1);
|
||||||
|
base[sizeof(base) - 1] = '\0';
|
||||||
}
|
}
|
||||||
|
|
||||||
memcpy(&nc[buf->size], s, len);
|
DIR *d = opendir(dirpath);
|
||||||
buf->b = nc;
|
if (!d) {
|
||||||
buf->size += len;
|
editor_set_status("No such dir: %s", dirpath);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Collect matches */
|
||||||
|
const char *names[128];
|
||||||
|
int isdir[128];
|
||||||
|
int n = 0;
|
||||||
|
size_t plen = strlen(base);
|
||||||
|
|
||||||
|
struct dirent *de;
|
||||||
|
while ((de = readdir(d)) != NULL) {
|
||||||
|
const char *name = de->d_name;
|
||||||
|
/* Skip . and .. */
|
||||||
|
if (strcmp(name, ".") == 0 || strcmp(name, "..") == 0) continue;
|
||||||
|
if (plen == 0 || strncmp(name, base, plen) == 0) {
|
||||||
|
if (n < 128) {
|
||||||
|
names[n] = strdup(name);
|
||||||
|
/* Build full path to test dir */
|
||||||
|
char full[PATH_MAX];
|
||||||
|
if (snprintf(full, sizeof(full), "%s/%s", dirpath, name) >= 0) {
|
||||||
|
isdir[n] = path_is_dir(full);
|
||||||
|
} else {
|
||||||
|
isdir[n] = 0;
|
||||||
|
}
|
||||||
|
n++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
closedir(d);
|
||||||
|
|
||||||
|
if (n == 0) {
|
||||||
|
editor_set_status("No file matches '%s' in %s", base, dirpath);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Compute LCP across matches */
|
||||||
|
size_t lcp = strlen(names[0]);
|
||||||
|
for (int i = 1; i < n; i++) {
|
||||||
|
size_t k = str_lcp2(names[0], names[i]);
|
||||||
|
if (k < lcp) lcp = k;
|
||||||
|
if (lcp == 0) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Build new buffer string: dirpath + '/' (if not root and present) + completion */
|
||||||
|
char newbuf[PATH_MAX];
|
||||||
|
newbuf[0] = '\0';
|
||||||
|
if (slash) {
|
||||||
|
/* Preserve original directory portion including trailing slash */
|
||||||
|
size_t dlen = (size_t)(slash - buf);
|
||||||
|
if (dlen >= sizeof(newbuf)) dlen = sizeof(newbuf) - 1;
|
||||||
|
memcpy(newbuf, buf, dlen);
|
||||||
|
newbuf[dlen] = '\0';
|
||||||
|
strncat(newbuf, "/", sizeof(newbuf) - strlen(newbuf) - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The part to append is: if unique -> full name (+ '/' if dir), else current base extended to LCP */
|
||||||
|
if (n == 1) {
|
||||||
|
strncat(newbuf, names[0], sizeof(newbuf) - strlen(newbuf) - 1);
|
||||||
|
if (isdir[0]) {
|
||||||
|
strncat(newbuf, "/", sizeof(newbuf) - strlen(newbuf) - 1);
|
||||||
|
}
|
||||||
|
/* Replace buffer */
|
||||||
|
strncpy(buf, newbuf[0] ? newbuf : names[0], 127);
|
||||||
|
buf[127] = '\0';
|
||||||
|
editor_set_status("Unique match: %s%s", names[0], isdir[0] ? "/" : "");
|
||||||
|
} else {
|
||||||
|
/* Extend to LCP */
|
||||||
|
char ext[256];
|
||||||
|
size_t cur = strlen(base);
|
||||||
|
if (lcp > cur) {
|
||||||
|
size_t to_copy = lcp - cur;
|
||||||
|
if (to_copy >= sizeof(ext)) to_copy = sizeof(ext) - 1;
|
||||||
|
memcpy(ext, names[0] + cur, to_copy);
|
||||||
|
ext[to_copy] = '\0';
|
||||||
|
/* Always start from current base prefix */
|
||||||
|
strncat(newbuf, base, sizeof(newbuf) - strlen(newbuf) - 1);
|
||||||
|
strncat(newbuf, ext, sizeof(newbuf) - strlen(newbuf) - 1);
|
||||||
|
} else {
|
||||||
|
/* No extension possible, keep base as-is */
|
||||||
|
strncat(newbuf, base, sizeof(newbuf) - strlen(newbuf) - 1);
|
||||||
|
}
|
||||||
|
strncpy(buf, newbuf, 127);
|
||||||
|
buf[127] = '\0';
|
||||||
|
|
||||||
|
/* Show candidates */
|
||||||
|
char msg[80];
|
||||||
|
size_t used = 0;
|
||||||
|
used += snprintf(msg + used, sizeof(msg) - used, "%d matches: ", n);
|
||||||
|
for (int i = 0; i < n && used < sizeof(msg) - 1; i++) {
|
||||||
|
used += snprintf(msg + used, sizeof(msg) - used, "%s%s%s",
|
||||||
|
(i ? ", " : ""), names[i], isdir[i] ? "/" : "");
|
||||||
|
}
|
||||||
|
editor_set_status("%s", msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Free duplicated names */
|
||||||
|
for (int i = 0; i < n; i++) free((void*)names[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Close the current buffer. If dirty, require confirmation (press C-k c twice) */
|
||||||
void
|
/* buffer_close_current now implemented in buffer.c */
|
||||||
ab_prependch(abuf *buf, const char c)
|
|
||||||
{
|
|
||||||
ab_prepend(buf, &c, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void
|
/* abuf implementations moved to abuf.c */
|
||||||
ab_prepend(abuf *buf, const char *s, const size_t len)
|
|
||||||
{
|
|
||||||
char *nc = realloc(buf->b, buf->size + len);
|
|
||||||
assert(nc != NULL);
|
|
||||||
|
|
||||||
memmove(nc + len, nc, buf->size);
|
|
||||||
memcpy(nc, s, len);
|
|
||||||
|
|
||||||
buf->b = nc;
|
|
||||||
buf->size += len;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void
|
|
||||||
ab_free(abuf *buf)
|
|
||||||
{
|
|
||||||
free(buf->b);
|
|
||||||
buf->b = NULL;
|
|
||||||
buf->size = 0;
|
|
||||||
buf->cap = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
char
|
char
|
||||||
@@ -1001,55 +1034,6 @@ delete_region(void)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void
|
|
||||||
kwrite(const int fd, const char* buf, const int len)
|
|
||||||
{
|
|
||||||
int wlen = 0;
|
|
||||||
|
|
||||||
wlen = write(fd, buf, len);
|
|
||||||
assert(wlen != -1);
|
|
||||||
assert(wlen == len);
|
|
||||||
if (wlen == -1) {
|
|
||||||
abort();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void
|
|
||||||
die(const char* s)
|
|
||||||
{
|
|
||||||
kwrite(STDOUT_FILENO, "\x1b[2J", 4);
|
|
||||||
kwrite(STDOUT_FILENO, "\x1b[H", 3);
|
|
||||||
|
|
||||||
perror(s);
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* get_winsz uses the TIOCGWINSZ to get the window size.
|
|
||||||
*
|
|
||||||
* there's a fallback way to do this, too, that involves moving the
|
|
||||||
* cursor down and to the left \x1b[999C\x1b[999B. I'm going to skip
|
|
||||||
* on this for now because it's bloaty and this works on OpenBSD and
|
|
||||||
* Linux, at least.
|
|
||||||
*/
|
|
||||||
int
|
|
||||||
get_winsz(int *rows, int *cols)
|
|
||||||
{
|
|
||||||
struct winsize ws;
|
|
||||||
|
|
||||||
if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == -1 ||
|
|
||||||
ws.ws_col == 0) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
*cols = ws.ws_col;
|
|
||||||
*rows = ws.ws_row;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void
|
void
|
||||||
jump_to_position(int col, int row)
|
jump_to_position(int col, int row)
|
||||||
{
|
{
|
||||||
@@ -1695,7 +1679,14 @@ char
|
|||||||
|
|
||||||
return buf;
|
return buf;
|
||||||
}
|
}
|
||||||
} else if ((c == TAB_KEY) || (c >= 0x20 && c < 0x7f)) {
|
} else if (c == TAB_KEY) {
|
||||||
|
/* invoke completion callback without inserting a TAB */
|
||||||
|
if (cb) {
|
||||||
|
cb(buf, c);
|
||||||
|
}
|
||||||
|
/* keep buflen in sync in case callback edited buf */
|
||||||
|
buflen = strlen(buf);
|
||||||
|
} else if (c >= 0x20 && c < 0x7f) {
|
||||||
if (buflen == bufsz - 1) {
|
if (buflen == bufsz - 1) {
|
||||||
bufsz *= 2;
|
bufsz *= 2;
|
||||||
buf = realloc(buf, bufsz);
|
buf = realloc(buf, bufsz);
|
||||||
@@ -1709,6 +1700,8 @@ char
|
|||||||
|
|
||||||
if (cb) {
|
if (cb) {
|
||||||
cb(buf, c);
|
cb(buf, c);
|
||||||
|
/* keep buflen in sync with any changes the callback made */
|
||||||
|
buflen = strlen(buf);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1843,13 +1836,17 @@ editor_openfile(void)
|
|||||||
{
|
{
|
||||||
char *filename;
|
char *filename;
|
||||||
|
|
||||||
/* TODO(kyle): combine with dirutils for tab-completion */
|
/* Add TAB completion for path input */
|
||||||
filename = editor_prompt("Load file: %s", NULL);
|
filename = editor_prompt("Load file: %s", file_open_prompt_cb);
|
||||||
if (filename == NULL) {
|
if (filename == NULL) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Open into a new buffer */
|
||||||
|
int nb = buffer_add_empty();
|
||||||
|
buffer_switch(nb);
|
||||||
open_file(filename);
|
open_file(filename);
|
||||||
|
buffer_save_current();
|
||||||
free(filename);
|
free(filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2156,9 +2153,8 @@ process_kcommand(int16_t c)
|
|||||||
editor_set_status("Jumped to mark");
|
editor_set_status("Jumped to mark");
|
||||||
break;
|
break;
|
||||||
case 'c':
|
case 'c':
|
||||||
len = editor.killring->size;
|
/* Close current buffer (was kill ring clear; that moved to C-k f) */
|
||||||
killring_flush();
|
buffer_close_current();
|
||||||
editor_set_status("Kill ring cleared (%d characters)", len);
|
|
||||||
break;
|
break;
|
||||||
case 'd':
|
case 'd':
|
||||||
if (editor.curx == 0 && cursor_at_eol()) {
|
if (editor.curx == 0 && cursor_at_eol()) {
|
||||||
@@ -2196,8 +2192,21 @@ process_kcommand(int16_t c)
|
|||||||
}
|
}
|
||||||
editor_openfile();
|
editor_openfile();
|
||||||
break;
|
break;
|
||||||
case 'f':
|
case 'f': {
|
||||||
editor_find();
|
/* Rebound: clear kill ring on C-k f */
|
||||||
|
len = editor.killring ? (int)editor.killring->size : 0;
|
||||||
|
killring_flush();
|
||||||
|
editor_set_status("Kill ring cleared (%d characters)", len);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'n':
|
||||||
|
buffer_next();
|
||||||
|
break;
|
||||||
|
case 'p':
|
||||||
|
buffer_prev();
|
||||||
|
break;
|
||||||
|
case 'b':
|
||||||
|
buffer_switch_by_name();
|
||||||
break;
|
break;
|
||||||
case 'g':
|
case 'g':
|
||||||
goto_line();
|
goto_line();
|
||||||
@@ -2511,9 +2520,9 @@ char
|
|||||||
*get_cloc_code_lines(const char *filename)
|
*get_cloc_code_lines(const char *filename)
|
||||||
{
|
{
|
||||||
char command[512];
|
char command[512];
|
||||||
char buffer[256];
|
char outbuf[256];
|
||||||
char *result = NULL;
|
char *result = NULL;
|
||||||
FILE* pipe = NULL;
|
FILE *pipe = NULL;
|
||||||
size_t len = 0;
|
size_t len = 0;
|
||||||
|
|
||||||
if (editor.filename == NULL) {
|
if (editor.filename == NULL) {
|
||||||
@@ -2542,20 +2551,20 @@ char
|
|||||||
pipe = popen(command, "r");
|
pipe = popen(command, "r");
|
||||||
if (!pipe) {
|
if (!pipe) {
|
||||||
snprintf(command, sizeof(command), "Error getting LOC: %s", strerror(errno));
|
snprintf(command, sizeof(command), "Error getting LOC: %s", strerror(errno));
|
||||||
result = (char*)malloc(sizeof(buffer) + 1);
|
result = (char*)malloc(sizeof(outbuf) + 1);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fgets(buffer, sizeof(buffer), pipe) != NULL) {
|
if (fgets(outbuf, sizeof(outbuf), pipe) != NULL) {
|
||||||
len = strlen(buffer);
|
len = strlen(outbuf);
|
||||||
if (len > 0 && buffer[len - 1] == '\n') {
|
if (len > 0 && outbuf[len - 1] == '\n') {
|
||||||
buffer[len - 1] = '\0';
|
outbuf[len - 1] = '\0';
|
||||||
}
|
}
|
||||||
|
|
||||||
result = malloc(strlen(buffer) + 1);
|
result = malloc(strlen(outbuf) + 1);
|
||||||
assert(result != NULL);
|
assert(result != NULL);
|
||||||
if (result) {
|
if (result) {
|
||||||
strcpy(result, buffer);
|
strcpy(result, outbuf);
|
||||||
pclose(pipe);
|
pclose(pipe);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -2588,80 +2597,7 @@ dump_pidfile(void)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/* terminal control moved to term.c */
|
||||||
* A text editor needs the terminal to be in raw mode; but the default
|
|
||||||
* is to be in canonical (cooked) mode, which is a buffered input mode.
|
|
||||||
*/
|
|
||||||
void
|
|
||||||
enable_termraw(void)
|
|
||||||
{
|
|
||||||
struct termios raw;
|
|
||||||
|
|
||||||
/* Read the current terminal parameters for standard input. */
|
|
||||||
if (tcgetattr(STDIN_FILENO, &raw) == -1) {
|
|
||||||
die("tcgetattr while enabling raw mode");
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Put the terminal into raw mode.
|
|
||||||
*/
|
|
||||||
cfmakeraw(&raw);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Set timeout for read(2).
|
|
||||||
*
|
|
||||||
* VMIN: what is the minimum number of bytes required for read
|
|
||||||
* to return?
|
|
||||||
*
|
|
||||||
* VTIME: max time before read(2) returns in hundreds of milli-
|
|
||||||
* seconds.
|
|
||||||
*/
|
|
||||||
raw.c_cc[VMIN] = 0;
|
|
||||||
raw.c_cc[VTIME] = 1;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Now write the terminal parameters to the current terminal,
|
|
||||||
* after flushing any waiting input out.
|
|
||||||
*/
|
|
||||||
if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw) == -1) {
|
|
||||||
die("tcsetattr while enabling raw mode");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void
|
|
||||||
display_clear(abuf *ab)
|
|
||||||
{
|
|
||||||
if (ab == NULL) {
|
|
||||||
kwrite(STDOUT_FILENO, ESCSEQ "2J", 4);
|
|
||||||
kwrite(STDOUT_FILENO, ESCSEQ "H", 3);
|
|
||||||
} else {
|
|
||||||
ab_append(ab, ESCSEQ "2J", 4);
|
|
||||||
ab_append(ab, ESCSEQ "H", 3);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void
|
|
||||||
disable_termraw(void)
|
|
||||||
{
|
|
||||||
display_clear(NULL);
|
|
||||||
|
|
||||||
if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &editor.entry_term) == -1) {
|
|
||||||
die("couldn't disable terminal raw mode");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void
|
|
||||||
setup_terminal(void)
|
|
||||||
{
|
|
||||||
if (tcgetattr(STDIN_FILENO, &editor.entry_term) == -1) {
|
|
||||||
die("can't snapshot terminal settings");
|
|
||||||
}
|
|
||||||
|
|
||||||
enable_termraw();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -3012,12 +2948,11 @@ install_signal_handlers(void)
|
|||||||
int
|
int
|
||||||
main(int argc, char *argv[])
|
main(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
char *fname = NULL;
|
|
||||||
char *lnarg = NULL;
|
|
||||||
int lineno = 0;
|
|
||||||
int opt;
|
int opt;
|
||||||
int debug = 0;
|
int debug = 0;
|
||||||
int jump = 0;
|
/* argv processing for multiple files and +lineno */
|
||||||
|
int pending_line = 0; /* line number that applies to next filename */
|
||||||
|
int first_loaded = 0; /* whether we've opened the first file into initial buffer */
|
||||||
|
|
||||||
install_signal_handlers();
|
install_signal_handlers();
|
||||||
|
|
||||||
@@ -3027,7 +2962,7 @@ main(int argc, char *argv[])
|
|||||||
debug = 1;
|
debug = 1;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
fprintf(stderr, "Usage: ke [-d] [-f logfile] [path]\n");
|
fprintf(stderr, "Usage: ke [-d] [-f logfile] [ +N ] [file ...]\n");
|
||||||
exit(EXIT_FAILURE);
|
exit(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3043,32 +2978,45 @@ main(int argc, char *argv[])
|
|||||||
setup_terminal();
|
setup_terminal();
|
||||||
init_editor();
|
init_editor();
|
||||||
|
|
||||||
if (argc > 0) {
|
/* Process remaining argv: accept multiple filenames; a "+N" applies to next filename */
|
||||||
fname = argv[0];
|
for (int i = 0; i < argc; i++) {
|
||||||
|
const char *arg = argv[i];
|
||||||
|
if (arg[0] == '+') {
|
||||||
|
/* parse line number; if invalid, set to 0 (ignored) */
|
||||||
|
const char *p = arg + 1;
|
||||||
|
int v = 0;
|
||||||
|
if (*p != '\0') {
|
||||||
|
v = atoi(p);
|
||||||
|
if (v < 1) v = 0;
|
||||||
|
}
|
||||||
|
pending_line = v;
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (argc > 1) {
|
/* It's a filename */
|
||||||
lnarg = argv[0];
|
if (!first_loaded) {
|
||||||
fname = argv[1];
|
/* initial empty buffer already exists; load into it */
|
||||||
if (lnarg[0] == '+') {
|
open_file(arg);
|
||||||
lnarg++;
|
if (pending_line > 0) {
|
||||||
|
jump_to_position(0, pending_line - 1);
|
||||||
|
pending_line = 0;
|
||||||
}
|
}
|
||||||
lineno = atoi(lnarg);
|
buffer_save_current();
|
||||||
jump = 1;
|
first_loaded = 1;
|
||||||
|
} else {
|
||||||
|
/* create and switch to a new buffer for subsequent files */
|
||||||
|
int nb = buffer_add_empty();
|
||||||
|
buffer_switch(nb);
|
||||||
|
open_file(arg);
|
||||||
|
if (pending_line > 0) {
|
||||||
|
jump_to_position(0, pending_line - 1);
|
||||||
|
pending_line = 0;
|
||||||
|
}
|
||||||
|
buffer_save_current();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fname != NULL) {
|
|
||||||
open_file(fname);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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");
|
||||||
if (jump) {
|
|
||||||
if (lineno < 1) {
|
|
||||||
editor_set_status("Invalid line number %s", lnarg);
|
|
||||||
} else {
|
|
||||||
jump_to_position(0, lineno - 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
display_clear(NULL);
|
display_clear(NULL);
|
||||||
loop();
|
loop();
|
||||||
|
|||||||
86
term.c
Normal file
86
term.c
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
#include <assert.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <termios.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <sys/ioctl.h>
|
||||||
|
|
||||||
|
#include "abuf.h"
|
||||||
|
#include "core.h"
|
||||||
|
#include "term.h"
|
||||||
|
|
||||||
|
#define ESCSEQ "\x1b["
|
||||||
|
|
||||||
|
|
||||||
|
static struct termios saved_entry_term;
|
||||||
|
|
||||||
|
|
||||||
|
void
|
||||||
|
enable_termraw(void)
|
||||||
|
{
|
||||||
|
struct termios raw;
|
||||||
|
|
||||||
|
if (tcgetattr(STDIN_FILENO, &raw) == -1) {
|
||||||
|
die("tcgetattr while enabling raw mode");
|
||||||
|
}
|
||||||
|
|
||||||
|
cfmakeraw(&raw);
|
||||||
|
raw.c_cc[VMIN] = 0;
|
||||||
|
raw.c_cc[VTIME] = 1;
|
||||||
|
|
||||||
|
if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw) == -1) {
|
||||||
|
die("tcsetattr while enabling raw mode");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void
|
||||||
|
display_clear(abuf *ab)
|
||||||
|
{
|
||||||
|
if (ab == NULL) {
|
||||||
|
kwrite(STDOUT_FILENO, ESCSEQ "2J", 4);
|
||||||
|
kwrite(STDOUT_FILENO, ESCSEQ "H", 3);
|
||||||
|
} else {
|
||||||
|
ab_append(ab, ESCSEQ "2J", 4);
|
||||||
|
ab_append(ab, ESCSEQ "H", 3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void
|
||||||
|
disable_termraw(void)
|
||||||
|
{
|
||||||
|
display_clear(NULL);
|
||||||
|
|
||||||
|
if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &saved_entry_term) == -1) {
|
||||||
|
die("couldn't disable terminal raw mode");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void
|
||||||
|
setup_terminal(void)
|
||||||
|
{
|
||||||
|
if (tcgetattr(STDIN_FILENO, &saved_entry_term) == -1) {
|
||||||
|
die("can't snapshot terminal settings");
|
||||||
|
}
|
||||||
|
|
||||||
|
enable_termraw();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int
|
||||||
|
get_winsz(int *rows, int *cols)
|
||||||
|
{
|
||||||
|
struct winsize ws;
|
||||||
|
|
||||||
|
if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == -1 || ws.ws_col == 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
*cols = ws.ws_col;
|
||||||
|
*rows = ws.ws_row;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
22
term.h
Normal file
22
term.h
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
#ifndef TERM_H
|
||||||
|
#define TERM_H
|
||||||
|
|
||||||
|
#include "abuf.h"
|
||||||
|
|
||||||
|
/* Terminal control/setup API */
|
||||||
|
void enable_termraw(void);
|
||||||
|
void disable_termraw(void);
|
||||||
|
void setup_terminal(void);
|
||||||
|
void display_clear(abuf *ab);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* get_winsz uses the TIOCGWINSZ to get the window size.
|
||||||
|
*
|
||||||
|
* there's a fallback way to do this, too, that involves moving the
|
||||||
|
* cursor down and to the left \x1b[999C\x1b[999B. I'm going to skip
|
||||||
|
* on this for now because it's bloaty and this works on OpenBSD and
|
||||||
|
* Linux, at least.
|
||||||
|
*/
|
||||||
|
int get_winsz(int *rows, int *cols);
|
||||||
|
|
||||||
|
#endif /* TERM_H */
|
||||||
104
undo.c
Normal file
104
undo.c
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
#include "abuf.h"
|
||||||
|
#include "undo.h"
|
||||||
|
|
||||||
|
|
||||||
|
undo_node
|
||||||
|
undo_node_new(undo_kind kind)
|
||||||
|
{
|
||||||
|
undo_node *node = NULL;
|
||||||
|
|
||||||
|
node = (undo_node *)malloc(sizeof(undo_node));
|
||||||
|
assert(node != NULL);
|
||||||
|
|
||||||
|
node->kind = kind;
|
||||||
|
node->row = node->col = 0;
|
||||||
|
|
||||||
|
abuf_init(node->text);
|
||||||
|
|
||||||
|
node->next = NULL;
|
||||||
|
node->parent = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void
|
||||||
|
undo_node_free(undo_node *node)
|
||||||
|
{
|
||||||
|
undo_node *next = NULL;
|
||||||
|
|
||||||
|
if (node == NULL) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
abuf_free(node-text);
|
||||||
|
next = node->next;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void
|
||||||
|
undo_node_free_all(undo_node *node)
|
||||||
|
{
|
||||||
|
undo_node *next = NULL;
|
||||||
|
|
||||||
|
if (node == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (node != NULL) {
|
||||||
|
undo_node_free(node);
|
||||||
|
free(node);
|
||||||
|
node = node->next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void
|
||||||
|
undo_tree_init(undo_tree *tree)
|
||||||
|
{
|
||||||
|
assert(tree != NULL);
|
||||||
|
|
||||||
|
tree->root = NULL;
|
||||||
|
tree->current = NULL;
|
||||||
|
tree->pending = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void
|
||||||
|
undo_tree_free(undo_tree *tree)
|
||||||
|
{
|
||||||
|
assert(tree == NULL);
|
||||||
|
|
||||||
|
undo_node_free(tree->pending);
|
||||||
|
undo_node_free_all(tree->root);
|
||||||
|
undo_tree_init(tree);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void
|
||||||
|
undo_begin(undo_tree *tree, undo_kind kind)
|
||||||
|
{
|
||||||
|
undo_node *pending = NULL;
|
||||||
|
|
||||||
|
if (tree->pending != NULL) {
|
||||||
|
if (tree->pending->kind == kind) {
|
||||||
|
/* don't initiate a new undo sequence if it's the same kind */
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
undo_commit(tree);
|
||||||
|
}
|
||||||
|
|
||||||
|
pending = undo_new_new(kind);
|
||||||
|
assert(pending != NULL);
|
||||||
|
|
||||||
|
tree->pending = pending;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void undo_prepend(abuf *buf);
|
||||||
|
void undo_append(buf *buf);
|
||||||
|
void undo_prependch(char c);
|
||||||
|
void undo_appendch(char c);
|
||||||
|
void undo_commit(void);
|
||||||
|
void undo_apply(undo_node *node);
|
||||||
|
void editor_undo(void);
|
||||||
|
void editor_redo(void);
|
||||||
|
|
||||||
44
undo.h
Normal file
44
undo.h
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
#ifndef KE_UNDO
|
||||||
|
#define KE_UNDO
|
||||||
|
|
||||||
|
|
||||||
|
typedef enum undo_kind {
|
||||||
|
UNDO_INSERT = 1 << 0,
|
||||||
|
UNDO_UNKNOWN = 1 << 1,
|
||||||
|
} undo_kind;
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct undo_node {
|
||||||
|
undo_kind op;
|
||||||
|
size_t row, col;
|
||||||
|
abuf text;
|
||||||
|
|
||||||
|
struct undo_node *next;
|
||||||
|
struct undo_node *parent;
|
||||||
|
|
||||||
|
} undo_node;
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct undo_tree {
|
||||||
|
undo_node *root; /* the start of the undo sequence */
|
||||||
|
undo_node *current; /* where we are currently at */
|
||||||
|
undo_node *pending; /* the current undo operations being built */
|
||||||
|
} undo_tree;
|
||||||
|
|
||||||
|
|
||||||
|
undo_node *undo_node_new(undo_kind kind);
|
||||||
|
void undo_node_free(undo_node *node);
|
||||||
|
void undo_tree_init(undo_tree *tree);
|
||||||
|
void undo_tree_free(undo_tree *tree);
|
||||||
|
void undo_begin(undo_tree *tree, undo_kind kind);
|
||||||
|
void undo_prepend(abuf *buf);
|
||||||
|
void undo_append(buf *buf);
|
||||||
|
void undo_prependch(char c);
|
||||||
|
void undo_appendch(char c);
|
||||||
|
void undo_commit(undo_tree *tree);
|
||||||
|
void undo_apply(undo_node *node);
|
||||||
|
void editor_undo(void);
|
||||||
|
void editor_redo(void);
|
||||||
|
|
||||||
|
|
||||||
|
#endif
|
||||||
Reference in New Issue
Block a user