Made FILE an interface to various backends. This allows application writers to override the standard FILE API functions with their own backends. This is highly unportable - it'd be nice if a real standard existed for this. glibc already does something like this internally, but AFAIK you can't hook into it. Added fdopen(3), fopen(3), fregister(3), funregister(3), fread(3), fwrite(3), fseek(3), clearerr(3), ferror(3), feof(3), rewind(3), ftell(3), fflush(3), fclose(3), fileno(3), fnewline(3), fcloseall(3), memset(3), stdio(3), vfprintf(3), fprintf(3), and vprintf(3). Added a file-descriptor backend to the FILE API. fd's {0, 1, 2} are now initialized as stdin, stdout, and stderr when the standard library initializes. fcloseall(3) is now called on exit(3). decl/intn_t_.h now @include(size_t.h) instead of declaring it itself. Added <stdint.h>. The following programs now flush stdout: cat(1), clear(1), editor(1), init(1), mxsh(1). printf(3) is now hooked up against vprintf(3), while Maxsi::PrintF remains using the system call, for now.
441 lines
9 KiB
C++
441 lines
9 KiB
C++
#include <stdio.h>
|
|
#include <unistd.h>
|
|
#include <fcntl.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <error.h>
|
|
#include <libmaxsi/platform.h>
|
|
#include <libmaxsi/sortix-keyboard.h>
|
|
|
|
const int MODE_QUIT = 0;
|
|
const int MODE_TEXT = 1;
|
|
const int MODE_CONFIRM_QUIT = 2;
|
|
const int MODE_SAVE = 3;
|
|
const int MODE_LOAD = 4;
|
|
|
|
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;
|
|
|
|
void clearbuffers()
|
|
{
|
|
for ( unsigned y = 0; y < HEIGHT + 1; y++ )
|
|
{
|
|
for ( unsigned x = 0; x < WIDTH + 1; x++ ) { buffers[y][x] = 0; }
|
|
}
|
|
|
|
bufferchanged = true;
|
|
}
|
|
|
|
using namespace Maxsi;
|
|
|
|
void cursorto(unsigned x, unsigned y)
|
|
{
|
|
printf("\e[%u;%uH", y+1+1, x+1);
|
|
fflush(stdout);
|
|
}
|
|
|
|
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;
|
|
|
|
int oldcursorx = -1;
|
|
int oldcursory = -1;
|
|
while ( true )
|
|
{
|
|
if ( oldcursorx != cursorx || oldcursory != cursory )
|
|
{
|
|
cursorto(cursorx, cursory);
|
|
oldcursorx = cursorx;
|
|
oldcursory = cursory;
|
|
}
|
|
|
|
unsigned method = System::Keyboard::POLL;
|
|
uint32_t codepoint = System::Keyboard::ReceiveKeystroke(method);
|
|
|
|
if ( codepoint == 0 ) { continue; }
|
|
if ( codepoint & Keyboard::DEPRESSED )
|
|
{
|
|
if ( codepoint == Keyboard::CTRL | Keyboard::DEPRESSED ) { ctrl = false; }
|
|
continue;
|
|
}
|
|
|
|
if ( ctrl )
|
|
{
|
|
if ( codepoint == 'o' || codepoint == 'O' ) { return MODE_SAVE; }
|
|
if ( codepoint == 'r' || codepoint == 'R' ) { return MODE_LOAD; }
|
|
if ( codepoint == 'x' || codepoint == 'X' ) { return MODE_CONFIRM_QUIT; }
|
|
continue;
|
|
}
|
|
|
|
switch ( codepoint )
|
|
{
|
|
case Keyboard::ESC:
|
|
return MODE_CONFIRM_QUIT;
|
|
break;
|
|
case Keyboard::CTRL:
|
|
ctrl = true;
|
|
break;
|
|
case Keyboard::UP:
|
|
if ( cursory ) { cursory--; }
|
|
break;
|
|
case '\n':
|
|
cursorx = 0;
|
|
if ( cursory < HEIGHT-1 ) { numlines++; }
|
|
case Keyboard::DOWN:
|
|
if ( cursory < numlines-1 ) { cursory++; }
|
|
break;
|
|
case Keyboard::LEFT:
|
|
if ( cursorx ) { cursorx--; }
|
|
break;
|
|
case Keyboard::RIGHT:
|
|
if ( cursorx < WIDTH-1 ) { cursorx++; }
|
|
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");
|
|
|
|
while ( true )
|
|
{
|
|
unsigned method = System::Keyboard::POLL;
|
|
uint32_t codepoint = System::Keyboard::ReceiveKeystroke(method);
|
|
|
|
if ( codepoint == 0 ) { continue; }
|
|
if ( codepoint & Keyboard::DEPRESSED ) { continue; }
|
|
|
|
switch ( codepoint )
|
|
{
|
|
case Keyboard::ESC:
|
|
return MODE_QUIT;
|
|
break;
|
|
case 'n':
|
|
case 'N':
|
|
return MODE_TEXT;
|
|
case 'y':
|
|
case '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);
|
|
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; }
|
|
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 or press ESC to abort.\n\n");
|
|
|
|
char writefilename[256];
|
|
strcpy(writefilename, filename);
|
|
|
|
retry:
|
|
size_t len = strlen(writefilename);
|
|
printf("File Name to Write: %s", writefilename);
|
|
fflush(stdout);
|
|
|
|
bool readytosave = false;
|
|
|
|
while ( !readytosave )
|
|
{
|
|
unsigned method = System::Keyboard::POLL;
|
|
uint32_t codepoint = System::Keyboard::ReceiveKeystroke(method);
|
|
|
|
if ( codepoint == 0 ) { continue; }
|
|
if ( codepoint & Keyboard::DEPRESSED ) { continue; }
|
|
|
|
switch ( codepoint )
|
|
{
|
|
case Keyboard::ESC:
|
|
return MODE_TEXT;
|
|
break;
|
|
case '\b':
|
|
if ( 0 < len ) { printf("\b"); fflush(stdout); writefilename[--len] = 0; }
|
|
break;
|
|
case '\n':
|
|
if ( len == 0 ) { return MODE_TEXT; }
|
|
readytosave = true;
|
|
printf("\n");
|
|
break;
|
|
default:
|
|
if ( 0x80 <= codepoint ) { continue; }
|
|
writefilename[len++] = codepoint;
|
|
writefilename[len+1] = 0;
|
|
char msg[2];
|
|
msg[0] = codepoint;
|
|
msg[1] = 0;
|
|
printf("%s", msg);
|
|
fflush(stdout);
|
|
}
|
|
}
|
|
|
|
if ( !savetofile(writefilename) ) { goto retry; }
|
|
|
|
strcpy(filename, writefilename);
|
|
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;
|
|
|
|
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 or press ESC to abort.\n\n");
|
|
|
|
char loadfilename[256];
|
|
loadfilename[0] = 0;
|
|
|
|
retry:
|
|
size_t len = strlen(loadfilename);
|
|
printf("File Name to Load: %s", loadfilename);
|
|
fflush(stdout);
|
|
|
|
bool readytoload = false;
|
|
|
|
while ( !readytoload )
|
|
{
|
|
unsigned method = System::Keyboard::POLL;
|
|
uint32_t codepoint = System::Keyboard::ReceiveKeystroke(method);
|
|
|
|
if ( codepoint == 0 ) { continue; }
|
|
if ( codepoint & Keyboard::DEPRESSED ) { continue; }
|
|
|
|
switch ( codepoint )
|
|
{
|
|
case Keyboard::ESC:
|
|
return MODE_TEXT;
|
|
break;
|
|
case '\b':
|
|
if ( 0 < len ) { printf("\b"); fflush(stdout); loadfilename[--len] = 0; }
|
|
break;
|
|
case '\n':
|
|
if ( len == 0 ) { return MODE_TEXT; }
|
|
readytoload = true;
|
|
printf("\n");
|
|
break;
|
|
default:
|
|
if ( 0x80 <= codepoint ) { continue; }
|
|
loadfilename[len++] = codepoint;
|
|
loadfilename[len+1] = 0;
|
|
char msg[2];
|
|
msg[0] = codepoint;
|
|
msg[1] = 0;
|
|
printf("%s", msg);
|
|
fflush(stdout);
|
|
}
|
|
}
|
|
|
|
if ( !loadfromfile(loadfilename) )
|
|
{
|
|
clearbuffers();
|
|
cursorx = 0;
|
|
cursory = 0;
|
|
goto retry;
|
|
}
|
|
|
|
strcpy(filename, loadfilename);
|
|
bufferchanged = false;
|
|
|
|
return MODE_TEXT;
|
|
}
|
|
|
|
void run()
|
|
{
|
|
bufferchanged = false;
|
|
|
|
unsigned mode = MODE_TEXT;
|
|
while ( mode != 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; }
|
|
|
|
if ( argc < 2 )
|
|
{
|
|
clearbuffers();
|
|
strcpy(filename, "");
|
|
}
|
|
else
|
|
{
|
|
strcpy(filename, argv[1]);
|
|
if ( !loadfromfile(filename) ) { clearbuffers(); }
|
|
cursorx = 0;
|
|
cursory = 0;
|
|
}
|
|
|
|
run();
|
|
|
|
return 0;
|
|
}
|