/************************************************************************** *** CmmEdit - A simple text editor. This is the tutorial program from *** *** chapter 3 of the CEnvi manual. *** **************************************************************************/ main(ArgCount,ArgList) { FileName = GetFileName(ArgCount,ArgList); ReadFile(FileName); if ( Edit() ) // Edit returns TRUE if changes made to file WriteFile(FileName); } GetFileName(argc,argv) // return a file name from the program input arguments, or prompt user for the // file name if none was supplied at the command line. exit() program if no // file name is gotten. { // If at least one argument was supplied to main() in addition to the source // file name (which is always supplied), then that argument is the file name. if ( 1 < argc ) return(argv[1]); // File name wasn't supplied on the command line, and so prompt for name. printf("Enter file name to edit: "); filespec = gets(); if ( filespec == NULL || filespec[0] == 0 ) // no name was entered so quit exit(EXIT_FAILURE); return(filespec); } Text[0] = ""; // Text is an array of s text strings; one for each file line ReadFile(FileSpec) // read FileSpec file into global data. exit() if error. { // Open the file, in text mode, for reading into Text. fp = fopen(FileSpec,"rt"); if ( fp == NULL ) { // The file doesn't exist, and so ask user if they want to quit. If they // don't want to create file then simply exit this program. If they do // want to create file then we're done, as Text is already initialized. printf("File \"%s\" does not exist. Create file? Y/N ",FileSpec); do { key = toupper(getch()); // make uppercase to compare to Y and N if ( key == 'N' ) exit(EXIT_FAILURE); } while( key != 'Y' ); // wasn't Y or N, and so try again } else { // File opened. Read each line of file into the next element of Text. for ( LineCount = 0; NULL != (line = fgets(fp)); LineCount++ ) { // line set to new string for next line in the text file. Set the next // line of Text to this line. Text[LineCount] = line; } fclose(fp); // Should always close a file that has been opened. } } WriteFile(FileSpec) // write global data to back to FileSpec. exit() if error. { // Open FileSpec for writing in text mode. If the file already exists then // truncate the file. If file doesn't exist then create it. fp = fopen(FileSpec,"wt"); if ( fp == NULL ) { printf("\aUnable to open \"%s\" for writing.\a\n"); exit(EXIT_FAILURE); } // write every line of Text into fp for ( i = 0; i <= GetArraySpan(Text); i++ ) fputs( Text[i], fp ); // close fp fclose(fp); } // define movement keys - Give values over 0x100 to distinguish from text #define MIN_CUR_MOVE 0x101 #define UP 0x101 #define DOWN 0x102 #define LEFT 0x103 #define RIGHT 0x104 #define PG_UP 0x105 #define PG_DN 0x106 #define HOME 0x107 #define END 0x108 #define BK_TAB 0x109 #define DELETE 0x110 GetKeyChar() // return key from keyboard, ascii for above #defined { if defined(_DOS_) || defined(_OS2_) { // DOS and OS/2 return 0 on first getch for extended keys KeyCode = getch(); if ( KeyCode == 0 ) { // set value for extended key; these value found using KeyCode.cmd switch( getch() ) { case 0x48: KeyCode = UP; break; case 0x50: KeyCode = DOWN; break; case 0x4B: KeyCode = LEFT; break; case 0x4D: KeyCode = RIGHT; break; case 0x49: KeyCode = PG_UP; break; case 0x51: KeyCode = PG_DN; break; case 0x47: KeyCode = HOME; break; case 0x4F: KeyCode = END; break; case 0x0F; KeyCode = BK_TAB; break; case 0x53; KeyCode = DELETE; break; default: break; // return 0, which will do nothing } } } else { // Windows version KeyCode = getch(); if ( 0x100 < KeyCode ) { switch ( KeyCode ) { // special values in the following table come from KeyCode.cmm case 0x126: KeyCode = UP; break; case 0x128: KeyCode = DOWN; break; case 0x125: KeyCode = LEFT; break; case 0x127: KeyCode = RIGHT; break; case 0x121: KeyCode = PG_UP; break; case 0x122: KeyCode = PG_DN; break; case 0x124: KeyCode = HOME; break; case 0x123: KeyCode = END; break; case 0x109; KeyCode = BK_TAB; break; case 0x12E; KeyCode = DELETE; break; default: KeyCode = 0; break; } } } return(KeyCode); } Edit() // Edit file. This is were the hard work happens. exit() if error. { // Return FALSE if no editing was done, else return TRUE. LineCount = 1 + GetArraySpan(Text); // how many lines in file // Initialize screen: get its dimensions, and cursor location. ScreenClear(); ScreenDimension = ScreenSize(); CursorCol = CursorRow = 0; // initialize cursor position // Starting at row 0, draw all lines on screen. Initialize Start as structure // for upper-left visible portion of file. Then draw the file. Start.Row = Start.Col = 0; DrawVisibleTextLines( Start, ScreenDimension, LineCount ); DrawnStart = Start; // remember which lines were drawn CursorStatus(CursorRow,CursorCol,Start,ScreenDimension); // FileWasEdited is boolean to say if changes made FileWasEdited = FALSE; // Stay here getting all keyboard input until escape is pressed #define ESCAPE_KEY '\033' while ( (key = GetKeyChar()) != ESCAPE_KEY ) { // special keyboard codes are returned if getch() first switch( key ) { case UP: CursorRow--; break; case DOWN: CursorRow++; break; case LEFT: CursorCol--; break; case RIGHT: CursorCol++; break; case HOME: CursorCol = 0; break; case END: // go to end of visible line, but not including newline CursorCol = strlen(Text[CursorRow]); if ( 0 < CursorCol && Text[CursorRow][CursorCol-1] == '\n' ) CursorCol--; break; case PG_UP: CursorRow -= (ScreenDimension.row - 1); Start.Row -= (ScreenDimension.row - 1); break; case PG_DN: CursorRow += (ScreenDimension.row - 1); Start.Row += (ScreenDimension.row - 1); break; #define TABSIZE 8 case '\t': CursorCol += TABSIZE; CursorCol -= CursorCol % TABSIZE; break; case BK_TAB: CursorCol -= TABSIZE; CursorCol -= CursorCol % TABSIZE; break; #define BACKSPACE '\010' case BACKSPACE: // Back space is just like deleting from one column to the left, // and so check that we're not on the first column and then move // left a column and let control fall to DELETE if ( --CursorCol < 0 ) { // backspace from beginning of line; move to end of previous line if ( CursorRow == 0 ) { // cannot backup to earlier row, so do nothing CursorCol = 0; break; } CursorCol = strlen(Text[--CursorRow]) - 1; } case DELETE: if ( DeleteCharacterAtCursor(CursorRow,CursorCol,LineCount) ) { FileWasEdited = TRUE; DrawnStart.row = -1; // force screen redraw } break; case '\r': // Add a newline at the current position InsertAsciiCharacter('\n',Text[CursorRow],CursorCol); FileWasEdited = TRUE; // a line must be opened up in Text, and all the data moved for( i = LineCount++; CursorRow + 1 < i; i-- ) strcpy( Text[i], Text[i-1] ); // move text from after cursor to next line, and end this line strcpy(Text[CursorRow+1],Text[CursorRow] + CursorCol + 1); Text[CursorRow][CursorCol + 1] = 0; // finally, move cursor to beginning of next line, and redraw screen CursorRow++, CursorCol = 0; DrawnStart.row = -1; // force screen redraw break; default: if ( isprint(key) ) { InsertAsciiCharacter(key,Text[CursorRow],CursorCol++); FileWasEdited = TRUE; // redraw this row ScreenCursor(0,CursorRow - Start.row); printf("%.*s",ScreenDimension.col,Text[CursorRow] + Start.col); } else { // the key that was pressed was not handled. Beep at the user as a // warning, but otherwise alter nothing. putchar('\a'); } break; } // Check that cursor position has not gone out of range if ( CursorRow < 0 ) CursorRow = 0; if ( CursorCol < 0 ) CursorCol = 0; if ( LineCount <= CursorRow ) CursorRow = LineCount - 1; // Check that Start.Row has not gone out of range MaxStartRow = LineCount - (ScreenDimension.row - 1) if ( MaxStartRow < Start.Row ) Start.Row = MaxStartRow; if ( Start.Row < 0 ) Start.Row = 0; // If cursor does not now fit on visible screen, then move // screen so that cursor does fit on it. while( CursorRow < Start.Row ) Start.Row--; while( CursorCol < Start.Col ) Start.Col--; while( Start.Row + ScreenDimension.Row - 1 <= CursorRow ) Start.Row++; while( Start.Col + ScreenDimension.Col <= CursorCol ) Start.Col++; // if screen must be redrawn, then do so now if ( DrawnStart != Start ) { ScreenClear(); DrawVisibleTextLines( Start, ScreenDimension, LineCount ); DrawnStart = Start; } // key was processed, so redisplay screen state CursorStatus(CursorRow,CursorCol,Start,ScreenDimension); } // Return TRUE if file was edited, else false ScreenClear(); return(FileWasEdited); } InsertAsciiCharacter(c,str,offset) // insert c in str at offset { // The newline at the end of the string can be a problem later, so for now // temporarily remove the newline then we'll put it back in when we're done. len = strlen(str); AddNewLine = ( len != 0 && str[len-1] == '\n' ); if ( AddNewLine ) str[--len] = 0; // If the current cursor position is longer than the line, then add spaces. while( len < offset ) str[len++] = ' '; // If this character won't be at end of the string, then move all characters // from here to the end of the string one space forward. This may be done // simply with a strcpy because Cmm ensures that overwriting is safe. if ( offset < len ) { strcpy(str + offset + 1,str + offset); len++; } // At last, put the character in the string str[offset] = c; if ( AddNewLine ) // put the newline character back into the string strcat(str,"\n"); } DeleteCharacterAtCursor(row,col,TotalLineCount) // delete character at cursor position. Return TRUE if a character was // delete else return FALSE. This function may alter TotalLineCount. { str = Text[row]; len = strlen(str); if ( row < (TotalLineCount - 1) ) len--; if ( col < len ) { // This is the simple case. copy string to this location from next char strcpy(str + col,str + col + 1); } else { // deleting from the end of the string or from beyond. Must bring in // from next row. if ( row == (TotalLineCount - 1) ) return(FALSE); // no following text to copy to here // fill in spaces from end of text to this location for( i = len; i <= col; i++ ) str[i] = ' '; // copy from next string to the end of this string strcpy( str + col, Text[row+1] ); // One newline has been removed, and so there are now one fewer lines // in the file. Copy all of rows down one element in the Text array. TotalLineCount--; for ( i = row + 1; i < TotalLineCount; i++ ) Text[i] = Text[i+1]; SetArraySpan(Text,TotalLineCount - 1); } return(TRUE); } DrawVisibleTextLines(StartPosition,ScreenSize,TextLineCount) // display visible portion of file. StartPosition is initial .row and .col // that is visible. ScreenSize show .col and .row width and height of screen. { // verify that the screen position is not invalid; negative would be bad. assert( 0 <= StartPosition.row && 0 <= StartPosition.col ); // Also, this function assumes that at least some lines are visible at the // top of the screen, and so verify that this is true. assert( StartPosition.row < TextLineCount ); // draw all visible lines from Text; leave bottom line free for messages. for ( row = 0; row < (ScreenSize.row-1); row++ ) { Line = Text[StartPosition.row + row]; // draw this line on the screen from StartPosition.row, remembering // to clip at the right edge of screen if the line is too long LineLen = strlen(Line) - StartPosition.col; if ( 0 < LineLen ) { // only print if characters to print ScreenCursor(0,row); printf("%.*s",ScreenSize.col,Line + StartPosition.col); } } } CursorStatus(CRow,CCol,StartPosition,ScreenSize) { // show current file cursor position; based at 1 ScreenCursor(5,ScreenSize.row-1); printf("Status: row %-3d col %-3d",CRow + 1,CCol + 1); // put cursor at correct position on screen ScreenCursor(CCol - StartPosition.Col,CRow - StartPosition.Row); } //DebugPrintf(FormatString,arg1,arg2,arg3/*etc...*/) // // printf() line on bottom of string, then get key //{ // // format message into a string // va_start(VaList,FormatString); // vsprintf(msg,FormatString,VaList); // va_end(VaList); // // // Save the cursor position, display this message on the bottom of the screen, // // get a key, and then return. This is very non-intrusive. // SaveCursor = ScreenCursor(); // ClearBottomLine(); // msg[ScreenSize().Col - 1] = '\0'; // don't let line get too long // while ( NULL != (nl = strchr(msg,'\n')) ) // change newlines to spaces // nl[0] = '_'; // ScreenCursor(0,ScreenSize().Row-1); // printf("%s",msg); // GetKeyChar(); // ClearBottomLine(); // ScreenCursor(SaveCursor.Col,SaveCursor.Row); //} // //ClearBottomLine() // called by DebugPrintf() to clear last lie //{ // ScreenCursor(0,ScreenSize().Row - 1); // printf("%*s",ScreenSize().Col-1,""); //}