17 Commits

Author SHA1 Message Date
0110f82705 bump version
Some checks failed
Release / Bump Homebrew formula (push) Has been cancelled
2025-11-23 14:26:17 -08:00
a62a8e50fa find_prev_word and delete_prev_word work. 2025-11-23 14:24:49 -08:00
33e19e7d76 add Makefile for basic build 2025-11-23 11:48:46 -08:00
fd01e3593f delete next word complete, delete prev in progress. 2025-11-23 11:48:35 -08:00
47bbc5339c forward/backward word. 2025-11-23 00:19:25 -08:00
2962a6c92e start word nav 2025-11-22 23:31:49 -08:00
7fa887273b move nix to nixos config 2025-11-22 23:31:49 -08:00
fb02f37512 make ASan a compile time option 2025-11-22 23:27:34 -08:00
1722dbee0b Fix check for render realloc. 2025-11-22 12:50:22 -08:00
14199afeb5 bump version
Some checks failed
Release / Bump Homebrew formula (push) Has been cancelled
2025-11-22 12:36:41 -08:00
9f3558c430 put it in build, not build/bin
Some checks failed
Release / Bump Homebrew formula (push) Has been cancelled
2025-11-22 12:27:02 -08:00
ce64e4637d clean up 2025-11-22 12:15:53 -08:00
36013e42e4 version bump
Some checks failed
Release / Bump Homebrew formula (push) Has been cancelled
2025-11-22 12:13:56 -08:00
dd667c1ef5 revert back to older version with some previous fixes
the ai was getting wild
2025-11-22 12:13:03 -08:00
c9977b0fc0 checkpoint
Some checks failed
Release / Bump Homebrew formula (push) Has been cancelled
2025-11-22 12:00:50 -08:00
dd2c888766 update man page
Some checks failed
Release / Bump Homebrew formula (push) Has been cancelled
2025-11-22 01:48:31 -08:00
2967998893 Update README
Some checks failed
Release / Bump Homebrew formula (push) Has been cancelled
2025-11-22 01:45:59 -08:00
5 changed files with 228 additions and 129 deletions

View File

