home *** CD-ROM | disk | FTP | other *** search
- /**************************************************************************
- *** 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,"");
- //}
-