XXIIVV
Working on Oscean with Left picture
15A08 — Working on Oscean with Left

Left is a text editor.

Left is graphical plain-text editor inspired by Acme on Plan9, in which operations such a find and copy/paste are driven by mouse chording.

A web version was initially created to help rekka with the writing of wiktopher, and later rewritten as a lightweight SDL graphical text editor. The web version features an auto-complete, synonyms suggestions, writing statistics, markup-based navigation and a speed-reader. These features will hopefully make their way into the new version.

left.c

To control the window size and default colors, edit the values defined at the top of the file itself. The font and interface icons are stored in the chr_format, you can make your own font with Nasu. To learn more, visit the repository.

cc left.c -std=c89 -Os -DNDEBUG -g0 -s -Wall -L/usr/local/lib -lSDL2 -o left

The following code is a single-file implementation written in ANSI C, the only dependecy is SDL2.

#include <SDL2/SDL.h>
#include <stdio.h>

/* 
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)
		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
setpixel(Uint32 *dst, int x, int y, int color)
{
	if(x >= 0 && x < WIDTH - 8 && y >= 0 && y < HEIGHT - 8)
		dst[(y + 8 * PAD) * WIDTH + (x + 8 * PAD)] = theme[color];
}

void
drawicon(Uint32 *dst, int x, int y, Uint8 *icon, int fg, int bg)
{
	int v, h;
	for(v = 0; v < 8; v++)
		for(h = 0; h < 8; h++) {
			int clr = (icon[v] >> (7 - h)) & 0x1;
			setpixel(dst, x + h, y + v, clr == 1 ? fg : bg);
		}
}

void
drawui(Uint32 *dst)
{
	int bottom = VER * 8 + 8;
	drawicon(dst, 8 * 0, bottom, icons[GUIDES ? 7 : 6], GUIDES ? 1 : 2, 0);
	drawicon(dst, (HOR - 1) * 8, bottom, icons[8], doc.unsaved ? 2 : 3, 0);
}

Uint8 *
geticon(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];
}

void
drawbody(Uint32 *dst)
{
	int i = 0, x = 0, line = 0;
	int block = 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] : ' '))
			block++;
		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;
			drawicon(dst,
				(x + numpad - view.x) * 8,
				(line - view.y) * 8,
				geticon(c),
				c == '\t' ? 3 : selected ? 0
										 : abs(block % 3) + 1,
				selected ? 2 : 0);
		}
		x++;
		if(c == '\n') {
			x = 0;
			line++;
		}
		if(GUIDES && cicc(c, i > 0 ? doc.text[i - 1] : ' '))
			block--;
		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 */
		drawicon(dst, HOR * 8 - 7, y * 8, icons[lpos == y || (y >= lfa && y < lfb) ? 5 : 4], lpos == y ? 1 : 4, 0);
		/* ruler */
		if(GUIDES)
			drawicon(dst, (RULER - view.x) * 8, y * 8, icons[0], 4, 0);
		/* line number */
		drawicon(dst, 0 * 8, y * 8, geticon(lineid < 100 || lineid > lmax ? '.' : '0' + ((lineid / 100) % 10)), lineid == at ? 2 : 3, 0);
		drawicon(dst, 1 * 8, y * 8, geticon(lineid < 10 || lineid > lmax ? '.' : '0' + ((lineid / 10) % 10)), lineid == at ? 2 : 3, 0);
		drawicon(dst, 2 * 8, y * 8, geticon(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
setscroll(int x, int y)
{
	view.y = clamp(y, 0, getrow(doc.tlen) - VER);
	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)
{
	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
savemode(int *i, int v)
{
	*i = v;
	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: savemode(&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) * (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: savemode(&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;
		else if(c == '(')
			wrap(sel, c, ')');
		else if(c == '[')
			wrap(sel, c, ']');
		else if(c == '{')
			wrap(sel, c, '}');
		else if(c == '<')
			wrap(sel, c, '>');
		else if(c == '"')
			wrap(sel, c, '"');
		else
			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;
}
— Submit an edit to left.c.txt(909 lines)
Original Writing Layout picture
11X04 — Original Writing Layout
Left Screencapture picture
11R07 — Left Screencapture
Tooling picture
11P10 — Tooling

incoming(2): themes computer

Last update on 15A09, edited 28 times. +120/174fh ---|||