19 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
734eb6e67d ke version 2.0
Some checks failed
Release / Bump Homebrew formula (push) Has been cancelled
- with multiple buffers
- some handy tab completion
- lots of cleanups
2025-11-28 02:52:24 -08:00
a0d760c7d2 Checkpoint file split. 2025-11-28 02:38:54 -08:00
4db6077738 Splitting into separate files. 2025-11-28 00:29:52 -08:00
d9777c9f02 Remove erow. 2025-11-27 18:40:18 -08:00
d9c5a6696e Fix ESC+BKSP.
Some checks failed
Release / Bump Homebrew formula (push) Has been cancelled
2025-11-27 14:04:55 -08:00
5c2571eba7 C-l should update window size.
Noticed this when splitting the screen in tmux.
2025-11-27 14:00:07 -08:00
9afd030b87 fix jump bug 2025-11-27 13:15:10 -08:00
2f198e611e less-compatible paging 2025-11-27 13:08:41 -08:00
e079726ced fix check for jump
Some checks failed
Release / Bump Homebrew formula (push) Has been cancelled
2025-11-27 13:02:18 -08:00
23 changed files with 4335 additions and 2993 deletions

View File

@@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.15)
project(ke C) # Specify C language explicitly
set(CMAKE_C_STANDARD 99)
set(KE_VERSION "1.5.4")
set(KE_VERSION "2.1.1")
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")
@@ -19,8 +19,34 @@ endif()
include(GNUInstallDirs)
set(SOURCES
abuf.c
core.c
term.c
buffer.c
editor.c
editing.c
killring.c
process.c
undo.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 main.c)
add_executable(ke ${SOURCES} ${HEADERS})
target_compile_definitions(ke PRIVATE KE_VERSION="ke version ${KE_VERSION}")
install(TARGETS ke RUNTIME DESTINATION bin)
install(FILES ke.1 TYPE MAN)

View File

@@ -11,8 +11,13 @@ LDFLAGS := -fsanitize=address
all: $(TARGET) test.txt
$(TARGET): main.c
$(CC) $(CFLAGS) -o $(TARGET) $(LDFLAGS) main.c
SRCS := main.c abuf.c core.c term.c buffer.c editor.c editing.c killring.c \
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)
$(CC) $(CFLAGS) -o $(TARGET) $(SRCS) $(LDFLAGS)
.PHONY: install
#install: $(TARGET)
@@ -31,3 +36,7 @@ test.txt:
gdb:
@test -f $(TARGET).pid || (echo "error: $(TARGET).pid not found" >&2; exit 1)
@gdb -p $$(cat $(TARGET).pid | tr -d ' \t\n\r') ./$(TARGET)
.PHONY: cloc
cloc:
cloc $(SRCS) $(HDRS)

131
abuf.c Normal file
View File

