2023-07-27 22:49:28 +03:00
|
|
|
/* nne: no-nonsense editor
|
|
|
|
|
* A complete text editor in a single ANSI C89 file under 1000 SLOC
|
|
|
|
|
* Usage: nne [file]
|
|
|
|
|
* Build with:
|
|
|
|
|
* cc -std=c89 -Os -O2 -s nne.c -o nne [-DNNE_IOBUFSZ=n] [-DNNE_TABWIDTH=m]
|
|
|
|
|
* See README.md for features, controls and other details
|
|
|
|
|
* Created by Luxferre in 2023, released into public domain */
|
|
|
|
|
|
|
|
|
|
#define _POSIX_SOURCE
|
|
|
|
|
#define _POSIX_C_SOURCE 1
|
|
|
|
|
#include <stdlib.h>
|
|
|
|
|
#include <unistd.h>
|
|
|
|
|
#include <stdio.h> /* we only use s(n)printf, never printf itself */
|
|
|
|
|
#include <string.h>
|
|
|
|
|
#include <stdarg.h> /* for message formatter */
|
|
|
|
|
#include <errno.h>
|
|
|
|
|
#include <termios.h>
|
|
|
|
|
#include <signal.h>
|
|
|
|
|
|
|
|
|
|
/* classic redefinitions */
|
|
|
|
|
#define uint unsigned int
|
|
|
|
|
#define ushort unsigned short
|
|
|
|
|
#define uchar unsigned char
|
|
|
|
|
#define NNE_CSZ sizeof(uint) /* single text character internal size */
|
|
|
|
|
/* max amount of chars to be input/output on prompts/statuses */
|
|
|
|
|
#ifndef NNE_IOBUFSZ
|
|
|
|
|
#define NNE_IOBUFSZ 1000
|
|
|
|
|
#endif
|
|
|
|
|
#ifndef NNE_TABWIDTH
|
|
|
|
|
#define NNE_TABWIDTH 2
|
|
|
|
|
#endif
|
|
|
|
|
#define NNE_PAGESIZE 2048 /* memory page size in bytes for main text buffer */
|
|
|
|
|
|
|
|
|
|
/* terminal control macros (constants) */
|
|
|
|
|
#define ERESET "\x1b[0m" /* reset the styling */
|
|
|
|
|
#define CLS "\x1b[2J" /* clear the entire screen */
|
|
|
|
|
#define LINECLR "\x1b[2K" /* clear the current line */
|
|
|
|
|
#define CURRESET "\x1b[0;0H" /* reset the visual cursor */
|
|
|
|
|
#define CURSHOW "\x1b[?25h" /* show the cursor */
|
|
|
|
|
#define CURHIDE "\x1b[?25l" /* hide the cursor */
|
|
|
|
|
#define ALTBUFON "\x1b[?47h" /* turn on alternate screen */
|
|
|
|
|
#define ALTBUFOFF "\x1b[?47l" /* turn off alternate screen */
|
|
|
|
|
|
|
|
|
|
/* terminal control macros (sprintf templates) */
|
|
|
|
|
#define CURSET "\x1b[%03u;%03uH" /* set the cursor position (line;col) */
|
|
|
|
|
|
|
|
|
|
/* some enums */
|
|
|
|
|
enum nne_modes { NNE_NORMAL = 1, NNE_CMD }; /* operation modes */
|
|
|
|
|
enum nne_keys { /* special keys */
|
|
|
|
|
K_ESC = 27, K_BACKSPACE = 127,
|
|
|
|
|
/* negative PC keys so that we don't conflict with any UTF-8 codepoint */
|
|
|
|
|
K_UP = 0xFFFFFF00, K_DOWN, K_RIGHT, K_LEFT, /* arrow keys */
|
|
|
|
|
K_INS, K_DEL, K_HOME, K_END, K_PGUP, K_PGDN, /* IBM PC specific keys */
|
|
|
|
|
K_MODCMD /* modstring pseudo-key */
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/* some terminal I/O helpers */
|
|
|
|
|
struct termios tty_opts_backup, tty_opts_raw;
|
|
|
|
|
|
|
|
|
|
/* editor status variables and buffers */
|
|
|
|
|
static ushort nne_termw = 80, nne_termh = 25; /* current terminal size */
|
|
|
|
|
static ushort nne_scrx, nne_scry; /* current on-screen cursor position */
|
|
|
|
|
static ushort nne_mode; /* current modes: NNE_NORMAL, NNE_CMD */
|
|
|
|
|
static ushort nne_status_override = 0; /* statusbar override */
|
|
|
|
|
static ushort nne_file_loaded = 0; /* set to 1 if a file is loaded */
|
|
|
|
|
static ushort nne_file_saved = 0; /* set to 1 if the file is just saved */
|
|
|
|
|
static char nne_fname[NNE_IOBUFSZ * NNE_CSZ]; /* pointer to the file name */
|
|
|
|
|
static char *nne_scrbuf; /* reallocatable screen buffer */
|
|
|
|
|
static uint nne_scrpos; /* absolute screen buffer position */
|
|
|
|
|
static uint nne_scrsize; /* byte length of the visual screen buffer */
|
|
|
|
|
static uint *nne_textbuf; /* (UTF-8) reallocatable main text buffer */
|
|
|
|
|
static char nne_msgbuf[NNE_IOBUFSZ] = {0}; /* output buffer */
|
|
|
|
|
static uint nne_cmdbuf[NNE_IOBUFSZ] = {0}; /* (UTF-8) prompt buffer */
|
|
|
|
|
static uint nne_searchbuf[NNE_IOBUFSZ] = {0}; /* (UTF-8) search buffer */
|
|
|
|
|
static uint nne_searchlen = 0; /* actual search buffer length */
|
|
|
|
|
static int nne_searchidx = -1; /* running search index */
|
|
|
|
|
static uint *nne_clipbuf; /* (UTF-8) reallocatable clipboard buffer */
|
|
|
|
|
static uint nne_cliplen = 0; /* actual clipboard buffer length */
|
|
|
|
|
static uint nne_len; /* current physical length of nne_textbuf */
|
|
|
|
|
static uint nne_real_len; /* current trackable length of nne_textbuf */
|
|
|
|
|
static int nne_pos; /* current in-document absolute position */
|
|
|
|
|
static uint nne_row, nne_col; /* current in-document cursor position */
|
|
|
|
|
static uint nne_scr_row; /* current wrapped cursor vertical position */
|
|
|
|
|
static uint nne_buflines; /* amount of lines loaded into the buffer */
|
|
|
|
|
static int nne_line_offset = 0; /* offset from the start to the screen */
|
|
|
|
|
|
|
|
|
|
/* elementary routines */
|
|
|
|
|
|
|
|
|
|
/* generic routines to output string constants */
|
|
|
|
|
void nnputs(char *str) {write(1, str, strlen(str));}
|
|
|
|
|
char* nnmsg(int desc, char *format, ...) {
|
|
|
|
|
va_list aptr;
|
|
|
|
|
int r;
|
|
|
|
|
memset(nne_msgbuf, 0, NNE_IOBUFSZ); /* zero out the message buffer */
|
|
|
|
|
va_start(aptr, format);
|
|
|
|
|
r = vsnprintf(nne_msgbuf, NNE_IOBUFSZ - 1, format, aptr);
|
|
|
|
|
va_end(aptr);
|
|
|
|
|
if(r > 0 && desc > 0) write(desc, nne_msgbuf, r);
|
|
|
|
|
return nne_msgbuf;
|
|
|
|
|
}
|
|
|
|
|
#define nnformat(...) nnmsg(0,__VA_ARGS__)
|
|
|
|
|
#define nnprintf(...) nnmsg(1,__VA_ARGS__)
|
|
|
|
|
#define nntrace(...) nnmsg(2,__VA_ARGS__)
|
|
|
|
|
/* write a widechar (internal) to stdout */
|
|
|
|
|
void nnwritew(int w) {uchar c;while((c=(w&255))>0){write(1,&c,1);w>>=8;}}
|
|
|
|
|
|
|
|
|
|
void cleanup() { /* screen and other resources cleanup routine */
|
|
|
|
|
int ecode = errno; /* save the error code */
|
|
|
|
|
signal(SIGWINCH, SIG_DFL); /* reset signal handler */
|
|
|
|
|
tcsetattr(0, TCSANOW, &tty_opts_backup); /* restore terminal options */
|
|
|
|
|
nnputs(ERESET ALTBUFOFF); /* return to the default screen buffer */
|
|
|
|
|
if(ecode) perror("Error"); /* print exit reason if errored out */
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
uchar readc() { /* read a single raw byte from stdin */
|
|
|
|
|
uchar c = 0;
|
|
|
|
|
int nread;
|
|
|
|
|
while((nread = read(0, &c, 1)) != 1)
|
|
|
|
|
if(nread == -1 && errno != EAGAIN && errno != EINTR)
|
|
|
|
|
exit(errno);
|
|
|
|
|
return c;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Reallocate a buffer based upon the page size, return the new bufptr */
|
|
|
|
|
void* page_realloc(void *buf, uint curlen, uint targetlen, uint *reslen, uint pagesize) {
|
|
|
|
|
if(targetlen < 1) targetlen = 1; /* safeguard */
|
|
|
|
|
if(pagesize < 1) pagesize = 1; /* safeguard */
|
|
|
|
|
uint alloclen = targetlen, r = targetlen % pagesize;
|
|
|
|
|
if(r > 0) alloclen = targetlen - r + pagesize; /* the next page multiple */
|
|
|
|
|
if(curlen != alloclen) {
|
|
|
|
|
buf = realloc(buf, alloclen);
|
|
|
|
|
if(buf == NULL) exit(errno);
|
|
|
|
|
}
|
|
|
|
|
*reslen = alloclen;
|
|
|
|
|
return buf;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* insert n bytes from src into dest of length destlen at position pos */
|
|
|
|
|
/* dest is also reallocated automatically with page_realloc */
|
|
|
|
|
/* returns the new bufptr */
|
|
|
|
|
void* meminsert(void *dest, uint destlen, uint pos, void *src, uint n, uint *reslen, uint pagesize) {
|
|
|
|
|
dest = page_realloc(dest, destlen, destlen + n, reslen, pagesize);
|
|
|
|
|
char *dd = (char *) dest;
|
|
|
|
|
/* i like to move it, move it */
|
|
|
|
|
memmove(dd + pos + n, dd + pos, destlen - pos);
|
|
|
|
|
memmove(dd + pos, src, n); /* fill the space from src */
|
|
|
|
|
return dest;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* erase n bytes in dest of length destlen at position pos */
|
|
|
|
|
/* dest is also reallocated automatically with page_realloc */
|
|
|
|
|
/* returns the new bufptr */
|
|
|
|
|
void* memerase(void *dest, uint destlen, uint pos, uint n, uint *reslen, uint pagesize) {
|
|
|
|
|
char *dd = (char *) dest;
|
|
|
|
|
/* i like to move it, move it */
|
|
|
|
|
memmove((dd + pos), (dd + pos + n), destlen - pos - n);
|
|
|
|
|
return page_realloc(dest, destlen, destlen - n, reslen, pagesize);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* editor core operations */
|
|
|
|
|
|
|
|
|
|
/* find the beginning of a particular line number (1-based) */
|
|
|
|
|
int nne_findline(int lineno) {
|
|
|
|
|
int pos, rc = 1;
|
|
|
|
|
if(lineno < 1) lineno = 1; /* safeguard */
|
|
|
|
|
for(pos=0;pos<nne_real_len-1;pos++) {
|
|
|
|
|
if(rc == lineno) break;
|
|
|
|
|
if(nne_textbuf[pos] == '\n') rc++; /* increment row */
|
|
|
|
|
}
|
|
|
|
|
return pos;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* find the physical (with wraps) line number by position */
|
|
|
|
|
int nne_findscrlineno(int pos) {
|
|
|
|
|
uint i, rc = 0, cc = 0, wcf = 0; /* row, column and line wrap counters */
|
|
|
|
|
if(pos < 0) pos = 0; /* safeguard */
|
|
|
|
|
if(pos > nne_real_len - 2) pos = nne_real_len - 2; /* safeguard */
|
|
|
|
|
for(i=0;i<pos;i++) {
|
|
|
|
|
if(nne_textbuf[i] == '\n') { /* newline encountered */
|
|
|
|
|
nne_buflines++; /* update total line count */
|
|
|
|
|
rc++; /* increment row */
|
|
|
|
|
cc = 0; /* reset column */
|
|
|
|
|
} else { /* increment column */
|
|
|
|
|
cc += (nne_textbuf[i] == '\t') ? NNE_TABWIDTH : 1;
|
2023-07-28 07:58:12 +03:00
|
|
|
if(((cc - 1) % nne_termw) >= (nne_termw - 1)) wcf++;
|
2023-07-27 22:49:28 +03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return rc + wcf + 1; /* newlines + wraps + 1 */
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* update screen coordinates with regards to scrolling parameters */
|
|
|
|
|
void nne_update_scrxy() {
|
|
|
|
|
/* find current virtual row and column */
|
|
|
|
|
nne_scr_row = nne_findscrlineno(nne_pos);
|
|
|
|
|
nne_scrx = 1 + ((nne_col - 1) % nne_termw);
|
|
|
|
|
/* calculate scroll-aware update */
|
|
|
|
|
nne_scry = nne_scr_row - nne_line_offset;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* update (1-based) row and column by the current nne_pos (0-based) */
|
|
|
|
|
void nne_update_coords() {
|
|
|
|
|
uint i, rc = 0, cc = 0; /* row, column and line wrap counters */
|
|
|
|
|
nne_buflines = 1; /* total line counter */
|
|
|
|
|
if(nne_pos >= nne_real_len) nne_pos = nne_real_len - 1; /* safeguard */
|
|
|
|
|
for(i=0;i<nne_real_len-1;i++) {
|
|
|
|
|
if(i < nne_pos) { /* count all stats until nne_pos */
|
|
|
|
|
if(nne_textbuf[i] == '\n') { /* newline encountered */
|
|
|
|
|
nne_buflines++; /* update total line count */
|
|
|
|
|
rc++; /* increment row */
|
|
|
|
|
cc = 0; /* reset column */
|
|
|
|
|
}
|
|
|
|
|
else cc += (nne_textbuf[i]=='\t') ? NNE_TABWIDTH : 1; /* incr. col */
|
|
|
|
|
} /* only count total lines otherwise */
|
|
|
|
|
else if(nne_textbuf[i] == '\n') nne_buflines++;
|
|
|
|
|
}
|
|
|
|
|
nne_row = rc + 1;
|
|
|
|
|
nne_col = cc + 1;
|
|
|
|
|
nne_update_scrxy();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* insert a character into nne_textbuf at current row and column */
|
|
|
|
|
/* also update row and col accordingly */
|
|
|
|
|
void nne_inschar(int c) {
|
|
|
|
|
nne_textbuf = meminsert(nne_textbuf, nne_real_len*NNE_CSZ, nne_pos*NNE_CSZ, &c, NNE_CSZ, &nne_len, NNE_PAGESIZE);
|
|
|
|
|
nne_pos++;
|
|
|
|
|
nne_update_coords();
|
|
|
|
|
nne_real_len++; /* only update the length after updating the coordinates */
|
|
|
|
|
nne_file_saved = 0; /* reset the save flag */
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* delete a character */
|
|
|
|
|
void nne_delchar() {
|
|
|
|
|
nne_textbuf = memerase(nne_textbuf, nne_real_len*NNE_CSZ, nne_pos*NNE_CSZ, NNE_CSZ, &nne_len, NNE_PAGESIZE);
|
|
|
|
|
nne_update_coords();
|
|
|
|
|
nne_real_len--; /* only update the length after updating the coordinates */
|
|
|
|
|
nne_file_saved = 0; /* reset the save flag */
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* load a file into nne_textbuf */
|
|
|
|
|
void nne_loadfile(char *fname) {
|
|
|
|
|
FILE *f = fopen(fname, "r");
|
|
|
|
|
if(f == NULL) {nne_file_loaded = 1; return;}
|
|
|
|
|
int c, c1, c2, c3, wc, flen, i;
|
|
|
|
|
fseek(f, 0, SEEK_END); /* seek to the end of the file */
|
|
|
|
|
flen = ftell(f); /* get the current file pointer */
|
|
|
|
|
fseek(f, 0, SEEK_SET); /* seek to the start of the file */
|
|
|
|
|
if(flen) { /* don't do anything if the file is empty */
|
|
|
|
|
uchar *pbuf = calloc(flen, 1); /* primary loading buffer */
|
|
|
|
|
if(fread(pbuf, flen, 1, f) < 0) exit(errno); /* populate pbuf */
|
|
|
|
|
fclose(f);
|
|
|
|
|
nne_real_len = flen + 2; /* reserve for the last char */
|
|
|
|
|
nne_textbuf = realloc(nne_textbuf, NNE_CSZ * nne_real_len);
|
|
|
|
|
memset(nne_textbuf, 0, NNE_CSZ * nne_real_len);
|
|
|
|
|
nne_pos = 0; /* reset the buffer position */
|
|
|
|
|
for(i=0;i<flen;) {
|
|
|
|
|
c = pbuf[i++];
|
|
|
|
|
if(c > 128) { /* assuming UTF-8 input: just store as little-endian */
|
|
|
|
|
wc = c1 = c2 = c3 = 0;
|
|
|
|
|
if((c & 0xf0) == 0xf0) { /* read 3 extra bytes (rare situation) */
|
|
|
|
|
if(i>=flen) break; c1 = pbuf[i++];
|
|
|
|
|
if(i>=flen) break; c2 = pbuf[i++];
|
|
|
|
|
if(i>=flen) break; c3 = pbuf[i++];
|
|
|
|
|
wc = c | (c1 << 8) | (c2 << 16) | (c3 << 24);
|
|
|
|
|
}
|
|
|
|
|
else if((c & 0xe0) == 0xe0) { /* read 2 extra bytes */
|
|
|
|
|
if(i>=flen) break; c1 = pbuf[i++];
|
|
|
|
|
if(i>=flen) break; c2 = pbuf[i++];
|
|
|
|
|
wc = c | (c1 << 8) | (c2 << 16);
|
|
|
|
|
}
|
|
|
|
|
else if((c & 0xc0) == 0xc0) { /* read 1 extra byte */
|
|
|
|
|
if(i>=flen) break; c1 = pbuf[i++];
|
|
|
|
|
wc = c | (c1 << 8);
|
|
|
|
|
}
|
|
|
|
|
nne_textbuf[nne_pos++] = wc;
|
|
|
|
|
}
|
|
|
|
|
else if(c>0) { /* low-ASCII character */
|
|
|
|
|
if(c == '\r') c = '\n'; /* convert CR to LF */
|
|
|
|
|
nne_textbuf[nne_pos++] = c;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
free(pbuf); /* we no longer need the primary buffer */
|
|
|
|
|
}
|
|
|
|
|
nne_pos = 0; /* reset the buffer position again */
|
|
|
|
|
nne_file_loaded = 1; /* mark the file load fact */
|
|
|
|
|
nne_file_saved = 1; /* by default, no changes are made */
|
|
|
|
|
nne_update_coords();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* save nne_textbuf into a file */
|
|
|
|
|
void nne_savefile(char *fname) {
|
|
|
|
|
FILE *f = fopen(fname, "w"); /* fully overwriting, be careful */
|
|
|
|
|
if(f == NULL) return;
|
|
|
|
|
int i, v, c;
|
|
|
|
|
for(i=0;i<nne_real_len-2;i++) {
|
|
|
|
|
v = nne_textbuf[i]; /* copy the currently written value */
|
|
|
|
|
while((c = v&255) > 0) { /* extract the byte */
|
|
|
|
|
if(fputc(c, f) < 1) exit(errno);
|
|
|
|
|
v >>= 8; /* shift to the next byte */
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
fclose(f);
|
|
|
|
|
nne_file_saved = 1; /* mark the file save fact */
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* UI operations */
|
|
|
|
|
|
|
|
|
|
uint inkey() { /* input a single key (logical) */
|
|
|
|
|
uchar c = readc(), c1, c2, c3;
|
|
|
|
|
if(c == K_ESC) { /* escape sequence start */
|
|
|
|
|
if(!(c1 = readc())) return K_ESC;
|
|
|
|
|
if(c1 == K_ESC) return K_MODCMD; /* modstring is ESC ESC */
|
|
|
|
|
if(!(c2 = readc())) return K_ESC;
|
|
|
|
|
else if(c1 == '[') {
|
|
|
|
|
if(c2 >= '0' && c2 <= '9') {
|
|
|
|
|
if(!(c3 = readc())) return K_ESC;
|
|
|
|
|
if(c3 == '~') {
|
|
|
|
|
switch(c2) {
|
|
|
|
|
case '1': return K_HOME;
|
|
|
|
|
case '2': return K_INS;
|
|
|
|
|
case '3': return K_DEL;
|
|
|
|
|
case '4': return K_END;
|
|
|
|
|
case '5': return K_PGUP;
|
|
|
|
|
case '6': return K_PGDN;
|
|
|
|
|
case '7': return K_HOME;
|
|
|
|
|
case '8': return K_END;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
switch(c2) {
|
|
|
|
|
case 'A': return K_UP;
|
|
|
|
|
case 'B': return K_DOWN;
|
|
|
|
|
case 'C': return K_RIGHT;
|
|
|
|
|
case 'D': return K_LEFT;
|
|
|
|
|
case 'H': return K_HOME;
|
|
|
|
|
case 'F': return K_END;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else if(c1 == 'O') {
|
|
|
|
|
switch(c2) {
|
|
|
|
|
case 'H': return K_HOME;
|
|
|
|
|
case 'F': return K_END;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return K_ESC;
|
|
|
|
|
}
|
|
|
|
|
else if(c > 128) { /* assuming UTF-8 input: just store as little-endian */
|
|
|
|
|
int wc = 0;
|
|
|
|
|
if((c & 0xf0) == 0xf0) { /* read 3 extra bytes (rare situation) */
|
|
|
|
|
c1 = readc(); c2 = readc(); c3 = readc();
|
|
|
|
|
wc = c | (c1 << 8) | (c2 << 16) | (c3 << 24);
|
|
|
|
|
}
|
|
|
|
|
else if((c & 0xe0) == 0xe0) { /* read 2 extra bytes */
|
|
|
|
|
c1 = readc(); c2 = readc();
|
|
|
|
|
wc = c | (c1 << 8) | (c2 << 16);
|
|
|
|
|
}
|
|
|
|
|
else if((c & 0xc0) == 0xc0) { /* read 1 extra byte */
|
|
|
|
|
c1 = readc();
|
|
|
|
|
wc = c | (c1 << 8);
|
|
|
|
|
}
|
|
|
|
|
return wc;
|
|
|
|
|
}
|
|
|
|
|
else return c; /* low-ASCII character */
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* input a string into nne_cmdbuf (NNE_IOBUFSZ uints long) */
|
|
|
|
|
/* stops on NNE_IOBUFSZ-1 chars or Return press */
|
|
|
|
|
/* Esc (or double Esc) aborts */
|
|
|
|
|
/* returns the resulting amount of input characters */
|
|
|
|
|
uint nne_prompt(char *prompt) {
|
|
|
|
|
nnprintf(CURSET LINECLR, nne_termh, 1); /* clear the status bar line */
|
|
|
|
|
uint c, rd = 0, endinput = 0, l = strlen(prompt);
|
|
|
|
|
/* print the prompt message and move the cursor after it */
|
|
|
|
|
if(l > 0) nnprintf("%s ", prompt);
|
|
|
|
|
memset(nne_cmdbuf, 0, NNE_IOBUFSZ * NNE_CSZ); /* zero out the input buffer */
|
|
|
|
|
while(rd < NNE_IOBUFSZ) {
|
|
|
|
|
c = inkey();
|
|
|
|
|
switch(c) {
|
|
|
|
|
case K_BACKSPACE:
|
|
|
|
|
if(rd > 0) { /* don't backspace if nothing entered */
|
|
|
|
|
c = nne_cmdbuf[rd-1]; /* store the character */
|
|
|
|
|
nne_cmdbuf[rd-1] = 0; /* zero out the character */
|
|
|
|
|
rd--; /* decrement the read counter */
|
|
|
|
|
/* move cursor back and erase until the end of the line */
|
|
|
|
|
nnprintf("\x1b[%uD\x1b[0K", c == '\t' ? NNE_TABWIDTH : 1);
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case K_ESC: case K_MODCMD: endinput = 1; rd = 0; break; /* abort */
|
|
|
|
|
case '\r': case '\n': endinput = 1; break; /* confirm */
|
|
|
|
|
default:
|
|
|
|
|
if((c == '\t') || (c > 31 && c < K_UP)) {
|
|
|
|
|
nne_cmdbuf[rd++] = c;
|
|
|
|
|
nnwritew(c); /* print the new character */
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if(endinput) break; /* end operation on abort or confirm */
|
|
|
|
|
}
|
|
|
|
|
return rd;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* input a non-negative integer value (uses nne_prompt and nne_cmdbuf) */
|
|
|
|
|
uint nne_digit_prompt(char *prompt) {
|
|
|
|
|
uint i=0, j, c, r = nne_prompt(prompt);
|
|
|
|
|
for(j=0;j<r;j++) { /* decimal digit parsing loop */
|
|
|
|
|
if(nne_cmdbuf[j] > '9') break;
|
|
|
|
|
c = nne_cmdbuf[j] - '0'; /* valid digit range */
|
|
|
|
|
if(c >= 0 && c < 10) i = i*10 + c;
|
|
|
|
|
else break;
|
|
|
|
|
}
|
|
|
|
|
return i;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
uint scrbuf_appendw(int wc) { /* append a widechar to the screen buffer */
|
|
|
|
|
uchar c;
|
|
|
|
|
while((c = (wc & 255))) { /* this is why we store UTF-8 as little-endian */
|
|
|
|
|
nne_scrbuf[nne_scrpos++] = c;
|
|
|
|
|
wc >>= 8;
|
|
|
|
|
}
|
|
|
|
|
return nne_scrpos;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
uint scrbuf_append(char *str) { /* append a string to the screen buffer */
|
|
|
|
|
uint l = strlen(str), i;
|
|
|
|
|
for(i=0;i<l;i++) nne_scrbuf[nne_scrpos++] = str[i];
|
|
|
|
|
return nne_scrpos;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void render() { /* main screen rendering function */
|
|
|
|
|
uint i, k, cc = 0, rc = 0, wcf = 0;
|
|
|
|
|
memset(nne_scrbuf, 0, nne_scrsize); /* prefill with zeroes */
|
|
|
|
|
nne_scrpos = 0; /* reset the position */
|
|
|
|
|
/* clear the screen, hide the cursor and reset its position */
|
|
|
|
|
scrbuf_append(CLS CURHIDE CURRESET);
|
|
|
|
|
/* render the active screen part */
|
|
|
|
|
nne_update_scrxy();
|
|
|
|
|
if((nne_scr_row - 1) < nne_line_offset) {
|
|
|
|
|
nne_line_offset = nne_scr_row - 1;
|
|
|
|
|
nne_update_scrxy();
|
|
|
|
|
}
|
|
|
|
|
if(nne_scry >= nne_termh) {
|
|
|
|
|
nne_line_offset = nne_scr_row - nne_termh + 1;
|
|
|
|
|
nne_update_scrxy();
|
|
|
|
|
}
|
|
|
|
|
/* find the offset line into i, use j as real newline counter */
|
|
|
|
|
/* also complete with wraps */
|
|
|
|
|
for(i=0,rc=0,wcf=0;i<nne_real_len - 1;i++) {
|
|
|
|
|
if((rc + wcf) == nne_line_offset) break;
|
|
|
|
|
if(nne_textbuf[i] == '\n') { /* newline encountered */
|
|
|
|
|
rc++; /* increment row */
|
|
|
|
|
cc = 0; /* reset column */
|
|
|
|
|
} else {
|
|
|
|
|
cc++; /* increment column */
|
|
|
|
|
if((cc % nne_termw) == (nne_termw - 1)) wcf++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
/* now count the newlines and wraps in rc */
|
|
|
|
|
for(rc=0,cc=0;rc<nne_termh-1;i++) {
|
|
|
|
|
if(i >= nne_real_len) break;
|
|
|
|
|
if(nne_textbuf[i] == '\n') {
|
|
|
|
|
scrbuf_append("\r\n");
|
|
|
|
|
rc++; /* increment row */
|
|
|
|
|
cc = 0; /* reset column */
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
cc++; /* increment column */
|
|
|
|
|
if((cc % nne_termw) == (nne_termw - 1)) rc++;
|
|
|
|
|
if(nne_textbuf[i] == '\t') {/* retab */
|
|
|
|
|
for(k=0;k<NNE_TABWIDTH;k++) scrbuf_appendw(' ');
|
|
|
|
|
}
|
|
|
|
|
/* visual \n autoconversion */
|
|
|
|
|
else if(nne_textbuf[i] == '\r') {} /* ignore */
|
|
|
|
|
else scrbuf_appendw(nne_textbuf[i]); /* add the character as is */
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
/* render the status bar */
|
|
|
|
|
/* put cursor to the current on-screen position and display it */
|
|
|
|
|
if(nne_status_override) {
|
|
|
|
|
scrbuf_append(nne_msgbuf);
|
|
|
|
|
nne_status_override = 0;
|
|
|
|
|
}
|
|
|
|
|
else scrbuf_append(nnformat(CURSET "%c %s %u,%u %02u%% %ux%u", nne_termh, 1,
|
|
|
|
|
(nne_mode == NNE_CMD) ? 'C' : '-', nne_fname, nne_row, nne_col,
|
|
|
|
|
(100*nne_pos/(nne_real_len <= 2 ? 2 : nne_real_len - 2)),
|
|
|
|
|
nne_termw, nne_termh));
|
|
|
|
|
scrbuf_append(nnformat(CURSET CURSHOW "\0", nne_scry, nne_scrx));
|
|
|
|
|
/* actually draw the screen buffer until the first zero byte */
|
|
|
|
|
nnputs(nne_scrbuf);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* platform-independent terminal size detection */
|
|
|
|
|
static void nne_termsize() {
|
|
|
|
|
if(write(1, "\x1b[s\x1b[999C\x1b[999B", 15) != 15) return;
|
|
|
|
|
if(write(1, "\x1b[6n", 4) != 4) return;
|
|
|
|
|
char buf[32], i;
|
|
|
|
|
for(i=0;i<31;i++) {
|
|
|
|
|
if(read(0, &buf[i], 1) != 1) break;
|
|
|
|
|
if(buf[i] == 'R') break;
|
|
|
|
|
}
|
|
|
|
|
if(write(1, "\x1b[u", 3) != 3) return; /* restore the cursor position */
|
|
|
|
|
buf[i] = 0;
|
|
|
|
|
if(buf[0] != '\x1b' || buf[1] != '[') return;
|
|
|
|
|
if(sscanf(&buf[2], "%hu;%hu", &nne_termh, &nne_termw) != 2) return;
|
|
|
|
|
nne_scrsize = (nne_termw * nne_termh) << 2; /* update the byte size */
|
|
|
|
|
nne_scrbuf = realloc(nne_scrbuf, nne_scrsize); /* reallocate accordingly */
|
|
|
|
|
if(nne_scrbuf == NULL) exit(errno); /* exit on memory error */
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void resizehandler(int sig) {
|
|
|
|
|
if(SIGWINCH == sig) {
|
|
|
|
|
nne_termsize();
|
|
|
|
|
signal(SIGWINCH, resizehandler); /* resetup the signal handler */
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* motion helper methods */
|
|
|
|
|
|
|
|
|
|
/* get line start by given position in nne_textbuf */
|
|
|
|
|
uint nne_linestart(uint pos) {
|
|
|
|
|
int st = pos - 1;
|
|
|
|
|
for(;st>=0;st--) if(nne_textbuf[st] == '\n') break;
|
|
|
|
|
return st + 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* get line end by given position in nne_textbuf */
|
|
|
|
|
uint nne_lineend(uint pos) {
|
|
|
|
|
uint ed = pos;
|
|
|
|
|
for(;ed<nne_real_len-2;ed++) if(nne_textbuf[ed] == '\n') break;
|
|
|
|
|
return ed;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* jump to a column (or to the end of the line if we don't fit) */
|
|
|
|
|
void nne_jumpcol(uint col) {
|
|
|
|
|
uint ls = nne_linestart(nne_pos); /* get current line start */
|
|
|
|
|
uint le = nne_lineend(nne_pos); /* get current line end */
|
|
|
|
|
col--; /* the column is 1-based, so decrement */
|
|
|
|
|
if(col > (le - ls)) nne_pos = le;
|
|
|
|
|
else nne_pos = ls + col;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* jump to the matching character (if found) */
|
|
|
|
|
void nne_jumpmatch(uchar c1, uchar c2, int dir) {
|
|
|
|
|
int balance = 0, xpos = nne_pos;
|
|
|
|
|
for(;xpos >=0 && xpos <= nne_real_len - 2;xpos += dir) {
|
|
|
|
|
if(nne_textbuf[xpos] == c1) balance++;
|
|
|
|
|
if(nne_textbuf[xpos] == c2) balance--;
|
|
|
|
|
if(!balance) break;
|
|
|
|
|
}
|
|
|
|
|
if(xpos >= 0 && xpos <= nne_real_len - 2) nne_pos = xpos;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* copy a text region into the clipboard buffer (return the copied length) */
|
|
|
|
|
uint nne_copyregion(uint start, uint end) {
|
|
|
|
|
uint l = end - start, i;
|
|
|
|
|
nne_clipbuf = realloc(nne_clipbuf, l * NNE_CSZ);
|
|
|
|
|
memset(nne_clipbuf, 0, l * NNE_CSZ);
|
|
|
|
|
for(i=0;i<l;i++) {
|
|
|
|
|
if(start + i < nne_real_len - 2)
|
|
|
|
|
nne_clipbuf[i] = nne_textbuf[start + i];
|
|
|
|
|
else {l = i; break;}
|
|
|
|
|
}
|
|
|
|
|
nne_cliplen = l;
|
|
|
|
|
return l;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
uchar nne_iswhitespace(uint c) { /* check if we encounter a whitespace */
|
|
|
|
|
return ((c == ' ') || (c == '\t') || (c == '\n') || (c == '\r')) ? 1 : 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* some motions duplicated on keyboard and modkeys */
|
|
|
|
|
|
|
|
|
|
void motion_del() { /* delete current character */
|
|
|
|
|
if(nne_pos < nne_real_len - 2) nne_delchar();
|
|
|
|
|
if(nne_pos == nne_real_len - 1) nne_pos--;
|
|
|
|
|
if(nne_pos < 0) nne_pos = 0;
|
|
|
|
|
}
|
|
|
|
|
void motion_home() {nne_pos = nne_linestart(nne_pos);}
|
|
|
|
|
void motion_end() {nne_pos = nne_lineend(nne_pos);}
|
|
|
|
|
void motion_left() {nne_pos--; if(nne_pos<0) nne_pos=0;}
|
|
|
|
|
void motion_right() {nne_pos++; if(nne_pos>=nne_real_len-1) nne_pos--;}
|
|
|
|
|
void motion_up() {motion_home(); motion_left(); motion_home(); nne_jumpcol(nne_col);}
|
|
|
|
|
void motion_down() {motion_end(); motion_right(); nne_jumpcol(nne_col);}
|
|
|
|
|
void motion_pgup() {ushort pg=nne_termh>>1,i;for(i=0;i<pg;i++)motion_up();}
|
|
|
|
|
void motion_pgdn() {ushort pg=nne_termh>>1,i;for(i=0;i<pg;i++)motion_down();}
|
|
|
|
|
int motion_save() { /* save the buffer into file */
|
|
|
|
|
int i, j, r; uchar c;
|
|
|
|
|
if(!nne_file_loaded) { /* we have a new file */
|
|
|
|
|
r = nne_prompt("New file name:");
|
|
|
|
|
if(r) { /* we entered something */
|
|
|
|
|
memset(nne_fname, 0, NNE_IOBUFSZ * NNE_CSZ);
|
|
|
|
|
for(i=0,j=0;i<r;i++)
|
|
|
|
|
while((c = (nne_cmdbuf[i]&255)) > 0) {
|
|
|
|
|
nne_fname[j++] = c;
|
|
|
|
|
nne_cmdbuf[i] >>= 8;
|
|
|
|
|
}
|
|
|
|
|
} else { /* no new name entered */
|
|
|
|
|
nnformat(CURSET "No file saved", nne_termh, 1);
|
|
|
|
|
nne_status_override = 1;
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
nne_savefile(nne_fname);
|
|
|
|
|
nne_file_loaded = 1; /* update the file load fact */
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* main key-driven action handler, returns 1 on success */
|
|
|
|
|
int nne_action(int key) {
|
|
|
|
|
int i, j, r; uchar c;
|
|
|
|
|
if(nne_mode == NNE_CMD) { /* modcommand mode */
|
|
|
|
|
switch(key) {
|
|
|
|
|
case 's': case 'w':
|
|
|
|
|
if(motion_save()) {
|
|
|
|
|
nnformat(CURSET "Saved %s, %u chars, %u lines",
|
|
|
|
|
nne_termh, 1, nne_fname, nne_real_len - 2, nne_buflines);
|
|
|
|
|
nne_status_override = 1;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case 'y': /* copy current line */
|
|
|
|
|
r = nne_copyregion(nne_linestart(nne_pos), nne_lineend(nne_pos) + 1);
|
|
|
|
|
nnformat(CURSET "Copied %u chars", nne_termh, 1, r);
|
|
|
|
|
nne_status_override = 1;
|
|
|
|
|
break;
|
|
|
|
|
case 'Y': /* copy multiple lines starting from the current one */
|
|
|
|
|
r = nne_digit_prompt("Copy lines:");
|
|
|
|
|
if(r < 1 || r > nne_buflines - nne_row) r = 1;
|
|
|
|
|
i = nne_lineend(nne_findline(nne_row + r - 1));
|
|
|
|
|
r = nne_copyregion(nne_linestart(nne_pos), i + 1);
|
|
|
|
|
nnformat(CURSET "Copied %u chars", nne_termh, 1, r);
|
|
|
|
|
nne_status_override = 1;
|
|
|
|
|
break;
|
|
|
|
|
case 'd': /* cut current line */
|
|
|
|
|
r = nne_copyregion(i = nne_linestart(nne_pos), nne_lineend(nne_pos) + 1);
|
|
|
|
|
nne_pos = i; /* go to the current line start */
|
|
|
|
|
for(i=0;i<r;i++) motion_del();
|
|
|
|
|
nnformat(CURSET "Cut %u chars", nne_termh, 1, r);
|
|
|
|
|
nne_status_override = 1;
|
|
|
|
|
break;
|
|
|
|
|
case 'D': /* cut multiple lines starting from the current one */
|
|
|
|
|
r = nne_digit_prompt("Cut lines:");
|
|
|
|
|
if(r < 1 || r > nne_buflines - nne_row) r = 1;
|
|
|
|
|
j = nne_lineend(nne_findline(nne_row + r - 1));
|
|
|
|
|
r = nne_copyregion(i = nne_linestart(nne_pos), j + 1);
|
|
|
|
|
nne_pos = i; /* go to the current line start */
|
|
|
|
|
for(i=0;i<r;i++) motion_del();
|
|
|
|
|
nnformat(CURSET "Cut %u chars", nne_termh, 1, r);
|
|
|
|
|
nne_status_override = 1;
|
|
|
|
|
break;
|
|
|
|
|
case 'v': case 'p': /* paste */
|
|
|
|
|
for(i=0;i<nne_cliplen;i++) nne_inschar(nne_clipbuf[i]);
|
|
|
|
|
nnformat(CURSET "Pasted %u chars", nne_termh, 1, nne_cliplen);
|
|
|
|
|
nne_status_override = 1;
|
|
|
|
|
break;
|
|
|
|
|
case 'l':
|
|
|
|
|
i = nne_digit_prompt("Jump to line #:");
|
|
|
|
|
if(i < 1) i = 1;
|
|
|
|
|
if(i > nne_buflines) i = nne_buflines;
|
|
|
|
|
nne_pos = nne_findline(i);
|
|
|
|
|
break;
|
|
|
|
|
case '/': /* search */
|
|
|
|
|
r = nne_prompt("Find:");
|
|
|
|
|
if(r) { /* init search buffer */
|
|
|
|
|
memset(nne_searchbuf, 0, NNE_IOBUFSZ * NNE_CSZ);
|
|
|
|
|
for(i=0;i<r;i++) nne_searchbuf[i] = nne_cmdbuf[i];
|
|
|
|
|
nne_searchlen = r;
|
|
|
|
|
nne_searchidx = -1; /* reset running search index */
|
|
|
|
|
}
|
|
|
|
|
if(nne_searchlen > 0) { /* perform the search */
|
|
|
|
|
for(i=nne_searchidx+1;i<nne_real_len-1;i++) {
|
|
|
|
|
r = 1; /* intermediate search result */
|
|
|
|
|
for(j=0;j<NNE_IOBUFSZ;j++) {
|
|
|
|
|
if(nne_searchbuf[j] == 0) break;
|
|
|
|
|
if(nne_searchbuf[j] != nne_textbuf[i+j]) {r = 0; break;}
|
|
|
|
|
}
|
|
|
|
|
if(r) {nne_pos = nne_searchidx = i; break;}
|
|
|
|
|
if(i == nne_real_len - 2) {
|
|
|
|
|
nne_searchidx = -1;
|
|
|
|
|
nnformat(CURSET "End of search results", nne_termh, 1);
|
|
|
|
|
nne_status_override = 1;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case 'u': /* discard unsaved data */
|
|
|
|
|
if(nne_file_loaded && !nne_file_saved) {
|
|
|
|
|
if(nne_prompt("Discard unsaved data? [y/n]")) {
|
|
|
|
|
c = nne_cmdbuf[0]&255;
|
|
|
|
|
if(c == 'y' || c == 'Y') {nne_loadfile(nne_fname);}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case 'e': /* external shell command runner */
|
|
|
|
|
if((r = nne_prompt("Shell command:")) > 0) {
|
|
|
|
|
char *rcmd = calloc(r*NNE_CSZ + 1, 1); /* allocate command buffer */
|
|
|
|
|
for(i=0,j=0;i<r;i++) /* retrieve raw bytes into command */
|
|
|
|
|
while((c = (nne_cmdbuf[i]&255)) > 0) {
|
|
|
|
|
rcmd[j++] = c;
|
|
|
|
|
nne_cmdbuf[i] >>= 8;
|
|
|
|
|
}
|
|
|
|
|
motion_save(); /* save file */
|
|
|
|
|
r = system(rcmd); /* run the shell command */
|
|
|
|
|
free(rcmd); /* free command buffer */
|
|
|
|
|
nne_loadfile(nne_fname); /* reload file */
|
|
|
|
|
nnformat(CURSET "Command exit code: %d", nne_termh, 1, r);
|
|
|
|
|
nne_status_override = 1;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case 'q': /* quit */
|
|
|
|
|
if(nne_file_loaded && !nne_file_saved) {
|
|
|
|
|
if(nne_prompt("Save? [y/n]")) {
|
|
|
|
|
c = nne_cmdbuf[0]&255;
|
|
|
|
|
if(c == 'y' || c == 'Y') {if(motion_save()) return 0;}
|
|
|
|
|
else if(c == 'n' || c == 'N') return 0;
|
|
|
|
|
else break;
|
|
|
|
|
}
|
|
|
|
|
} else return 0;
|
|
|
|
|
break;
|
|
|
|
|
case '0': motion_home(); break;
|
|
|
|
|
case '4': motion_end(); break;
|
|
|
|
|
case '5': /* bracket matcher */
|
|
|
|
|
switch(nne_textbuf[nne_pos]) {
|
|
|
|
|
case '(': nne_jumpmatch('(', ')', 1); break;
|
|
|
|
|
case ')': nne_jumpmatch(')', '(', -1); break;
|
|
|
|
|
case '[': nne_jumpmatch('[', ']', 1); break;
|
|
|
|
|
case ']': nne_jumpmatch(']', '[', -1); break;
|
|
|
|
|
case '{': nne_jumpmatch('{', '}', 1); break;
|
|
|
|
|
case '}': nne_jumpmatch('}', '{', -1); break;
|
|
|
|
|
case '<': nne_jumpmatch('<', '>', 1); break;
|
|
|
|
|
case '>': nne_jumpmatch('>', '<', -1); break;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case '8': nne_pos = 0; break; /* jump to file start */
|
|
|
|
|
case '9': /* jump to file end */
|
|
|
|
|
nne_pos = nne_real_len - 2;
|
|
|
|
|
if(nne_pos < 0) nne_pos = 0;
|
|
|
|
|
break;
|
|
|
|
|
case K_LEFT: /* find previous word */
|
|
|
|
|
while((nne_pos > 0) && !nne_iswhitespace(nne_textbuf[nne_pos]))
|
|
|
|
|
motion_left(); /* go to the first whitespace before this word */
|
|
|
|
|
while((nne_pos > 0) && nne_iswhitespace(nne_textbuf[nne_pos]))
|
|
|
|
|
motion_left(); /* go to the end of the previous word */
|
|
|
|
|
while((nne_pos > 0) && !nne_iswhitespace(nne_textbuf[nne_pos]))
|
|
|
|
|
motion_left(); /* go to the first whitespace before the previous word */
|
|
|
|
|
while(nne_iswhitespace(nne_textbuf[nne_pos]))
|
|
|
|
|
motion_right(); /* go to the beginning of the previous word */
|
|
|
|
|
break;
|
|
|
|
|
case K_RIGHT: /* find next word */
|
|
|
|
|
while((nne_pos < nne_real_len - 2) && !nne_iswhitespace(nne_textbuf[nne_pos]))
|
|
|
|
|
motion_right(); /* go to the first whitespace after this word */
|
|
|
|
|
while((nne_pos < nne_real_len - 2) && nne_iswhitespace(nne_textbuf[nne_pos]))
|
|
|
|
|
motion_right(); /* go to the beginning of the next word */
|
|
|
|
|
break;
|
|
|
|
|
case K_UP: motion_pgup(); break;
|
|
|
|
|
case K_DOWN: motion_pgdn(); break;
|
|
|
|
|
case K_BACKSPACE: motion_del(); break;
|
|
|
|
|
case '\t': nne_inschar('\t'); break; /* insert literal tab */
|
|
|
|
|
}
|
|
|
|
|
nne_mode = NNE_NORMAL; /* exit the modcommand mode afterwards */
|
|
|
|
|
}
|
|
|
|
|
else { /* assume NNE_NORMAL, normal mode */
|
|
|
|
|
switch(key) {
|
|
|
|
|
case K_MODCMD: nne_mode = NNE_CMD; break;
|
|
|
|
|
case K_LEFT: motion_left(); break;
|
|
|
|
|
case K_RIGHT: motion_right(); break;
|
|
|
|
|
case K_UP: motion_up(); break;
|
|
|
|
|
case K_DOWN: motion_down(); break;
|
|
|
|
|
case K_HOME: motion_home(); break;
|
|
|
|
|
case K_END: motion_end(); break;
|
|
|
|
|
case K_PGUP: motion_pgup(); break;
|
|
|
|
|
case K_PGDN: motion_pgdn(); break;
|
|
|
|
|
case K_DEL: motion_del(); break;
|
|
|
|
|
case K_BACKSPACE: /* delete previous character */
|
|
|
|
|
nne_pos--;
|
|
|
|
|
if(nne_pos < 0) nne_pos = 0; else nne_delchar();
|
|
|
|
|
break;
|
|
|
|
|
case '\t': /* tabstop */
|
|
|
|
|
for(i=0;i<NNE_TABWIDTH;i++) nne_inschar(' ');
|
|
|
|
|
break;
|
|
|
|
|
case '\r': case '\n': /* newline, autoindent */
|
|
|
|
|
if(key == '\r') key = '\n'; /* convert CR to LF */
|
|
|
|
|
r = nne_linestart(nne_pos); /* get this line start */
|
|
|
|
|
j = nne_pos; /* cache current position as line end */
|
|
|
|
|
nne_inschar(key); /* output this newline */
|
|
|
|
|
if(nne_row > 1) /* no autoindent on the first row */
|
|
|
|
|
for(;((i = nne_textbuf[r]) == ' ' || i == '\t') && r < j;r++)
|
|
|
|
|
nne_inschar(i); /* copy previous whitespace characters */
|
|
|
|
|
break;
|
|
|
|
|
default: /* normal insertion for supported characters */
|
|
|
|
|
if(key > 31 && key < K_UP) nne_inschar(key);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
/* update all wraps and cursor position */
|
|
|
|
|
nne_update_coords();
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int main(int argc, char* argv[]) { /* editor entry point */
|
|
|
|
|
/* use the alternative screen buffer and enable UTF-8 */
|
|
|
|
|
nnputs(ALTBUFON CLS "\x1b%G\x1b[?7h");
|
|
|
|
|
/* prepare screen */
|
|
|
|
|
tcgetattr(0, &tty_opts_backup);
|
|
|
|
|
atexit(&cleanup);
|
|
|
|
|
/* cfmakeraw is non-POSIX, so emulating it */
|
|
|
|
|
tty_opts_raw.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP |
|
|
|
|
|
INLCR | IGNCR | ICRNL | IXON);
|
|
|
|
|
tty_opts_raw.c_oflag &= ~OPOST;
|
|
|
|
|
tty_opts_raw.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
|
|
|
|
|
tty_opts_raw.c_cflag &= ~(CSIZE | PARENB);
|
|
|
|
|
tty_opts_raw.c_cflag |= CS8;
|
|
|
|
|
tty_opts_raw.c_cc[VMIN] = 0;
|
|
|
|
|
tty_opts_raw.c_cc[VTIME] = 1;
|
|
|
|
|
tty_opts_raw.c_iflag |= IUTF8;
|
|
|
|
|
tcsetattr(0, TCSANOW, &tty_opts_raw);
|
|
|
|
|
nne_scrbuf = malloc(0); /* allocate the minimum for screen */
|
|
|
|
|
resizehandler(SIGWINCH); /* populate the dimensions now */
|
|
|
|
|
/* end prepare screen */
|
|
|
|
|
/* prepare editor parameters */
|
|
|
|
|
nne_scrx = nne_scry = nne_row = nne_col = 1;
|
|
|
|
|
nne_buflines = 0;
|
|
|
|
|
nne_mode = NNE_NORMAL;
|
|
|
|
|
nne_pos = 0;
|
|
|
|
|
nne_len = nne_real_len = 1;
|
|
|
|
|
nne_textbuf = malloc(0); /* allocate the minimum for text */
|
|
|
|
|
nne_clipbuf = malloc(0); /* allocate the minimum for clipboard */
|
|
|
|
|
nne_inschar(' '); /* initialize the last character in the buffer */
|
|
|
|
|
if(argc > 1) { /* file name exists */
|
|
|
|
|
memmove(nne_fname, argv[1], NNE_IOBUFSZ);
|
|
|
|
|
nne_loadfile(nne_fname);
|
|
|
|
|
} else memmove(nne_fname, "(new)", 6);
|
|
|
|
|
/* end prepare editor parameters */
|
|
|
|
|
render();
|
|
|
|
|
while(nne_action(inkey())) render(); /* main loop */
|
|
|
|
|
cleanup();
|
|
|
|
|
if(nne_textbuf) free(nne_textbuf); /* free main text */
|
|
|
|
|
if(nne_scrbuf) free(nne_scrbuf); /* free screen */
|
|
|
|
|
if(nne_clipbuf) free(nne_clipbuf); /* free clipboard */
|
|
|
|
|
return 0;
|
|
|
|
|
}
|