#include #include /* Copyright (c) 2020 Devine Lu Linvega Permission to use, copy, modify, and distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE. */ #define HOR 74 #define VER 32 #define PAD 2 #define SZ (HOR * VER * 16) #define TEXTSZ 2048 * 64 typedef struct { int unsaved, tlen, clen; char name[256], *text, *clip; } Document; typedef struct { int x, y; } Point2d; typedef struct { int from, to; } Range; int WIDTH = 8 * HOR + 8 * PAD * 2; int HEIGHT = 8 * (VER + 2) + 8 * PAD * 2; int FPS = 30, GUIDES = 1, ZOOM = 2, DOWN = 0, RULER = 60; Document doc; Range sel; Point2d view; Uint32 theme[] = { 0x000000, 0xFFFFFF, 0x72DEC2, 0x666666, 0x222222}; Uint8 icons[][8] = { {0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00}, /* ruler */ {0x55, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0xaa}, /* unknown */ {0x00, 0x00, 0x00, 0x10, 0x08, 0x10, 0x00, 0x00}, /* tab */ {0x04, 0x08, 0x50, 0xa4, 0x50, 0x08, 0x04, 0x00}, {0x28, 0x14, 0x28, 0x14, 0x28, 0x14, 0x28, 0x14}, /* scroll:bg */ {0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c}, /* scroll:fg */ {0x00, 0x00, 0x00, 0x82, 0x44, 0x38, 0x00, 0x00}, /* eye open */ {0x00, 0x38, 0x44, 0x92, 0x28, 0x10, 0x00, 0x00}, /* eye closed */ {0x10, 0x54, 0x28, 0xc6, 0x28, 0x54, 0x10, 0x00} /* unsaved */ }; Uint8 font[][8] = { {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, /* space */ {0x00, 0x08, 0x08, 0x08, 0x08, 0x08, 0x00, 0x08}, {0x00, 0x14, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x22, 0x7f, 0x22, 0x22, 0x22, 0x7f, 0x22}, {0x00, 0x08, 0x7f, 0x40, 0x3e, 0x01, 0x7f, 0x08}, {0x00, 0x21, 0x52, 0x24, 0x08, 0x12, 0x25, 0x42}, {0x00, 0x3e, 0x41, 0x42, 0x38, 0x05, 0x42, 0x3d}, {0x00, 0x08, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00}, /* quote */ {0x00, 0x08, 0x10, 0x10, 0x10, 0x10, 0x10, 0x08}, {0x00, 0x08, 0x04, 0x04, 0x04, 0x04, 0x04, 0x08}, {0x00, 0x00, 0x2a, 0x1c, 0x3e, 0x1c, 0x2a, 0x00}, {0x00, 0x00, 0x08, 0x08, 0x3e, 0x08, 0x08, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x10}, {0x00, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08}, {0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40}, {0x00, 0x3e, 0x41, 0x41, 0x41, 0x41, 0x41, 0x3e}, {0x00, 0x08, 0x18, 0x08, 0x08, 0x08, 0x08, 0x1c}, {0x00, 0x7e, 0x01, 0x01, 0x3e, 0x40, 0x40, 0x7f}, {0x00, 0x7e, 0x01, 0x01, 0x3e, 0x01, 0x01, 0x7e}, {0x00, 0x11, 0x21, 0x41, 0x7f, 0x01, 0x01, 0x01}, {0x00, 0x7f, 0x40, 0x40, 0x3e, 0x01, 0x01, 0x7e}, {0x00, 0x3e, 0x41, 0x40, 0x7e, 0x41, 0x41, 0x3e}, {0x00, 0x7f, 0x01, 0x01, 0x02, 0x04, 0x08, 0x08}, {0x00, 0x3e, 0x41, 0x41, 0x3e, 0x41, 0x41, 0x3e}, /* 8 */ {0x00, 0x3e, 0x41, 0x41, 0x3f, 0x01, 0x02, 0x04}, {0x00, 0x00, 0x00, 0x08, 0x00, 0x08, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x08, 0x00, 0x08, 0x08, 0x10}, {0x00, 0x00, 0x08, 0x10, 0x20, 0x10, 0x08, 0x00}, {0x00, 0x00, 0x00, 0x1c, 0x00, 0x1c, 0x00, 0x00}, {0x00, 0x00, 0x10, 0x08, 0x04, 0x08, 0x10, 0x00}, {0x00, 0x3e, 0x41, 0x01, 0x06, 0x08, 0x00, 0x08}, {0x00, 0x3e, 0x41, 0x5d, 0x55, 0x45, 0x59, 0x26}, {0x00, 0x3e, 0x41, 0x41, 0x7f, 0x41, 0x41, 0x41}, {0x00, 0x7e, 0x41, 0x41, 0x7e, 0x41, 0x41, 0x7e}, {0x00, 0x3e, 0x41, 0x40, 0x40, 0x40, 0x41, 0x3e}, {0x00, 0x7c, 0x42, 0x41, 0x41, 0x41, 0x42, 0x7c}, {0x00, 0x3f, 0x40, 0x40, 0x7f, 0x40, 0x40, 0x3f}, {0x00, 0x7f, 0x40, 0x40, 0x7e, 0x40, 0x40, 0x40}, {0x00, 0x3e, 0x41, 0x50, 0x4e, 0x41, 0x41, 0x3e}, {0x00, 0x41, 0x41, 0x41, 0x7f, 0x41, 0x41, 0x41}, {0x00, 0x1c, 0x08, 0x08, 0x08, 0x08, 0x08, 0x1c}, {0x00, 0x7f, 0x01, 0x01, 0x01, 0x01, 0x01, 0x7e}, {0x00, 0x41, 0x42, 0x44, 0x78, 0x44, 0x42, 0x41}, {0x00, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x7f}, {0x00, 0x63, 0x55, 0x49, 0x41, 0x41, 0x41, 0x41}, {0x00, 0x61, 0x51, 0x51, 0x49, 0x49, 0x45, 0x43}, {0x00, 0x1c, 0x22, 0x41, 0x41, 0x41, 0x22, 0x1c}, {0x00, 0x7e, 0x41, 0x41, 0x7e, 0x40, 0x40, 0x40}, {0x00, 0x3e, 0x41, 0x41, 0x41, 0x45, 0x42, 0x3d}, {0x00, 0x7e, 0x41, 0x41, 0x7e, 0x44, 0x42, 0x41}, {0x00, 0x3f, 0x40, 0x40, 0x3e, 0x01, 0x01, 0x7e}, {0x00, 0x7f, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08}, {0x00, 0x41, 0x41, 0x41, 0x41, 0x41, 0x42, 0x3d}, {0x00, 0x41, 0x41, 0x41, 0x41, 0x22, 0x14, 0x08}, {0x00, 0x41, 0x41, 0x41, 0x41, 0x49, 0x55, 0x63}, {0x00, 0x41, 0x22, 0x14, 0x08, 0x14, 0x22, 0x41}, {0x00, 0x41, 0x22, 0x14, 0x08, 0x08, 0x08, 0x08}, {0x00, 0x7f, 0x01, 0x02, 0x1c, 0x20, 0x40, 0x7f}, {0x00, 0x18, 0x10, 0x10, 0x10, 0x10, 0x10, 0x18}, {0x00, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01}, {0x00, 0x18, 0x08, 0x08, 0x08, 0x08, 0x08, 0x18}, {0x00, 0x08, 0x14, 0x22, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f}, {0x00, 0x08, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x7e, 0x01, 0x3e, 0x41, 0x7d}, {0x00, 0x00, 0x00, 0x40, 0x7e, 0x41, 0x41, 0x7e}, {0x00, 0x00, 0x00, 0x3e, 0x41, 0x40, 0x41, 0x3e}, {0x00, 0x00, 0x00, 0x01, 0x3f, 0x41, 0x41, 0x3f}, {0x00, 0x00, 0x00, 0x3e, 0x41, 0x7e, 0x40, 0x3f}, {0x00, 0x00, 0x00, 0x3f, 0x40, 0x7e, 0x40, 0x40}, {0x00, 0x00, 0x00, 0x3f, 0x41, 0x3f, 0x01, 0x7e}, {0x00, 0x00, 0x00, 0x40, 0x7e, 0x41, 0x41, 0x41}, {0x00, 0x00, 0x00, 0x08, 0x00, 0x08, 0x08, 0x08}, {0x00, 0x00, 0x00, 0x7f, 0x01, 0x01, 0x02, 0x7c}, {0x00, 0x00, 0x00, 0x41, 0x46, 0x78, 0x46, 0x41}, {0x00, 0x00, 0x00, 0x40, 0x40, 0x40, 0x40, 0x3f}, {0x00, 0x00, 0x00, 0x76, 0x49, 0x41, 0x41, 0x41}, {0x00, 0x00, 0x00, 0x61, 0x51, 0x49, 0x45, 0x43}, {0x00, 0x00, 0x00, 0x3e, 0x41, 0x41, 0x41, 0x3e}, {0x00, 0x00, 0x00, 0x7e, 0x41, 0x7e, 0x40, 0x40}, {0x00, 0x00, 0x00, 0x3f, 0x41, 0x3f, 0x01, 0x01}, {0x00, 0x00, 0x00, 0x5e, 0x61, 0x40, 0x40, 0x40}, {0x00, 0x00, 0x00, 0x3f, 0x40, 0x3e, 0x01, 0x7e}, {0x00, 0x00, 0x00, 0x7f, 0x08, 0x08, 0x08, 0x08}, {0x00, 0x00, 0x00, 0x41, 0x41, 0x41, 0x42, 0x3d}, {0x00, 0x00, 0x00, 0x41, 0x41, 0x22, 0x14, 0x08}, {0x00, 0x00, 0x00, 0x41, 0x41, 0x41, 0x49, 0x37}, {0x00, 0x00, 0x00, 0x41, 0x22, 0x1c, 0x22, 0x41}, {0x00, 0x00, 0x00, 0x41, 0x22, 0x1c, 0x08, 0x08}, {0x00, 0x00, 0x00, 0x7f, 0x01, 0x3e, 0x40, 0x7f}, {0x00, 0x08, 0x10, 0x10, 0x20, 0x10, 0x10, 0x08}, {0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08}, {0x00, 0x08, 0x04, 0x04, 0x02, 0x04, 0x04, 0x08}, {0x00, 0x00, 0x00, 0x30, 0x49, 0x06, 0x00, 0x00}}; SDL_Window *gWindow = NULL; SDL_Renderer *gRenderer = NULL; SDL_Texture *gTexture = NULL; Uint32 *pixels; #pragma mark - HELPERS int clamp(int val, int min, int max) { return (val >= min) ? (val <= max) ? val : max : min; } int cian(char c) { return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9'); } int cico(char c, char cc) { return c == '[' || c == '"' || (c == '/' && cc == '*'); } int cicc(char c, char cc) { return c == ']' || c == '"' || (c == '/' && cc == '*'); } int slen(char *s) { int i = 0; while(s[i] && s[++i]) ; return i; } char * scpy(char *src, char *dst, int len) { int i = 0; while((dst[i] = src[i]) && i < len - 2) i++; dst[i + 1] = '\0'; return dst; } int ssin(char *s, char *ss) { int a = 0, b = 0; while(s[a]) { if(s[a] == ss[b]) { if(!ss[b + 1]) return a - b; b++; } else b = 0; a++; } return -1; } Range Sel(int from, int to) { Range s; s.from = from; s.to = to; return s; } #pragma mark - IO void popblock(Range s) { int i, len = clamp(s.to + 1 - s.from, 1, doc.tlen); if(doc.tlen <= 0) return; for(i = s.from; i < doc.tlen; ++i) doc.text[i] = doc.text[clamp(i + len, 0, TEXTSZ - 1)]; doc.tlen -= len; doc.unsaved = 1; } void pushblock(Range s, char *src) { int i = 0, len = slen(src); if(doc.tlen + len >= TEXTSZ - 1) return; for(i = doc.tlen + len; i >= s.from + len; --i) doc.text[i] = doc.text[i - len]; for(i = 0; i < len; ++i) doc.text[s.from + i] = src[i]; doc.tlen += len; doc.unsaved = 1; } void popchar(int p) { int i; if(doc.tlen <= 0 || p < 0) return; for(i = p; i < doc.tlen; ++i) doc.text[i] = doc.text[i + 1]; doc.tlen--; doc.unsaved = 1; } void pushchar(int p, char c) { int i; if(doc.tlen + 1 >= TEXTSZ - 1) return; for(i = doc.tlen + 1; i > p; --i) doc.text[i] = doc.text[i - 1]; doc.text[p] = c; doc.tlen++; doc.unsaved = 1; } int getcol(int p) { int i = 0, res = 0; char c; if(p == 0) return 0; while((c = doc.text[i++])) { if(c == '\n') res = 0; else res++; if(i == p) return res; } return res; } int getrow(int p) { int i = 0, res = 0; char c; if(p == 0) return 0; while((c = doc.text[i++])) { if(c == '\n') res++; if(i == p) return res; } return res; } int getcell(int x, int y) { int i = 0, col = 0, row = 0; for(i = 0; i < doc.tlen; ++i) { char c = doc.text[i]; if(y == row && (c == '\n' || x == col)) return i; if(c == '\n') { row++; col = 0; } else col++; } return -1; } int nextword(int i) { char c; while(i < doc.tlen && (c = doc.text[i])) { if(!cian(c)) return i - 1; i++; } return doc.tlen - 1; } int prevword(int i) { char c; while(i >= 0 && (c = doc.text[i])) { if(!cian(c)) return i + 1; i--; } return 0; } #pragma mark - DRAW void clear(Uint32 *dst) { int i, j; for(i = 0; i < HEIGHT; i++) for(j = 0; j < WIDTH; j++) dst[i * WIDTH + j] = theme[0]; } void putpixel(Uint32 *dst, int x, int y, int color) { if(x >= 0 && x < WIDTH - 8 && y >= 0 && y < HEIGHT - 8) dst[(y + PAD * 8) * WIDTH + (x + PAD * 8)] = theme[color]; } void drawicn(Uint32 *dst, int x, int y, Uint8 *sprite, int fg, int bg) { int v, h; for(v = 0; v < 8; v++) for(h = 0; h < 8; h++) { int ch1 = (sprite[v] >> (7 - h)) & 0x1; putpixel(dst, x + h, y + v, ch1 ? fg : bg); } } void drawui(Uint32 *dst) { int bottom = VER * 8 + 8; drawicn(dst, 8 * 0, bottom, icons[GUIDES ? 7 : 6], GUIDES ? 1 : 2, 0); drawicn(dst, (HOR - 1) * 8, bottom, icons[8], doc.unsaved ? 2 : 3, 0); } Uint8 * geticn(char c) { if(c < 0) /* unknown */ return icons[1]; if(c == '\t' && GUIDES) /* tab */ return icons[2]; if(c < 32) /* special */ return font[0]; return font[(int)c - 32]; } int getfg(char c, int cmtline, int cmtblock, int selected) { if(selected) return 0; if(c == '\t' || cmtline) return 3; if(cmtblock) return 2; return 1; } void drawbody(Uint32 *dst) { int i = 0, x = 0, line = 0; int cmtblock = 0, cmtline = 0; int numpad = 4; for(i = 0; i <= doc.tlen; ++i) { char c = doc.text[i]; if(GUIDES && cico(c, i < doc.tlen ? doc.text[i + 1] : ' ')) cmtblock++; if(GUIDES && c == '#' && (i == 0 || doc.text[i - 1] == '\n')) cmtline = 1; if(x - view.x >= 0 && x - view.x - 1 < HOR - 6 && line >= view.y && line < VER + view.y) { int selected = i >= sel.from && i <= sel.to; drawicn(dst, (x + numpad - view.x) * 8, (line - view.y) * 8, geticn(c), getfg(c, cmtline, cmtblock, selected), selected ? 2 : 0); } x++; if(c == '\n') { x = 0; cmtline = 0; line++; } if(GUIDES && cicc(c, i > 0 ? doc.text[i - 1] : ' ')) cmtblock--; if(line - view.y > VER) break; } } void drawdecor(Uint32 *dst) { int y, at = getrow(sel.from), lmax = getrow(doc.tlen); int lpos = (int)(at / (double)lmax * (VER - 1)); int lfa = (int)(view.y / (double)lmax * (VER - 1)); int lfb = (int)((view.y + VER) / (double)lmax * (VER - 1)); for(y = 0; y < VER; ++y) { int lineid = y + view.y; /* scrollbar */ drawicn(dst, HOR * 8 - 7, y * 8, icons[lpos == y || (y >= lfa && y < lfb) ? 5 : 4], lpos == y ? 1 : 4, 0); /* ruler */ if(GUIDES) drawicn(dst, (RULER - view.x) * 8, y * 8, icons[0], 4, 0); /* line number */ drawicn(dst, 0 * 8, y * 8, geticn(lineid < 100 || lineid > lmax ? '.' : '0' + ((lineid / 100) % 10)), lineid == at ? 2 : 3, 0); drawicn(dst, 1 * 8, y * 8, geticn(lineid < 10 || lineid > lmax ? '.' : '0' + ((lineid / 10) % 10)), lineid == at ? 2 : 3, 0); drawicn(dst, 2 * 8, y * 8, geticn(lineid > lmax ? '.' : '0' + (lineid % 10)), lineid == at ? 2 : 3, 0); } } void redraw(Uint32 *dst) { clear(dst); drawdecor(dst); drawbody(dst); drawui(dst); SDL_UpdateTexture(gTexture, NULL, dst, WIDTH * sizeof(Uint32)); SDL_RenderClear(gRenderer); SDL_RenderCopy(gRenderer, gTexture, NULL, NULL); SDL_RenderPresent(gRenderer); } #pragma mark - OPTIONS int error(char *msg, const char *err) { printf("Error %s: %s\n", msg, err); return 0; } void setoption(int *i, int v) { *i = v; redraw(pixels); } void setscroll(int x, int y) { view.y = clamp(y, 0, getrow(doc.tlen)); view.x = x; redraw(pixels); } void setscrollto(Range r) { /* TODO: cleanup */ int colfrom, colto, rowfrom, rowto; colfrom = getcol(r.from); colto = getcol(r.to); rowfrom = getrow(r.from); rowto = getrow(r.to); /* vertical scroll */ if(rowto - view.y > VER - 1) view.y = rowto - VER + 1; else if(rowfrom - view.y < 0) view.y = rowfrom; /* horizontal scroll */ if(colto >= view.x + HOR - 4) view.x = -(HOR - 6 - colto); else if(colfrom < view.x) view.x = colfrom; } void setselection(int from, int to) { if(from == -1) return; sel.from = clamp(from, 0, doc.tlen); sel.to = to < from ? from : clamp(to, 0, doc.tlen); setscrollto(sel); redraw(pixels); } void docopy(char *src, Range r) { int len = clamp(r.to - r.from + 2, 1, TEXTSZ); scpy(src + r.from, doc.clip, len); setselection(sel.from, -1); } void docut(char *src, Range r) { int len = clamp(r.to - r.from + 2, 1, TEXTSZ); scpy(src + r.from, doc.clip, len); popblock(sel); setselection(sel.from, -1); } void dopaste(Range s) { if(sel.from != sel.to) popblock(sel); pushblock(s, doc.clip); setselection(s.from + slen(doc.clip), -1); } void dofind(char *src, int len) { int next; char buf[256]; scpy(src, buf, len); next = ssin(src + 1, buf); if(next > 0) setselection(sel.from + next + 1, sel.from + next + len + 1); } void erase(int shift) { if(sel.to - sel.from == 0) { popchar(sel.from - shift); setselection(clamp(sel.from - shift, 0, sel.to), -1); } else { popblock(sel); setselection(sel.from, -1); } } void insert(char c) { if(sel.to - sel.from > 0) erase(0); pushchar(sel.from, c); setselection(sel.from + 1, -1); } void wrap(Range s, char c, char cc) { if(s.to - s.from == 0) { insert(c); return; } pushchar(s.to + 1, cc); pushchar(s.from, c); setselection(s.to + 2, -1); } void modindent(Range s, int mod) { Range range = Sel(getcell(0, getrow(s.from)), getcell(0, getrow(s.to) + 1)); if(mod > 0) pushchar(range.from, '\t'); else if(mod < 0) if(doc.text[range.from] == '\t') popchar(range.from); setselection(getcell(0, getrow(s.from)), -1); } void modcase(Range s, int mod) { int i; for(i = s.from; i <= s.to; ++i) { char c = doc.text[i]; if(mod > 0 && c >= 'a' && c <= 'z') doc.text[i] = c - 'a' + 'A'; else if(mod < 0 && c >= 'A' && c <= 'Z') doc.text[i] = c - 'A' + 'a'; } redraw(pixels); } void makedoc(Document *d, char *name) { d->text = (char *)malloc(TEXTSZ); d->text[0] = '\0'; d->tlen = 0; d->unsaved = 0; scpy(name, d->name, 256); printf("Make: %s\n", d->name); setselection(0, -1); } int savedoc(Document *d, char *name) { int i; FILE *f = fopen(name, "w"); for(i = 0; i < d->tlen; ++i) fputc(d->text[i], f); d->unsaved = 0; scpy(name, d->name, 256); fclose(f); printf("Saved: %s\n", d->name); redraw(pixels); return 1; } int opendoc(Document *d, char *name) { char line[256]; FILE *f = fopen(name, "r"); if(!f) return error("Load", "Invalid input file"); d->text = (char *)malloc(TEXTSZ); d->tlen = 0; while(fgets(line, 256, f)) { int i = 0; char c; while((c = line[i++])) { if(d->tlen >= TEXTSZ - 1) { makedoc(d, "untitled->txt"); return error("Load", "Text too long."); } d->text[d->tlen++] = c; } } d->text[d->tlen] = '\0'; d->unsaved = 0; scpy(name, d->name, 256); printf("Loaded: %s\n", d->name); fclose(f); setselection(0, -1); return 1; } void selectoption(int option) { switch(option) { case 0: setoption(&GUIDES, !GUIDES); break; case HOR - 1: savedoc(&doc, doc.name); break; } } void quit(void) { free(doc.text); free(pixels); SDL_DestroyTexture(gTexture); gTexture = NULL; SDL_DestroyRenderer(gRenderer); gRenderer = NULL; SDL_DestroyWindow(gWindow); gWindow = NULL; SDL_Quit(); exit(0); } #pragma mark - TRIGGERS void domouse(SDL_Event *event) { int xcell, ycell; switch(event->type) { case SDL_MOUSEBUTTONUP: DOWN = 0; break; case SDL_MOUSEBUTTONDOWN: xcell = event->motion.x / ZOOM / 8 - PAD; ycell = event->motion.y / ZOOM / 8 - PAD; DOWN = 1; if(ycell == VER + 1) /* options */ selectoption(xcell); else if(xcell >= HOR - 1) /* scrollbar */ setscroll(0, (ycell + 1) * (getrow(doc.tlen) / VER)); else if(xcell >= 0 && xcell <= 3) /* line number */ setselection(getcell(0, view.y + ycell), getcell(0, view.y + ycell + 1) - 1); else if(xcell >= 4 && ycell >= 0 && ycell <= VER) { if(event->button.button != 1) dofind(doc.text + sel.from, sel.to - sel.from); else if(SDL_GetModState() & KMOD_LSHIFT) setselection(sel.from, getcell(view.x + xcell - 4, view.y + ycell)); else setselection(getcell(view.x + xcell - 4, view.y + ycell), -1); if(event->button.clicks == 2) { Range word = Sel(prevword(sel.from), nextword(sel.from)); setselection(word.from, word.to); } } break; case SDL_MOUSEMOTION: if(DOWN) { xcell = event->motion.x / ZOOM / 8 - PAD; ycell = event->motion.y / ZOOM / 8 - PAD; if(xcell >= HOR - 1) setscroll(0, ycell * (getrow(doc.tlen) / VER) - 1); else if(xcell >= 4 && ycell >= 0 && ycell <= VER) setselection(sel.from, getcell(view.x + xcell - 5, view.y + ycell)); } break; } } void dokey(SDL_Event *event) { int shift = SDL_GetModState() & KMOD_LSHIFT || SDL_GetModState() & KMOD_RSHIFT; int ctrl = SDL_GetModState() & KMOD_LCTRL || SDL_GetModState() & KMOD_RCTRL; int alt = SDL_GetModState() & KMOD_LALT || SDL_GetModState() & KMOD_RALT; if(ctrl) { switch(event->key.keysym.sym) { /* Generic */ case SDLK_n: makedoc(&doc, "untitled.txt"); break; case SDLK_r: opendoc(&doc, doc.name); break; case SDLK_s: savedoc(&doc, doc.name); break; case SDLK_h: setoption(&GUIDES, !GUIDES); break; /* Edit */ case SDLK_x: docut(doc.text, sel); break; case SDLK_c: docopy(doc.text, sel); break; case SDLK_v: dopaste(sel); break; case SDLK_a: setselection(0, doc.tlen - 1); break; case SDLK_LEFT: setselection(getcell(0, getrow(sel.from)), -1); break; /* go end of line */ case SDLK_RIGHT: setselection(getcell(0, getrow(sel.from) + 1) - 1, -1); break; /* go beginnign of line */ case SDLK_LEFTBRACKET: modindent(sel, -1); break; case SDLK_RIGHTBRACKET: modindent(sel, 1); break; } } else if(alt) { switch(event->key.keysym.sym) { case SDLK_u: modcase(sel, 1); break; case SDLK_l: modcase(sel, -1); break; } } else { switch(event->key.keysym.sym) { case SDLK_RETURN: insert('\n'); break; case SDLK_TAB: insert('\t'); break; case SDLK_ESCAPE: setselection(sel.from, sel.from); break; case SDLK_BACKSPACE: erase(1); break; case SDLK_DELETE: erase(0); break; case SDLK_DOWN: if(shift) setselection(sel.from, getcell(getcol(sel.to), getrow(sel.to) + 1)); else setselection(getcell(getcol(sel.from), getrow(sel.from) + 1), -1); break; case SDLK_UP: if(shift) setselection(sel.from, getcell(getcol(sel.to), getrow(sel.to) - 1)); else setselection(getcell(getcol(sel.from), getrow(sel.from) - 1), -1); break; case SDLK_PAGEDOWN: setselection(getcell(getcol(sel.from), getrow(sel.from) + 10), -1); break; case SDLK_PAGEUP: setselection(getcell(getcol(sel.from), getrow(sel.from) - 10), -1); break; case SDLK_LEFT: setselection(sel.from - (shift ? 0 : 1), shift ? sel.to - 1 : sel.from - 1); break; case SDLK_RIGHT: setselection(sel.from + (shift ? 0 : 1), shift ? sel.to + 1 : sel.from + 1); break; } } } void dotext(SDL_Event *event) { int i; if(SDL_GetModState() & KMOD_LCTRL || SDL_GetModState() & KMOD_RCTRL) return; for(i = 0; i < SDL_TEXTINPUTEVENT_TEXT_SIZE; ++i) { char c = event->text.text[i]; if(c < ' ' || c > '~') break; switch(c) { case '(': wrap(sel, c, ')'); break; case '[': wrap(sel, c, ']'); break; case '{': wrap(sel, c, '}'); break; case '<': wrap(sel, c, '>'); break; case '"': wrap(sel, c, '"'); break; default: insert(c); } } } void dowheel(SDL_Event *event) { if(SDL_GetModState() & (KMOD_LCTRL | KMOD_RCTRL)) setselection(getcell(getcol(sel.from), getrow(sel.from) - event->wheel.y), -1); else setscroll(0, view.y - event->wheel.y); } int init(void) { if(SDL_Init(SDL_INIT_VIDEO) < 0) return error("Init", SDL_GetError()); gWindow = SDL_CreateWindow("Left", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, WIDTH * ZOOM, HEIGHT * ZOOM, SDL_WINDOW_SHOWN); if(gWindow == NULL) return error("Window", SDL_GetError()); gRenderer = SDL_CreateRenderer(gWindow, -1, 0); if(gRenderer == NULL) return error("Renderer", SDL_GetError()); gTexture = SDL_CreateTexture(gRenderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STATIC, WIDTH, HEIGHT); if(gTexture == NULL) return error("Texture", SDL_GetError()); pixels = (Uint32 *)malloc(WIDTH * HEIGHT * sizeof(Uint32)); if(pixels == NULL) return error("Pixels", "Failed to allocate memory"); clear(pixels); doc.clip = (char *)malloc(TEXTSZ); return 1; } int main(int argc, char *argv[]) { int ticknext = 0; if(!init()) return error("Init", "Failure"); if(argc > 1) { if(!opendoc(&doc, argv[1])) makedoc(&doc, argv[1]); } else makedoc(&doc, "untitled.txt"); while(1) { int tick = SDL_GetTicks(); SDL_Event event; if(tick < ticknext) SDL_Delay(ticknext - tick); ticknext = tick + (1000 / FPS); while(SDL_PollEvent(&event) != 0) { switch(event.type) { case SDL_QUIT: quit(); break; case SDL_MOUSEBUTTONUP: case SDL_MOUSEBUTTONDOWN: case SDL_MOUSEMOTION: domouse(&event); break; case SDL_TEXTINPUT: dotext(&event); break; case SDL_KEYDOWN: dokey(&event); break; case SDL_MOUSEWHEEL: dowheel(&event); break; case SDL_WINDOWEVENT: if(event.window.event == SDL_WINDOWEVENT_EXPOSED) redraw(pixels); break; } } } quit(); return 0; }