Category : C Source Code
Archive   : CENVID17.ZIP
Filename : CMMEDIT.CMM

 
Output of file : CMMEDIT.CMM contained in archive : CENVID17.ZIP
/**************************************************************************
*** 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,"");
//}



  3 Responses to “Category : C Source Code
Archive   : CENVID17.ZIP
Filename : CMMEDIT.CMM

  1. Very nice! Thank you for this wonderful archive. I wonder why I found it only now. Long live the BBS file archives!

  2. This is so awesome! 😀 I’d be cool if you could download an entire archive of this at once, though.

  3. But one thing that puzzles me is the “mtswslnkmcjklsdlsbdmMICROSOFT” string. There is an article about it here. It is definitely worth a read: http://www.os2museum.com/wp/mtswslnk/