From 5acb9bb0fa5920bc135cce8fd5a4381ecb6612cc Mon Sep 17 00:00:00 2001 From: "Michael D. Lowis" Date: Mon, 3 Oct 2016 20:43:11 -0400 Subject: [PATCH] Added basic editing capabilties and mode toggling --- Makefile | 2 +- edit.h | 20 +++++++++--- utf8.c | 96 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ xedit.c | 36 +++++++++++++++++++-- 4 files changed, 146 insertions(+), 8 deletions(-) create mode 100644 utf8.c diff --git a/Makefile b/Makefile index 702fb2e..840f74b 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ LDFLAGS = -L/opt/X11/lib -lX11 -lXft CFLAGS = --std=c99 -Wall -Wextra -I. -I/opt/X11/include -I/opt/local/include/freetype2 -I/usr/include/freetype2 -SRCS = xedit.c buf.c screen.c +SRCS = xedit.c buf.c screen.c utf8.c OBJS = $(SRCS:.c=.o) TESTSRCS = tests/tests.c tests/buf.c TESTOBJS = $(TESTSRCS:.c=.o) diff --git a/edit.h b/edit.h index b38f798..3173a73 100644 --- a/edit.h +++ b/edit.h @@ -72,13 +72,25 @@ Rune screen_getcell(unsigned row, unsigned col); *****************************************************************************/ void die(char* msg); +/* UTF-8 Handling + *****************************************************************************/ +#define UTF_MAX 6u +#define RUNE_SELF ((Rune)0x80) +#define RUNE_ERR ((Rune)0xFFFD) +#define RUNE_MAX ((Rune)0x10FFFF) +#define RUNE_EOF ((Rune)EOF) + +size_t utf8encode(char str[UTF_MAX], Rune rune); +bool utf8decode(Rune* rune, size_t* length, int byte); + /* Configuration *****************************************************************************/ enum { - Width = 640, - Height = 480, - TabWidth = 4, - BufSize = 8192, + Width = 640, + Height = 480, + TabWidth = 4, + ScrollLines = 1, + BufSize = 8192, }; static enum { DARK = 0, LIGHT = 1 } ColorBase = DARK; diff --git a/utf8.c b/utf8.c new file mode 100644 index 0000000..02074a2 --- /dev/null +++ b/utf8.c @@ -0,0 +1,96 @@ +/** + @brief Simple UTF-8 encoding and decoding routines. + @author Michael D. Lowis + @license BSD 2-clause License +*/ +#include "edit.h" + +const uint8_t UTF8_SeqBits[] = { 0x00u, 0x80u, 0xC0u, 0xE0u, 0xF0u, 0xF8u, 0xFCu, 0xFEu }; +const uint8_t UTF8_SeqMask[] = { 0x00u, 0xFFu, 0x1Fu, 0x0Fu, 0x07u, 0x03u, 0x01u, 0x00u }; +const uint8_t UTF8_SeqLens[] = { 0x01u, 0x00u, 0x02u, 0x03u, 0x04u, 0x05u, 0x06u, 0x00u }; + +bool runevalid(Rune val) { + return (val <= RUNE_MAX) + && ((val & 0xFFFEu) != 0xFFFEu) + && ((val < 0xD800u) || (val > 0xDFFFu)) + && ((val < 0xFDD0u) || (val > 0xFDEFu)); +} + +size_t runelen(Rune rune) { + if(!runevalid(rune)) + return 0; + else if(rune <= 0x7F) + return 1; + else if(rune <= 0x07FF) + return 2; + else if(rune <= 0xFFFF) + return 3; + else + return 4; +} + +uint8_t utfseq(uint8_t byte) { + for (int i = 1; i < 8; i++) + if ((byte & UTF8_SeqBits[i]) == UTF8_SeqBits[i-1]) + return UTF8_SeqLens[i-1]; + return 0; +} + +size_t utf8encode(char str[UTF_MAX], Rune rune) { + size_t len = runelen(rune); + str[0] = (len == 1 ? 0x00 : UTF8_SeqBits[len]) + | (UTF8_SeqMask[len] & (rune >> (6 * (len-1)))); + for (size_t i = 1; i < len; i++) + str[i] = 0x80u | (0x3Fu & (rune >> (6 * (len-i-1)))); + return len; +} + +bool utf8decode(Rune* rune, size_t* length, int byte) { + /* Handle the start of a new rune */ + if (*length == 0) { + /* If we were fed in an EOF as a start byte, handle it here */ + if (byte == EOF) { + *rune = RUNE_EOF; + } else { + /* Otherwise, decode the first byte of the rune */ + *length = utfseq(byte); + *rune = (*length == 0) ? RUNE_ERR : (byte & UTF8_SeqMask[*length]); + (*length)--; + } + /* Handle continuation bytes */ + } else if ((byte & 0xC0) == 0x80) { + /* add bits from continuation byte to rune value + * cannot overflow: 6 byte sequences contain 31 bits */ + *rune = (*rune << 6) | (byte & 0x3F); /* 10xxxxxx */ + (*length)--; + /* Sanity check the final rune value before finishing */ + if ((*length == 0) && !runevalid(*rune)) + *rune = RUNE_ERR; + /* Didn't get the continuation byte we expected */ + } else { + *rune = RUNE_ERR; + } + /* Tell the caller whether we finished or not */ + return ((*length == 0) || (*rune == RUNE_ERR)); +} + +size_t utflen(const char* s) { + size_t len = 0; + Rune rune = 0; + while (*s && !utf8decode(&rune, &len, *(s++))) + len++; + return len; +} + +Rune fgetrune(FILE* f) { + Rune rune = 0; + size_t length = 0; + while (!utf8decode(&rune, &length, fgetc(f))); + return rune; +} + +void fputrune(Rune rune, FILE* f) { + char utf[UTF_MAX] = {0}; + utf8encode(utf, rune); + fprintf(f, "%s", utf); +} diff --git a/xedit.c b/xedit.c index 08992ba..77145f7 100644 --- a/xedit.c +++ b/xedit.c @@ -138,6 +138,28 @@ static void handle_key(XEvent* e) { case XK_Up: CursorPos = buf_byline(&Buffer, CursorPos, -1); break; + + case XK_Escape: + InsertMode = false; + break; + + case XK_BackSpace: + if (InsertMode) + buf_del(&Buffer, --CursorPos); + break; + + default: + if (len > 0) { + Rune r; + size_t len = 0; + if (buf[0] == '\r') + buf[0] = '\n'; + for(int i = 0; i < 8 && !utf8decode(&r, &len, buf[i]); i++); + printf("Rune: '%c'\n", (char)r); + if (InsertMode) + buf_ins(&Buffer, CursorPos++, r); + } + break; } } @@ -150,8 +172,10 @@ static void handle_mousebtn(XEvent* e) { case Button3: /* Right Button */ break; case Button4: /* Wheel Up */ + CursorPos = buf_byline(&Buffer, CursorPos, -ScrollLines); break; case Button5: /* Wheel Down */ + CursorPos = buf_byline(&Buffer, CursorPos, ScrollLines); break; } } @@ -222,9 +246,15 @@ static void redraw(void) { if (CursorPos == pos) csrx = x, csry = y; Rune r = buf_get(&Buffer, pos++); - if (r == '\n') { break; } - if (r == '\t') { x += 4; break; } - screen_setcell(y,x,r); + if (r == '\n') { + screen_setcell(y,x,' '); + break; + } else if (r == '\t') { + screen_setcell(y,x,' '); + x += TabWidth - (x % TabWidth) - 1; + } else { + screen_setcell(y,x,r); + } } } EndPos = pos-1; -- 2.49.0