/*******************************************************************************
Copyright(C) Jonas 'Sortie' Termansen 2011, 2012.
This program is free software: you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by the Free
Software Foundation, either version 3 of the License, or (at your option)
any later version.
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
more details.
You should have received a copy of the GNU General Public License along with
this program. If not, see .
editor.cpp
A simple and hacked together text editor.
*******************************************************************************/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
const int MODE_QUIT = 1;
const int MODE_TEXT = 2;
const int MODE_CONFIRM_QUIT = 3;
const int MODE_SAVE = 4;
const int MODE_LOAD = 5;
const unsigned WIDTH = 80;
const unsigned HEIGHT = 24;
char buffers[HEIGHT+1][WIDTH+1];
unsigned cursorx = 0;
unsigned cursory = 0;
unsigned numlines = 1;
char filename[256];
bool bufferchanged = false;
void clearbuffers()
{
for ( unsigned y = 0; y < HEIGHT + 1; y++ )
{
for ( unsigned x = 0; x < WIDTH + 1; x++ ) { buffers[y][x] = 0; }
}
bufferchanged = true;
}
void cursorto(unsigned x, unsigned y)
{
printf("\e[%u;%uH", y+1+1, x+1);
fflush(stdout);
}
char* readline(int fd)
{
unsigned oldtermmode;
if ( gettermmode(fd, &oldtermmode) ) { return NULL; }
unsigned termmode = TERMMODE_UNICODE
| TERMMODE_SIGNAL
| TERMMODE_UTF8
| TERMMODE_LINEBUFFER
| TERMMODE_ECHO;
if ( settermmode(fd, termmode) ) { return NULL; }
size_t lineused = 0;
size_t linelength = 32UL;
char* line = new char[linelength + 1];
line[0] = '\0';
while ( true )
{
char c;
ssize_t numbytes = read(fd, &c, sizeof(c));
if ( numbytes < 0 ) { delete[] line; line = NULL; break; }
if ( !numbytes ) { break; }
if ( c == '\n' ) { break; }
if ( lineused == linelength )
{
size_t newlinelength = 2 * linelength;
char* newline = new char[newlinelength];
if ( !newline ) { delete[] line; line = NULL; break; }
memcpy(newline, line, lineused * sizeof(*line));
delete[] line;
line = newline;
linelength = newlinelength;
}
line[lineused++] = c;
line[lineused] = '\0';
}
if ( settermmode(fd, oldtermmode) ) { delete[] line; line = NULL; }
return line;
}
void drawtextmode()
{
const char* printfilename = ( strlen(filename) > 0 ) ? filename : "New Buffer";
printf("\e[30m\e[47m\e[2J\e[H");
printf("Text Editor\t\tfile: %s\n", printfilename);
printf("\e[37m\e[40m\e[0J");
for ( unsigned y = 0; y < HEIGHT; y++ )
{
printf("%s", buffers[y]);
if ( y < HEIGHT-1 ) { printf("\n"); }
}
fflush(stdout);
cursorto(cursorx, cursory);
}
unsigned textmode()
{
drawtextmode();
bool ctrl = false;
unsigned dectrlmode = 0;
int oldcursorx = -1;
int oldcursory = -1;
while ( true )
{
if ( oldcursorx != (int) cursorx || oldcursory != (int) cursory )
{
cursorto(cursorx, cursory);
oldcursorx = cursorx;
oldcursory = cursory;
}
unsigned termmode = TERMMODE_KBKEY | TERMMODE_UNICODE | TERMMODE_SIGNAL;
if ( settermmode(0, termmode) ) { error(1, errno, "settermmode"); }
uint32_t codepoint = 0;
ssize_t numbytes = read(0, &codepoint, sizeof(codepoint));
if ( !numbytes ) { break; }
if ( numbytes < 0 ) { error(1, errno, "read stdin"); }
if ( numbytes < (ssize_t) sizeof(codepoint) ) {
printf("unexpectedly got %zi bytes\n", numbytes);
printf("bytes: %x\n", codepoint);
fprintf(stderr, "bad stdin data\n"); exit(1); }
if ( !codepoint ) { continue; }
int kbkey = KBKEY_DECODE(codepoint);
if ( kbkey )
{
if ( kbkey == -KBKEY_LCTRL && dectrlmode ) { return dectrlmode; }
int abskbkey = (kbkey < 0) ? -kbkey : kbkey;
if ( abskbkey == KBKEY_LCTRL ) { ctrl = (0 < kbkey); continue; }
switch ( kbkey )
{
case -KBKEY_ESC:
return MODE_CONFIRM_QUIT;
break;
case KBKEY_UP:
if ( cursory ) { cursory--; }
break;
case KBKEY_DOWN:
if ( cursory < numlines-1 ) { cursory++; }
break;
case KBKEY_LEFT:
if ( cursorx ) { cursorx--; }
break;
case KBKEY_RIGHT:
if ( cursorx < WIDTH-1 ) { cursorx++; }
break;
case KBKEY_O:
if ( ctrl ) { dectrlmode = MODE_SAVE; }
break;
case KBKEY_R:
if ( ctrl ) { dectrlmode = MODE_LOAD; }
break;
case KBKEY_X:
if ( ctrl ) { dectrlmode = MODE_CONFIRM_QUIT; }
break;
}
continue;
}
if ( ctrl ) { continue; }
switch ( codepoint )
{
case '\n':
cursorx = 0;
if ( cursory < HEIGHT-1 ) { numlines++; cursory++; }
break;
case '\b':
if ( cursorx )
{
cursorx--;
for ( unsigned x = cursorx; x < WIDTH; x++ )
{
buffers[cursory][x] = buffers[cursory][x+1];
bufferchanged = true;
}
printf("\e[2K\r%s", buffers[cursory]);
fflush(stdout);
}
else if ( 0 < cursory && strlen(buffers[cursory]) == 0 )
{
for ( unsigned y = cursory; y < numlines; y++ )
{
for ( unsigned x = 0; x < WIDTH; x++ )
{
buffers[y][x] = buffers[y+1][x];
}
}
numlines--;
cursory--;
cursorx = strlen(buffers[cursory]);
drawtextmode();
}
break;
}
size_t linelen = strlen(buffers[cursory]);
if ( linelen < cursorx ) { cursorx = linelen; }
if ( WIDTH <= cursorx ) { continue; }
if ( codepoint >= 0x80 ) { continue; }
if ( codepoint == '\t' ) { continue; }
if ( codepoint == '\b' ) { continue; }
if ( codepoint == '\n' ) { continue; }
if ( codepoint == '\r' ) { continue; }
char msg[2];
msg[0] = codepoint;
msg[1] = 0;
printf("%s", msg);
buffers[cursory][cursorx++] = codepoint;
fflush(stdout);
bufferchanged = true;
if ( WIDTH <= cursorx ) { cursorx = WIDTH-1; }
}
return MODE_QUIT;
}
unsigned confirmquit()
{
if ( !bufferchanged ) { return MODE_QUIT; }
printf("\e37m\e40m\e[2J\e[H");
printf("There are unsaved changes: Are you sure you want to quit? (Y/N)\n");
if ( settermmode(0, TERMMODE_KBKEY | TERMMODE_SIGNAL | TERMMODE_ECHO) )
{
error(1, errno, "settermmode");
}
while ( true )
{
uint32_t codepoint;
ssize_t numbytes = read(0, &codepoint, sizeof(codepoint));
if ( !numbytes ) { exit(0); }
if ( numbytes < 0 ) { error(1, errno, "read stdin"); }
if ( numbytes < (ssize_t) sizeof(codepoint) ) { fprintf(stderr, "bad stdin data\n"); exit(1); }
if ( !codepoint ) { continue; }
int kbkey = KBKEY_DECODE(codepoint);
if ( !kbkey ) { continue; }
if ( 0 < kbkey ) { continue; }
switch ( kbkey )
{
case -KBKEY_ESC:
return MODE_QUIT;
break;
case -KBKEY_N:
return MODE_TEXT;
case -KBKEY_Y:
return MODE_QUIT;
default:
printf("Would you like to quit? N for No, Y for Yes\n");
}
}
}
bool savetofile(const char* path)
{
int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0777);
if ( fd < 0 ) { error(0, errno, "%s", path); return false; }
for ( unsigned y = 0; y < numlines; y++ )
{
size_t len = strlen(buffers[y]);
buffers[y][len] = '\n';
bool result = writeall(fd, buffers[y], len+1) == len+1;
buffers[y][len] = 0;
if ( !result ) { error(0, errno, "write: %s", path); close(fd); return false; }
}
if ( close(fd) ) { error(0, errno, "close: %s", path); return false; }
strcpy(filename, path);
return true;
}
int savemode()
{
printf("\e37m\e40m\e[2J\e[H");
printf("Please enter the filename you wish to save the text to and press "
"enter. Type an empty filename to abort.\n\n");
char* storage = NULL;
do
{
delete[] storage;
printf("File to Write: ");
fflush(stdout);
storage = readline(0);
if ( !storage ) { error(1, errno, "readline"); }
if ( !storage[0] ) { delete[] storage; return MODE_TEXT; }
} while ( !savetofile(storage) );
delete[] storage;
bufferchanged = false;
printf("Succesfully saved\n");
sleep(1);
return MODE_TEXT;
}
bool loadfromfile(const char* path)
{
int fd = open(path, O_RDONLY, 0777);
if ( fd < 0 ) { error(0, errno, "%s", path); return false; }
clearbuffers();
const size_t BUFFER_SIZE = 256;
char buffer[BUFFER_SIZE];
bool done = false;
while ( !done )
{
ssize_t bytesread = read(fd, buffer, BUFFER_SIZE);
if ( bytesread < 0 ) { error(0, errno, "read: %s", path); close(fd); return false; }
if ( bytesread == 0 ) { break; }
for ( ssize_t i = 0; i < bytesread; i++ )
{
if ( buffer[i] == '\n' )
{
if ( HEIGHT-1 <= ++cursory ) { done = true; break; }
cursorx = 0;
continue;
}
if ( WIDTH <= cursorx ) { continue; }
buffers[cursory][cursorx++] = buffer[i];
}
}
numlines = cursory + 1;
close(fd);
cursorx = 0;
cursory = 0;
strcpy(filename, path);
return true;
}
int loadmode()
{
printf("\e37m\e40m\e[2J\e[H");
printf("Please enter the filename you wish to load text from and press "
"enter. Type an empty filename to abort.\n\n");
char* storage = NULL;
do
{
delete[] storage;
printf("File to Load: ");
fflush(stdout);
storage = readline(0);
if ( !storage ) { error(1, errno, "readline"); }
if ( !storage[0] ) { delete[] storage; return MODE_TEXT; }
} while ( !loadfromfile(storage) );
delete[] storage;
bufferchanged = false;
return MODE_TEXT;
}
void run()
{
bufferchanged = false;
unsigned mode = MODE_TEXT;
while ( mode != (unsigned) MODE_QUIT )
{
switch ( mode )
{
case MODE_TEXT:
mode = textmode();
break;
case MODE_CONFIRM_QUIT:
mode = confirmquit();
break;
case MODE_SAVE:
mode = savemode();
break;
case MODE_LOAD:
mode = loadmode();
break;
default:
printf("Application Bug: Unknown Mode\n");
sleep(1);
mode = MODE_TEXT;
break;
}
}
printf("\e[37m\e[40m\e[2J\e[H");
fflush(stdout);
}
int main(int argc, char* argv[])
{
if ( !isatty(1) ) { error(1, errno, "stdout must be a tty"); return 1; }
struct winsize ws;
if ( tcgetwinsize(1, &ws) != 0 )
error(1, errno, "tcgetwinsize");
if ( ws.ws_col != 80 || ws.ws_row != 25 )
{
fprintf(stderr, "Sorry, this application only works with 80x25 "
"terminal resolutions, please fix it. :)\n");
exit(1);
}
if ( argc < 2 )
{
clearbuffers();
strcpy(filename, "");
}
else
{
strcpy(filename, argv[1]);
if ( !loadfromfile(filename) ) { clearbuffers(); }
cursorx = 0;
cursory = 0;
}
run();
return 0;
}