-#------------------------------------------------------------------------------
-# Build Configuration
-#------------------------------------------------------------------------------
-# name and version
-PROJNAME = edit
-VERSION = 0.0.1
+LDFLAGS = -L/opt/X11/lib -lXft -lX11
+CFLAGS = --std=c99 -Wall -Wextra -I. -I/opt/X11/include -I/opt/local/include/freetype2 -I/usr/include/freetype2
+SRCS = xedit.c buf.c
+OBJS = $(SRCS:.c=.o)
+TESTSRCS = tests/tests.c tests/buf.c
+TESTOBJS = $(TESTSRCS:.c=.o)
-# tools
-CC = c99
-LD = ${CC}
+all: edit test
-# flags
-INCS = -Isource/
-CPPFLAGS = -D_XOPEN_SOURCE=700
-CFLAGS = ${INCS} ${CPPFLAGS}
-LDFLAGS = ${LIBS} -lncurses
+test: unittests
+ ./unittests
-# commands
-COMPILE = @echo CC $@; ${CC} ${CFLAGS} -c -o $@ $<
-LINK = @echo LD $@; ${LD} -o $@ $^ ${LDFLAGS}
-CLEAN = @rm -f
+edit: $(OBJS)
+ $(CC) -o $@ $^ $(LDFLAGS)
-#------------------------------------------------------------------------------
-# Build-Specific Macros
-#------------------------------------------------------------------------------
-# library macros
-BIN = ${PROJNAME}
-DEPS = ${OBJS:.o=.d}
-OBJS = source/main.o
-
-# distribution dir and tarball settings
-DISTDIR = ${PROJNAME}-${VERSION}
-DISTTAR = ${DISTDIR}.tar
-DISTGZ = ${DISTTAR}.gz
-DISTFILES = config.mk LICENSE.md Makefile README.md source
-
-# load user-specific settings
--include config.mk
-
-#------------------------------------------------------------------------------
-# Phony Targets
-#------------------------------------------------------------------------------
-.PHONY: all options tests
-
-all: options ${BIN}
-
-options:
- @echo "Toolchain Configuration:"
- @echo " CC = ${CC}"
- @echo " CFLAGS = ${CFLAGS}"
- @echo " LD = ${LD}"
- @echo " LDFLAGS = ${LDFLAGS}"
-
-dist: clean
- @echo DIST ${DISTGZ}
- @mkdir -p ${DISTDIR}
- @cp -R ${DISTFILES} ${DISTDIR}
- @tar -cf ${DISTTAR} ${DISTDIR}
- @gzip ${DISTTAR}
- @rm -rf ${DISTDIR}
+unittests: $(TESTOBJS)
+ $(CC) -o $@ $^ $(LDFLAGS)
clean:
- ${CLEAN} ${DEPS}
- ${CLEAN} ${BIN} ${OBJS}
- ${CLEAN} ${OBJS:.o=.gcno} ${OBJS:.o=.gcda}
- ${CLEAN} ${DISTTAR} ${DISTGZ}
-
-#------------------------------------------------------------------------------
-# Target-Specific Rules
-#------------------------------------------------------------------------------
-.c.o:
- ${COMPILE}
-
-${BIN}: ${OBJS}
- ${LINK}
-
-# load dependency files
--include ${DEPS}
+ $(RM) edit unittests $(OBJS) $(TESTOBJS)
+.PHONY: all test
--- /dev/null
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "edit.h"
+
+void buf_load(Buf* buf, char* path)
+{
+ unsigned i = 0;
+ FILE* in = fopen(path, "rb");
+ int c;
+ while (EOF != (c = fgetc(in)))
+ buf_ins(buf, i++, (Rune)c);
+ fclose(in);
+}
+
+void buf_initsz(Buf* buf, size_t sz)
+{
+ buf->bufsize = sz;
+ buf->bufstart = (Rune*)malloc(buf->bufsize * sizeof(Rune));
+ buf->bufend = buf->bufstart + buf->bufsize;
+ buf->gapstart = buf->bufstart;
+ buf->gapend = buf->bufend;
+}
+
+static void syncgap(Buf* buf, unsigned off)
+{
+ //printf("assert: %u <= %u\n", off, buf_end(buf));
+ assert(off <= buf_end(buf));
+ /* If the buffer is full, resize it before syncing */
+ if (0 == (buf->gapend - buf->gapstart)) {
+ Buf newbuf;
+ buf_initsz(&newbuf, buf->bufsize << 1);
+
+ for (Rune* curr = buf->bufstart; curr < buf->gapstart; curr++)
+ *(newbuf.gapstart++) = *(curr);
+ for (Rune* curr = buf->gapend; curr < buf->bufend; curr++)
+ *(newbuf.gapstart++) = *(curr);
+
+ free(buf->bufstart);
+ *buf = newbuf;
+ }
+
+ /* Move the gap to the desired offset */
+ Rune* newpos = (buf->bufstart + off);
+ if (newpos < buf->gapstart) {
+ while (newpos < buf->gapstart)
+ *(--buf->gapend) = *(--buf->gapstart);
+ } else {
+ while (newpos > buf->gapstart)
+ *(buf->gapstart++) = *(buf->gapend++);
+ }
+}
+
+void buf_init(Buf* buf)
+{
+ buf_initsz(buf, BufSize);
+}
+
+void buf_clr(Buf* buf)
+{
+ free(buf->bufstart);
+ buf_init(buf);
+}
+
+void buf_del(Buf* buf, unsigned off)
+{
+ syncgap(buf, off);
+ buf->gapend++;
+}
+
+void buf_ins(Buf* buf, unsigned off, Rune rune)
+{
+ syncgap(buf, off);
+ *(buf->gapstart++) = rune;
+}
+
+Rune buf_get(Buf* buf, unsigned off)
+{
+ if (off >= buf_end(buf)) return (Rune)'\n';
+ size_t bsz = (buf->gapstart - buf->bufstart);
+ if (off < bsz)
+ return *(buf->bufstart + off);
+ else
+ return *(buf->gapend + (off - bsz));
+}
+
+unsigned buf_bol(Buf* buf, unsigned off)
+{
+ if (off == 0) return 0;
+ if (buf_get(buf, off) == '\n' && buf_get(buf, off-1) == '\n') return off;
+ for (Rune r; (r = buf_get(buf, off)) != '\n'; off--);
+ return off+1;
+}
+
+unsigned buf_eol(Buf* buf, unsigned off)
+{
+ if (off >= buf_end(buf)) return off;
+ if (buf_get(buf, off) == '\n') return off;
+ for (Rune r; (r = buf_get(buf, off)) != '\n'; off++);
+ return off-1;
+}
+
+unsigned buf_end(Buf* buf)
+{
+ size_t bufsz = buf->bufend - buf->bufstart;
+ size_t gapsz = buf->gapend - buf->gapstart;
+ return (bufsz - gapsz);
+}
+
+static unsigned move_back(Buf* buf, unsigned pos) {
+ if (pos == 0) return 0;
+ if (buf_get(buf, pos) == '\n' &&
+ buf_get(buf, pos-1) == '\n' &&
+ buf_get(buf, pos-2) != '\n')
+ pos = pos - 2;
+ else if (buf_get(buf, pos) != '\n' &&
+ buf_get(buf, pos-1) == '\n' &&
+ buf_get(buf, pos-2) != '\n')
+ pos = pos - 2;
+ else
+ pos = pos - 1;
+ return pos;
+}
+
+static unsigned move_forward(Buf* buf, unsigned pos) {
+ if (pos+2 >= buf_end(buf)) return buf_end(buf)-2;
+ if (buf_get(buf, pos) != '\n' &&
+ buf_get(buf, pos+1) == '\n')
+ pos = pos + 2;
+ else
+ pos = pos + 1;
+ return pos;
+}
+
+unsigned buf_byrune(Buf* buf, unsigned pos, int count)
+{
+ int move = (count < 0 ? -1 : 1);
+ count *= move;
+ for (; count > 0; count--)
+ pos = (move > 0 ? &move_forward : &move_back)(buf, pos);
+ return pos;
+}
+
+unsigned get_column(Buf* buf, unsigned pos)
+{
+ Rune r;
+ unsigned cols = 0;
+ if (buf_get(buf, pos) == '\n') return 0;
+ while ((r = buf_get(buf, pos)) != '\n')
+ pos--, cols += (r == '\t' ? TabWidth : 1);
+ return cols-1;
+}
+
+unsigned set_column(Buf* buf, unsigned pos, unsigned col)
+{
+ Rune r;
+ pos = buf_bol(buf, pos);
+ while ((r = buf_get(buf, pos)) != '\n' && col) {
+ unsigned inc = (r == '\t' ? TabWidth : 1);
+ pos++, col -= (inc > col ? col : inc);
+ }
+
+ if (buf_get(buf, pos) == '\n' &&
+ buf_get(buf, pos-1) != '\n')
+ pos--;
+ return pos;
+}
+
+unsigned buf_byline(Buf* buf, unsigned pos, int count)
+{
+ int move = (count < 0 ? -1 : 1);
+ count *= move; // remove the sign if there is one
+ unsigned col = get_column(buf, pos);
+ for (; count > 0; count--)
+ pos = (move < 0 ? move_back(buf, buf_bol(buf, pos))
+ : move_forward(buf, buf_eol(buf, pos)));
+ return set_column(buf, pos, col);
+}
+++ /dev/null
-#------------------------------------------------------------------------------
-# User and Platform Specific Configuration Options
-#------------------------------------------------------------------------------
-# Override the tools used with platform specific ones
-#CC = cc
-#LD = ${CC}
-#AR = ar
-
-# GCC dependency generation
-#COMPILE += && gcc ${INCS} -MM -MT $@ -MF ${@:.o=.d} ${<:.o=.c}
-
-# Enable output of debug symbols
-#CFLAGS += -g
-
-# Enable output of coverage information
-#CFLAGS += --coverage
-#LDFLAGS += --coverage
--- /dev/null
+/* Definitons
+ *****************************************************************************/
+enum ColorId {
+ CLR_BASE03 = 0,
+ CLR_BASE02,
+ CLR_BASE01,
+ CLR_BASE00,
+ CLR_BASE0,
+ CLR_BASE1,
+ CLR_BASE2,
+ CLR_BASE3,
+ CLR_YELLOW,
+ CLR_ORANGE,
+ CLR_RED,
+ CLR_MAGENTA,
+ CLR_VIOLET,
+ CLR_BLUE,
+ CLR_CYAN,
+ CLR_GREEN,
+};
+
+typedef uint32_t Color;
+typedef uint32_t Rune;
+
+/* Buffer management functions
+ *****************************************************************************/
+typedef struct buf {
+ size_t bufsize;
+ Rune* bufstart;
+ Rune* bufend;
+ Rune* gapstart;
+ Rune* gapend;
+} Buf;
+
+void buf_load(Buf* buf, char* path);
+void buf_initsz(Buf* buf, size_t sz);
+void buf_init(Buf* buf);
+void buf_clr(Buf* buf);
+void buf_del(Buf* buf, unsigned pos);
+void buf_ins(Buf* buf, unsigned pos, Rune);
+Rune buf_get(Buf* buf, unsigned pos);
+unsigned buf_bol(Buf* buf, unsigned pos);
+unsigned buf_eol(Buf* buf, unsigned pos);
+unsigned buf_end(Buf* buf);
+unsigned buf_byrune(Buf* buf, unsigned pos, int count);
+unsigned buf_byline(Buf* buf, unsigned pos, int count);
+
+/* Miscellaneous Functions
+ *****************************************************************************/
+void die(char* msg);
+
+/* Configuration
+ *****************************************************************************/
+enum {
+ Width = 640,
+ Height = 480,
+ TabWidth = 4,
+ BufSize = 8192,
+};
+
+static enum { DARK = 0, LIGHT = 1 } ColorBase = DARK;
+
+static Color Palette[][2] = {
+/* Color Name = Dark Light */
+ [CLR_BASE03] = { 0x002b36, 0xfdf6e3 },
+ [CLR_BASE02] = { 0x073642, 0xeee8d5 },
+ [CLR_BASE01] = { 0x586e75, 0x93a1a1 },
+ [CLR_BASE00] = { 0x657b83, 0x839496 },
+ [CLR_BASE0] = { 0x839496, 0x657b83 },
+ [CLR_BASE1] = { 0x93a1a1, 0x586e75 },
+ [CLR_BASE2] = { 0xeee8d5, 0x073642 },
+ [CLR_BASE3] = { 0xfdf6e3, 0x002b36 },
+ [CLR_YELLOW] = { 0xb58900, 0xb58900 },
+ [CLR_ORANGE] = { 0xcb4b16, 0xcb4b16 },
+ [CLR_RED] = { 0xdc322f, 0xdc322f },
+ [CLR_MAGENTA] = { 0xd33682, 0xd33682 },
+ [CLR_VIOLET] = { 0x6c71c4, 0x6c71c4 },
+ [CLR_BLUE] = { 0x268bd2, 0x268bd2 },
+ [CLR_CYAN] = { 0x2aa198, 0x2aa198 },
+ [CLR_GREEN] = { 0x859900, 0x859900 },
+};
+
+#define FONTNAME "Monaco:pixelsize=15:antialias=true:autohint=true"
+
+++ /dev/null
-/* Includes
- *****************************************************************************/
-static void cleanup(void);
-#define CLEANUP_HOOK cleanup
-#include <util.h>
-#include <ncurses.h>
-
-/* Type Definitions
- *****************************************************************************/
-typedef struct Line {
- struct Line* prev;
- struct Line* next;
- size_t length;
- char* text;
-} Line;
-
-typedef struct {
- char* name;
- Line* start;
- Line* first;
- Line* last;
-} File;
-
-typedef struct {
- int x;
- int y;
-} Pos;
-
-typedef struct {
- Line* line;
- int offset;
-} FilePos;
-
-/* Globals
- *****************************************************************************/
-static bool ScreenDirty = true;
-static File CurrFile = { .name = NULL, .start = NULL, .first = NULL, .last = NULL };
-static Pos Curr = { .x = 0, .y = 0 };
-static Pos Max = { .x = 0, .y = 0 };
-static FilePos Loc = { .line = NULL, .offset = 0 };
-
-/* Macros
- *****************************************************************************/
-#define line_length() (Loc.line->length)
-#define visible_length() (Loc.line->length-2 - Loc.offset)
-
-/* Declarations
- *****************************************************************************/
-static void setup(void);
-static void load(char* fname);
-static void edit(void);
-static void input(int ch);
-static void cursor_left(void);
-static void cursor_down(void);
-static void cursor_up(void);
-static void cursor_right(void);
-static void cursor_home(void);
-static void cursor_end(void);
-
-/* Main Routine
- *****************************************************************************/
-int main(int argc, char** argv)
-{
- atexit(cleanup);
- setup();
- if (argc > 1) {
- load(argv[1]);
- edit();
- } else {
- die("no filename provided");
- }
- return EXIT_SUCCESS;
-}
-
-/* Definitions
- *****************************************************************************/
-static void setup(void)
-{
- initscr();
- getmaxyx(stdscr, Max.y, Max.x);
- raw();
- keypad(stdscr, true);
- noecho();
-}
-
-static void cleanup(void)
-{
- /* shutdown ncurses */
- endwin();
- /* free up malloced data */
- free(CurrFile.name);
- Line* line = CurrFile.first;
- while (line) {
- Line* deadite = line;
- line = line->next;
- free(deadite->text);
- free(deadite);
- }
-}
-
-static void load(char* fname)
-{
- FILE* file = efopen(fname, "r");
- CurrFile.name = estrdup(fname);
- while (!feof(file)) {
- Line* line = (Line*)ecalloc(1,sizeof(Line));
- line->text = efreadline(file);
- line->length = strlen(line->text);
- if (CurrFile.first == NULL) {
- CurrFile.start = line;
- CurrFile.first = line;
- CurrFile.last = line;
- Loc.line = line;
- } else {
- CurrFile.last->next = line;
- line->prev = CurrFile.last;
- CurrFile.last = line;
- }
- }
- fclose(file);
-}
-
-static void edit(void)
-{
- int ch = 0, x = 0;
- do {
- /* Handle input */
- input(ch);
- /* Refresh the screen */
- if (ScreenDirty) {
- clear();
- Line* line = CurrFile.start;
- for (int i = 0; (i < Max.y) && line; i++, line = line->next) {
- if (line->length > Loc.offset) {
- mvprintw(i, 0, "%s", &(line->text[Loc.offset]));
- }
- }
- refresh();
- ScreenDirty = false;
- }
- /* Place the cursor */
- if (line_length() <= 1)
- x = 0;
- else if (Curr.x > visible_length())
- x = visible_length();
- else
- x = Curr.x;
- move(Curr.y, x);
- } while((ch = getch()) != 'q');
-}
-
-static void input(int ch)
-{
- switch (ch) {
- case KEY_LEFT:
- case 'h':
- cursor_left();
- break;
-
- case KEY_DOWN:
- case 'j':
- cursor_down();
- break;
-
- case KEY_UP:
- case 'k':
- cursor_up();
- break;
-
- case KEY_RIGHT:
- case 'l':
- cursor_right();
- break;
-
- case KEY_HOME:
- case '0':
- cursor_home();
- break;
-
- case KEY_END:
- case '$':
- cursor_end();
- break;
- }
-}
-
-static void cursor_left(void)
-{
- Curr.x--;
- if (Curr.x < 0) {
- Curr.x = 0;
- Loc.offset--;
- if (Loc.offset < 0)
- Loc.offset = 0;
- ScreenDirty = true;
- } else if (Curr.x > visible_length()) {
- Curr.x = visible_length()-1;
- }
-}
-
-static void cursor_down(void)
-{
- if (Loc.line->next) {
- Curr.y++;
- if (Curr.y >= Max.y) {
- Curr.y = Max.y-1;
- if (CurrFile.start->next) {
- CurrFile.start = CurrFile.start->next;
- ScreenDirty = true;
- }
- }
- Loc.line = Loc.line->next;
- }
-}
-
-static void cursor_up(void)
-{
- if (Loc.line->prev) {
- Curr.y--;
- if (Curr.y < 0) {
- Curr.y = 0;
- if (CurrFile.start->prev) {
- CurrFile.start = CurrFile.start->prev;
- ScreenDirty = true;
- }
- }
- Loc.line = Loc.line->prev;
- }
-}
-
-static void cursor_right(void)
-{
- if ((line_length() > 1) && (Curr.x < visible_length()))
- {
- Curr.x++;
- if (Curr.x >= Max.x) {
- Curr.x = Max.x-1;
- Loc.offset++;
- if (Loc.offset > visible_length())
- Loc.offset = visible_length();
- ScreenDirty = true;
- }
- }
-}
-
-static void cursor_home(void)
-{
- Curr.x = 0;
- Loc.offset = 0;
- ScreenDirty = true;
-}
-
-static void cursor_end(void)
-{
- Curr.x = ((line_length() <= 1) ? 0 : visible_length());
- Loc.offset = (line_length() > Max.x) ? line_length()-1 - Max.x : Loc.offset;
- ScreenDirty = true;
-}
+++ /dev/null
-#ifndef UTIL_H
-#define UTIL_H
-
-/* Standard Macros and Types */
-#include <stddef.h>
-#include <stdint.h>
-#include <stdbool.h>
-#include <errno.h>
-
-/* Useful Standard Functions */
-#include <signal.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <stdarg.h>
-#include <string.h>
-
-/* Generic Death Function */
-static void die(const char* msgfmt, ...)
-{
- va_list args;
- va_start(args, msgfmt);
- #ifdef CLEANUP_HOOK
- CLEANUP_HOOK();
- #endif
- fprintf(stderr, "Error: ");
- vfprintf(stderr, msgfmt, args);
- fprintf(stderr, "\n");
- va_end(args);
- exit(EXIT_FAILURE);
-}
-
-/* Signal Handling */
-static void esignal(int sig, void (*func)(int))
-{
- errno = 0;
- func = signal(sig, func);
- if (func == SIG_ERR || errno > 0)
- die("failed to register signal handler for signal %d", sig);
-}
-
-static int eraise(int sig)
-{
- int ret;
- if ((ret = raise(sig)) != 0)
- die("failed to raise signal %d", sig);
- return ret;
-}
-
-/* Dynamic Allocation */
-static void* ecalloc(size_t num, size_t size)
-{
- void* ret;
- if (NULL == (ret = calloc(num,size)))
- die("out of memory");
- return ret;
-}
-
-static void* emalloc(size_t size)
-{
- void* ret;
- if (NULL == (ret = malloc(size)))
- die("out of memory");
- return ret;
-}
-
-static void* erealloc(void* ptr, size_t size)
-{
- void* ret;
- if (NULL == (ret = realloc(ptr,size)))
- die("out of memory");
- return ret;
-}
-
-/* File Handling */
-static FILE* efopen(const char* filename, const char* mode)
-{
- FILE* file;
- errno = 0;
- if (NULL == (file = fopen(filename, mode)) || errno != 0)
- die("failed to open file: %s", filename);
- return file;
-}
-
-static char* efreadline(FILE* input)
-{
- size_t size = 8;
- size_t index = 0;
- char* str = (char*)emalloc(size);
- memset(str, 0, 8);
- if (feof(input)) {
- free(str);
- return NULL;
- }
- while (true) {
- char ch = fgetc(input);
- if (ch == EOF) break;
- str[index++] = ch;
- str[index] = '\0';
- if (index+1 >= size) {
- size = size << 1;
- str = erealloc(str, size);
- }
- if (ch == '\n') break;
- }
- return str;
-}
-
-/* String Handling */
-char* estrdup(const char *s)
-{
- char* ns = (char*)emalloc(strlen(s) + 1);
- strcpy(ns,s);
- return ns;
-}
-
-#endif
--- /dev/null
+/**
+ @brief A minimalistic unit testing framework for C.
+ @author Michael D. Lowis
+ @license BSD 2-clause License
+*/
+#ifndef ATF_H
+#define ATF_H
+
+#include <stddef.h>
+#include <stdbool.h>
+
+extern char* Curr_Test;
+void atf_init(int argc, char** argv);
+void atf_test_start(char* file, unsigned int line, char* name);
+bool atf_test_assert(bool success, char* expr_str, char* file, int line);
+void atf_test_fail(char* expr, char* file, int line);
+int atf_print_results(void);
+
+#define CHECK(expr) \
+ if(atf_test_assert((expr), #expr, __FILE__, __LINE__)) break
+
+#define TEST_SUITE(name) \
+ void name(void)
+
+#define TEST(desc) \
+ for(atf_test_start(__FILE__,__LINE__,#desc); Curr_Test != NULL; Curr_Test = NULL)
+
+#define RUN_TEST_SUITE(name) \
+ name();
+
+#define RUN_EXTERN_TEST_SUITE(name) \
+ do { extern TEST_SUITE(name); RUN_TEST_SUITE(name); } while(0)
+
+#define PRINT_TEST_RESULTS \
+ atf_print_results
+
+/* Function Definitions
+ *****************************************************************************/
+#ifdef INCLUDE_DEFS
+#include <stdio.h>
+#include <stdlib.h>
+#ifndef NO_SIGNALS
+#include <signal.h>
+#endif
+
+char* Curr_Test = NULL;
+char* Curr_File = NULL;
+unsigned int Curr_Line = 0;
+static unsigned int Total = 0;
+static unsigned int Failed = 0;
+
+#ifndef NO_SIGNALS
+static void handle_signal(int sig) {
+ /* Determine the signal name */
+ char* sig_name = NULL;
+ switch(sig) {
+ case SIGABRT: sig_name = "SIGABRT"; break;
+ case SIGBUS: sig_name = "SIGBUS"; break;
+ case SIGFPE: sig_name = "SIGFPE"; break;
+ case SIGILL: sig_name = "SIGILL"; break;
+ case SIGSEGV: sig_name = "SIGSEGV"; break;
+ case SIGSYS: sig_name = "SIGSYS"; break;
+ /* If we don't recognize it then just return and let the default handler
+ catch it. */
+ default: return;
+ }
+ /* Error and exit. No summary will be printed but the user will know which
+ test has crashed. */
+ fprintf(stderr,"%s:%d:0:%s:CRASH (signal: %d - %s)\n", Curr_File, Curr_Line, Curr_Test, sig, sig_name);
+ Failed++;
+ (void)atf_print_results();
+ exit(1);
+}
+#endif
+
+void atf_init(int argc, char** argv) {
+ /* I reserve the right to use these later */
+ (void)argc;
+ (void)argv;
+
+#ifndef NO_SIGNALS
+ /* Init signal handler */
+ signal(SIGABRT, handle_signal);
+ signal(SIGBUS, handle_signal);
+ signal(SIGFPE, handle_signal);
+ signal(SIGILL, handle_signal);
+ signal(SIGSEGV, handle_signal);
+ signal(SIGSYS, handle_signal);
+#endif
+}
+
+void atf_test_start(char* file, unsigned int line, char* name) {
+ Curr_File = file;
+ Curr_Line = line;
+ Curr_Test = name;
+ Total++;
+}
+
+bool atf_test_assert(bool success, char* expr, char* file, int line) {
+ bool failed = !success;
+ if (failed) atf_test_fail(expr,file,line);
+ return failed;
+}
+
+void atf_test_fail(char* expr, char* file, int line) {
+ Failed++;
+ printf("%s:%d:0:%s:FAIL:( %s )\n", file, line, Curr_Test, expr); \
+}
+
+int atf_print_results(void) {
+ static const char* results_string =
+ "\nUnit Test Summary"
+ "\n-----------------"
+ "\nTotal: %d"
+ "\nPassed: %d"
+ "\nFailed: %d"
+ "\n\n";
+ printf(results_string, Total, Total - Failed, Failed);
+ return Failed;
+}
+
+#undef INCLUDE_DEFS
+#endif
+
+#endif /* ATF_H */
--- /dev/null
+#include "atf.h"
+#include "../buf.c"
+
+static Buf TestBuf;
+
+static void set_buffer_text(char* str) {
+ int i = 0;
+ buf_clr(&TestBuf);
+ for (Rune* curr = TestBuf.bufstart; curr < TestBuf.bufend; curr++)
+ *curr = '-';
+ while (*str)
+ buf_ins(&TestBuf, i++, (Rune)*str++);
+}
+
+static bool buf_text_eq(char* str) {
+ for (unsigned i = 0; i < buf_end(&TestBuf); i++) {
+// printf("%c", (char)buf_get(&TestBuf, i));
+ if ((Rune)*(str++) != buf_get(&TestBuf, i))
+ return false;
+ }
+// puts("");
+ return true;
+}
+
+TEST_SUITE(BufferTests) {
+ /* Init and Clear
+ *************************************************************************/
+ TEST(buf_initsz should init the buffer to the given size) {
+ buf_initsz(&TestBuf, 1024);
+ CHECK(TestBuf.bufsize == 1024);
+ CHECK(TestBuf.bufstart == TestBuf.gapstart);
+ CHECK(TestBuf.bufend == TestBuf.gapend);
+ CHECK(TestBuf.bufend - TestBuf.bufstart == 1024);
+ CHECK(TestBuf.gapend - TestBuf.gapstart == 1024);
+ free(TestBuf.bufstart);
+ }
+
+ TEST(buf_init should init the buffer to the default size) {
+ buf_init(&TestBuf);
+ CHECK(TestBuf.bufsize == BufSize);
+ CHECK(TestBuf.bufstart == TestBuf.gapstart);
+ CHECK(TestBuf.bufend == TestBuf.gapend);
+ CHECK(TestBuf.bufend - TestBuf.bufstart == BufSize);
+ CHECK(TestBuf.gapend - TestBuf.gapstart == BufSize);
+ }
+
+ TEST(buf_clr should clear the buffer) {
+ buf_initsz(&TestBuf, 1024);
+ buf_clr(&TestBuf);
+ CHECK(TestBuf.bufsize == BufSize);
+ CHECK(TestBuf.bufstart == TestBuf.gapstart);
+ CHECK(TestBuf.bufend == TestBuf.gapend);
+ CHECK(TestBuf.bufend - TestBuf.bufstart == BufSize);
+ CHECK(TestBuf.gapend - TestBuf.gapstart == BufSize);
+ }
+
+ /* Insertions
+ *************************************************************************/
+ TEST(buf_ins should insert at 0 in empty buf) {
+ buf_clr(&TestBuf);
+ buf_ins(&TestBuf, 0, 'a');
+ CHECK(buf_text_eq("a"));
+ }
+
+ TEST(buf_ins should insert at 0) {
+ buf_clr(&TestBuf);
+ buf_ins(&TestBuf, 0, 'b');
+ buf_ins(&TestBuf, 0, 'a');
+ CHECK(buf_text_eq("ab"));
+ }
+
+ TEST(buf_ins should insert at 1) {
+ buf_clr(&TestBuf);
+ buf_ins(&TestBuf, 0, 'a');
+ buf_ins(&TestBuf, 1, 'b');
+ CHECK(buf_text_eq("ab"));
+ }
+
+ TEST(buf_ins should insert at 1) {
+ buf_clr(&TestBuf);
+ buf_ins(&TestBuf, 0, 'a');
+ buf_ins(&TestBuf, 1, 'c');
+ buf_ins(&TestBuf, 1, 'b');
+ CHECK(buf_text_eq("abc"));
+ }
+
+ TEST(buf_ins should sentence in larger text) {
+ set_buffer_text(
+ "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam elementum eros quis venenatis. "
+ );
+
+ buf_ins(&TestBuf, 5, ' ');
+ buf_ins(&TestBuf, 6, 'a');
+
+ CHECK(buf_text_eq(
+ "Lorem a ipsum dolor sit amet, consectetur adipiscing elit. Aliquam elementum eros quis venenatis. "
+ ));
+ }
+
+ /* Deletions
+ *************************************************************************/
+
+ /* Beginning and End of line
+ *************************************************************************/
+ TEST(buf_bol should move to first non newline character of line) {
+ set_buffer_text("\nabc\n");
+ CHECK(1 == buf_bol(&TestBuf, 3));
+ }
+
+ TEST(buf_bol should do nothing for blank line) {
+ set_buffer_text("\n\n");
+ CHECK(1 == buf_bol(&TestBuf, 1));
+ }
+
+ TEST(buf_eol should move to last non newline character of line) {
+ set_buffer_text("\nabc\n");
+ CHECK(3 == buf_eol(&TestBuf, 1));
+ }
+
+ TEST(buf_eol should do nothing for blank line) {
+ set_buffer_text("\n\n");
+ CHECK(1 == buf_eol(&TestBuf, 1));
+ }
+
+ /* Movement by Rune
+ *************************************************************************/
+ TEST(buf_byrune should do nothing for -1 at beginning of file)
+ {
+ set_buffer_text("abc\n");
+ CHECK(0 == buf_byrune(&TestBuf, 0, -1));
+ }
+
+ TEST(buf_byrune should do nothing for -2 at beginning of file)
+ {
+ set_buffer_text("abc\n");
+ CHECK(0 == buf_byrune(&TestBuf, 0, -2));
+ }
+
+ TEST(buf_byrune should do nothing for +1 at end of file)
+ {
+ set_buffer_text("abc\n");
+ CHECK(2 == buf_byrune(&TestBuf, 2, 1));
+ }
+
+ TEST(buf_byrune should do nothing for +2 at end of file)
+ {
+ set_buffer_text("abc\n");
+ CHECK(2 == buf_byrune(&TestBuf, 2, 2));
+ }
+
+ TEST(buf_byrune should skip newlines for -1)
+ {
+ set_buffer_text("ab\ncd\n");
+ CHECK(1 == buf_byrune(&TestBuf, 3, -1));
+ }
+
+ TEST(buf_byrune should skip newlines for +1)
+ {
+ set_buffer_text("ab\ncd\n");
+ CHECK(3 == buf_byrune(&TestBuf, 1, 1));
+ }
+
+ TEST(buf_byrune should not skip blank lines for -1)
+ {
+ set_buffer_text("ab\n\ncd\n");
+ CHECK(3 == buf_byrune(&TestBuf, 4, -1));
+ }
+
+ TEST(buf_byrune should not skip blank lines for +1)
+ {
+ set_buffer_text("ab\n\ncd\n");
+ CHECK(3 == buf_byrune(&TestBuf, 1, 1));
+ }
+
+ TEST(buf_byrune should move from blank line to non-blank line for -1)
+ {
+ set_buffer_text("ab\n\ncd\n");
+ CHECK(1 == buf_byrune(&TestBuf, 3, -1));
+ }
+
+ TEST(buf_byrune should move from blank line to non-blank line for +1)
+ {
+ set_buffer_text("ab\n\ncd\n");
+ CHECK(4 == buf_byrune(&TestBuf, 3, 1));
+ }
+
+ /* Movement by Line
+ *************************************************************************/
+
+}
--- /dev/null
+#define INCLUDE_DEFS
+#include "atf.h"
+
+int main(int argc, char** argv) {
+ atf_init(argc,argv);
+ RUN_EXTERN_TEST_SUITE(BufferTests);
+ return atf_print_results();
+}
--- /dev/null
+#include <signal.h>
+#include <stdio.h>
+#include <stdbool.h>
+
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/Xft/Xft.h>
+#include <X11/cursorfont.h>
+
+#include "edit.h"
+
+/*****************************************************************************/
+
+struct {
+ Display* display;
+ Visual* visual;
+ Colormap colormap;
+ unsigned depth;
+ int screen;
+ GC gc;
+ XftFont* font;
+ Window window;
+ Pixmap pixmap;
+ XftDraw* xft;
+ int width;
+ int height;
+ XIC xic;
+ XIM xim;
+} X;
+Buf Buffer;
+unsigned StartRow = 0;
+unsigned CursorPos = 0;
+unsigned LastDrawnPos = 0;
+
+void die(char* m) {
+ fprintf(stderr, "dying, %s\n", m);
+ exit(1);
+}
+
+static int init(void) {
+ signal(SIGPIPE, SIG_IGN); // Ignore the SIGPIPE signal
+ /* open the X display and get basic attributes */
+ if (!(X.display = XOpenDisplay(0)))
+ die("cannot open display");
+ Window root = DefaultRootWindow(X.display);
+ XWindowAttributes wa;
+ XGetWindowAttributes(X.display, root, &wa);
+ X.visual = wa.visual;
+ X.colormap = wa.colormap;
+ X.screen = DefaultScreen(X.display);
+ X.depth = DefaultDepth(X.display, X.screen);
+
+ /* create the main window */
+ X.window = XCreateSimpleWindow(X.display, root, 0, 0, Width, Height, 0, 0, WhitePixel(X.display, X.screen));
+ XSetWindowAttributes swa;
+ swa.backing_store = WhenMapped;
+ swa.bit_gravity = NorthWestGravity;
+ XChangeWindowAttributes(X.display, X.window, CWBackingStore|CWBitGravity, &swa);
+ XStoreName(X.display, X.window, "edit");
+ XSelectInput(X.display, X.window, StructureNotifyMask|ButtonPressMask|ButtonReleaseMask|Button1MotionMask|KeyPressMask|ExposureMask|FocusChangeMask);
+
+ /* simulate an initial resize and map the window */
+ XConfigureEvent ce;
+ ce.type = ConfigureNotify;
+ ce.width = Width;
+ ce.height = Height;
+ XSendEvent(X.display, X.window, False, StructureNotifyMask, (XEvent *)&ce);
+ XMapWindow(X.display, X.window);
+
+ /* set input methods */
+ if((X.xim = XOpenIM(X.display, 0, 0, 0)))
+ X.xic = XCreateIC(X.xim, XNInputStyle, XIMPreeditNothing|XIMStatusNothing, XNClientWindow, X.window, XNFocusWindow, X.window, NULL);
+
+ /* allocate font */
+ if(!(X.font = XftFontOpenName(X.display, X.screen, FONTNAME)))
+ die("cannot open default font");
+
+ /* initialize the graphics context */
+ XGCValues gcv;
+ gcv.foreground = WhitePixel(X.display, X.screen);
+ gcv.graphics_exposures = False;
+ X.gc = XCreateGC(X.display, X.window, GCForeground|GCGraphicsExposures, &gcv);
+
+ /* initialize pixmap and drawing context */
+ X.pixmap = XCreatePixmap(X.display, X.window, Width, Height, X.depth);
+ X.xft = XftDrawCreate(X.display, X.pixmap, X.visual, X.colormap);
+
+ return XConnectionNumber(X.display);
+}
+
+static void deinit(void) {
+ if (X.pixmap != None) {
+ XftDrawDestroy(X.xft);
+ XFreePixmap(X.display, X.pixmap);
+ }
+ XCloseDisplay(X.display);
+}
+
+static void handle_key(XEvent* e) {
+ int len;
+ char buf[8];
+ KeySym key;
+ Status status;
+ /* Read the key string */
+ if (X.xic)
+ len = Xutf8LookupString(X.xic, &e->xkey, buf, sizeof(buf), &key, &status);
+ else
+ len = XLookupString(&e->xkey, buf, sizeof(buf), &key, 0);
+ /* Handle the key */
+ switch (key) {
+ case XK_F6:
+ ColorBase = !ColorBase;
+ break;
+
+ case XK_Left:
+ CursorPos = buf_byrune(&Buffer, CursorPos, -1);
+ break;
+
+ case XK_Right:
+ CursorPos = buf_byrune(&Buffer, CursorPos, 1);
+ break;
+
+ case XK_Down:
+ CursorPos = buf_byline(&Buffer, CursorPos, 1);
+ break;
+
+ case XK_Up:
+ CursorPos = buf_byline(&Buffer, CursorPos, -1);
+ break;
+ }
+}
+
+static void handle_mousebtn(XEvent* e) {
+ switch (e->xbutton.button) {
+ case Button1: /* Left Button */
+ break;
+ case Button2: /* Middle Button */
+ break;
+ case Button3: /* Right Button */
+ break;
+ case Button4: /* Wheel Up */
+ StartRow = buf_byline(&Buffer, StartRow, -2);
+ break;
+ case Button5: /* Wheel Down */
+ StartRow = buf_byline(&Buffer, StartRow, 2);
+ break;
+ }
+}
+
+static void handle_event(XEvent* e) {
+ switch (e->type) {
+ case FocusIn:
+ if (X.xic) XSetICFocus(X.xic);
+ break;
+
+ case FocusOut:
+ if (X.xic) XUnsetICFocus(X.xic);
+ break;
+
+ case Expose: // Draw the window
+ XCopyArea(X.display, X.pixmap, X.window, X.gc, 0, 0, X.width, X.height, 0, 0);
+ XFlush(X.display);
+ break;
+
+ case ConfigureNotify: // Resize the window
+ if (e->xconfigure.width != X.width || e->xconfigure.height != X.height) {
+ X.width = e->xconfigure.width;
+ X.height = e->xconfigure.height;
+ X.pixmap = XCreatePixmap(X.display, X.window, X.width, X.height, X.depth);
+ X.xft = XftDrawCreate(X.display, X.pixmap, X.visual, X.colormap);
+ }
+ break;
+
+ case ButtonPress: // Mouse button handling
+ handle_mousebtn(e);
+ break;
+
+ case KeyPress: // Keyboard events
+ handle_key(e);
+ break;
+ }
+}
+
+static XftColor xftcolor(enum ColorId cid, uint16_t alpha) {
+ Color c = Palette[cid][ColorBase];
+ XftColor xc;
+ xc.color.red = ((c & 0x00FF0000) >> 8);
+ xc.color.green = ((c & 0x0000FF00));
+ xc.color.blue = ((c & 0x000000FF) << 8);
+ xc.color.alpha = alpha;
+ XftColorAllocValue(X.display, X.visual, X.colormap, &xc.color, &xc);
+ return xc;
+}
+
+static void redraw(void) {
+ int fheight = X.font->height;
+ int fwidth = X.font->max_advance_width;
+ /* Allocate the colors */
+ XftColor bkgclr = xftcolor(CLR_BASE03, UINT16_MAX);
+ XftColor gtrclr = xftcolor(CLR_BASE02, UINT16_MAX);
+ XftColor csrclr = xftcolor(CLR_BASE3, UINT16_MAX);
+ XftColor txtclr = xftcolor(CLR_BASE0, UINT16_MAX);
+
+ /* draw the background color */
+ XftDrawRect(X.xft, &bkgclr, 0, 0, X.width, X.height);
+ /* draw the status background */
+ XftDrawRect(X.xft, >rclr, 0, 0, X.width, fheight);
+
+ /* Scroll the view until the cursor is visible */
+ if (CursorPos > LastDrawnPos)
+ StartRow = buf_byline(&Buffer, StartRow, 1);
+ else if (CursorPos < StartRow)
+ StartRow = buf_byline(&Buffer, StartRow, -1);
+
+ /* Draw document text */
+ int x = 0, y = 2;
+ for (LastDrawnPos = StartRow; LastDrawnPos < buf_end(&Buffer); LastDrawnPos++) {
+ if (x * fwidth >= X.width)
+ y++, x = 0;
+ if (y * fheight >= X.height)
+ break;
+ FcChar32 ch = (FcChar32)buf_get(&Buffer, LastDrawnPos);
+ if (ch == '\n') { y++, x = 0; continue; }
+ if (ch == '\t') { x += TabWidth; continue; }
+ if (LastDrawnPos == CursorPos) {
+ XftDrawRect(X.xft, &csrclr, x * fwidth, ((y-1) * fheight) + 2, fwidth, fheight);
+ XftDrawString32(X.xft, &bkgclr, X.font, x * fwidth, y * fheight, (FcChar32 *)&ch, 1);
+ } else {
+ XftDrawString32(X.xft, &txtclr, X.font, x * fwidth, y * fheight, (FcChar32 *)&ch, 1);
+ }
+ x++;
+ }
+
+ /* flush the pixels to the screen */
+ XCopyArea(X.display, X.pixmap, X.window, X.gc, 0, 0, X.width, X.height, 0, 0);
+ XFlush(X.display);
+}
+
+int main(int argc, char** argv) {
+ init();
+ buf_init(&Buffer);
+ if (argc > 1)
+ buf_load(&Buffer, argv[1]);
+ XEvent e;
+ while (true) {
+ XPeekEvent(X.display,&e);
+ while (XPending(X.display)) {
+ XNextEvent(X.display, &e);
+ if (!XFilterEvent(&e, None))
+ handle_event(&e);
+ }
+ redraw();
+ }
+ deinit();
+ return 0;
+}