@@ -0,0 +1,131 @@
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include "abuf.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
ab_init(abuf *buf)
{
assert(buf != NULL);
buf->b = NULL;
buf->size = buf->cap = 0;
}
void
ab_init_cap(abuf *buf, const size_t cap)
{
ab_init(buf);
if (cap > 0) {
ab_resize(buf, cap);
}
}
void
ab_resize(abuf *buf, size_t cap)
{
char *newbuf = NULL;
cap = cap_growth(buf->cap, cap) + 1;
newbuf = realloc(buf->b, cap);
assert(newbuf != NULL);
buf->cap = cap;
buf->b = newbuf;
}
void
ab_appendch(abuf *buf, char c)
{
abuf_grow(buf, 1);
ab_append(buf, &c, 1);
}
void
ab_append(abuf *buf, const char *s, size_t len)
{
char *nc = NULL;
abuf_grow(buf, len);
nc = buf->b;
memcpy(&nc[buf->size], s, len);
buf->b = nc;
buf->size += 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
ab_prependch(abuf *buf, const char c)
{
abuf_grow(buf, 1);
ab_prepend(buf, &c, 1);
}
void
ab_prepend(abuf *buf, const char *s, const size_t len)
{
char *nc = NULL;
abuf_grow(buf, len);
nc = buf->b;
assert(nc != NULL);
memmove(nc + len, nc, buf->size);
memcpy(nc, s, len);
buf->b = nc;
buf->size += 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
ab_free(abuf *buf)
{
free(buf->b);
buf->b = NULL;
buf->size = 0;
buf->cap = 0;
}

32
abuf.h Normal file
View File

@@ -0,0 +1,32 @@
/*
* abuf.h - append/prepend buffer utilities
*/
#ifndef KE_ABUF_H
#define KE_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_init_cap(abuf *buf, size_t cap);
void ab_resize(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_append_ab(abuf *buf, const abuf *other);
void ab_prependch(abuf *buf, const char c);
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);
#endif

463
buffer.c Normal file
View File

@@ -0,0 +1,463 @@
/* buffer.c - multiple file buffers */
#include <assert.h>
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "abuf.h"
#include "buffer.h"
#include "core.h"
#include "editor.h"
#define NO_NAME "[No Name]"
/* externs from other modules */
char *editor_prompt(const 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;
size_t i = 0;
if (name == NULL) {
return -1;
}
for (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 i = 0;
size_t plen = (prefix ? strlen(prefix) : 0);
for (i = 0; i < editor.bufcount; i++) {
matched = 0;
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 != TAB_KEY) {
return;
}
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);
}
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 = 0;
editor.bufcap = 0;
idx = buffer_add_empty();
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
buffer_add_empty(void)
{
buffer *buf = NULL;
int idx = 0;
buffer_list_resize();
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;
undo_tree_init(&buf->undo);
editor.buffers[editor.bufcount] = buf;
idx = (int)editor.bufcount;
editor.bufcount++;
return idx;
}
buffer *
buffer_current(void)
{
if (editor.bufcount == 0 || editor.curbuf >= editor.bufcount) {
return NULL;
}
return editor.buffers[editor.curbuf];
}
int
buffer_is_unnamed_and_empty(const buffer *b)
{
if (b == NULL) {
return 0;
}
if (b->filename != NULL) {
return 0;
}
if (b->dirty) {
return 0;
}
if (b->nrows != 0) {
return 0;
}
if (b->row != NULL) {
return 0;
}
return 1;
}
void
buffer_switch(const int idx)
{
buffer *b = NULL;
if (idx < 0 || (size_t)idx >= editor.bufcount) {
return;
}
if (editor.curbuf == (size_t)idx) {
return;
}
b = editor.buffers[idx];
editor.curbuf = (size_t)idx;
editor.dirtyex = 1;
editor_set_status("Switched to buffer %d: %s", editor.curbuf, buffer_name(b));
}
void
buffer_next(void)
{
size_t idx = 0;
if (editor.bufcount <= 1) {
return;
}
idx = (editor.curbuf + 1) % editor.bufcount;
buffer_switch((int)idx);
}
void
buffer_prev(void)
{
size_t idx = 0;
if (editor.bufcount <= 1) {
return;
}
idx = (editor.curbuf == 0) ? (editor.bufcount - 1) : (editor.curbuf - 1);
buffer_switch((int)idx);
}
void
buffer_close_current(void)
{
buffer *b = NULL;
size_t closing = 0;
int target = 0;
int nb = 0;
/* sanity check */
if (editor.bufcount == 0 || editor.curbuf >= editor.bufcount) {
editor_set_status("No buffer to close.");
return;
}
closing = editor.curbuf;
if (editor.bufcount > 1) {
target = (closing > 0) ? (int) (closing - 1) : (int) (closing + 1);
buffer_switch(target);
} else {
nb = buffer_add_empty();
buffer_switch(nb);
}
b = editor.buffers[closing];
if (b) {
if (b->row) {
for (size_t 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 = 0;
} 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;
if (editor.bufcount <= 1) {
editor_set_status("No other buffers.");
return;
}
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 open buffer: %s", name);
}
free(name);
}

50
buffer.h Normal file
View File

@@ -0,0 +1,50 @@
#ifndef KE_BUFFER_H
#define KE_BUFFER_H
#include "abuf.h"
#include "undo.h"
typedef struct buffer {
size_t curx, cury;
size_t rx;
size_t nrows;
size_t rowoffs, coloffs;
abuf *row;
char *filename;
int dirty;
int mark_set;
size_t mark_curx, mark_cury;
undo_tree undo;
} buffer;
buffer *buffer_current(void);
#define CURBUF (buffer_current())
#define EROW (CURBUF->row)
#define ENROWS (CURBUF->nrows)
#define ECURX (CURBUF->curx)
#define ECURY (CURBUF->cury)
#define ERX (CURBUF->rx)
#define EROWOFFS (CURBUF->rowoffs)
#define ECOLOFFS (CURBUF->coloffs)
#define EFILENAME (CURBUF->filename)
#define EDIRTY (CURBUF->dirty)
#define EMARK_SET (CURBUF->mark_set)
#define EMARK_CURX (CURBUF->mark_curx)
#define EMARK_CURY (CURBUF->mark_cury)
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);
int buffer_is_unnamed_and_empty(const buffer *b);
#endif

151
core.c Normal file
View File

@@ -0,0 +1,151 @@
#include <sys/stat.h>
#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
int
path_is_dir(const char *path)
{
struct stat st;
if (path == NULL) {
return 0;
}
if (stat(path, &st) == 0) {
return S_ISDIR(st.st_mode);
}
return 0;
}
size_t
str_lcp2(const char *a, const char *b)
{
size_t i = 0;
if (!a || !b) {
return 0;
}
while (a[i] && b[i] && a[i] == b[i]) {
i++;
}
return i;
}
void
swap_size_t(size_t *first, size_t *second)
{
*first ^= *second;
*second ^= *first;
*first ^= *second;
}
int
next_power_of_2(int n)
{
if (n < 2) {
n = 2;
}
n--;
n |= n >> 1;
n |= n >> 2;
n |= n >> 4;
n |= n >> 8;
n |= n >> 16;
return n + 1;
}
int
cap_growth(int cap, int sz)
{
if (cap == 0) {
cap = INITIAL_CAPACITY;
}
while (cap <= sz) {
cap = next_power_of_2(cap + 1);
}
return cap;
}
size_t
kstrnlen(const char *buf, const size_t max)
{
if (buf == NULL) {
return 0;
}
return strnlen(buf, max);
}
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);
}

