home *** CD-ROM | disk | FTP | other *** search
/ OS/2 Shareware BBS: 10 Tools / 10-Tools.zip / cenvi23.zip / CMMEDIT.CMM < prev    next >
Text File  |  1996-02-13  |  16KB  |  409 lines

  1. /**************************************************************************
  2.  *** CmmEdit - A simple text editor.  This is the tutorial program from ***
  3.  *** ver.2     chapter 3 of the CEnvi manual.                           ***
  4.  **************************************************************************/
  5.  
  6. main(ArgCount,ArgList)
  7. {
  8.    FileName = GetFileName(ArgCount,ArgList);
  9.    ReadFile(FileName);
  10.    if ( Edit() )  // Edit returns TRUE if changes made to file
  11.       WriteFile(FileName);
  12. }
  13.  
  14.  
  15. GetFileName(argc,argv)
  16.    // return a file name from the program input arguments, or prompt user for the
  17.    // file name if none was supplied at the command line.  exit() program if no
  18.    // file name is gotten.
  19. {
  20.    // If at least one argument was supplied to main() in addition to the source
  21.    // file name (which is always supplied), then that argument is the  file name.
  22.    if ( 1 < argc )
  23.       return(argv[1]);
  24.  
  25.    // File name wasn't supplied on the command line, and so prompt for name.
  26.    printf("Enter file name to edit: ");
  27.    filespec = gets();
  28.    if ( filespec == NULL || filespec[0] == 0 )  // no name was entered so quit
  29.       exit(EXIT_FAILURE);
  30.    return(filespec);
  31. }
  32.  
  33. Text[0] = "";  // Text is an array of s text strings; one for each file line
  34.  
  35. ReadFile(FileSpec)   // read FileSpec file into global data.  exit() if error.
  36. {
  37.    // Open the file, in text mode, for reading into Text.
  38.    fp = fopen(FileSpec,"rt");
  39.    if ( fp == NULL ) {
  40.       // The file doesn't exist, and so ask user if they want to quit.  If they
  41.       // don't want to create file then simply exit this program.  If they do
  42.       // want to create file then we're done, as Text is already initialized.
  43.       printf("File \"%s\" does not exist.  Create file?  Y/N ",FileSpec);
  44.       do {
  45.          key = toupper(getch()); // make uppercase to compare to Y and N
  46.          if ( key == 'N' )
  47.             exit(EXIT_FAILURE);
  48.       } while( key != 'Y' );     // wasn't Y or N, and so try again
  49.    } else {
  50.       // File opened.  Read each line of file into the next element of Text.
  51.       for ( LineCount = 0; NULL != (line = fgets(fp)); LineCount++ ) {
  52.          // line set to new string for next line in the text file.  Set the next
  53.          // line of Text to this line.
  54.          Text[LineCount] = line;
  55.       }
  56.       fclose(fp); // Should always close a file that has been opened.
  57.    }
  58. }
  59.  
  60. WriteFile(FileSpec)  // write global data to back to FileSpec.  exit() if error.
  61. {
  62.    // Open FileSpec for writing in text mode.  If the file already exists then
  63.    // truncate the file.  If file doesn't exist then create it.
  64.    fp = fopen(FileSpec,"wt");
  65.    if ( fp == NULL ) {
  66.       printf("\aUnable to open \"%s\" for writing.\a\n");
  67.       exit(EXIT_FAILURE);
  68.    }
  69.  
  70.    // write every line of Text into fp
  71.    for ( i = 0; i <= GetArraySpan(Text); i++ )
  72.       fputs( Text[i], fp );
  73.  
  74.    // close fp
  75.    fclose(fp);
  76. }
  77.  
  78.  
  79. // define movement keys - Give values over 0x100 to distinguish from text
  80. #define  MIN_CUR_MOVE   0x101
  81. #define  UP             0x101
  82. #define  DOWN           0x102
  83. #define  LEFT           0x103
  84. #define  RIGHT          0x104
  85. #define  PG_UP          0x105
  86. #define  PG_DN          0x106
  87. #define  HOME           0x107
  88. #define  END            0x108
  89. #define  BK_TAB         0x109
  90. #define  DELETE         0x110
  91.  
  92. GetKeyChar()   // return key from keyboard, ascii for above #defined
  93. {
  94.    if defined(_DOS_) || defined(_DOS32_) || defined(_OS2_) || defined(_NTCON_) || defined(_95CON_) {
  95.       // DOS and OS/2 return 0 on first getch for extended keys
  96.       KeyCode = getch();
  97.       if ( KeyCode == 0 ) {
  98.          // set value for extended key; these value found using KeyCode.cmd
  99.          switch( getch() ) {
  100.             case 0x48:     KeyCode = UP;        break;
  101.             case 0x50:     KeyCode = DOWN;      break;
  102.             case 0x4B:     KeyCode = LEFT;      break;
  103.             case 0x4D:     KeyCode = RIGHT;     break;
  104.             case 0x49:     KeyCode = PG_UP;     break;
  105.             case 0x51:     KeyCode = PG_DN;     break;
  106.             case 0x47:     KeyCode = HOME;      break;
  107.             case 0x4F:     KeyCode = END;       break;
  108.             case 0x0F;     KeyCode = BK_TAB;    break;
  109.             case 0x53;     KeyCode = DELETE;    break;
  110.             default: break;   // return 0, which will do nothing
  111.          }
  112.       }
  113.    } else {
  114.       // Windows version
  115.       KeyCode = getch();
  116.       if ( 0x100 < KeyCode ) {
  117.          switch ( KeyCode ) {
  118.             // special values in the following table come from KeyCode.cmm
  119.             case 0x126:    KeyCode = UP;        break;
  120.             case 0x128:    KeyCode = DOWN;      break;
  121.             case 0x125:    KeyCode = LEFT;      break;
  122.             case 0x127:    KeyCode = RIGHT;     break;
  123.             case 0x121:    KeyCode = PG_UP;     break;
  124.             case 0x122:    KeyCode = PG_DN;     break;
  125.             case 0x124:    KeyCode = HOME;      break;
  126.             case 0x123:    KeyCode = END;       break;
  127.             case 0x109;    KeyCode = BK_TAB;    break;
  128.             case 0x12E;    KeyCode = DELETE;    break;
  129.             default:       KeyCode = 0;         break;
  130.          }
  131.       }
  132.    }
  133.    return(KeyCode);
  134. }
  135.  
  136.  
  137. Edit()   // Edit file.  This is were the hard work happens.  exit() if error.
  138. {        // Return FALSE if no editing was done, else return TRUE.
  139.    LineCount = 1 + GetArraySpan(Text); // how many lines in file
  140.  
  141.    // Initialize screen: get its dimensions, and cursor location.
  142.    ScreenClear();
  143.    ScreenDimension = ScreenSize();
  144.    CursorCol = CursorRow = 0; // initialize cursor position
  145.  
  146.    // Starting at row 0, draw all lines on screen. Initialize Start as structure
  147.    // for upper-left visible portion of file. Then draw the file.
  148.    Start.Row = Start.Col = 0;
  149.    DrawVisibleTextLines( Start, ScreenDimension, LineCount );
  150.    DrawnStart = Start;  // remember which lines were drawn
  151.    CursorStatus(CursorRow,CursorCol,Start,ScreenDimension);
  152.  
  153.    // FileWasEdited is boolean to say if changes made
  154.    FileWasEdited = FALSE;
  155.  
  156.    // Stay here getting all keyboard input until escape is pressed
  157.    #define  ESCAPE_KEY  '\033'
  158.    while ( (key = GetKeyChar()) != ESCAPE_KEY ) {
  159.  
  160.       // special keyboard codes are returned if getch() first
  161.       switch( key ) {
  162.          case UP:    CursorRow--;   break;
  163.          case DOWN:  CursorRow++;   break;
  164.          case LEFT:  CursorCol--;   break;
  165.          case RIGHT: CursorCol++;   break;
  166.          case HOME:  CursorCol = 0; break;
  167.          case END:
  168.             // go to end of visible line, but not including newline
  169.             CursorCol = strlen(Text[CursorRow]);
  170.             if ( 0 < CursorCol  &&  Text[CursorRow][CursorCol-1] == '\n' )
  171.                CursorCol--;
  172.             break;
  173.          case PG_UP:
  174.             CursorRow -= (ScreenDimension.row - 1);
  175.             Start.Row -= (ScreenDimension.row - 1);
  176.             break;
  177.          case PG_DN:
  178.             CursorRow += (ScreenDimension.row - 1);
  179.             Start.Row += (ScreenDimension.row - 1);
  180.             break;
  181.  
  182.          #define  TABSIZE  8
  183.          case '\t':
  184.             CursorCol += TABSIZE;
  185.             CursorCol -= CursorCol % TABSIZE;
  186.             break;
  187.          case BK_TAB:
  188.             CursorCol -= TABSIZE;
  189.             CursorCol -= CursorCol % TABSIZE;
  190.             break;
  191.  
  192.          #define BACKSPACE '\010'
  193.          case BACKSPACE:
  194.             // Back space is just like deleting from one column to the left,
  195.             // and so check that we're not on the first column and then move
  196.             // left a column and let control fall to DELETE
  197.             if ( --CursorCol < 0 )    {
  198.                // backspace from beginning of line; move to end of previous line
  199.                if ( CursorRow == 0 ) {
  200.                   // cannot backup to earlier row, so do nothing
  201.                   CursorCol = 0;
  202.                   break;
  203.                }
  204.                CursorCol = strlen(Text[--CursorRow]) - 1;
  205.             }
  206.          case DELETE:
  207.             if ( DeleteCharacterAtCursor(CursorRow,CursorCol,LineCount) ) {
  208.                FileWasEdited = TRUE;
  209.                DrawnStart.row = -1; // force screen redraw
  210.             }
  211.             break;
  212.  
  213.          case '\r':
  214.             // Add a newline at the current position
  215.             InsertAsciiCharacter('\n',Text[CursorRow],CursorCol);
  216.             FileWasEdited = TRUE;
  217.  
  218.             // a line must be opened up in Text, and all the data moved
  219.             for( i = LineCount++; CursorRow + 1 < i; i-- )
  220.                strcpy( Text[i], Text[i-1] );
  221.  
  222.             // move text from after cursor to next line, and end this line
  223.             strcpy(Text[CursorRow+1],Text[CursorRow] + CursorCol + 1);
  224.             Text[CursorRow][CursorCol + 1] = 0;
  225.  
  226.             // finally, move cursor to beginning of next line, and redraw screen
  227.             CursorRow++, CursorCol = 0;
  228.             DrawnStart.row = -1; // force screen redraw
  229.  
  230.             break;
  231.  
  232.          default:
  233.             if ( isprint(key) ) {
  234.                InsertAsciiCharacter(key,Text[CursorRow],CursorCol++);
  235.                FileWasEdited = TRUE;
  236.                // redraw this row
  237.                ScreenCursor(0,CursorRow - Start.row);
  238.                printf("%.*s",ScreenDimension.col,Text[CursorRow] + Start.col);
  239.             } else {
  240.                // the key that was pressed was not handled.  Beep at the user as a
  241.                // warning, but otherwise alter nothing.
  242.                putchar('\a');
  243.             }
  244.             break;
  245.       }
  246.  
  247.       // Check that cursor position has not gone out of range
  248.       if ( CursorRow < 0 ) CursorRow = 0;
  249.       if ( CursorCol < 0 ) CursorCol = 0;
  250.       if ( LineCount <= CursorRow ) CursorRow = LineCount - 1;
  251.  
  252.       // Check that Start.Row has not gone out of range
  253.       MaxStartRow = LineCount - (ScreenDimension.row - 1)
  254.       if ( MaxStartRow < Start.Row )
  255.          Start.Row = MaxStartRow;
  256.       if ( Start.Row < 0 ) Start.Row = 0;
  257.  
  258.       // If cursor does not now fit on visible screen, then move
  259.       // screen so that cursor does fit on it.
  260.       while( CursorRow < Start.Row ) Start.Row--;
  261.       while( CursorCol < Start.Col ) Start.Col--;
  262.       while( Start.Row + ScreenDimension.Row - 1 <= CursorRow ) Start.Row++;
  263.       while( Start.Col + ScreenDimension.Col <= CursorCol ) Start.Col++;
  264.  
  265.       // if screen must be redrawn, then do so now
  266.       if ( DrawnStart != Start ) {
  267.          ScreenClear();
  268.          DrawVisibleTextLines( Start, ScreenDimension, LineCount );
  269.          DrawnStart = Start;
  270.       }
  271.  
  272.       // key was processed, so redisplay screen state
  273.       CursorStatus(CursorRow,CursorCol,Start,ScreenDimension);
  274.    }
  275.  
  276.    // Return TRUE if file was edited, else false
  277.    ScreenClear();
  278.    return(FileWasEdited);
  279. }
  280.  
  281.  
  282. InsertAsciiCharacter(c,str,offset)    // insert c in str at offset
  283. {
  284.    // The newline at the end of the string can be a problem later, so for now
  285.    // temporarily remove the newline then we'll put it back in when we're done.
  286.    len = strlen(str);
  287.    AddNewLine = ( len != 0  &&  str[len-1] == '\n' );
  288.    if ( AddNewLine )
  289.       str[--len] = 0;
  290.  
  291.    // If the current cursor position is longer than the line, then add spaces.
  292.    while( len < offset )
  293.       str[len++] = ' ';
  294.  
  295.    // If this character won't be at end of the string, then move all characters
  296.    // from here to the end of the string one space forward.  This may be done
  297.    // simply with a strcpy because Cmm ensures that overwriting is safe.
  298.    if ( offset < len ) {
  299.       strcpy(str + offset + 1,str + offset);
  300.       len++;
  301.    }
  302.  
  303.    // At last, put the character in the string
  304.    str[offset] = c;
  305.  
  306.    if ( AddNewLine ) // put the newline character back into the string
  307.       strcat(str,"\n");
  308. }
  309.  
  310.  
  311. DeleteCharacterAtCursor(row,col,TotalLineCount)
  312.    // delete character at cursor position.  Return TRUE if a character was
  313.    // delete else return FALSE.  This function may alter TotalLineCount.
  314. {
  315.    str = Text[row];
  316.    len = strlen(str);
  317.    if ( row < (TotalLineCount - 1) )
  318.       len--;
  319.  
  320.    if ( col < len ) {
  321.       // This is the simple case.  copy string to this location from next char
  322.       strcpy(str + col,str + col + 1);
  323.    } else {
  324.       // deleting from the end of the string or from beyond.  Must bring in
  325.       // from next row.
  326.       if ( row == (TotalLineCount - 1) )
  327.          return(FALSE); // no following text to copy to here
  328.  
  329.       // fill in spaces from end of text to this location
  330.       for( i = len; i <= col; i++ )
  331.          str[i] = ' ';
  332.  
  333.       // copy from next string to the end of this string
  334.       strcpy( str + col, Text[row+1] );
  335.       // One newline has been removed, and so there are now one fewer lines
  336.       // in the file.  Copy all of rows down one element in the Text array.
  337.       TotalLineCount--;
  338.       for ( i = row + 1; i < TotalLineCount; i++ )
  339.          Text[i] = Text[i+1];
  340.       SetArraySpan(Text,TotalLineCount - 1);
  341.    }
  342.    return(TRUE);
  343. }
  344.  
  345.  
  346. DrawVisibleTextLines(StartPosition,ScreenSize,TextLineCount)
  347.    // display visible portion of file. StartPosition is initial .row and .col
  348.    // that is visible.  ScreenSize show .col and .row width and height of screen.
  349. {
  350.    // verify that the screen position is not invalid; negative would be bad.
  351.    assert( 0 <= StartPosition.row  &&  0 <= StartPosition.col );
  352.    // Also, this function assumes that at least some lines are visible at the
  353.    // top of the screen, and so verify that this is true.
  354.    assert( StartPosition.row < TextLineCount );
  355.  
  356.    // draw all visible lines from Text; leave bottom line free for messages.
  357.    for ( row = 0; row < (ScreenSize.row-1); row++ ) {
  358.       Line = Text[StartPosition.row + row];
  359.       // draw this line on the screen from StartPosition.row, remembering
  360.       // to clip at the right edge of screen if the line is too long
  361.       LineLen = strlen(Line) - StartPosition.col;
  362.       if ( 0 < LineLen ) { // only print if characters to print
  363.          ScreenCursor(0,row);
  364.          printf("%.*s",ScreenSize.col,Line + StartPosition.col);
  365.       }
  366.    }
  367. }
  368.  
  369.  
  370. CursorStatus(CRow,CCol,StartPosition,ScreenSize)
  371. {
  372.    // show current file cursor position; based at 1
  373.    ScreenCursor(5,ScreenSize.row-1);
  374.    printf("Status: row %-3d col %-3d",CRow + 1,CCol + 1);
  375.  
  376.    // put cursor at correct position on screen
  377.    ScreenCursor(CCol - StartPosition.Col,CRow - StartPosition.Row);
  378. }
  379.  
  380.  
  381. //DebugPrintf(FormatString,arg1,arg2,arg3/*etc...*/)
  382. //   // printf() line on bottom of string, then get key
  383. //{
  384. //   // format message into a string
  385. //   va_start(VaList,FormatString);
  386. //   vsprintf(msg,FormatString,VaList);
  387. //   va_end(VaList);
  388. //
  389. //   // Save the cursor position, display this message on the bottom of the screen,
  390. //   // get a key, and then return.  This is very non-intrusive.
  391. //   SaveCursor = ScreenCursor();
  392. //   ClearBottomLine();
  393. //   msg[ScreenSize().Col - 1] = '\0';   // don't let line get too long
  394. //   while ( NULL != (nl = strchr(msg,'\n')) ) // change newlines to spaces
  395. //      nl[0] = '_';
  396. //   ScreenCursor(0,ScreenSize().Row-1);
  397. //   printf("%s",msg);
  398. //   GetKeyChar();
  399. //   ClearBottomLine();
  400. //   ScreenCursor(SaveCursor.Col,SaveCursor.Row);
  401. //}
  402. //
  403. //ClearBottomLine() // called by DebugPrintf() to clear last lie
  404. //{
  405. //   ScreenCursor(0,ScreenSize().Row - 1);
  406. //   printf("%*s",ScreenSize().Col-1,"");
  407. //}
  408.  
  409.