Compare commits
102 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a574df2ab7 | |||
| a9bcb0d36b | |||
| 7b20e9ee37 | |||
| 5d581c1c2f | |||
| 78e4f84f7b | |||
| 734eb6e67d | |||
| a0d760c7d2 | |||
| 4db6077738 | |||
| d9777c9f02 | |||
| d9c5a6696e | |||
| 5c2571eba7 | |||
| 9afd030b87 | |||
| 2f198e611e | |||
| e079726ced | |||
| 3c79e368fa | |||
| bd68b809cd | |||
| f200a7bfcd | |||
| 58daeac6ad | |||
| 3ef6bab265 | |||
| 3800751bcf | |||
| 87272a669b | |||
| b77748f7c0 | |||
| c70b502006 | |||
| e14d620887 | |||
| db38266849 | |||
| 6d1b7f8e56 | |||
| 64647f77b0 | |||
| 2c3b2ae0f0 | |||
| a605e47458 | |||
| 40ee1e8d7b | |||
| 4464159301 | |||
| dc9fb58a41 | |||
| bbd682cec7 | |||
| ace25c5c65 | |||
| 09881706f2 | |||
| 2579bb01c8 | |||
| a528b3fc3e | |||
| faa70af9b7 | |||
| e1abaae6aa | |||
| 329b92f382 | |||
| 12553f301b | |||
| 75b19042b9 | |||
| 1a184b1a08 | |||
| acde895fb7 | |||
| 78d522ba98 | |||
| feb6667a24 | |||
|
|
66c79e0762 | ||
| a51b98c31f | |||
| 707362574c | |||
| 56db8bd8d2 | |||
| a32ef2ff53 | |||
| 0c0c3d9ce5 | |||
| 7245003769 | |||
| 542b1d90a0 | |||
| a359f4e6c4 | |||
| a03dd0c433 | |||
| 561faf537c | |||
| 3a36b35c1f | |||
|
|
b1cb2532f6 | ||
| 9f33cdc0e4 | |||
| afcc33329f | |||
| 128235283c | |||
| af3c0eddc4 | |||
| f8f01f7dd6 | |||
| 8206ab0a40 | |||
| af8dcb847e | |||
| 9434a34116 | |||
| 3b2b60466c | |||
| 52e1f2dd8b | |||
| 8bb9228339 | |||
| e937e3398c | |||
| f147c866ab | |||
| 1227d0abf4 | |||
| d3591331a5 | |||
| 5eafc1a34b | |||
| a0103dd5aa | |||
| 3782880062 | |||
| e345b55595 | |||
| d1978a3b98 | |||
| 5ee8234ab7 | |||
| f82d1f8831 | |||
| 2019ec10ce | |||
| 0110f82705 | |||
| a62a8e50fa | |||
| 33e19e7d76 | |||
| fd01e3593f | |||
| 47bbc5339c | |||
| 2962a6c92e | |||
| 7fa887273b | |||
| fb02f37512 | |||
| 1722dbee0b | |||
| 14199afeb5 | |||
| 9f3558c430 | |||
| ce64e4637d | |||
| 36013e42e4 | |||
| dd667c1ef5 | |||
| c9977b0fc0 | |||
| dd2c888766 | |||
| 2967998893 | |||
| a400cdf5ad | |||
| c8a43fb328 | |||
| 3ea7c31cba |
25
.github/workflows/release.yml
vendored
25
.github/workflows/release.yml
vendored
@@ -10,29 +10,6 @@ permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
goreleaser:
|
||||
name: GoReleaser
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: 'go.mod'
|
||||
cache: true
|
||||
|
||||
- name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@v6
|
||||
with:
|
||||
distribution: goreleaser
|
||||
version: latest
|
||||
args: release --clean
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GH_CPAT }}
|
||||
homebrew:
|
||||
name: Bump Homebrew formula
|
||||
# Skip this job in case of git pushes to prerelease tags
|
||||
@@ -49,7 +26,7 @@ jobs:
|
||||
- uses: mislav/bump-homebrew-formula-action@v3
|
||||
with:
|
||||
formula-name: ke
|
||||
formula-path: Formula/kw.rb
|
||||
formula-path: Formula/ke.rb
|
||||
homebrew-tap: kisom/homebrew-tap
|
||||
base-branch: master
|
||||
commit-message: |
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,2 +1,4 @@
|
||||
*.log
|
||||
build
|
||||
ke
|
||||
*.txt
|
||||
@@ -2,20 +2,37 @@ cmake_minimum_required(VERSION 3.15)
|
||||
project(ke C) # Specify C language explicitly
|
||||
|
||||
set(CMAKE_C_STANDARD 99)
|
||||
set(KE_VERSION "1.0.3")
|
||||
set(KE_VERSION "2.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 -Werror=stringop-truncation")
|
||||
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(ke main.c)
|
||||
|
||||
# Define KE_VERSION for use in C code (e.g., #define KE_VERSION)
|
||||
add_executable(ke
|
||||
abuf.c
|
||||
term.c
|
||||
buffer.c
|
||||
editor.c
|
||||
core.c
|
||||
core.h
|
||||
main.c
|
||||
)
|
||||
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
|
||||
)
|
||||
40
Makefile
Normal file
40
Makefile
Normal file
@@ -0,0 +1,40 @@
|
||||
TARGET := ke
|
||||
KE_VERSION := devel
|
||||
DEST := $(HOME)/.local/bin/$(TARGET)
|
||||
|
||||
CFLAGS := -Wall -Wextra -pedantic -Wshadow -Werror -std=c99 -g
|
||||
CFLAGS += -Wno-unused-result
|
||||
CFLAGS += -D_DEFAULT_SOURCE -D_XOPEN_SOURCE
|
||||
CFLAGS += -fsanitize=address -fno-omit-frame-pointer
|
||||
|
||||
LDFLAGS := -fsanitize=address
|
||||
|
||||
all: $(TARGET) test.txt
|
||||
|
||||
SRCS := main.c abuf.c term.c buffer.c editor.c core.c
|
||||
HDRS := abuf.h term.h buffer.h editor.h core.h
|
||||
|
||||
$(TARGET): $(SRCS)
|
||||
$(CC) $(CFLAGS) -o $(TARGET) $(SRCS) $(LDFLAGS)
|
||||
|
||||
.PHONY: install
|
||||
#install: $(TARGET)
|
||||
install:
|
||||
cp $(TARGET) $(DEST)
|
||||
|
||||
clean:
|
||||
rm -f $(TARGET)
|
||||
rm -f asan.log*
|
||||
|
||||
.PHONY: test.txt
|
||||
test.txt:
|
||||
cp test.txt.bak $@
|
||||
|
||||
.PHONY: gdb
|
||||
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)
|
||||
17
README.txt
17
README.txt
@@ -6,4 +6,19 @@ used fairly often.
|
||||
|
||||
See the man page for more info.
|
||||
|
||||
Released under an ISC license.
|
||||
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.
|
||||
|
||||
Started by following along with kilo:
|
||||
https://viewsourcecode.org/snaptoken/kilo/
|
||||
|
||||
E.g., in the devlogs
|
||||
https://log.wntrmute.dev/2020/02/20200207
|
||||
|
||||
17
TODO
17
TODO
@@ -1,15 +1,18 @@
|
||||
[X] goto-line
|
||||
[X] text-corruption bug
|
||||
[ ] alt-modifiers
|
||||
[ ] refresh-screen
|
||||
[ ] functions -> keymapping?
|
||||
[x] refresh-screen
|
||||
[ ] functions -> keymapping? (what did this even mean)
|
||||
[X] rendering: need to skip over control characters like we do with tabs
|
||||
[X] control-d -> delete
|
||||
[ ] control-g -> exit buf prompt
|
||||
[ ] load-file prompt on dirty buffer
|
||||
[x] control-g -> exit buf prompt
|
||||
[x] load-file prompt on dirty buffer
|
||||
[ ] multiple files
|
||||
[x] utf-8
|
||||
[x] kill ring
|
||||
[x] mark/region
|
||||
|
||||
What would it take for ke to be your daily drives?
|
||||
+ C-u
|
||||
+ Alt nav (backspace, delete, f, b, etc)
|
||||
|
||||
[x] C-u (repeat actions)
|
||||
[x] Alt nav (backspace, delete, f, b, etc)
|
||||
[-] undo tree (C-k u/U)
|
||||
|
||||
107
abuf.c
Normal file
107
abuf.c
Normal file
@@ -0,0 +1,107 @@
|
||||
#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_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_free(abuf *buf)
|
||||
{
|
||||
free(buf->b);
|
||||
buf->b = NULL;
|
||||
buf->size = 0;
|
||||
buf->cap = 0;
|
||||
}
|
||||
30
abuf.h
Normal file
30
abuf.h
Normal file
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* 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_prependch(abuf *buf, const char c);
|
||||
void ab_prepend(abuf *buf, const char *s, const size_t len);
|
||||
void ab_free(abuf *buf);
|
||||
|
||||
|
||||
#endif
|
||||
466
buffer.c
Normal file
466
buffer.c
Normal file
@@ -0,0 +1,466 @@
|
||||
/* 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;
|
||||
|
||||
editor.buffers[editor.bufcount] = buf;
|
||||
idx = (int)editor.bufcount;
|
||||
editor.bufcount++;
|
||||
return idx;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
buffer_save_current(void)
|
||||
{
|
||||
/* No-op: editor no longer mirrors per-buffer fields */
|
||||
(void)editor;
|
||||
}
|
||||
|
||||
|
||||
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);
|
||||
}
|
||||
49
buffer.h
Normal file
49
buffer.h
Normal file
@@ -0,0 +1,49 @@
|
||||
#ifndef KE_BUFFER_H
|
||||
#define KE_BUFFER_H
|
||||
|
||||
#include "abuf.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;
|
||||
} buffer;
|
||||
|
||||
/* Access current buffer and convenient aliases for file-specific fields */
|
||||
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
|
||||
113
core.c
Normal file
113
core.c
Normal file
@@ -0,0 +1,113 @@
|
||||
#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
|
||||
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);
|
||||
}
|
||||
39
core.h
Normal file
39
core.h
Normal file
@@ -0,0 +1,39 @@
|
||||
#ifndef KE_CORE_H
|
||||
#define KE_CORE_H
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
|
||||
#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
|
||||
|
||||
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
|
||||
40
default.nix
Normal file
40
default.nix
Normal file
@@ -0,0 +1,40 @@
|
||||
{
|
||||
lib,
|
||||
stdenv,
|
||||
cmake,
|
||||
installShellFiles,
|
||||
...
|
||||
}:
|
||||
let
|
||||
cmakeContent = builtins.readFile ./CMakeLists.txt;
|
||||
cmakeLines = lib.splitString "\n" cmakeContent;
|
||||
versionLine = lib.findFirst (l: builtins.match ".*set\\(KE_VERSION \".+\"\\).*" l != null) (throw "KE_VERSION not found in CMakeLists.txt") cmakeLines;
|
||||
version = builtins.head (builtins.match ".*set\\(KE_VERSION \"(.+)\"\\).*" versionLine);
|
||||
in
|
||||
stdenv.mkDerivation {
|
||||
pname = "ke";
|
||||
inherit version;
|
||||
|
||||
src = lib.cleanSource ./.;
|
||||
|
||||
nativeBuildInputs = [
|
||||
cmake
|
||||
installShellFiles
|
||||
];
|
||||
|
||||
cmakeFlags = [
|
||||
"-DENABLE_ASAN=on"
|
||||
"-DCMAKE_BUILD_TYPE=Debug"
|
||||
];
|
||||
|
||||
installPhase = ''
|
||||
runHook preInstall
|
||||
|
||||
mkdir -p $out/bin
|
||||
cp ke $out/bin/
|
||||
|
||||
installManPage ../ke.1
|
||||
|
||||
runHook postInstall
|
||||
'';
|
||||
}
|
||||
109
editor.c
Normal file
109
editor.c
Normal file
@@ -0,0 +1,109 @@
|
||||
/* 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)
|
||||
{
|
||||
/* Reset the current buffer's contents/state. */
|
||||
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
35
editor.h
Normal 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 */
|
||||
27
flake.lock
generated
Normal file
27
flake.lock
generated
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"nodes": {
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1763835633,
|
||||
"narHash": "sha256-HzxeGVID5MChuCPESuC0dlQL1/scDKu+MmzoVBJxulM=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "050e09e091117c3d7328c7b2b7b577492c43c134",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nixos",
|
||||
"ref": "nixos-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
||||
18
flake.nix
Normal file
18
flake.nix
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
description = "Kyle's Text Editor";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
|
||||
};
|
||||
|
||||
outputs =
|
||||
{ self, nixpkgs }:
|
||||
let
|
||||
pkgs = import nixpkgs { system = "x86_64-linux"; };
|
||||
in
|
||||
{
|
||||
packages.x86_64-linux = {
|
||||
default = pkgs.callPackage ./default.nix { };
|
||||
};
|
||||
};
|
||||
}
|
||||
75
ke.1
75
ke.1
@@ -19,32 +19,89 @@ is
|
||||
K-command mode is entered using C-k. This is taken from Wordstar and just
|
||||
so happens to be blessed with starting with a most excellent letter of
|
||||
grandeur. Many commands work with and without control; for example,
|
||||
saving a file can be done with either C-k s or C-k C-s.
|
||||
.Pp
|
||||
saving a file can be done with either C-k s or C-k C-s. Other commands work
|
||||
with ESC or CTRL.
|
||||
.Sh K-COMMANDS
|
||||
k-command mode can be exited with ESC or C-g.
|
||||
.Bl -tag -width xxxxxxxxxxxx -offset indent
|
||||
.It C-k BACKSPACE
|
||||
Delete from the cursor to the beginning of the line.
|
||||
.It C-k C-d
|
||||
Delete the current row.
|
||||
.It C-k SPACE
|
||||
Toggle the mark.
|
||||
.It C-k -
|
||||
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
|
||||
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
|
||||
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. Also C-k C-g.
|
||||
Go to a specific line.
|
||||
.It C-k j
|
||||
Jump to the mark.
|
||||
.It C-k l
|
||||
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. Also C-k C-q.
|
||||
Exit the editor. If the file has unsaved changes,
|
||||
a warning will be printed; a second C-k q will exit.
|
||||
.It C-k C-q
|
||||
Immediately exit the editor.
|
||||
.It C-k C-r
|
||||
Reload the current buffer from disk.
|
||||
.It C-k s
|
||||
save the file, prompting for a filename if needed. Also C-k C-s.
|
||||
Save the file, prompting for a filename if needed. Also C-k C-s.
|
||||
.It C-k u
|
||||
Undo changes (not implemented; marking this k-command as taken).
|
||||
.It C-k U
|
||||
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 \
|
||||
.It C-k y
|
||||
Yank the kill ring.
|
||||
.It C-k \[char92]
|
||||
Dump core.
|
||||
.El
|
||||
.Sh OTHER KEYBINDINGS
|
||||
.Bl -tag -width xxxxxxxxxxxx -offset indent
|
||||
.It C-g
|
||||
In general, C-g cancels an operation.
|
||||
.It C-l
|
||||
Refresh the display.
|
||||
.It C-s
|
||||
Incremental find.
|
||||
.It C-u
|
||||
Universal argument. C-u followed by numbers will repeat an
|
||||
operation n times.
|
||||
.It C-w
|
||||
Kill the region if the mark is set.
|
||||
.It C-y
|
||||
Yank the kill ring.
|
||||
.It ESC BACKSPACE
|
||||
Delete the previous word.
|
||||
.It ESC b
|
||||
Move to the previous word.
|
||||
.It ESC d
|
||||
Delete the next word.
|
||||
.It ESC f
|
||||
Move to the next word.
|
||||
.It ESC w
|
||||
Save the region (if the mark is set) to the kill ring.
|
||||
.It
|
||||
.El
|
||||
.Sh FIND
|
||||
The find operation is an incremental search. The up or left arrow keys will
|
||||
go to the previous result, while the down or right arrow keys will go to
|
||||
|
||||
206
scratch.c
Normal file
206
scratch.c
Normal file
@@ -0,0 +1,206 @@
|
||||
/*
|
||||
* 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;
|
||||
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];
|
||||
char word[REFLOW_MARGIN + 1];
|
||||
char *e = NULL;
|
||||
char *p = NULL;
|
||||
char *s = NULL;
|
||||
|
||||
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 < ENROWS - 1 &&
|
||||
EROW[end_row + 1].size > 0) {
|
||||
end_row++;
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
if (in_paragraph) {
|
||||
ab_append(&buf, "\n", 1);
|
||||
in_paragraph = 0;
|
||||
}
|
||||
|
||||
ab_append(&buf, "\n", 1);
|
||||
continue;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
ab_append(&buf, row->b + indent_len, row->size - indent_len);
|
||||
ab_append(&buf, " ", 1);
|
||||
}
|
||||
|
||||
if (in_paragraph) {
|
||||
ab_append(&buf, "\n", 1);
|
||||
}
|
||||
|
||||
|
||||
p = buf.b;
|
||||
col = 0;
|
||||
|
||||
while (p != NULL && *p != '\0') {
|
||||
while (*p && isspace((unsigned char)*p) &&
|
||||
*p != '\n') {
|
||||
p++;
|
||||
}
|
||||
|
||||
if (*p == '\0') {
|
||||
break;
|
||||
}
|
||||
|
||||
wlen = 0;
|
||||
while (*p && !isspace((unsigned char)*p)) {
|
||||
if (wlen < REFLOW_MARGIN)
|
||||
word[wlen++] = *p;
|
||||
p++;
|
||||
}
|
||||
word[wlen] = '\0';
|
||||
|
||||
if (*p == '\n' && (p[1] == '\n' || p[1] == '\0')) {
|
||||
ab_append(&out, "\n", 1); /* flush */
|
||||
col = 0;
|
||||
p++; /* consume the extra \n */
|
||||
continue;
|
||||
}
|
||||
|
||||
this_len = wlen;
|
||||
if (col > 0) {
|
||||
this_len++; /* space before word */
|
||||
}
|
||||
|
||||
if (col == 0) {
|
||||
ab_append(&out, indent, indent_len);
|
||||
col = indent_len;
|
||||
}
|
||||
|
||||
if (col + this_len > REFLOW_MARGIN && col > 0) {
|
||||
ab_append(&out, "\n", 1);
|
||||
ab_append(&out, indent, indent_len);
|
||||
col = indent_len;
|
||||
}
|
||||
|
||||
if (col > 0) {
|
||||
ab_append(&out, " ", 1);
|
||||
col++;
|
||||
}
|
||||
|
||||
ab_append(&out, word, wlen);
|
||||
col += wlen;
|
||||
}
|
||||
|
||||
if (col > 0) {
|
||||
ab_append(&out, "\n", 1);
|
||||
}
|
||||
|
||||
/* the old switcharoo */
|
||||
buf = out;
|
||||
ab_free(&out);
|
||||
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
ab_free(&buf);
|
||||
|
||||
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();
|
||||
}
|
||||
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(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;
|
||||
}
|
||||
22
term.h
Normal file
22
term.h
Normal file
@@ -0,0 +1,22 @@
|
||||
#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);
|
||||
|
||||
/*
|
||||
* 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 */
|
||||
15
test.txt.bak
Normal file
15
test.txt.bak
Normal file
@@ -0,0 +1,15 @@
|
||||
hello
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
world it is me
|
||||
but I am not me
|
||||
114
undo.c
Normal file
114
undo.c
Normal file
@@ -0,0 +1,114 @@
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#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;
|
||||
|
||||
ab_init(&node->text);
|
||||
|
||||
node->next = NULL;
|
||||
node->parent = NULL;
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
undo_node_free(undo_node *node)
|
||||
{
|
||||
undo_node *next = NULL;
|
||||
|
||||
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)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
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);
|
||||
void undo_apply(struct editor *editor);
|
||||
void editor_undo(undo_tree *tree);
|
||||
void editor_redo(undo_tree *tree);
|
||||
|
||||
50
undo.h
Normal file
50
undo.h
Normal file
@@ -0,0 +1,50 @@
|
||||
#include <stddef.h>
|
||||
|
||||
#include "abuf.h"
|
||||
#include "editor.h"
|
||||
|
||||
|
||||
#ifndef KE_UNDO_H
|
||||
#define KE_UNDO_H
|
||||
|
||||
|
||||
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 *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(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);
|
||||
void undo_apply(struct editor *editor);
|
||||
void editor_undo(undo_tree *tree);
|
||||
void editor_redo(undo_tree *tree);
|
||||
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user