// Simple text editor /* global structure of program: - check first argument, exit if none - read file from first argument into mem - using a rectangular buffer (uninitialized 2d array, pointer to FILE_MEMORY_ADDR) with a max linewidth - while reading file, fill line until newline or MAX_LINE_WIDTH (if max, then error and exit) - add zeros until max_line_width after newline character - if current line number >= MAX_LINES: error out of mem and exit - use a >8bit code for indicating end of file, so print can stop - keep track of cursor x and y, which corresponds to line and pos in mem buffer - need for fast (asm!) memory move operations for inserting and deleting line based stuff (within the same line does not matter that much) - need for fast (asm!) print function which prints TEXT_WINDOW_HEIGHT lines with an xscroll offset (with palette indication that a line extends right) - end of file should have a special palette color - header line as first line with filename, mode, etc (like nano) - keep track of window (start) cursor (only y, line number) - when cursor y < window cursor, window cursor --, if y > window cursor + TEXT_WINDOW_HEIGHT, window cursor++ (check on start and end of file boundaries) - after each operation, print from window start - print cursor on top of character, by inverting the palette - escape for menu - save - function to - exit */ /* future possible features that are not that hard to implement - use control key (which should be implemented in hid fifo first!) - then, ctrl s and other stuff can be implemented - selection mode (set start and end x,y), use a color palette to indicate selection - copy or delete (or cut, by copy and then delete) the selection into a separate buffer (check on bounds!) - paste by inserting from the buffer - text search which selects the matches one by one (different mode again) - tab inserts 2 spaces, maybe stops at even x for alignment? - insert mode that overwrites characters instead of moving them */ #define word char #include "lib/math.c" #include "lib/sys.c" #include "lib/gfx.c" #include "lib/stdlib.c" #include "lib/fs.c" #define FBUF_ADDR 0x440000 // location of input file buffer #define FILE_MEMORY_ADDR 0x480000 // location of file content #define MAX_LINE_WIDTH 240 // max width of line in mem buffer, multiple of 40 (screen width) #define MAX_LINES 8192 // maximum number of lines to prevent out of memory writes (8192*256*4 = 8MiB) #define TEXT_WINDOW_HEIGHT 20 // number of lines to print on screen #define SCREEN_WIDTH 40 #define SCREEN_HEIGHT 25 // Palettes #define PALETTE_CURSOR 1 #define PALETTE_NEWLINE 2 #define PALETTE_EOF 3 #define PALETTE_SELECTED 4 #define PALETTE_HEADER 5 // HID #define ARROW_LEFT 256 #define ARROW_RIGHT 257 #define ARROW_UP 258 #define ARROW_DOWN 259 #define BTN_INSERT 260 #define BTN_HOME 261 #define BTN_PAGEUP 262 #define BTN_END 263 #define BTN_PAGEDOWN 264 char infilename[96]; // input filename to edit the contents of char headerText[SCREEN_WIDTH]; // text on header (first line) // Buffer for file contents in memory char (*mem)[MAX_LINE_WIDTH] = (char (*)[MAX_LINE_WIDTH]) FILE_MEMORY_ADDR; // 2d array containing all lines of the input file // Buffer for file reading // Length of buffer always should be less than 65536, since this is the maximum FS_readFile can do in a single call #define FBUF_LEN 4096 #define EOF -1 char *inputBuffer = (char*) FBUF_ADDR; //[FBUF_LEN]; word inbufStartPos = 0; // where in the file the buffer starts word inbufCursor = 0; // where in the buffer we currently are working word lastLineNumber = 0; // Cursors word windowYscroll = 0; // vertical line offset for drawing window word windowXscroll = 0; // horizontal position offset for drawing window word userCursorX = 0; // char position within line, of user cursor word userCursorY = 0; // line of user cursor // Opens file for reading // requires full paths // returns 1 on success word fopenRead() { if (infilename[0] != '/') { BDOS_PrintConsole("E: Filename should be a full path\n"); return 0; } FS_close(); // to be sure // convert to uppercase strToUpper(infilename); // init read buffer inbufStartPos = 0; // start at 0 inbufCursor = 0; // start at 0 // if the resulting path is correct (can be file or directory) if (FS_sendFullPath(infilename) == FS_ANSW_USB_INT_SUCCESS) { // if we can successfully open the file (not directory) if (FS_open() == FS_ANSW_USB_INT_SUCCESS) { FS_setCursor(0); // set cursor to start return 1; } else { return 0; } } else { return 0; } return 0; } // opens file for writing by recreating it // should not be called before fopenRead, therefore exits on error word fopenWrite() { if (infilename[0] != '/') { BDOS_PrintConsole("E: Filename should be a full path\n"); exit(); } FS_close(); // to be sure // convert to uppercase strToUpper(infilename); // if current path is correct (can be file or directory) if (FS_sendFullPath(infilename) == FS_ANSW_USB_INT_SUCCESS) { // create the file if (FS_createFile() == FS_ANSW_USB_INT_SUCCESS) { //BDOS_PrintConsole("File created\n"); // open again and start at 0 FS_sendFullPath(infilename); FS_open(); FS_setCursor(0); // set cursor to start return 1; } else { BDOS_PrintConsole("E: Could not create file\n"); exit(); } } else { BDOS_PrintConsole("E: Invalid path\n"); exit(); } exit(); return 0; } // Writes data of given length to opened file void fputData(char* datBuf, word lenOfData) { if (lenOfData == 0) { return; } word bytesWritten = 0; // loop until all bytes are sent while (bytesWritten != lenOfData) { word partToSend = lenOfData - bytesWritten; // send in parts of 0xFFFF if (partToSend > 0xFFFF) partToSend = 0xFFFF; // write away if (FS_writeFile((datBuf +bytesWritten), partToSend) != FS_ANSW_USB_INT_SUCCESS) BDOS_PrintConsole("write error\n"); // Update the amount of bytes sent bytesWritten += partToSend; } } // Write mem to file void writeMemToFile() { fopenWrite(); // open file for writing word y; for (y = 0; y <= lastLineNumber; y++) { word lineEndPos = 0; while (mem[y][lineEndPos] != '\n' && mem[y][lineEndPos] != EOF) { lineEndPos++; if (lineEndPos == MAX_LINE_WIDTH) { exitRoutine(); BDOS_PrintConsole("E: could not find end of line\n"); FS_close(); exit(); } } if (mem[y][lineEndPos] == EOF) { fputData(mem[y], lineEndPos); FS_close(); return; } lineEndPos++; // include the newline token fputData(mem[y], lineEndPos); } FS_close(); } // returns the current char at cursor within the opened file (EOF if end of file) // increments the cursor word fgetc() { // workaround for missing compiler check for ALU constants > 11 bit word stdioBufLen = FBUF_LEN; if (inbufCursor >= FBUF_LEN || inbufCursor == 0) // we are at the end of the buffer (or it is not initialized yet) { // get filesize word sizeOfFile = FS_getFileSize(); // if we cannot completely fill the buffer: if (inbufStartPos + stdioBufLen > sizeOfFile) { // fill the buffer, and append with EOF token FS_readFile(inputBuffer, sizeOfFile - inbufStartPos, 0); inputBuffer[sizeOfFile - inbufStartPos] = EOF; } else { // fill buffer again FS_readFile(inputBuffer, FBUF_LEN, 0); } inbufStartPos += stdioBufLen; // for the next fill inbufCursor = 0; // start at the beginning of the buffer again } // return from the buffer, and increment char gotchar = inputBuffer[inbufCursor]; inbufCursor++; // BDOS_PrintcConsole(gotchar); // useful for debugging return gotchar; } // reads a line from the input file word readFileLine() { char c = fgetc(); char cprev = c; word currentChar = 0; // stop on EOF or newline or max line width reached while (c != EOF && c != '\n' && currentChar < (MAX_LINE_WIDTH-1)) { mem[lastLineNumber][currentChar] = c; currentChar++; cprev = c; c = fgetc(); } // error when line was too long if (c != EOF && c != '\n' && currentChar >= (MAX_LINE_WIDTH-1)) { BDOS_PrintConsole("E: line is too long\n"); exit(); } mem[lastLineNumber][currentChar] = c; // add EOF or \n // append line with zeros while (currentChar < (MAX_LINE_WIDTH-1)) { currentChar++; char fillerChar = 0; if (c == EOF) { fillerChar = EOF; } mem[lastLineNumber][currentChar] = fillerChar; } return c; } void readInputFile() { while (readFileLine() != EOF) { if (lastLineNumber >= MAX_LINES) { BDOS_PrintConsole("E: File too large\n"); exit(); } else { lastLineNumber++; } } FS_close(); } // Applies window to mem and prints on screen (using window plane) void printWindow() { printHeader(); char* vramWindowpattern = (char*) 0xC01420; word vramPaletteOffset = 2048; word lastLineFound = 0; word x, y; for (y = 0; y < SCREEN_HEIGHT-1; y++) // leave first line open { for (x = 0; x < SCREEN_WIDTH; x++) { word c = mem[y + windowYscroll][x + windowXscroll]; if (c == '\n') { word vramOffset = GFX_WindowPosFromXY(x, y+1); *(vramWindowpattern + vramOffset) = 7; *(vramWindowpattern + vramOffset + vramPaletteOffset) = PALETTE_NEWLINE; } else if (c == EOF) { // print empty space if EOF is already drawn if (lastLineFound) { word vramOffset = GFX_WindowPosFromXY(x, y+1); *(vramWindowpattern + vramOffset) = 0; *(vramWindowpattern + vramOffset + vramPaletteOffset) = 0; } else { word vramOffset = GFX_WindowPosFromXY(x, y+1); *(vramWindowpattern + vramOffset) = 4; *(vramWindowpattern + vramOffset + vramPaletteOffset) = PALETTE_EOF; lastLineFound = 1; // notify, but continue rendering the line } } else { word vramOffset = GFX_WindowPosFromXY(x, y+1); *(vramWindowpattern + vramOffset) = c; *(vramWindowpattern + vramOffset + vramPaletteOffset) = 0; } } if (lastLineFound) { return; } } } // Remove cursor from screen (bg plane) void clearCursor() { word vramCursorOffset = GFX_BackgroundPosFromXY(userCursorX, userCursorY+1); char* vramBackgroundpalette = (char*) 0xC00C20; *(vramBackgroundpalette + vramCursorOffset) = 0; } // Print the cursor on screen (using background plane) void printCursor() { word vramCursorOffset = GFX_BackgroundPosFromXY(userCursorX, userCursorY+1); char* vramBackgroundpalette = (char*) 0xC00C20; *(vramBackgroundpalette + vramCursorOffset) = PALETTE_CURSOR; } // Print the header on screen (using window plane) void printHeader() { GFX_printWindowColored(headerText, SCREEN_WIDTH, 0, PALETTE_HEADER); } void setPalettes() { word* paletteAddress = (word*) 0xC00400; paletteAddress[PALETTE_CURSOR] = 0x7 << 24; paletteAddress[PALETTE_NEWLINE] = 12; paletteAddress[PALETTE_EOF] = 224; paletteAddress[PALETTE_HEADER] = (0x51 << 24) + 0xDA; } // Make sure the cursor stays left from the newline void fixCursorToNewline() { word xpos = userCursorX+windowXscroll; word ypos = userCursorY+windowYscroll; word newLinePos = 0; while (mem[ypos][newLinePos] != '\n' && mem[ypos][newLinePos] != EOF) { newLinePos++; if (newLinePos == MAX_LINE_WIDTH) { exitRoutine(); BDOS_PrintConsole("E: could not find newline\n"); exit(); } } clearCursor(); word appliedScolling = 0; while (xpos > newLinePos) { if (userCursorX > 0) { userCursorX--; } else { if (windowXscroll > 0) { windowXscroll--; appliedScolling = 1; } else { exitRoutine(); BDOS_PrintConsole("E: scroll left limit reached\n"); exit(); } } xpos = userCursorX+windowXscroll; } if (appliedScolling) { printWindow(); } } // Move the cursor to targetPos of the previous line, from the left // Assumes targetPos is valid and ypos is not 0 // Does not print window at the end, caller should do that void gotoPosOfPrevLine(word targetPos) { clearCursor(); userCursorX = 0; windowXscroll = 0; if (userCursorY > 0) { userCursorY--; } else { if (windowYscroll > 0) { windowYscroll--; } else { exitRoutine(); BDOS_PrintConsole("E: could not move up a line\n"); exit(); } } word xpos = 0; word ypos = userCursorY+windowYscroll; while (xpos != targetPos) { if (userCursorX < SCREEN_WIDTH-1) { userCursorX++; } else { if (windowXscroll < MAX_LINE_WIDTH-SCREEN_WIDTH) { windowXscroll++; } } xpos = userCursorX + windowXscroll; if (xpos == MAX_LINE_WIDTH) { exitRoutine(); BDOS_PrintConsole("E: target out of bounds\n"); exit(); } } printCursor(); } // Move the cursor to the newline character of the previous line void gotoEndOfPrevLine() { userCursorX = 0; windowXscroll = 0; word appliedScolling = 0; if (userCursorY > 0) { userCursorY--; } else { if (windowYscroll > 0) { windowYscroll--; appliedScolling = 1; } else { // don't need to do anything when on the first line return; } } word xpos = 0; word ypos = userCursorY+windowYscroll; while (mem[ypos][xpos] != '\n' && mem[ypos][xpos] != EOF) { if (userCursorX < SCREEN_WIDTH-1) { userCursorX++; } else { if (windowXscroll < MAX_LINE_WIDTH-SCREEN_WIDTH) { windowXscroll++; appliedScolling = 1; } } xpos = userCursorX + windowXscroll; if (xpos == MAX_LINE_WIDTH) { exitRoutine(); BDOS_PrintConsole("E: could not find newline\n"); exit(); } } if (appliedScolling) { printWindow(); } } void moveCursorDown() { // skip if current line contains EOF word ypos = userCursorY+windowYscroll; word i; for (i = 0; i < MAX_LINE_WIDTH; i++) { if (mem[ypos][i] == '\n') { break; } if (mem[ypos][i] == EOF) { return; } } clearCursor(); if (userCursorY < SCREEN_HEIGHT-2) { userCursorY++; } else { if (windowYscroll < lastLineNumber-(SCREEN_HEIGHT-2)) { windowYscroll++; printWindow(); } } fixCursorToNewline(); printCursor(); } void moveCursorUp() { clearCursor(); if (userCursorY > 0) { userCursorY--; } else { if (windowYscroll > 0) { windowYscroll--; printWindow(); } } fixCursorToNewline(); printCursor(); } void moveCursorLeft() { clearCursor(); if (userCursorX > 0) { userCursorX--; } else { if (windowXscroll > 0) { windowXscroll--; printWindow(); } else { // move to previous line windowXscroll = MAX_LINE_WIDTH - SCREEN_WIDTH; userCursorX = SCREEN_WIDTH - 1; gotoEndOfPrevLine(); } } fixCursorToNewline(); printCursor(); } void moveCursorRight() { word xpos = userCursorX + windowXscroll; if (mem[userCursorY+windowYscroll][xpos] == EOF) { // do nothing at EOF return; } clearCursor(); if (mem[userCursorY+windowYscroll][xpos] == '\n') { // if we are at the end of the line, move to next line word appliedScolling = windowXscroll; windowXscroll = 0; userCursorX = 0; moveCursorDown(); if (appliedScolling) { printWindow(); } } else { if (userCursorX < SCREEN_WIDTH-1) { userCursorX++; } else { if (windowXscroll < MAX_LINE_WIDTH-SCREEN_WIDTH) { windowXscroll++; printWindow(); } } } printCursor(); } void pageUp() { clearCursor(); word i; for (i = 0; i < SCREEN_HEIGHT-2; i++) { if (userCursorY > 0) { userCursorY--; } else { if (windowYscroll > 0) { windowYscroll--; } } } printWindow(); fixCursorToNewline(); printCursor(); } void pageDown() { clearCursor(); word lastLineFound = 0; word i; for (i = 0; i < SCREEN_HEIGHT-2; i++) { if (lastLineFound) { break; } // skip if current line contains EOF word ypos = userCursorY+windowYscroll; word i; for (i = 0; i < MAX_LINE_WIDTH; i++) { if (mem[ypos][i] == '\n') { break; } if (mem[ypos][i] == EOF) { lastLineFound = 1; break; } } if (userCursorY < SCREEN_HEIGHT-2) { userCursorY++; } else { if (windowYscroll < lastLineNumber-(SCREEN_HEIGHT-2)) { windowYscroll++; } } } printWindow(); fixCursorToNewline(); printCursor(); } void home() { clearCursor(); userCursorX = 0; windowXscroll = 0; printWindow(); printCursor(); } void end() { clearCursor(); userCursorX = 0; windowXscroll = 0; word xpos = 0; word ypos = userCursorY+windowYscroll; while (mem[ypos][xpos] != '\n' && mem[ypos][xpos] != EOF) { if (userCursorX < SCREEN_WIDTH-1) { userCursorX++; } else { if (windowXscroll < MAX_LINE_WIDTH-SCREEN_WIDTH) { windowXscroll++; } } xpos = userCursorX + windowXscroll; if (xpos == MAX_LINE_WIDTH) { exitRoutine(); BDOS_PrintConsole("E: could not find newline\n"); exit(); } } printWindow(); printCursor(); } void removeCurrentLine() { word ypos = userCursorY+windowYscroll; // move all lines below ypos up by one, starting at beginning word i; for (i = ypos+1; i <= lastLineNumber; i++) { memcpy(mem[i-1], mem[i], MAX_LINE_WIDTH); } lastLineNumber--; } void insertNewLine() { word ypos = userCursorY+windowYscroll; if (lastLineNumber == MAX_LINES-1) { // ignore if we reached the memory limit return; } clearCursor(); // move all lines below ypos down by one, starting at the end word i; for (i = lastLineNumber; i > ypos; i--) { memcpy(mem[i+1], mem[i], MAX_LINE_WIDTH); } lastLineNumber++; // move everything right from cursor to new line // this should include the newline/EOF automatically word newLineTmpCursor = 0; word xpos = userCursorX+windowXscroll; while(xpos < MAX_LINE_WIDTH) { mem[ypos+1][newLineTmpCursor] = mem[ypos][xpos]; mem[ypos][xpos] = 0; newLineTmpCursor++; xpos++; } // insert newline character at current line xpos = userCursorX+windowXscroll; mem[ypos][xpos] = '\n'; // move the cursor down to the start of the new line windowXscroll = 0; userCursorX = 0; moveCursorDown(); printWindow(); // always needed in case of a new line printCursor(); } // Insert character c at cursor void insertCharacter(char c) { word xpos = userCursorX+windowXscroll; word ypos = userCursorY+windowYscroll; memmove(&mem[ypos][xpos+1], &mem[ypos][xpos], MAX_LINE_WIDTH-(xpos+1)); mem[ypos][xpos] = c; printWindow(); moveCursorRight(); } // Remove character at cursor void removeCharacter() { word xpos = userCursorX+windowXscroll; word ypos = userCursorY+windowYscroll; if (xpos > 0) { memmove(&mem[ypos][xpos-1], &mem[ypos][xpos], MAX_LINE_WIDTH-(xpos-1)); mem[ypos][MAX_LINE_WIDTH-1] = 0; // remove new char at right of line printWindow(); moveCursorLeft(); } else { if (ypos > 0) { // append current line to previous line at newline word prevLinePasteLocation = 0; word newLinePos = 0; while (mem[ypos-1][newLinePos] != '\n') { newLinePos++; if (newLinePos == MAX_LINE_WIDTH) { exitRoutine(); BDOS_PrintConsole("E: could not find newline\n"); exit(); } } // copy to overwrite the newline memcpy(&mem[ypos-1][newLinePos], &mem[ypos][0], MAX_LINE_WIDTH-(newLinePos)); prevLinePasteLocation = newLinePos; // check if resulting line has a newline or EOF, else add to end newLinePos = 0; while (mem[ypos-1][newLinePos] != '\n' && mem[ypos-1][newLinePos] != EOF && newLinePos < MAX_LINE_WIDTH) { newLinePos++; if (newLinePos == MAX_LINE_WIDTH) { if (ypos == lastLineNumber) { mem[ypos-1][MAX_LINE_WIDTH-1] = EOF; } else { mem[ypos-1][MAX_LINE_WIDTH-1] = '\n'; } } } // remove the current line removeCurrentLine(); // move cursor to the place we pasted the line gotoPosOfPrevLine(prevLinePasteLocation); // update the view printWindow(); } } } void addEditHeader() { headerText[29] = 'Y'; headerText[30] = ':'; itoa(userCursorY+windowYscroll, &headerText[31]); headerText[35] = 'X'; headerText[36] = ':'; itoa(userCursorX+windowXscroll, &headerText[37]); } void exitRoutine() { GFX_clearWindowtileTable(); GFX_clearWindowpaletteTable(); GFX_clearBGtileTable(); GFX_clearBGpaletteTable(); } int main() { // input file BDOS_GetArgN(1, infilename); // error if none if (infilename[0] == 0) { BDOS_PrintConsole("E: Missing filename\n"); exit(); } // Make full path if it is not already if (infilename[0] != '/') { char bothPath[96]; bothPath[0] = 0; strcat(bothPath, BDOS_GetPath()); if (bothPath[strlen(bothPath)-1] != '/') { strcat(bothPath, "/"); } strcat(bothPath, infilename); strcpy(infilename, bothPath); } if (!fopenRead()) { BDOS_PrintConsole("E: Could not open file\n"); exit(); } // Open the input file BDOS_PrintConsole("Opening "); BDOS_PrintConsole(infilename); BDOS_PrintConsole("...\n"); readInputFile(); strcpy(headerText, infilename); // init gfx GFX_clearWindowtileTable(); GFX_clearWindowpaletteTable(); GFX_clearBGtileTable(); GFX_clearBGpaletteTable(); setPalettes(); addEditHeader(); printWindow(); printCursor(); // main loop while (1) { if (HID_FifoAvailable()) { word c = HID_FifoRead(); switch (c) { case 27: // escape memcpy(&headerText[24], "Save? Type y/n/c", 16); printHeader(); // ask for confirmation while (c != 'y' && c != 'n' && c != 'Y' && c != 'N' && c != 'c' && c != 'C') { // wait until a character is pressed while (HID_FifoAvailable() == 0); c = HID_FifoRead(); } if (c == 'y' || c == 'Y') { writeMemToFile(); exitRoutine(); return 'q'; // exit } if (c == 'n' || c == 'N') { exitRoutine(); return 'q'; // exit } memcpy(&headerText[24], " ", 16); // clear header text break; case ARROW_LEFT: moveCursorLeft(); break; case ARROW_RIGHT: moveCursorRight(); break; case ARROW_DOWN: moveCursorDown(); break; case ARROW_UP: moveCursorUp(); break; case BTN_HOME: home(); break; case BTN_END: end(); break; case BTN_PAGEUP: pageUp(); break; case BTN_PAGEDOWN: pageDown(); break; case 0x8: // backspace removeCharacter(); break; case '\n': insertNewLine(); break; case '\t': insertCharacter(' '); insertCharacter(' '); break; default: // default to inserting the character as text insertCharacter(c); break; } addEditHeader(); printHeader(); } } return 'q'; } void interrupt() { // Handle all interrupts word i = getIntID(); switch(i) { case INTID_TIMER1: timer1Value = 1; // Notify ending of timer1 break; } }