ste/src/ste.c

600 lines
13 KiB
C
Raw Normal View History

2019-10-22 16:26:30 +02:00
#include <ncurses.h>
#include <stdlib.h>
#include <string.h>
2019-10-25 20:20:38 +02:00
#include <ctype.h>
2019-11-08 15:43:41 +01:00
#include <locale.h>
2019-10-22 16:26:30 +02:00
2019-11-18 23:54:59 +01:00
#include "fbuffer.h"
2019-11-19 16:37:58 +01:00
#include "config.h"
2019-11-18 23:54:59 +01:00
2019-10-22 16:26:30 +02:00
/* defines */
#define CTRL(k) ((k) & 0x1f) // Control mask modifier
#define STAT_SIZE 128
2019-11-13 18:55:48 +01:00
#define _XOPEN_SOURCE_EXTENDED
#define _GNU_SOURCE
2019-11-27 12:24:24 +01:00
#define SBUF_SIZE 2048
// Search buffer
typedef struct sbuf {
char c[SBUF_SIZE];
int num;
} sbuf;
2019-10-22 16:26:30 +02:00
/* main data structure containing:
* -cursor position
2019-10-30 00:30:46 +01:00
* +real (matching memory)
* +offset (memory and screen offset)
* +render (drawn on the screen)
2019-10-22 16:26:30 +02:00
* -window size
* -statusbar message */
struct term {
struct {
int x;
int y;
int off_x;
int off_y;
2019-10-22 16:26:30 +02:00
int r_x;
int r_y;
2019-10-22 16:26:30 +02:00
} cur;
struct {
int x;
int y;
} dim;
char statusbar[STAT_SIZE];
2019-10-22 16:26:30 +02:00
int pad;
2019-11-27 12:24:24 +01:00
char mode_b;
sbuf search_buffer;
2019-10-22 16:26:30 +02:00
} t;
2019-11-27 12:24:24 +01:00
fbuffer rows;
2019-10-22 16:26:30 +02:00
2019-11-13 18:55:48 +01:00
const char *msg[] = {
"Find: ",
"Nigger"
};
2019-10-22 16:26:30 +02:00
/* Prototypes */
/* draw operations */
static void drawBar (char *s);
static void drawScreen ();
static void drawLines (void);
static void curUpdateRender (void);
2019-10-22 16:26:30 +02:00
static void cursorMove(int a);
2019-10-29 20:28:08 +01:00
static int decimalSize (int n);
static inline void lnMove (int y, int x);
2019-10-22 16:26:30 +02:00
/* Terminal operations */
static void termInit (void);
static void termExit (void);
static void termDie (char *s);
/* file operations */
static void fileOpen (char *filename);
void fileSave (char *filename);
/* buffer operations */
2019-11-13 18:55:48 +01:00
static int editorFind (const char* needle, int* y, int* x);
2019-10-22 16:26:30 +02:00
/* garbage */
2019-11-03 23:11:05 +01:00
static void handleDel (int select);
2019-10-22 16:26:30 +02:00
/* testing */
2019-10-29 17:12:00 +01:00
static void updateInfo (void);
static int whatsThat (void);
2019-11-27 12:24:24 +01:00
static void insert (sbuf *buf, int c);
static inline void flush (sbuf *buf);
2019-10-22 16:26:30 +02:00
/* --------------------------------- main ------------------------------------ */
int main (int argc, char *argv[])
{
/* Initialize the first row */
2019-11-18 23:54:59 +01:00
bufInit(&rows);
2019-10-22 16:26:30 +02:00
/* Try to open the file */
2019-11-01 00:05:47 +01:00
if (argc < 2) {
2019-10-22 16:26:30 +02:00
perror("File not found");
exit(1);
} else fileOpen(argv[1]);
/* Initialize the terminal in raw mode,
* start curses and initialize the term struct */
termInit();
/* Set the statusbar left (static) message */
snprintf(t.statusbar, STAT_SIZE, "%s %d lines %dx%d", argv[1], rows.rownum, t.dim.y, t.dim.x);
2019-10-22 16:26:30 +02:00
/* Main event loop */
int c;
while (1) {
2019-11-12 16:38:45 +01:00
updateInfo();
/* Redraw the screen */
drawScreen();
2019-10-22 16:26:30 +02:00
/* Wait for an event (keypress) */
switch (c = getch()) {
case (CTRL('q')):
termExit();
break;
2019-10-22 16:26:30 +02:00
case (KEY_LEFT):
case (KEY_RIGHT):
case (KEY_UP):
case (KEY_DOWN):
cursorMove(c);
break;
2019-10-28 18:37:18 +01:00
case (KEY_BACKSPACE):
2019-11-03 23:11:05 +01:00
handleDel(0);
2019-10-28 18:37:18 +01:00
break;
2019-10-28 18:37:18 +01:00
case (KEY_DC):
2019-11-03 23:11:05 +01:00
handleDel(1);
2019-10-28 18:37:18 +01:00
break;
2019-10-29 17:12:00 +01:00
case (KEY_ENTER):
case (10):
case ('\r'):
2019-11-27 12:24:24 +01:00
if ((t.mode_b & 0x1) == 0x0) {
rowAddRow(&rows, t.cur.y, t.cur.x);
t.cur.y++;
t.cur.x = 0;
} else {
2019-11-27 12:39:26 +01:00
editorFind(t.search_buffer.c, &t.cur.y, &t.cur.x);
2019-11-27 12:24:24 +01:00
// Toggle mode
t.mode_b ^= 0x1;
flush (&t.search_buffer);
}
2019-10-29 17:12:00 +01:00
break;
2019-10-29 17:12:00 +01:00
case (KEY_END):
t.cur.y = rows.rownum - 1;
2019-10-29 17:12:00 +01:00
break;
2019-10-29 20:28:08 +01:00
case (KEY_HOME):
t.cur.y = 0;
break;
2019-11-13 18:55:48 +01:00
case (CTRL('f')):
2019-11-27 12:24:24 +01:00
// Toggle mode
t.mode_b ^= 0x1;
flush (&t.search_buffer);
2019-11-13 18:55:48 +01:00
break;
2019-10-25 20:20:38 +02:00
default:
2019-11-27 12:24:24 +01:00
if ((t.mode_b & 0x1) == 0x0) {
if (c == KEY_STAB) c = '\t';
rowAddChar(&rows.rw[t.cur.y], c, t.cur.x);
t.cur.x++;
} else {
insert(&t.search_buffer, c);
}
2019-11-11 22:17:47 +01:00
break;
2019-10-22 16:26:30 +02:00
}
}
/* If by chance i find myself here be sure
* end curses mode and clenaup */
termExit();
return 0;
}
/* ----------------------------- end of main ---------------------------------- */
void termInit (void)
{
2019-11-08 15:43:41 +01:00
/* Init locales */
setlocale(LC_ALL, "");
2019-10-22 16:26:30 +02:00
/* Init the screen and refresh */
initscr();
refresh();
/* Enable raw mode, this makes the termianl ignore
* interrupt signals like CTRL+C and CTRL+Z
* allowing us to make our own bindings */
raw();
/* Allow use of function keys */
keypad(stdscr, TRUE);
/* Turn off echoing */
noecho();
/* Set the tab size */
set_tabsize(TABSIZE);
/* Start color mode */
start_color();
2019-10-23 00:32:45 +02:00
init_pair(2, COLOR_BLACK, COLOR_CYAN);
2019-10-29 17:12:00 +01:00
init_pair(1, COLOR_RED, COLOR_BLACK);
2019-10-23 00:32:45 +02:00
/* Set default color */
2019-10-25 20:20:38 +02:00
//bkgd(COLOR_PAIR(1));
2019-10-22 16:26:30 +02:00
/* Populate the main data structure */
2019-10-29 17:12:00 +01:00
updateInfo();
2019-10-22 16:26:30 +02:00
/* Initialize the data staructure */
t.cur.x = t.cur.off_x = 0;
t.cur.y = t.cur.off_y = 0;
}
/* Calculate the correct spacing for the line numbers
* based on the size of the file */
2019-10-29 20:28:08 +01:00
int decimalSize (int n)
2019-10-22 16:26:30 +02:00
{
2019-10-29 20:28:08 +01:00
static int l;
2019-10-22 16:26:30 +02:00
for (l = 0; n > 0; l++) n /= 10;
return l + 1;
}
void termExit (void)
{
erase();
refresh();
endwin();
exit(0);
}
void termDie (char *s)
{
2019-10-23 00:32:45 +02:00
erase();
2019-10-22 16:26:30 +02:00
refresh();
endwin();
perror(s);
exit(1);
}
/* ----------------------------- term operations -------------------------------- */
void drawScreen ()
{
/* Clear the screen */
erase();
/* Update Scroll */
curUpdateRender();
2019-10-22 16:26:30 +02:00
/* draw the lines */
drawLines();
/* draw the bar */
2019-11-27 12:39:26 +01:00
drawBar(
((t.mode_b & 0x1) == 0) ? t.statusbar :
t.search_buffer.c
);
2019-10-22 16:26:30 +02:00
/* move back to the cursor position */
lnMove(t.cur.r_y, t.cur.r_x);
2019-10-22 16:26:30 +02:00
/* refresh the screen */
refresh();
}
/* Draw all the appropriate lines (following cursor) to the screen */
void drawLines (void)
{
2019-11-12 16:38:45 +01:00
register short ln, i;
register int start;
2019-10-22 16:26:30 +02:00
for (i = 0, ln = 0; i < t.dim.y + t.cur.off_y; i++) {
2019-10-22 16:26:30 +02:00
ln = i + t.cur.off_y;
2019-11-13 18:55:48 +01:00
/* vi style tildes */
if (ln >= rows.rownum) {
mvaddch(i, 0, '~');
} else {
/* Draw the line number */
attron(COLOR_PAIR(1));
mvprintw(i, 0, "%d", ln + 1);
mvaddch(i, t.pad - 1, ACS_VLINE);
2019-11-13 18:55:48 +01:00
attroff(COLOR_PAIR(1));
lnMove(i, 0);
/* Draw the line matcing render memory */
if (&rows.rw[ln] == NULL) termDie("drawlines NULL");
if (rows.rw[ln].r_size > t.cur.off_x) {
start = t.cur.off_x;
while (isCont(rows.rw[ln].render[start])) start++;
addnstr(&rows.rw[ln].render[start], (t.dim.x + 1) + (rows.rw[ln].utf >> 2));
}
}
2019-10-30 23:11:28 +01:00
lnMove(i + 1, 0);
2019-10-22 16:26:30 +02:00
}
lnMove(t.cur.y, t.cur.x);
}
/* Move avoiding the space allocated for line numbers */
inline void lnMove (int y, int x)
2019-10-22 16:26:30 +02:00
{
move(y, x + t.pad);
2019-10-22 16:26:30 +02:00
}
/* Draw the status bar at the bottom of the screen */
void drawBar (char *s)
{
/* Set maximum contrast for bar */
2019-10-25 20:20:38 +02:00
attron(COLOR_PAIR(2));
2019-10-22 16:26:30 +02:00
/* get the length of the statusbar text */
int len = strlen(s);
/* Print the message */
mvprintw(t.dim.y, 0, s);
/* Fill everything else with spaces */
static int i;
for (i = len; i <= t.dim.x + t.pad; i++)
2019-10-22 16:26:30 +02:00
mvaddch(t.dim.y, i, ' ');
static char m[40];
sprintf(m, "x: %d y: %d Zoom: %c", t.cur.x, t.cur.y, whatsThat());
2019-10-22 16:26:30 +02:00
mvaddstr(t.dim.y, t.dim.x + t.pad - strlen(m), m);
/* Return to normal contrast mode */
2019-10-25 20:20:38 +02:00
attroff(COLOR_PAIR(2));
2019-10-22 16:26:30 +02:00
}
/* -------------------------------- draw operations -------------------------------- */
/* Open a file and put it into a buffer line by line */
void fileOpen (char *filename)
{
FILE *fp = fopen(filename, "r");
2019-11-01 00:05:47 +01:00
if (fp == NULL) termDie("Cannot open file");
2019-10-22 16:26:30 +02:00
/* Init the linebuffer */
char *line = NULL;
/* Set linecap to 0 so getline will atumatically allocate
* memory for the line buffer*/
size_t linecap = 0;
ssize_t linelen;
/* getline returns -1 if no new line is present */
while ((linelen = getline(&line, &linecap, fp)) != -1) {
while (linelen > 0 && (line[linelen - 1] == '\n' || line[linelen - 1] == '\r'))
linelen--;
2019-11-18 23:54:59 +01:00
rowAddLast(&rows, line, linelen);
2019-10-22 16:26:30 +02:00
}
/* free the line buffer */
free(line);
/* close the file */
fclose(fp);
}
2019-11-03 23:11:05 +01:00
/*------------------------------------- file operations ----------------------------------*/
2019-10-22 16:26:30 +02:00
/* take care of the cursor movement */
2019-11-05 17:15:39 +01:00
void cursorMove (int a)
2019-10-22 16:26:30 +02:00
{
switch (a) {
case (KEY_LEFT):
if (t.cur.x <= 0) {
2019-11-05 17:15:39 +01:00
if (t.cur.y) {
2019-10-22 16:26:30 +02:00
t.cur.y--;
2019-11-05 17:15:39 +01:00
t.cur.x = rows.rw[t.cur.y].size;
2019-10-22 16:26:30 +02:00
}
2019-11-08 15:43:41 +01:00
} else if (isCont(rows.rw[t.cur.y].chars[t.cur.x - 1])) {
do {
t.cur.x--;
} while(!isStart(rows.rw[t.cur.y].chars[t.cur.x]));
2019-11-21 22:28:04 +01:00
} else
2019-11-05 17:15:39 +01:00
t.cur.x--;
2019-10-22 16:26:30 +02:00
break;
2019-11-21 22:28:04 +01:00
2019-10-22 16:26:30 +02:00
case (KEY_RIGHT):
2019-11-05 17:15:39 +01:00
if (t.cur.x >= rows.rw[t.cur.y].size) {
if (t.cur.y < rows.rownum - 1) {
t.cur.y++;
t.cur.x = 0;
}
2019-11-08 15:43:41 +01:00
} else if (isStart(rows.rw[t.cur.y].chars[t.cur.x])) {
do {
t.cur.x++;
} while(isCont(rows.rw[t.cur.y].chars[t.cur.x]));
2019-11-21 22:28:04 +01:00
} else
2019-11-05 17:15:39 +01:00
t.cur.x++;
2019-10-22 16:26:30 +02:00
break;
2019-11-21 22:28:04 +01:00
2019-10-22 16:26:30 +02:00
case (KEY_UP):
2019-11-05 17:15:39 +01:00
if (t.cur.y) {
t.cur.y--;
if (t.cur.x > rows.rw[t.cur.y].size)
t.cur.x = rows.rw[t.cur.y].size;
2019-10-22 16:26:30 +02:00
}
break;
case (KEY_DOWN):
2019-11-05 17:15:39 +01:00
if (t.cur.y < rows.rownum - 1) {
2019-10-22 16:26:30 +02:00
t.cur.y++;
2019-11-05 17:15:39 +01:00
if (t.cur.x > rows.rw[t.cur.y].size)
t.cur.x = rows.rw[t.cur.y].size;
2019-10-22 16:26:30 +02:00
}
break;
}
2019-11-05 17:15:39 +01:00
}
2019-10-22 16:26:30 +02:00
2019-11-05 17:15:39 +01:00
void curUpdateRender ()
2019-10-22 16:26:30 +02:00
{
2019-11-11 22:17:47 +01:00
/*
Whole file (memory)
_____________________________
|(0, 0) |
| |
| off_y |
| +--------------+ |
| | | |
| | | |
| | | off_x |
| off_x| | + |
| | | dim_x |
| | | |
| | | |
| | | |
| +--------------+ |
| off_y + dim.y |
| |
| |
| |
|_____________________________| rows.rownum
The inner sqaure represents the render area
and it is delimited by:
left: x -> t.cur.off_x
right: x -> t.cur.off_x + (t.dim.x - 1)
upper: y -> t.cur.off_y
lower: y -> t.cur.off_y + (t.dim.y - 1)
2019-11-21 22:28:04 +01:00
The rows are drawn top to botom, starting
2019-11-11 22:17:47 +01:00
from the left most char of the render area
until row end or when the characters hit
the right bound.
*/
/* Adjust x and y if they are out of bounds */
if (t.cur.y < 0 || t.cur.y >= rows.rownum) t.cur.y = 0;
if (t.cur.x < 0) t.cur.x = 0;
2019-11-05 17:15:39 +01:00
if (t.cur.y >= t.cur.off_y && t.cur.y < t.cur.off_y + t.dim.y) {
t.cur.r_y = t.cur.y - t.cur.off_y;
2019-10-22 16:26:30 +02:00
2019-11-05 17:15:39 +01:00
} else if (t.cur.y >= t.cur.off_y + t.dim.y) {
if (t.cur.y == t.cur.off_y + t.dim.y) t.cur.off_y++;
2019-11-13 18:55:48 +01:00
else t.cur.off_y += t.cur.y - (t.cur.off_y + t.dim.y) + 1;
2019-11-05 17:15:39 +01:00
t.cur.r_y = t.dim.y - 1;
2019-11-21 22:28:04 +01:00
2019-11-05 17:15:39 +01:00
} else if (t.cur.y < t.cur.off_y) {
t.cur.off_y -= t.cur.off_y - t.cur.y;
t.cur.r_y = 0;
}
2019-11-08 15:43:41 +01:00
static int i, c;
for (c = i = 0, t.cur.r_x = 0; i < t.cur.x; i++) {
c = rows.rw[t.cur.y].chars[i];
2019-11-21 22:28:04 +01:00
2019-11-08 15:43:41 +01:00
/* continue (skip increment) if you encounter a continuation char */
if (isCont(c)) continue;
else if (isStart(c)) t.cur.r_x++;
2019-11-09 15:59:19 +01:00
if (c == '\t') t.cur.r_x += (TABSIZE - 1);
2019-11-08 15:43:41 +01:00
2019-11-05 17:15:39 +01:00
t.cur.r_x++;
}
2019-11-09 15:59:19 +01:00
if (t.cur.r_x >= t.cur.off_x && t.cur.r_x < t.cur.off_x + t.dim.x) {
t.cur.r_x -= t.cur.off_x;
2019-11-05 17:15:39 +01:00
} else if (t.cur.r_x < t.cur.off_x) {
t.cur.off_x -= t.cur.off_x - t.cur.r_x;
t.cur.r_x = 0;
2019-11-05 17:15:39 +01:00
2019-11-09 15:59:19 +01:00
} else if (t.cur.r_x >= t.cur.off_x + t.dim.x) {
2019-11-13 18:55:48 +01:00
t.cur.off_x += t.cur.r_x - (t.cur.off_x + t.dim.x);
2019-11-05 17:15:39 +01:00
t.cur.r_x = t.dim.x;
2019-10-22 16:26:30 +02:00
}
2019-11-05 17:15:39 +01:00
}
2019-10-22 16:26:30 +02:00
/*---------------------------------- scroll ------------------------------------*/
/* See whats under the cursor (memory) */
int whatsThat (void) {
int c = rows.rw[t.cur.y].chars[t.cur.x];
2019-10-22 16:26:30 +02:00
switch (c) {
case ('\t'):
return '^';
break;
case (' '):
return '~';
break;
2019-10-23 00:32:45 +02:00
case ('\0'):
return '.';
break;
2019-10-22 16:26:30 +02:00
default:
return c;
break;
}
return 0;
}
2019-11-03 15:30:24 +01:00
2019-11-03 23:11:05 +01:00
void handleDel (int select)
{
if (!select) {
if (t.cur.x <= 0 && t.cur.y > 0) {
t.cur.x = rows.rw[t.cur.y - 1].size;
2019-11-11 22:17:47 +01:00
rowAppendString(&rows.rw[t.cur.y - 1], rows.rw[t.cur.y].chars, rows.rw[t.cur.y].size);
2019-11-18 23:54:59 +01:00
rowDeleteRow(&rows, t.cur.y);
2019-11-03 23:11:05 +01:00
t.cur.y--;
} else {
2019-11-12 16:38:45 +01:00
if (isUtf(rows.rw[t.cur.y].chars[t.cur.x - 1])) {
do {
if (rowDeleteChar(&rows.rw[t.cur.y], 0, t.cur.x))
t.cur.x--;
} while (!isStart(rows.rw[t.cur.y].chars[t.cur.x - 1]));
}
if (rowDeleteChar(&rows.rw[t.cur.y], 0, t.cur.x))
t.cur.x--;
2019-11-03 23:11:05 +01:00
}
2019-11-03 15:30:24 +01:00
} else {
if (t.cur.x >= rows.rw[t.cur.y].size) {
2019-11-11 22:17:47 +01:00
rowAppendString(&rows.rw[t.cur.y], rows.rw[t.cur.y + 1].chars, rows.rw[t.cur.y + 1].size);
2019-11-18 23:54:59 +01:00
rowDeleteRow(&rows, t.cur.y + 1);
2019-11-03 23:11:05 +01:00
} else {
2019-11-12 16:38:45 +01:00
if (isStart(rows.rw[t.cur.y].chars[t.cur.x])) {
do {
rowDeleteChar(&rows.rw[t.cur.y], 1, t.cur.x);
} while (isCont(rows.rw[t.cur.y].chars[t.cur.x]));
} else rowDeleteChar(&rows.rw[t.cur.y], 1, t.cur.x);
2019-11-03 23:11:05 +01:00
}
2019-11-03 15:30:24 +01:00
}
}
2019-10-22 16:26:30 +02:00
void updateInfo (void)
{
getmaxyx(stdscr, t.dim.y, t.dim.x);
t.dim.y -= 1;
2019-10-30 23:11:28 +01:00
t.pad = decimalSize(rows.rownum - 1);
2019-10-22 16:26:30 +02:00
t.dim.x -= t.pad + 1;
}
2019-11-11 22:17:47 +01:00
/* Check for utf-8 char type */
2019-11-08 15:43:41 +01:00
int isUtf (int c) {
return (c >= 0x80 || c < 0 ? 1 : 0);
}
int isCont (int c) {
return ((c &= 0xC0) == 0x80 ? 1 : 0);
}
int isStart (int c) {
return (isUtf(c) && !isCont(c) ? 1 : 0);
}
2019-11-13 18:55:48 +01:00
2019-11-21 23:01:19 +01:00
int editorFind (const char* needle, int *y, int *x)
2019-11-13 18:55:48 +01:00
{
2019-11-21 23:01:19 +01:00
/* Create a pointer to store the location of the match */
2019-11-13 18:55:48 +01:00
char *res = NULL;
2019-11-21 23:01:19 +01:00
static int i, c;
2019-11-13 18:55:48 +01:00
2019-11-21 23:01:19 +01:00
/* Search trough all the buffer */
2019-11-13 18:55:48 +01:00
for (i = *y + 1; i < rows.rownum; i++) {
res = strstr(rows.rw[i].chars, needle);
if (res != NULL) break;
}
if (res == NULL) return 0;
2019-11-21 23:01:19 +01:00
/* If something wa found convert from pointer to yx coodinates */
2019-11-13 18:55:48 +01:00
*y = c = i;
for (i = 0; i <= rows.rw[c].size; i++)
if (&rows.rw[c].chars[i] == res) break;
*x = i;
return 1;
}
2019-11-27 12:24:24 +01:00
void insert (sbuf *buf, int c)
{
2019-11-27 12:39:26 +01:00
if (buf->num < SBUF_SIZE - 2)
2019-11-27 12:24:24 +01:00
buf->c[buf->num++] = c;
2019-11-27 12:39:26 +01:00
buf->c[buf->num] = '\0';
2019-11-27 12:24:24 +01:00
}
void flush (sbuf *buf)
{
buf->num = 0;
}
2019-10-23 00:32:45 +02:00
/*--------------------------------- testing ------------------------------------*/