@@ -2,20 +2,29 @@ cmake_minimum_required(VERSION 3.15)
project(ke C) # Specify C language explicitly project(ke C) # Specify C language explicitly
set(CMAKE_C_STANDARD 99) set(CMAKE_C_STANDARD 99)
set(KE_VERSION "1.0.4") set(KE_VERSION "1.1.0")
set(CMAKE_C_FLAGS "-Wall -Wextra -pedantic -Wshadow -Werror -std=c99 -g") set(CMAKE_C_FLAGS "-Wall -Wextra -pedantic -Wshadow -Werror -std=c99 -g")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -D_DEFAULT_SOURCE -D_XOPEN_SOURCE") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -D_DEFAULT_SOURCE -D_XOPEN_SOURCE")
# Optionally enable AddressSanitizer (ASan)
option(ENABLE_ASAN "Enable AddressSanitizer for builds" OFF)
if (ENABLE_ASAN)
message(STATUS "ASan enabled")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address -fno-omit-frame-pointer")
# Ensure the sanitizer is linked too (especially important on some platforms)
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=address")
endif()
include(GNUInstallDirs)
# Add executable # Add executable
add_executable(ke main.c) add_executable(ke main.c)
# Define KE_VERSION for use in C code (e.g., #define KE_VERSION)
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(FILES ke.1 TYPE MAN)
install(TARGETS ke RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
install(FILES ke.1 DESTINATION ${CMAKE_INSTALL_MANDIR}/man1)
# Set output properties
set_target_properties(ke PROPERTIES
FOLDER bin
RUNTIME_OUTPUT_DIRECTORY bin
)

17
Makefile Normal file
View File

@@ -0,0 +1,17 @@
TARGET := ke
KE_VERSION := devel
CFLAGS := -Wall -Wextra -pedantic -Wshadow -Werror -std=c99 -g
CFLAGS += -D_DEFAULT_SOURCE -D_XOPEN_SOURCE
CFLAGS += -fsanitize=address -fno-omit-frame-pointer
LDFLAGS := -fsanitize=address
all: $(TARGET)
$(TARGET): main.c
$(CC) $(CFLAGS) -o $(TARGET) $(LDFLAGS) main.c
clean:
rm -f $(TARGET)

View File

@@ -6,6 +6,13 @@ used fairly often.
See the man page for more info. See the man page for more info.
It should be available via homebrew, even. It should be available via homebrew, even:
brew tap kisom/homebrew-tap
brew install ke
To get verbose ASAN messages:
export LSAN_OPTIONS=verbosity=1:log_threads=1
Released under an ISC license. Released under an ISC license.

4
ke.1
View File

@@ -24,10 +24,10 @@ saving a file can be done with either C-k s or C-k C-s.
.Bl -tag -width xxxxxxxxxxxx -offset indent .Bl -tag -width xxxxxxxxxxxx -offset indent
.It C-k BACKSPACE .It C-k BACKSPACE
Delete from the cursor to the beginning of the line. Delete from the cursor to the beginning of the line.
.It C-k C-d
Delete the current row.
.It C-k d .It C-k d
Delete from the cursor to the end of the line. Delete from the cursor to the end of the line.
.It C-k C-d
Delete the entire link.
.It C-k e .It C-k e
Edit a new file. Also C-k C-e. Edit a new file. Also C-k C-e.
.It C-k f .It C-k f

288
main.c
View File

@@ -14,7 +14,6 @@
#include <ctype.h> #include <ctype.h>
#include <errno.h> #include <errno.h>
#include <fcntl.h> #include <fcntl.h>
#include <limits.h>
#include <stdarg.h> #include <stdarg.h>
#include <stdint.h> #include <stdint.h>
#include <stdio.h> #include <stdio.h>
@@ -25,6 +24,10 @@
#include <unistd.h> #include <unistd.h>
#ifndef KE_VERSION
#define KE_VERSION "ke dev build"
#endif
#define ESCSEQ "\x1b[" #define ESCSEQ "\x1b["
#define CTRL_KEY(key) ((key)&0x1f) #define CTRL_KEY(key) ((key)&0x1f)
#define TAB_STOP 8 #define TAB_STOP 8
@@ -44,33 +47,35 @@
#define TAB_STOP 8 #define TAB_STOP 8
#define INITIAL_BUFSIZE 64 #define INITIAL_CAPACITY 64
int int
next_power_of_2(int n) next_power_of_2(int n)
{ {
if (n < 2) {
n = 2;
}
n--; n--;
n |= n >> 1; n |= n >> 1;
n |= n >> 2; n |= n >> 2;
n |= n >> 4; n |= n >> 4;
n |= n >> 8; n |= n >> 8;
n |= n >> 16; n |= n >> 16;
return n + 1; return n + 1;
} }
/*
* cap_growth is a generalized strategy to growing buffers.
*/
int int
cap_growth(int cap, int sz) cap_growth(int cap, int sz)
{ {
if (cap == 0) { if (cap == 0) {
return INITIAL_BUFSIZE; cap = INITIAL_CAPACITY;
} }
while (sz < cap) { while (cap <= sz) {
cap = next_power_of_2(cap); cap = next_power_of_2(cap + 1);
} }
return cap; return cap;
@@ -107,16 +112,22 @@ struct erow {
char nibble_to_hex(char c); char nibble_to_hex(char c);
int erow_render_to_cursor(struct erow *row, int cx); int erow_render_to_cursor(struct erow *row, int cx);
int erow_cursor_to_render(struct erow *row, int rx); int erow_cursor_to_render(struct erow *row, int rx);
int erow_init(struct erow *row, int len);
void erow_update(struct erow *row); void erow_update(struct erow *row);
int erow_init(struct erow *row, int len);
void erow_insert(int at, char *s, int len); void erow_insert(int at, char *s, int len);
void erow_free(struct erow *row); void erow_free(struct erow *row);
void editor_set_status(const char *fmt, ...); void editor_set_status(const char *fmt, ...);
/* miscellaneous */ /* miscellaneous */
void die(const char *s); void die(const char *s);
int iswspace(const char c);
int get_winsz(int *rows, int *cols); int get_winsz(int *rows, int *cols);
void goto_line(void); void goto_line(void);
int cursor_at_eol(void);
void find_next_word(void);
void delete_next_word(void);
void find_prev_word(void);
void delete_prev_word(void);
void delete_row(int at); void delete_row(int at);
void row_append_row(struct erow *row, char *s, int len); void row_append_row(struct erow *row, char *s, int len);
void row_insert_ch(struct erow *row, int at, int16_t c); void row_insert_ch(struct erow *row, int at, int16_t c);
@@ -137,7 +148,6 @@ void move_cursor(int16_t c);
void newline(void); void newline(void);
void process_kcommand(int16_t c); void process_kcommand(int16_t c);
void process_normal(int16_t c); void process_normal(int16_t c);
void navonly_escape(int16_t c);
void process_escape(int16_t c); void process_escape(int16_t c);
int process_keypress(void); int process_keypress(void);
void enable_termraw(void); void enable_termraw(void);
@@ -216,7 +226,7 @@ init_editor(void)
editor.rows = 0; editor.rows = 0;
if (get_winsz(&editor.rows, &editor.cols) == -1) { if (get_winsz(&editor.rows, &editor.cols) == -1) {
// die("can't get window size - is this an interactive terminal?"); die("can't get window size");
} }
editor.rows--; /* status bar */ editor.rows--; /* status bar */
editor.rows--; /* message line */ editor.rows--; /* message line */
@@ -237,8 +247,7 @@ init_editor(void)
/* /*
* reset_editor presumes that editor has been initialized. That is, * reset_editor presumes that editor has been initialized.
* before reset_editor is called, init_editor should be called.
*/ */
void void
reset_editor(void) reset_editor(void)
@@ -260,37 +269,24 @@ reset_editor(void)
void void
ab_append(struct abuf *buf, const char *s, int len) ab_append(struct abuf *buf, const char *s, int len)
{ {
const int delta = (len < 0) ? 0 : len;
char *nc = buf->b; char *nc = buf->b;
int sz; int sz = buf->len + len;
assert((delta >= 0 && buf->len < INT_MAX - delta)); if (sz >= buf->cap) {
sz = buf->len + delta; while (sz > buf->cap) {
if (sz > buf->cap) {
if (buf->cap == 0) { if (buf->cap == 0) {
buf->cap = 64; buf->cap = 1;
} } else {
while (sz < buf->cap) {
if (buf->cap > INT_MAX/2) {
buf->cap = INT_MAX;
break;
}
buf->cap *= 2; buf->cap *= 2;
} }
}
assert(sz <= buf->cap);
nc = realloc(nc, buf->cap); nc = realloc(nc, buf->cap);
assert(nc != NULL); assert(nc != NULL);
} }
if (delta > 0) { memcpy(&nc[buf->len], s, len);
memcpy(&nc[buf->len], s, (size_t)delta);
buf->b = nc; buf->b = nc;
buf->len += delta; buf->len += len; /* DANGER: overflow */
}
} }
@@ -374,7 +370,7 @@ erow_update(struct erow *row)
} }
} }
if (row->rsize) { if (row->rsize || row->render != NULL) {
free(row->render); free(row->render);
row->rsize = 0; row->rsize = 0;
} }
@@ -461,18 +457,24 @@ erow_free(struct erow *row)
void void
die(const char *s) die(const char *s)
{ {
/*
* NOTE(kyle): this is a duplication of the code in display.c
* but I would like to be able to import these files from there.
*/
write(STDOUT_FILENO, "\x1b[2J", 4); write(STDOUT_FILENO, "\x1b[2J", 4);
write(STDOUT_FILENO, "\x1b[H", 3); write(STDOUT_FILENO, "\x1b[H", 3);
if (errno != 0) {
perror(s); perror(s);
} else {
fprintf(stderr, "%s\n", s);
}
exit(1); exit(1);
} }
int
iswspace(const char c)
{
return isspace(c) || c == '\t';
}
/* /*
* get_winsz uses the TIOCGWINSZ to get the window size. * get_winsz uses the TIOCGWINSZ to get the window size.
* *
@@ -518,6 +520,121 @@ goto_line(void)
} }
int
cursor_at_eol(void)
{
assert(editor.curx >= 0);
assert(editor.cury >= 0);
assert(editor.cury <= editor.nrows);
assert(editor.curx <= editor.row[editor.cury].size);
return editor.curx == editor.row[editor.cury].size;
}
void
find_next_word(void)
{
while (cursor_at_eol()) {
move_cursor(ARROW_RIGHT);
}
if (isalnum(editor.row[editor.cury].line[editor.curx])) {
while (!iswspace(editor.row[editor.cury].line[editor.curx]) && !cursor_at_eol()) {
move_cursor(ARROW_RIGHT);
}
return;
}
if (iswspace(editor.row[editor.cury].line[editor.curx])) {
while (iswspace(editor.row[editor.cury].line[editor.curx])) {
move_cursor(ARROW_RIGHT);
}
find_next_word();
}
}
void
delete_next_word(void)
{
while (cursor_at_eol()) {
move_cursor(ARROW_RIGHT);
deletech();
}
if (isalnum(editor.row[editor.cury].line[editor.curx])) {
while (!iswspace(editor.row[editor.cury].line[editor.curx]) && !cursor_at_eol()) {
move_cursor(ARROW_RIGHT);
deletech();
}
return;
}
if (iswspace(editor.row[editor.cury].line[editor.curx])) {
while (iswspace(editor.row[editor.cury].line[editor.curx])) {
move_cursor(ARROW_RIGHT);
deletech();
}
delete_next_word();
}
}
void
find_prev_word(void)
{
if (editor.cury == 0 && editor.curx == 0) {
return;
}
move_cursor(ARROW_LEFT);
while (cursor_at_eol() || iswspace(editor.row[editor.cury].line[editor.curx])) {
if (editor.cury == 0 && editor.curx == 0) {
return;
}
move_cursor(ARROW_LEFT);
}
while (editor.curx > 0 && !iswspace(editor.row[editor.cury].line[editor.curx - 1])) {
move_cursor(ARROW_LEFT);
}
}
void
delete_prev_word(void)
{
if (editor.cury == 0 && editor.curx == 0) {
return;
}
deletech();
while (editor.cury > 0 || editor.curx > 0) {
if (editor.curx == 0) {
deletech();
} else {
if (!iswspace(editor.row[editor.cury].line[editor.curx - 1])) {
break;
}
deletech();
}
}
while (editor.curx > 0) {
if (iswspace(editor.row[editor.cury].line[editor.curx - 1])) {
break;
}
deletech();
}
}
void void
delete_row(int at) delete_row(int at)
{ {
@@ -549,9 +666,6 @@ row_append_row(struct erow *row, char *s, int len)
void void
row_insert_ch(struct erow *row, int at, int16_t c) row_insert_ch(struct erow *row, int at, int16_t c)
{ {
int ncap = 0;
char *nline = NULL;
/* /*
* row_insert_ch just concerns itself with how to update a row. * row_insert_ch just concerns itself with how to update a row.
*/ */
@@ -560,19 +674,6 @@ row_insert_ch(struct erow *row, int at, int16_t c)
} }
assert(c > 0); assert(c > 0);
if (row->size == row->cap) {
ncap = cap_growth(row->cap, row->size+1);
nline = realloc(row->line, ncap);
assert(nline != NULL);
if (nline == NULL) {
return;
}
row->cap = ncap;
row->line = nline;
}
row->line = realloc(row->line, row->size+2); row->line = realloc(row->line, row->size+2);
assert(row->line != NULL); assert(row->line != NULL);
memmove(&row->line[at+1], &row->line[at], row->size - at + 1); memmove(&row->line[at+1], &row->line[at], row->size - at + 1);
@@ -614,6 +715,9 @@ insertch(int16_t c)
} }
/*
* deletech
*/
void void
deletech(void) deletech(void)
{ {
@@ -697,14 +801,9 @@ rows_to_buffer(int *buflen)
} }
if (len == 0) { if (len == 0) {
if (buflen != NULL) {
*buflen = 0;
}
return NULL; return NULL;
} }
assert(buflen != NULL);
*buflen = len; *buflen = len;
buf = malloc(len); buf = malloc(len);
assert(buf != NULL); assert(buf != NULL);
@@ -762,7 +861,7 @@ save_file(void)
status = 0; status = 0;
save_exit: save_exit:
if (fd != -1) close(fd); if (fd) close(fd);
if (buf) { if (buf) {
free(buf); free(buf);
buf = NULL; buf = NULL;
@@ -1008,11 +1107,7 @@ editor_openfile(void)
return; return;
} }
/* open_file() will handle freeing the previous file/buffer state
* via reset_editor() and will strdup() the filename internally. */
open_file(filename); open_file(filename);
free(filename);
} }
@@ -1230,26 +1325,6 @@ process_normal(int16_t c)
void void
process_escape(int16_t c) process_escape(int16_t c)
{ {
struct erow *row = NULL;
/* if there are no rows, there's nothing to do */
if (editor.nrows <= 0) {
return;
}
if (editor.cury <0) {
editor.cury = 0;
} else if (editor.cury >= editor.nrows) {
editor.cury = editor.nrows - 1;
}
row = &editor.row[editor.cury];
if (editor.curx < 0) {
editor.curx = 0;
} else if (editor.curx > row->size) {
editor.curx = row->size;
}
editor_set_status("hi"); editor_set_status("hi");
switch (c) { switch (c) {
@@ -1261,21 +1336,17 @@ process_escape(int16_t c)
editor.cury = 0; editor.cury = 0;
editor.curx = 0; editor.curx = 0;
break; break;
case BACKSPACE: case 'b':
row = &editor.row[editor.cury]; /* cury may have changed */ find_prev_word();
if (editor.curx == 0 || editor.curx < row->size) {
break; break;
} case 'd':
delete_next_word();
if ((unsigned char)isalnum(row->line[editor.curx])) { break;
editor_set_status("is alnum"); case 'f':
while (editor.curx > 0 && isalnum(row->line[editor.curx])) { find_next_word();
process_normal(BACKSPACE); break;
} case BACKSPACE:
} else { delete_prev_word();
editor_set_status("not alnum");
process_normal(BACKSPACE);
}
break; break;
default: default:
editor_set_status("unknown ESC key: %04x", c); editor_set_status("unknown ESC key: %04x", c);
@@ -1396,8 +1467,7 @@ draw_rows(struct abuf *ab)
{ {
assert(editor.cols >= 0); assert(editor.cols >= 0);
int cols = editor.cols > 0 ? editor.cols : 1; char buf[editor.cols];
char buf[cols];
int buflen, filerow, padding; int buflen, filerow, padding;
int y; int y;
@@ -1420,6 +1490,7 @@ draw_rows(struct abuf *ab)
ab_append(ab, "|", 1); ab_append(ab, "|", 1);
} }
} else { } else {
erow_update(&editor.row[filerow]);
buflen = editor.row[filerow].rsize - editor.coloffs; buflen = editor.row[filerow].rsize - editor.coloffs;
if (buflen < 0) { if (buflen < 0) {
buflen = 0; buflen = 0;
@@ -1428,7 +1499,6 @@ draw_rows(struct abuf *ab)
if (buflen > editor.cols) { if (buflen > editor.cols) {
buflen = editor.cols; buflen = editor.cols;
} }
ab_append(ab, editor.row[filerow].render+editor.coloffs, ab_append(ab, editor.row[filerow].render+editor.coloffs,
buflen); buflen);
} }
@@ -1571,10 +1641,10 @@ editor_set_status(const char *fmt, ...)
void void
loop(void) loop(void)
{ {
display_refresh(); int up = 1; /* update on the first runthrough */
while (1) { while (1) {
int update = 0; if (up) display_refresh();
/* /*
* ke should only refresh the display if it has received keyboard * ke should only refresh the display if it has received keyboard
@@ -1582,12 +1652,8 @@ loop(void)
* handling pastes without massive screen flicker. * handling pastes without massive screen flicker.
* *
*/ */
while (process_keypress()) { if ((up = process_keypress()) != 0) {
update = 1; while (process_keypress()) ;
}
if (update) {
display_refresh();
} }
} }
} }