52
core.h Normal file
View File

@@ -0,0 +1,52 @@
#ifndef KE_CORE_H
#define KE_CORE_H
#include <stddef.h>
#ifndef KE_VERSION
#define KE_VERSION "ke dev build"
#endif
#define ESCSEQ "\x1b["
#define CTRL_KEY(key) ((key)&0x1f)
#define TAB_STOP 8
#define MSG_TIMEO 3
#define TAB_STOP 8
#define INITIAL_CAPACITY 8
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
int path_is_dir(const char *path);
size_t str_lcp2(const char *a, const char *b);
void swap_size_t(size_t *first, size_t *second);
int next_power_of_2(int n);
int cap_growth(int cap, int sz);
size_t kstrnlen(const char *buf, size_t max);
void kwrite(int fd, const char *buf, int len);
void die(const char *s);
#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

110
editor.c Normal file
View File

@@ -0,0 +1,110 @@
/* editor.c - editor-wide state and functions */
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "abuf.h"
#include "buffer.h"
#include "core.h"
#include "editor.h"
#include "term.h"
/*
* Global editor instance
*/
struct editor editor = {
.cols = 0,
.rows = 0,
.mode = 0,
.killring = NULL,
.kill = 0,
.no_kill = 0,
.dirtyex = 0,
.uarg = 0,
.ucount = 0,
.msgtm = 0,
.buffers = NULL,
.bufcount = 0,
.curbuf = 0,
.bufcap = 0,
};
void
editor_set_status(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
vsnprintf(editor.msg, sizeof(editor.msg), fmt, ap);
va_end(ap);
editor.msgtm = time(NULL);
}
void
init_editor(void)
{
editor.cols = 0;
editor.rows = 0;
if (get_winsz(&editor.rows, &editor.cols) == -1) {
die("can't get window size");
}
editor.rows--; /* status bar */
editor.rows--; /* message line */
/* don't clear out the kill ring:
* killing / yanking across files is helpful, and killring
* is initialized to NULL at program start.
*/
editor.kill = 0;
editor.no_kill = 0;
editor.msg[0] = '\0';
editor.msgtm = 0;
/* initialize buffer system on first init */
if (editor.buffers == NULL && editor.bufcount == 0) {
editor.bufcap = 0;
buffers_init();
}
}
void
reset_editor(void)
{
buffer *b = buffer_current();
if (b == NULL) {
return;
}
if (b->row) {
for (size_t i = 0; i < b->nrows; i++) {
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;
}

35
editor.h Normal file
View File

@@ -0,0 +1,35 @@
#ifndef KE_EDITOR_H
#define KE_EDITOR_H
#include <termios.h>
#include <time.h>
#include "abuf.h"
#include "buffer.h"
struct editor {
size_t rows, cols;
int mode;
abuf *killring;
int kill; /* KILL CHAIN (\m/) */
int no_kill; /* don't kill in delete_row */
int dirtyex;
char msg[80];
int uarg, ucount; /* C-u support */
time_t msgtm;
struct buffer **buffers; /* array of buffers */
size_t bufcount; /* number of buffers */
size_t curbuf; /* current buffer index */
size_t bufcap; /* current buffer capacity */
};
extern struct editor editor;
void editor_set_status(const char *fmt, ...);
void init_editor(void);
void reset_editor(void);
#endif /* KE_EDITOR_H */

13
ke.1
View File

@@ -32,8 +32,11 @@ Toggle the mark.
If the mark is set, unindent the region.
.It C-k =
If the mark is set, indent the region.
.It C-k b
Switch to a buffer.
.It C-k c
Clear (flush) the kill ring.
Close the current buffer. If no other buffers are open, an empty
buffer will be opened. To exit, use C-k q.
.It C-k d
Delete from the cursor to the end of the line.
.It C-k C-d
@@ -41,7 +44,7 @@ Delete the entire line.
.It C-k e
Edit a new file. Also C-k C-e.
.It C-k f
Incremental find.
Flush the kill ring.
.It C-k g
Go to a specific line.
.It C-k j
@@ -50,6 +53,8 @@ Jump to the mark.
List the number of lines of code in a saved file.
.It C-k m
Run make(1).
.It C-k p
Switch to the next buffer.
.It C-k q
Exit the editor. If the file has unsaved changes,
a warning will be printed; a second C-k q will exit.
@@ -60,9 +65,9 @@ Reload the current buffer from disk.
.It C-k s
Save the file, prompting for a filename if needed. Also C-k C-s.
.It C-k u
Undo changes.
Undo changes (not implemented; marking this k-command as taken).
.It C-k U
Redo changes.
Redo changes (not implemented; marking this k-command as taken).
.It C-k x
save the file and exit. Also C-k C-x.
.It C-k y

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

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

312
term.c Normal file
View File

@@ -0,0 +1,312 @@
#include <sys/ioctl.h>
#include <assert.h>
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <termios.h>
#include <unistd.h>
#include "abuf.h"
#include "core.h"
#include "buffer.h"
#include "editing.h"
#include "editor.h"
#include "process.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(size_t *rows, size_t *cols)
{
struct winsize ws;
if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == -1 || ws.ws_col == 0) {
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 ");
}
rlen = snprintf(rstatus,
sizeof(rstatus),
"L%lu/%lu C%lu %s",
ECURY + 1,
ENROWS,
ECURX + 1,
mstatus);
ab_append(ab, ESCSEQ "7m", 4);
ab_append(ab, status, len);
while (len < editor.cols) {
if (editor.cols - len == rlen) {
ab_append(ab, rstatus, rlen);
break;
}
ab_append(ab, " ", 1);
len++;
}
ab_append(ab, ESCSEQ "m", 3);
ab_append(ab, "\r\n", 2);
}
void
draw_message_line(abuf *ab)
{
size_t len = strlen(editor.msg);
ab_append(ab, ESCSEQ "K", 3);
if (len > editor.cols) {
len = editor.cols;
}
if (len && time(NULL) - editor.msgtm < MSG_TIMEO) {
ab_append(ab, editor.msg, len);
}
}
void
scroll(void)
{
const abuf *row = NULL;
ERX = 0;
if (ECURY < ENROWS) {
row = &EROW[ECURY];
ERX = erow_render_to_cursor(row, ECURX);
}
if (ECURY < EROWOFFS) {
EROWOFFS = ECURY;
}
if (ECURY >= EROWOFFS + editor.rows) {
EROWOFFS = ECURY - editor.rows + 1;
}
if (ERX < ECOLOFFS) {
ECOLOFFS = ERX;
}
if (ERX >= ECOLOFFS + editor.cols) {
ECOLOFFS = ERX - editor.cols + 1;
}
}
void
display_refresh(void)
{
char buf[32] = {0};
abuf ab = ABUF_INIT;
scroll();
ab_append(&ab, ESCSEQ "?25l", 6);
ab_append(&ab, ESCSEQ "H", 3);
display_clear(&ab);
draw_rows(&ab);
draw_status_bar(&ab);
draw_message_line(&ab);
snprintf(buf,
sizeof(buf),
ESCSEQ "%lu;%luH",
(ECURY - EROWOFFS) + 1,
(ERX - ECOLOFFS) + 1);
ab_append(&ab, buf, kstrnlen(buf, 32));
/* ab_append(&ab, ESCSEQ "1;2H", 7); */
ab_append(&ab, ESCSEQ "?25h", 6);
kwrite(STDOUT_FILENO, ab.b, (int)ab.size);
ab_free(&ab);
}

30
term.h Normal file
View File

@@ -0,0 +1,30 @@
#ifndef KE_TERM_H
#define KE_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);
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.
*
* 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(size_t *rows, size_t *cols);
#endif /* KE_TERM_H */

240
undo.c Normal file
View File

@@ -0,0 +1,240 @@
/*
* undo.c: ke's undo system
*/
#include <assert.h>
#include <stdlib.h>
#include "abuf.h"
#include "buffer.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;
ab_init(&node->text);
node->next = NULL;
node->next = NULL;
return node;
}
void
undo_node_free(undo_node *node)
{
if (node == NULL) {
return;
}
ab_free(&node->text);
}
void
undo_node_free_all(undo_node *node)
{
undo_node *next = NULL;
if (node == NULL) {
return;
}
while (node != NULL) {
next = node->next;
undo_node_free(node);
free(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_node_new(kind);
assert(pending != NULL);
tree->pending = pending;
}
void
undo_prepend(undo_tree *tree, abuf *buf)
{
assert(tree != NULL);
assert(tree->pending != NULL);
ab_prepend_ab(&tree->pending->text, buf);
}
void
undo_append(undo_tree *tree, abuf *buf)
{
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);
}

56
undo.h Normal file
View File

@@ -0,0 +1,56 @@
#include <stddef.h>
#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 {
UNDO_INSERT = 1 << 0,
UNDO_UNKNOWN = 1 << 1,
} undo_kind;
typedef struct undo_node {
undo_kind kind;
size_t row, col;
abuf text;
struct undo_node *next;
struct undo_node *prev;
} 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(undo_tree *tree, abuf *buf);
void undo_append(undo_tree *tree, abuf *buf);
void undo_prependch(undo_tree *tree, char c);
void undo_appendch(undo_tree *tree, char c);
void undo_commit(undo_tree *tree);
int undo_apply(struct buffer *buf, int direction);
int editor_undo(struct buffer *buf);
int editor_redo(struct buffer *buf);
#endif