From: Michael D. Lowis Date: Sun, 25 Sep 2016 21:46:43 +0000 (-0400) Subject: Replaced current implementation with gap-buffer and X11 ui X-Git-Url: https://git.mdlowis.com/?a=commitdiff_plain;h=12f43ebba9ccf335f79ccd2582af15acd52e5c0d;p=projs%2Ftide.git Replaced current implementation with gap-buffer and X11 ui --- diff --git a/Makefile b/Makefile index 304fa69..bc11ad5 100644 --- a/Makefile +++ b/Makefile @@ -1,79 +1,22 @@ -#------------------------------------------------------------------------------ -# 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 diff --git a/buf.c b/buf.c new file mode 100644 index 0000000..0213e2a --- /dev/null +++ b/buf.c @@ -0,0 +1,180 @@ +#include +#include +#include +#include + +#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); +} diff --git a/config.mk b/config.mk deleted file mode 100644 index 2176092..0000000 --- a/config.mk +++ /dev/null @@ -1,17 +0,0 @@ -#------------------------------------------------------------------------------ -# 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 diff --git a/edit.h b/edit.h new file mode 100644 index 0000000..1faf6f9 --- /dev/null +++ b/edit.h @@ -0,0 +1,84 @@ +/* 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" + diff --git a/source/main.c b/source/main.c deleted file mode 100644 index 8f86979..0000000 --- a/source/main.c +++ /dev/null @@ -1,258 +0,0 @@ -/* Includes - *****************************************************************************/ -static void cleanup(void); -#define CLEANUP_HOOK cleanup -#include -#include - -/* 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; -} diff --git a/source/util.h b/source/util.h deleted file mode 100644 index d32bcef..0000000 --- a/source/util.h +++ /dev/null @@ -1,116 +0,0 @@ -#ifndef UTIL_H -#define UTIL_H - -/* Standard Macros and Types */ -#include -#include -#include -#include - -/* Useful Standard Functions */ -#include -#include -#include -#include -#include - -/* 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 diff --git a/tests/atf.h b/tests/atf.h new file mode 100755 index 0000000..b0f07c7 --- /dev/null +++ b/tests/atf.h @@ -0,0 +1,125 @@ +/** + @brief A minimalistic unit testing framework for C. + @author Michael D. Lowis + @license BSD 2-clause License +*/ +#ifndef ATF_H +#define ATF_H + +#include +#include + +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 +#include +#ifndef NO_SIGNALS +#include +#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 */ diff --git a/tests/buf.c b/tests/buf.c new file mode 100644 index 0000000..2977917 --- /dev/null +++ b/tests/buf.c @@ -0,0 +1,190 @@ +#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 + *************************************************************************/ + +} diff --git a/tests/tests.c b/tests/tests.c new file mode 100644 index 0000000..d1465d6 --- /dev/null +++ b/tests/tests.c @@ -0,0 +1,8 @@ +#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(); +} diff --git a/xedit.c b/xedit.c new file mode 100644 index 0000000..601ff49 --- /dev/null +++ b/xedit.c @@ -0,0 +1,256 @@ +#include +#include +#include + +#include +#include +#include +#include + +#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; +}