[Back to TEXTEDIT SWAG index] [Back to Main SWAG index] [Original]

    Unit MPEditor;  { VERSION 1.0  May 14, 1994 }
    {  A general full-screen text editor that should compile under varying
       versions of Borland's Turbo Pascal, relying solely on the CRT
       (and System) units.

     (c) 1994, Michael Perry / Progressive Computer Services, Inc.
                All rights reserved worldwide.

      LICENSE:  This code is NOT Public Domain; It can be freely
                distributed as long as it is not modified in any
                form.  If you incorporate any part of the code into
                a program, please credit Mike Perry in the program;
                No other remuneration or consideration necessary,
                although it would be nice to get a "thank you" postcard.
                If you have suggestions or enhancements (please do)
                send them to me and I'll make sure that you get
                credit and that this unit will be continually updated.

        Author: Mike Perry / Progressive Computer Services, Inc.
                PO Box 7638, Metairie, LA 70010 USA
                BBS: (504) 835-0085  FAX: (504) 834-2160
                Fidonet: 1:396/21  Cserve: 71127,2105  AOL: Arpegi0
                Internet: 71127.2105@compuserve.com    PAN: MIKEPERRY
                Unison: MIKEP

      USAGE:    MPEDITOR manipulates text in an allocated area of memory
                specified by the TXT^ pointer.  To incorporate this unit
                into your program, simply copy your text into the array of
                byte specified by TXT^ and call the appropriate procedures.
                Supported commands:
                  Arrow keys for cursor movement, INS, DEL, PgUp, PgDn
                  CTRL-(left/right) moves cursor to next word;
                  ^Y = delete line,
                  ^B = reformat text from cursor to end of paragraph
                  Ctrl-PgUp = go to top of file
                  Ctrl-PgDn = go to end of file
      NOTES:
      % Statements in the unit which are commented out pertain to features
        and options which are either for demonstration purposes or have
        yet to be implemented; look for updates soon.
      % This editor unit assumes that linefeed characters (#10) are
        stripped from the input prior to editing.  If you import
        data to be edited, make sure you convert CR+LF to CR!!
      % The following routines are critical to performance and if
        implemented in ASM would improve efficiency of the unit:
        GET_LINE_DATA, SET_POSITION, SET_CURSOR
        If you can help, contact me at the addresses at the top.
    }
    {$R-}   { range checking off - causes error when referencing buffer array }

    { If you want to implement your own screen i/o routines, look for the
      USEQWIK directive which identifies areas in the program where you can
      make modifications to suit your objective }
    {-$DEFINE USEQWIK} { implements FAST direct screen writing / requires }
                       { Eagle Performance Software's QWIK screen units }
                       { available as Shareware }
    INTERFACE

    {$IFDEF USEQWIK}
      USES     CRT,Qwik;
    {$ELSE}
      USES     CRT;
    {$ENDIF}

    CONST
      TEXTMAX:WORD= 40000; { Maximum size of text editing buffer in bytes }
      CR       = 13;       { ordinal value used to indicate a new line }
      SPACE    = 32;       { ordinal value used to indicate a space }
      REFORMAT = 2;        { ordinal value to initiate reformat command / CTRL-B }
      TABSIZE  = 5;        { tab stop every five characters }

    TYPE
      TXT_TYPE  = ARRAY [1..1] OF BYTE;

    VAR
      TXT       : ^TXT_TYPE;    { TEXT BUFFER, pointer to memory block }

      { operational status variables ------(set during operation)------------- }
      TEXTSIZE  : LONGINT;      { size of txt array in use / max current index }
      POSITION  : LONGINT;      { index of current (cursor) position in TXT array }
      WINTOP    : LONGINT;      { index position in buffer of top of text window }
      XLINE     : BOOLEAN;      { TRUE if cursor position outside of data area }
      INSERTON  : BOOLEAN;      { TRUE indicates insert mode on }
      VROW,
      VCOLUMN   : BYTE;         { VIRTUAL ROW/COLUMN position within editing area }
      WIDTH,
      HEIGHT    : BYTE;         { width and height of current editing window }
      SCRBUMP   : BYTE;         { chars to bump over display / not to exceed
    WIDTH! }
      OFFSET    : LONGINT;      { screen display offset, column position to begin
    displaying }
    {  MARKSTART,
       MARKEND  : LONGINT;      { start/end index of marked text / NOT IMPLEMENTED
    }
      { operational configuration vars ----(set by user)----------------------- }
      MARKATTR,                 { marked text attribute }
      BACKATTR,                 { background text window attribute }
      NORMATTR,                 { attribute values for normal & hilighted text }
      BORDATTR  : WORD;         { attribute value for border }
      R1,C1,                    { row,column of upper left coord of edit window }
      R2,C2     : BYTE;         { row,column of lower right coord on edit win }
      MAXCOLUMN : LONGINT;      { maximum line length/column size, 0=No Limit }
    {  MAXROW    : LONGINT;      { maximum number of lines allowed, 0=No Limit }

      { prototypes ------------------------------------------------------------ }

       {FUNCTION GETINPUT(VAR FUNCTIONKEY:BOOLEAN):BYTE; NEAR }
       {FUNCTION SPACES(COUNT:BYTE):STRING;              NEAR }
       {PROCEDURE CLEAR_TXT;                             NEAR }
      FUNCTION GETTEXTEND:LONGINT;
      FUNCTION INITIALIZE_TXT(VAR PTXT:POINTER;SIZE:LONGINT):BOOLEAN;
      PROCEDURE DISPOSE_TXT(VAR PTXT:POINTER;SIZE:LONGINT);
       {PROCEDURE DRAW_BOX(R1,C1,R2,C2:BYTE);            NEAR }
       {PROCEDURE INITIALIZE_WINDOW(R1,C1,R2,C2:BYTE);   NEAR }
       {PROCEDURE CLEAR_WINDOW;                          NEAR }
       {PROCEDURE CLEAR_LINE;                            NEAR }
       {PROCEDURE BUMP_TXT(COUNT:LONGINT);               NEAR }
       {PROCEDURE DEL_CHARS(COUNT:LONGINT);              NEAR }
       {PROCEDURE GET_LINE_DATA(POS:LONGINT; VAR STARTINDEX,ENDINDEX,COL:LONGINT);}
       {PROCEDURE STUFF_TXT(s:string);                   NEAR }
      FUNCTION WINBOTTOM:LONGINT;
       {PROCEDURE SHOW_LINE;                             NEAR }
      PROCEDURE SHOW_TXT;
      PROCEDURE DISPLAY_TXT(VAR PT:POINTER);
      PROCEDURE SCROLLUP(LINES:BYTE);
      PROCEDURE SCROLLDOWN(LINES:BYTE);
       {PROCEDURE SET_POSITION;                          NEAR }
       {PROCEDURE SET_CURSOR;                            NEAR }
       {PROCEDURE WORD_WRAP(startpoint,endpoint,length:LONGINT);}
       {FUNCTION LINEUP:LONGINT;                         NEAR }
       {FUNCTION LINEDOWN:LONGINT;                       NEAR }
      PROCEDURE READ_TXT(VAR PT:POINTER;FILENAME:STRING;VAR TEXTSIZE:LONGINT);
       {PROCEDURE DIRECTION(C:BYTE);                     NEAR }
       {FUNCTION PARSE_INPUT:BYTE;                       NEAR }
      PROCEDURE SETUP_TEXT_SETTINGS(Row1,Column1,Row2,Column2:BYTE;DRAWBOX:BOOLEAN) ;
      PROCEDURE EDIT(PT:POINTER;VAR RETURNCODE:BYTE);

    IMPLEMENTATION

    (***************************************************************************)
    FUNCTION GETINPUT(VAR FUNCTIONKEY:BOOLEAN):BYTE;
    { read keyboard and return character/function key }
    VAR CH: CHAR;
    BEGIN
      CH:=ReadKey;
      IF (CH=#0) THEN
        BEGIN
          CH:=ReadKey;
          FUNCTIONKEY:=TRUE;
        END
      ELSE FUNCTIONKEY:=FALSE;
      GETINPUT:=ORD(CH);
    END;
    (***************************************************************************)
    FUNCTION SPACES(COUNT:BYTE):STRING;
    { returns COUNT number of spaces }
    { NOTE: Unpredictable results if count exceeds 255!! }
    var temp:string;
    BEGIN
      TEMP:='';
      WHILE COUNT>0 DO BEGIN
        TEMP:=TEMP+#32;
        DEC(COUNT);
      END;
      SPACES:=TEMP;
    END;
    (***************************************************************************)
    PROCEDURE CLEAR_TXT;
    { zeros the text array & associated values }
    BEGIN
      fillchar(txt^,TEXTMAX,0);
      textsize:=0;
      position:=1;
    END;
    (***************************************************************************)
    FUNCTION GETTEXTEND:LONGINT;
    { find the end of text by looking for null character
      %% This is a technique that I use to identify the actual size of
         a text buffer; if you're reading data from a disk structure,
         unless you save the actual size, you'll need to determine it.
         I make sure any unused area in my text buffer is padded with
         nuls }
    var I:longint;
    BEGIN
      FOR I:=1 TO TEXTMAX DO
        IF TXT^[I]=0 THEN BEGIN
          GETTEXTEND:=I-1;
          EXIT;
        END;
      GETTEXTEND:=TEXTSIZE;
    END;
    (***************************************************************************)
    FUNCTION INITIALIZE_TXT(VAR PTXT:POINTER;SIZE:LONGINT):BOOLEAN;
    { create/allocate memory for text buffer }
    BEGIN
      if MaxAvail < SIZE then
        INITIALIZE_TXT:=FALSE     { not enough available memory }
      else BEGIN
        GETMEM(PTXT,SIZE);
        INITIALIZE_TXT:=TRUE;
        TEXTMAX:=SIZE;            { set max size of text }
        TXT:=PTXT;                { establish pointer for routines }
        CLEAR_TXT;                { zero text }
      END;
    END;

    (***************************************************************************)
    PROCEDURE DISPOSE_TXT(VAR PTXT:POINTER;SIZE:LONGINT);
    { disposes text buffer }
    BEGIN
      FREEMEM(PTXT,SIZE);
    END;
    (***************************************************************************)
    PROCEDURE DRAW_BOX(R1,C1,R2,C2:BYTE);
    { surrounds the specified area with a box }
    { NOTE! There are no checks to make sure the box area isn't off the screen!
            and this (and other) routines must be slightly modified if you want
            the text area to fill up the entire screen due to anomolies with
            TP's WINDOW function }
    var I:byte;
    BEGIN
    {$IFDEF USEQWIK}
      { draw horizontal line }
      FOR I:=(C1-1) TO (C2+1) DO BEGIN
        qwrite(R1-1,I,BORDATTR,'Ä');
        qwrite(R2+1,I,BORDATTR,'Ä');
      END;
      { draw vertical line }
      FOR I:=(R1-1) TO (R2+1) DO BEGIN
        QWRITE(I,C1-1,BORDATTR,'³');
        QWRITE(I,C2+1,BORDATTR,'³');
      END;
      QWRITE(R1-1,C1-1,BORDATTR,'Ú');
      QWRITE(R2+1,C1-1,BORDATTR,'À');
      QWRITE(R1-1,C2+1,BORDATTR,'¿');
      QWRITE(R2+1,C2+1,BORDATTR,'Ù');
    {$ELSE}
      TEXTATTR:=BORDATTR;
      { draw horizontal line }
      FOR I:=(C1-1) TO (C2+1) DO BEGIN
        GOTOXY(I,R1-1); WRITE('Ä');
        GOTOXY(I,R2+1); WRITE('Ä');
      END;
      { draw vertical line }
      FOR I:=(R1-1) TO (R2+1) DO BEGIN
        GOTOXY(C1-1,I); WRITE('³');
        GOTOXY(C2+1,I); WRITE('³');
      END;
      GOTOXY(c1-1,r1-1); WRITE('Ú');
      GOTOXY(c1-1,r2+1); WRITE('À');
      GOTOXY(c2+1,r1-1); WRITE('¿');
      GOTOXY(c2+1,r2+1); WRITE('Ù');
      TEXTATTR:=NORMATTR;
    {$ENDIF}
    END;
    (***************************************************************************)
    PROCEDURE INITIALIZE_WINDOW(R1,C1,R2,C2:BYTE);
    { defines text window and clears screen }
    BEGIN
    {$IFNDEF USEQWIK}
      WINDOW(C1,R1,C2+1,R2);
    {$ENDIF}
    END;
    (***************************************************************************)
    PROCEDURE CLEAR_WINDOW;
    { clears the current text window }
    BEGIN
    {$IFDEF USEQWIK}
      QFILL(r1,c1,HEIGHT,WIDTH,BACKATTR,#32);
    {$ELSE}
      textattr:=backattr;
    { since TP forces an unwanted scroll when writing to the lower right corner
      of a window, we create a window 1-column larger and init a smaller one when
      we want to clear the screen, there is an extra column defined in the
      working window so that unwanted scrolls are not accomplished }
      WINDOW(C1,R1,C2,R2);
      CLRSCR;
      WINDOW(C1,R1,C2+1,R2);
    {$ENDIF}
    END;
    (***************************************************************************)
    PROCEDURE CLEAR_LINE;
    { clears the current line }
    BEGIN
    {$IFDEF USEQWIK}
      QFILL(R1+VROW-1,C1,1,WIDTH,BACKATTR,#32);
      { FYI, the arguments for QFILL are:
         QFILL(row,column,rows,columns,attribute,char);  }
    {$ELSE}
      TEXTATTR:=BACKATTR;
      WINDOW(C1,R1,C2,R2);
      gotoxy(1,vrow); clreol;
      WINDOW(C1,R1,C2+1,R2);
    {$ENDIF}
    END;
    (***************************************************************************)
    PROCEDURE BUMP_TXT(COUNT:LONGINT);
    { moves text at POSITION index over COUNT bytes, for inserting data }
    { this procedure does NOT change, position or cursor indexes }
    VAR I:LONGINT;
    BEGIN
      inc(textsize,COUNT);
      for i:=textsize downto position do { move everything forward 1 }
        txt^[i]:=txt^[i-COUNT];
    END;
    (***************************************************************************)
    PROCEDURE DEL_CHARS(COUNT:LONGINT);
    { erase COUNT chars at position, shorten text array }
    var I:longint;
    BEGIN
      FOR I:=POSITION TO (TEXTSIZE-1) DO
        TXT^[I]:=TXT^[I+COUNT];
      DEC(TEXTSIZE,COUNT);
    END;

    (***************************************************************************)
    PROCEDURE GET_LINE_DATA(POS:LONGINT; VAR STARTINDEX,ENDINDEX,COL:LONGINT);
    { given the array index (position), calculate the start & ending index of the
      current line, also returning VIRTUAL column position on the current line

      procedure returns:  1,1,1 if at the top of the file;
      procedure returns:  textsize,textsize,1 at bottom of file

      This is one procedure, that if implemented in ASM would improve the
      overall performance of this unit (I'm open to suggestions).
    }
    VAR i:longint;
    BEGIN
      startindex:=0; endindex:=0; col:=0;
      if pos<1 then exit;               { invalid position }
      if pos>textsize then begin        { at end of text }
        endindex:=textsize+1;
      end else begin
        for i:=pos to textsize do       { find end of line index }
          if txt^[i]=CR then begin      { found CR at end of line }
            endindex:=i;
            i:=textsize;                { force end of loop }
          end;
        if endindex=0 then endindex:=textsize+1;  { last line obviously }
      end;
                                        { find beginning of line index }
      for i:=(endindex-1) downto 1 do   { FOR checks=endvalue, if not increments! }
        if txt^[i]=CR then begin        { found CR at beginning of line }
          startindex:=i+1;              { index of previous CR+1 }
          i:=1;                         { force end of loop }
        end;
      if startindex=0 then startindex:=1;  { begin of line is top of text }

      col:=pos-startindex+1;            { calculate VIRTUAL column position }
    END;
    (***************************************************************************)
    PROCEDURE STUFF_TXT(s:string);
    { add string/char to txt array, bump POSITION up one }
    VAR j,b1,e1,col1:longint; t:byte;
    BEGIN
      t:=length(s);
      if ((inserton) or (txt^[position]=CR)) and ((textsize+t)>textmax) then
    begin  { no more room }
        write(#7); exit;
      end;

    { if xline, and text added, make sure to bump position to the end of line }
      IF (XLINE) THEN BEGIN { pad the short line with spaces to the position }
        GET_LINE_DATA(POSITION, b1,e1,col1);
        j:=(offset+vcolumn)-(e1-b1+1);  { number of spaces to pad }
        IF ((textsize+t+j)>textmax) then BEGIN { check for avail space }
          write(#7); exit;
        END;
        bump_txt(j);
        for b1:=position to (position+j-1) do
          txt^[b1]:=SPACE;
        XLINE:=FALSE;
        POSITION:=POSITION+J;
      END;

      if (inserton) OR (txt^[position]=CR) then
        if (position<=textsize) then begin { insert }
          bump_txt(t);
          FOR J:=1 TO T DO BEGIN
            txt^[position]:=ORD(S[J]);  { add/replace character }
            INC(POSITION);              { move pointer up one }
            INC(VCOLUMN);
          END;
          exit;
        end else begin                  { append / position > textsize }
          FOR J:=1 TO T DO BEGIN
            inc(textsize);
            txt^[textsize]:=ORD(S[J]);
            INC(VCOLUMN);
          END;
          position:=textsize+1;         { position pointer at end of text }
        end
      else if position<=textsize then begin { overwrite }
        FOR J:=1 TO T DO BEGIN
          txt^[position]:=ORD(S[J]);        { overwrite current position }
          inc(position);                    { move pointer one over }
        END;
        INC(VCOLUMN,T);
      end else begin { append }
        if ((textsize+T)>=textmax) then begin  { can't append if buffer full }
          write(#7);
          exit;
        end;
        FOR J:=1 TO T DO BEGIN
          inc(textsize);
          txt^[textsize]:=ORD(S[J]);
        END;
        position:=textsize+1;
        INC(VCOLUMN,T);
      end;
    END;
    (***************************************************************************)
    FUNCTION WINBOTTOM:LONGINT;
    { returns the text array index value of the last character in the text window }
    var i:longint; linecount:byte;
    BEGIN
      LINECOUNT:=0;
      FOR I:=WINTOP TO TEXTSIZE DO BEGIN
        IF TXT^[I]=CR THEN INC(LINECOUNT);
        IF LINECOUNT=HEIGHT THEN BEGIN { found last line in text window }
          WINBOTTOM:=I;
          EXIT;
        END;
      END; { for loop thru text }
      WINBOTTOM:=TEXTSIZE; { end before last text line found, so text ends in
    window }
    END;

    (***************************************************************************)
    PROCEDURE SHOW_LINE;
    { rewrites the current line to the window }
    VAR I,b1,e1,col1:longint; S:STRING;
    BEGIN
      GET_LINE_DATA(position, b1,e1,col1);
      IF (B1>TEXTSIZE) THEN BEGIN CLEAR_LINE; EXIT; END; { nothing there }
      col1:=(offset+b1+width-1); if col1>textsize then col1:=textsize; {eol pos}
      S:='';
      for i:=(offset+b1) to col1 {(offset+b1+width-1)} do begin
        if txt^[i]=CR then i:=col1 {(offset+b1+width-1)} { force end }
        else begin
          s:=s+chr(txt^[i]);
        end;
      end;
      CLEAR_LINE;
    {$ifdef USEQWIK}
      QWRITE(R1+VROW-1,C1,NORMATTR,S);
      GOTORC(R1+VROW-1,C1+VCOLUMN-1);
    {$ELSE}
      TEXTATTR:=NORMATTR;
      GOTOXY(1,VROW);  WRITE(S);
      GOTOXY(VCOLUMN,VROW);
    {$ENDIF}
    END;
    (***************************************************************************)
    PROCEDURE SHOW_TXT;
    { display text to screen area
      sets VROW and VCOLUMN to match displayed area where position
      is and moves cursor to that location }
    var I,R,C,CWIDTH:LONGINT;
    BEGIN
      R:=1; C:=1;  { set start row/column }
      CWIDTH:=0;
      CLEAR_WINDOW;

      { % hide cursor }
      FOR I:=WINTOP TO TEXTSIZE DO BEGIN
        IF (R>HEIGHT) OR (I>TEXTSIZE) THEN begin { check for outside vertical
    boundaries OR end }
          {$IFDEF USEQWIK}
            GOTORC(R1+VROW-1,C1+VCOLUMN-1);
          {$ELSE}
            GOTOXY(VCOLUMN,VROW);
          {$ENDIF}
          EXIT;  { done, filled window }
        END;
        IF (TXT^[I]=CR) THEN BEGIN   { -------------- check for carriage return }
          INC(R);   { bump row down }
          CWIDTH:=0;
          C:=1;
    { % IF TXT^[I+1]=10 THEN INC(I);  { check for additional LF / skip over }

        END ELSE BEGIN   { ----------------------------------- printable char }
          INC(CWIDTH);
          IF CWIDTH>OFFSET THEN       { if screen offset in effect }
            IF C<= WIDTH THEN BEGIN    { if line not off the screen }
    {$IFDEF USEQWIK}
              QWRITE(R1+R-1,C1+C-1,NORMATTR,CHR(TXT^[I]));
    {$ELSE}
              GOTOXY(C,R); textattr:=normattr;
              WRITE(CHR(TXT^[I]));
    {$ENDIF}
              INC(C);
            END else INC(C);  { increment column counter anyway even though not
    printed }
         END;
         IF I>TEXTSIZE THEN I:=TEXTSIZE;  { if bumped past, set to end loop }
      END;  { FOR loop }
    {$IFDEF USEQWIK}
      GOTORC(R1+VROW-1,C1+VCOLUMN-1);
    {$ELSE}
      GOTOXY(VCOLUMN,VROW);
    {$ENDIF}
      EXIT;
    END;
    (***************************************************************************)
    PROCEDURE DISPLAY_TXT(VAR PT:POINTER);
    { display text, specified by pointer
      can be used by an external viewing routine }
    VAR TEMP:POINTER;
    BEGIN
      TEMP:=addr(TXT);
      TXT:=PT;
      SHOW_TXT;
      TXT:=TEMP;
    END;
    (***************************************************************************)
    PROCEDURE SCROLLUP(LINES:BYTE);
    { scroll screen up x lines; does not change cursor or text pointer }
    var i,b1,e1,col1,LINECOUNT:longint;
    BEGIN
      LINECOUNT:=0;
      FOR I:=WINTOP DOWNTO 1 DO BEGIN
        if txt^[i]=CR then begin  { found end of prev line }
           INC(LINECOUNT);
           IF (LINECOUNT=LINES) THEN BEGIN
             GET_LINE_DATA(I, b1,e1,col1);
             WINTOP:=B1;
             EXIT;
           END;
        END; { cr found }
      END; { for loop }
      WINTOP:=1;
    END;
    (***************************************************************************)
    PROCEDURE SCROLLDOWN(LINES:BYTE);
    { scroll screen down x lines, does not change cursor or text pointer }
    var i,b1,e1,col1,LINECOUNT:longint;
    BEGIN
      linecount:=0;
      for i:=WINTOP to textsize do begin
        if txt^[i]=CR then begin
          { i=index pos of CR of next line }
          inc(linecount);
          if (linecount=lines) {or (i=textsize)} then begin
            WINTOP:=i+1;
            SHOW_TXT;
            EXIT;
          end;  { found specified number of lines }
        end; { if CR found }
      end; { loop thru text }
    END;

    (***************************************************************************)
    PROCEDURE SET_POSITION;
    { sets POSITION index based on cursor location and WINTOP index }
    var I,b1,e1,col1,R,LINECOUNT:longint;
    BEGIN
      R:=1; LINECOUNT:=1;
      FOR I:=WINTOP TO TEXTSIZE DO BEGIN
        if (VROW=R) then begin  { line cursor on found }
          GET_LINE_DATA(I, b1,e1,col1);
          IF ((E1-B1+1) < (VCOLUMN+OFFSET)) THEN BEGIN
            POSITION:=E1;
            XLINE:=TRUE;
          END ELSE BEGIN
            POSITION:=B1+VCOLUMN-1;
            XLINE:=FALSE;
          END;
          EXIT;
        end; { cursor line found }
        if txt^[i]=CR then begin
          INC(R);
          INC(LINECOUNT);
        end;
      END; { for loop thru text }
      POSITION:=TEXTSIZE+1;  { assuming cursor at end of text then }
      VROW:=LINECOUNT;  { not sure if this should be here, but takes care
                          of case where scrolling causes most of screen to
                          be past the end of file (where the cursor pos is)
                        }
      GET_LINE_DATA(POSITION, b1,e1,col1);
      IF (VCOLUMN>COL1) THEN XLINE:=TRUE ELSE BEGIN
        POSITION:=B1+VCOLUMN+OFFSET-1;
        XLINE:=FALSE;
      END;
    END;
    (***************************************************************************)
    PROCEDURE SET_CURSOR;
    { finds position and sets VCOLUMN & VROW and OFFSET appropriately in window }
    { ALWAYS sets XLINE to FALSE }
    var i,b1,e1,col1,R,C:longint; screenchanged:BOOLEAN;
    BEGIN
      R:=1; C:=1; SCREENCHANGED:=FALSE; XLINE:=FALSE;
      FOR I:=WINTOP TO TEXTSIZE+1 DO BEGIN
        IF I=POSITION THEN BEGIN   { found it ------------------------ }
          if (c>offset) and (c<=(offset+width)) then begin  { in window }
            dec(c,offset);
          end else BEGIN
            SCREENCHANGED:=TRUE;
            IF (C<=WIDTH) THEN BEGIN
              OFFSET:=0;
            END ELSE BEGIN
              OFFSET:=(C-WIDTH)+SCRBUMP;
              C:=WIDTH-SCRBUMP;
            END;
          END;
          VCOLUMN:=C;
          VROW:=R;
          IF (SCREENCHANGED) THEN SHOW_TXT else
    {$IFDEF USEQWIK}
            GOTORC(R1+VROW-1,C1+VCOLUMN-1);
    {$ELSE}
            GOTOXY(VCOLUMN,VROW);
    {$ENDIF}
          EXIT;
        END; { position found }
      IF TXT^[I]=CR THEN BEGIN
        INC(R); C:=1;
      END ELSE INC(C);
      IF (R>HEIGHT) {OR (R<1)} THEN BEGIN
        GET_LINE_DATA(WINTOP, b1,e1,col1);
        WINTOP:=E1+1;
        R:=HEIGHT;
        SCREENCHANGED:=TRUE;
      END;
     END; { for }
    END;
    (***************************************************************************)
    PROCEDURE WORD_WRAP(startpoint,endpoint,length:LONGINT);
    { word wrap a section of text }
    var ccount,i,spacepos,lastcr:longint; showit:boolean;
    BEGIN
      IF LENGTH=0 THEN EXIT;  { no length specified so get outta here }
      SPACEPOS:=0; SHOWIT:=FALSE; CCOUNT:=0; LASTCR:=-1;
      FOR I:=STARTPOINT TO (ENDPOINT-1) DO BEGIN
        INC(CCOUNT);
        IF TXT^[I]=SPACE THEN SPACEPOS:=I;
        IF TXT^[I]=CR THEN    { end wrap when to CRs follow, otherwise -> space }
          IF LASTCR=(I-1) THEN BEGIN
            TXT^[I-1]:=CR; { restore prev CR }
            SET_CURSOR;
            SHOW_TXT;
            EXIT;
          END ELSE BEGIN
            TXT^[I]:=SPACE;
            SPACEPOS:=I;
            LASTCR:=I;
          END;
        IF (CCOUNT)>LENGTH THEN BEGIN  { past point }
          IF SPACEPOS=0 THEN BEGIN     { force a CR }
            SPACEPOS:=POSITION; { save pos }
            POSITION:=I;
            BUMP_TXT(1);  { insert 1 byte at position }
            INC(ENDPOINT);
            POSITION:=SPACEPOS; { restore pos }
            TXT^[I]:=CR;
            CCOUNT:=0;
          END ELSE BEGIN
            TXT^[SPACEPOS]:=CR; { turn last space into a CR }
            CCOUNT:=I-SPACEPOS; { calc next line len w/wrap }
          END;
          SHOWIT:=TRUE;
        END; { line past length }
      END; { for }
      IF SHOWIT THEN BEGIN
        SET_CURSOR;
        SHOW_TXT;
      END;
    END;

    (***************************************************************************)
    FUNCTION LINEUP:LONGINT;
    { returns new index in file, one line up }
    { MOVES cursor on screen as well }
    var b1,b2,e1,e2, col1,col2,len1,len2:longint;
    BEGIN
      GET_LINE_DATA(position, b1,e1,col1); { get data on current line }
      len1:=e1-b1+1;                       { length of line + CR }
      if b1=1 then BEGIN                   { check for top of text }
        LINEUP:=POSITION; EXIT;
      END;
      GET_LINE_DATA(B1-1,     b2,e2,col2); { get data on previous line }
      len2:=e2-b2+1;
      IF (XLINE) THEN BEGIN
        col2:=b2+vcolumn+offset-1; { in case of move to non-xline, set position }
      END ELSE
        col2:=b2+col1-1;  { index position of one line up, tentative }

      if col2<1 then col2:=1 else { top of file }
        if (col2>e2) then begin   { previous line shorter than current line }
          col2:=e2;               { make one line up, end of previous line }
          XLINE:=TRUE;
        end else begin
          XLINE:=FALSE;
        end;
      LINEUP:=COL2;

      IF (WINTOP>col2) THEN BEGIN  { scroll the screen up }
        WINTOP:=B2;
        SHOW_TXT;
      END ELSE DEC(VROW);  { bump cursor up }
    {$IFDEF USEQWIK}
      GOTORC(R1+VROW-1,C1+VCOLUMN-1);
    {$ELSE}
      GOTOXY(VCOLUMN,VROW);
    {$ENDIF}
    END;
    (***************************************************************************)
    FUNCTION LINEDOWN:LONGINT;
    { returns new index in file, one line down }
    { MOVES cursor on screen as well }
    var b1,b2,e1,e2, col1,col2,len1,len2:longint;
    BEGIN
      GET_LINE_DATA(position, b1,e1,col1); { get data on current line }
      len1:=e1-b1+1;                       { calc length of line incl. CR }
      if e1>=textsize then begin           { can't go down on last line }
        LINEDOWN:=POSITION; EXIT;
      end;
      GET_LINE_DATA(e1+1,     b2,e2,col2); { get data on next line }
      len2:=e2-b2+1;

      IF (XLINE) THEN BEGIN
        col2:=b2+vcolumn+offset-1;   { in case of move to non-xline, set position }
      END ELSE
        col2:=b2+col1-1;   { index position of one line down, tentative }

      if (col2>e2) then begin  { next line position is past end of next line }
          col2:=e2;            { make one line down, end of next line }
          xline:=TRUE;
        end else begin
          xline:=FALSE;
      end;
      LINEDOWN:=COL2;

      IF (VROW=HEIGHT) THEN BEGIN   { down off screen, scroll text up }
        SCROLLDOWN(1);  {WINTOP:=B2;}
      END ELSE INC(VROW);  { bump screen down }
    {$IFDEF USEQWIK}
      GOTORC(R1+VROW-1,C1+VCOLUMN-1);
    {$ELSE}
      GOTOXY(VCOLUMN,VROW);
    {$ENDIF}
    END;
    (***************************************************************************)
    PROCEDURE READ_TXT(VAR PT:POINTER;FILENAME:STRING;VAR TEXTSIZE:LONGINT);
    { reads text from file into buffer, strips LFs }
    { HORRIBLY SLOW, but not intended to be a real part of this unit }
    VAR
        F    :FILE OF BYTE;
        PTXT :^TXT_TYPE;
    BEGIN
      PTXT:=PT;
      ASSIGN(F,FILENAME);
      RESET(F);
      TEXTSIZE:=0;
      IF IORESULT<>0 THEN EXIT;
      IF EOF(F) THEN BEGIN CLOSE(F); EXIT; END;
      WHILE NOT(EOF(F)) DO BEGIN
        INC(TEXTSIZE);
        READ(F,PTXT^[TEXTSIZE]);
        if (Ptxt^[textsize]=10) then begin
          dec(textsize);  { remove LFs }
        end;
        IF TEXTSIZE>=TEXTMAX THEN BEGIN
          CLOSE(F);
          EXIT;
        END;
      END;
      CLOSE(F);
    END;

    (***************************************************************************)
    PROCEDURE DIRECTION(C:BYTE);
    { act on direction keys }
    var b1,e1,col1:longint; T:BYTE;
    BEGIN
          case C of
           72:begin  { up }
               POSITION:=LINEUP;
             end;
           80:begin  { down }
                POSITION:=LINEDOWN;
              end;
           75:begin  { left }
                if position=1 then begin
                  write(#7);
                  exit;
                end;
                if (xline) then begin
                  dec(vcolumn);
                  { check to see if moved onto text }
                  get_line_data(position,b1,e1,col1);
                  if (offset+vcolumn)=(e1-b1+1) then begin
                    xline:=FALSE;
                  end;
                end else begin  { not xline }
                  if txt^[position-1]=CR then begin      { back up one line? }
                    get_line_data(position-1,b1,e1,col1);
                    vcolumn:=col1;
                    if col1>width then begin { left to prev line off screen }
                      offset:=col1-width+2;
                      vcolumn:=col1-offset;
                      SHOW_TXT;
                    end else offset:=0;
                    dec(vrow)
                  end else begin
                    dec(vcolumn);
                  end;
                  dec(position);
                end; { xline }
                if (vcolumn<1) and (offset>0) then begin
                  vcolumn:=1;
                  dec(offset);
                  SHOW_TXT;
                end;
    {$IFDEF USEQWIK}
                GOTORC(R1+VROW-1,C1+VCOLUMN-1);
    {$ELSE}
                GOTOXY(VCOLUMN,VROW);
    {$ENDIF}
              end;
           77:begin  { right }
                if (xline) then begin
                  inc(vcolumn);
                end else begin
                  inc(position);
                  inc(vcolumn);
                  if (txt^[position-1]=CR) OR ((position-1)>=TEXTSIZE) then begin
    { at eol }
                    dec(position);
                    xline:=true;
                  end;
                end;

                IF (MAXCOLUMN>0) AND ((VCOLUMN+OFFSET)>MAXCOLUMN) THEN BEGIN
                  GET_LINE_DATA(POSITION,b1,e1,col1);
                  IF E1>=TEXTSIZE THEN BEGIN
                    DEC(VCOLUMN);
                  END ELSE BEGIN
                    POSITION:=E1+1;
                    SET_CURSOR;
                    EXIT;
                  END;
                END;

                if vcolumn>width then begin { moved outside window }
                  inc(offset);
                  dec(vcolumn);
                  SHOW_TXT;
                end;
    {$IFDEF USEQWIK}
                GOTORC(R1+VROW-1,C1+VCOLUMN-1);
    {$ELSE}
                GOTOXY(VCOLUMN,VROW);
    {$ENDIF}
              end;
           71:begin  { HOME, to beginning of current line }
                GET_LINE_DATA(POSITION,B1,E1,COL1);
                POSITION:=B1; VCOLUMN:=1;
                IF OFFSET>0 THEN BEGIN
                  OFFSET:=0;
                  SHOW_TXT;
                END;
    {$IFDEF USEQWIK}
                GOTORC(R1+VROW-1,C1+VCOLUMN-1);
    {$ELSE}
                GOTOXY(VCOLUMN,VROW);
    {$ENDIF}
                XLINE:=FALSE;
              end;
           79:begin  { END, to end of current line }
                GET_LINE_DATA(POSITION,B1,E1,COL1);
                POSITION:=E1;
                { calculate offset & cursor position }
                IF (E1-(B1+OFFSET)+1)>WIDTH THEN BEGIN  { off screen }
                  offset:=(e1-b1+1)-width+2; {SCRBUMP}
                  vcolumn:=width-2; {SCRBUMP}
                  SHOW_TXT;
                END ELSE VCOLUMN:=((E1-B1+1)-offset);
    {$IFDEF USEQWIK}
                GOTORC(R1+VROW-1,C1+VCOLUMN-1);
    {$ELSE}
                GOTOXY(VCOLUMN,VROW);
    {$ENDIF}
              end;
           73:begin  { PGUP, up one screen }
                IF (WINTOP=1) THEN BEGIN
                  POSITION:=1;
                  VCOLUMN:=1; VROW:=1;
    {$IFDEF USEQWIK}
                  GOTORC(R1+VROW-1,C1+VCOLUMN-1);
    {$ELSE}
                  GOTOXY(VCOLUMN,VROW);
    {$ENDIF}
                END ELSE BEGIN
                  SCROLLUP(HEIGHT);
                  SET_POSITION;
                  SHOW_TXT;
                END;
              end;
           81:begin  { PGDN, down one screen }
                IF (WINBOTTOM=TEXTSIZE) THEN BEGIN
                  POSITION:=TEXTSIZE+1;
                  SET_CURSOR;
                END ELSE BEGIN
                  SCROLLDOWN(HEIGHT);
                  SET_POSITION;
                  SHOW_TXT;
                END;
              end;
            82:BEGIN
                 INSERTON:=NOT(INSERTON);     { INS toggle insert status }
    { %      IF (INSERTON) THEN GETBLOCKCURSOR ELSE GETUNDERLINECURSOR;}
               END;
            83:BEGIN                        { DEL }
                 T:=ORD(TXT^[POSITION]);
                 IF POSITION=TEXTSIZE+1 THEN EXIT;
                 IF (XLINE) THEN BEGIN   { hitting DEL past eol, special case }
                   STUFF_TXT(#0);
                   DEC(POSITION);
                   DEC(VCOLUMN);
                   DEL_CHARS(1);
                 END;
                 DEL_CHARS(1);
                 IF (T=CR) OR (POSITION>=TEXTSIZE) THEN SHOW_TXT ELSE SHOW_LINE;
               END;
           132:begin  { CTRL-PgUP - top of text }
                 POSITION:=1; VROW:=1; VCOLUMN:=1; XLINE:=FALSE;
                 IF (WINTOP=1) AND (OFFSET=0) THEN
    {$IFDEF USEQWIK}
                   GOTORC(R1+VROW-1,C1+VCOLUMN-1)
    {$ELSE}
                   GOTOXY(VCOLUMN,VROW)
    {$ENDIF}
                 ELSE BEGIN
                   WINTOP:=1; OFFSET:=0;
                   SHOW_TXT;
                 END;
              end;
           115:begin  { CTRL <- }
                 if position<3 then exit;
                 for col1:=(position-2) downto 1 do
                   if (txt^[col1]=SPACE) then begin
                     position:=col1+1;
                     if position<wintop then begin
                       wintop:=1;
     { this could be avoided if set-cursor started at 1 instead of wintop,
       but it would reduce overal performance }
                       set_cursor;
                       show_txt
                     end else
                       set_cursor;
                     exit;
                   end;
               end;
           116:begin  { CTRL -> }
                 if position>=textsize then exit;
                 for col1:=position+1 to textsize do
                   if (txt^[col1]=SPACE) then begin
                     position:=col1+1;
                     set_cursor;
                     exit;
                   end;
               end;
           118:begin  { CTRL-PgDN - end of text }
                 position:=textsize+1;
                 SET_CURSOR;
             end;
           67:BEGIN    { F9 }
              END;
          end; { case }
    end;

    (***************************************************************************)
    FUNCTION PARSE_INPUT:BYTE;
    { main encapsulation of editing routine, read keys and act }
    var c         :byte;
        fkey      :boolean;
        leaving   :boolean;
        b1,e1,col1:longint;
    { RETURNS:
          1=ESC
          2=ALT-X
          3=F1
          4=F10
          5=F2
    }
    BEGIN
      LEAVING:=FALSE;
      REPEAT
        c:=getinput(fkey);

        IF (C=27) OR ((FKEY) AND (C IN [59,45,60,68])) THEN BEGIN  { exit
    conditions }
          IF C=27 THEN PARSE_INPUT:=1 ELSE  { esc }
          IF C=45 THEN PARSE_INPUT:=2 ELSE  { Alt-X }
          IF C=59 THEN PARSE_INPUT:=3 ELSE  { F1 }
          IF C=68 THEN PARSE_INPUT:=4 ELSE  { F10 }
          IF C=60 THEN PARSE_INPUT:=5;      { F2 }
          EXIT;
        END ELSE
        IF (FKEY) THEN BEGIN  { ------------------ eval FNC & CURSOR keys ----- }
          DIRECTION(C);
        END { if function key pressed }
        ELSE BEGIN                      { alphanumeric key - process data }
          CASE C OF                     { check alpha keys }
           REFORMAT:BEGIN               { CTRL-B, 02, REFORMAT }
                GET_LINE_DATA(POSITION,b1,e1,col1);
                WORD_WRAP(B1,TEXTSIZE,MAXCOLUMN);
              END;
           CR:begin                     { carriage return }
                IF (INSERTON) OR (POSITION>TEXTSIZE) THEN BEGIN
                  OFFSET:=0;
                  INC(VROW);
                  IF VROW>HEIGHT THEN BEGIN
                     SCROLLDOWN(1);
                     DEC(VROW);
                   END;
                   STUFF_TXT(CHR(C));
                   VCOLUMN:=1;
                   SHOW_TXT;
                 end ELSE BEGIN  { enter pressed with overwrite on }
                   GET_LINE_DATA(POSITION,B1,E1,COL1);
                   POSITION:=E1+1;
                   OFFSET:=0;
                   SET_CURSOR;
                   show_txt;
                 END;
               END;
            08:IF POSITION<>1 THEN BEGIN    { backspace }
                 IF (XLINE) THEN BEGIN   { can't erase dead zone }
                    DEC(VCOLUMN);        { just move cursor left }
                    SET_POSITION;
                 END ELSE BEGIN
                   DEC(POSITION);
                   IF TXT^[POSITION]=CR THEN BEGIN { backspace/erase line }
                     DEL_CHARS(1);
                     SET_CURSOR;
                     SHOW_TXT;
                   END ELSE BEGIN
                     DEL_CHARS(1);
                     DEC(VCOLUMN);
                     IF (VCOLUMN=0) THEN
                       IF (OFFSET>=SCRBUMP) THEN BEGIN
                         DEC(OFFSET,SCRBUMP); VCOLUMN:=SCRBUMP;
                         SHOW_TXT;
                       END ELSE BEGIN
                         OFFSET:=0; SET_CURSOR;
                         SHOW_TXT;
                       END
                     ELSE SHOW_LINE;
                   END;
                 END; { xline / else }
    {$IFDEF USEQWIK}
                 GOTORC(R1+VROW-1,C1+VCOLUMN-1);
    {$ELSE}
                 GOTOXY(VCOLUMN,VROW);
    {$ENDIF}
               END;
             09:BEGIN                      { TAB }
                  GET_LINE_DATA(POSITION, b1,e1,col1);
                  col1:=tabsize-((position-b1) mod tabsize); { spaces to next tab
    stop }
                  IF (INSERTON) THEN BEGIN
                    stuff_txt(SPACES(COL1));
                    SHOW_LINE;
                  END ELSE BEGIN
                    INC(VCOLUMN,COL1);
                    IF VCOLUMN>WIDTH THEN BEGIN
                      INC(OFFSET,SCRBUMP);
                      DEC(VCOLUMN,SCRBUMP);
                      SHOW_TXT;
                    END;
                    IF (POSITION+COL1)>E1 THEN BEGIN
                      POSITION:=E1; XLINE:=TRUE;
                    END ELSE begin POSITION:=POSITION+COL1; XLINE:=FALSE; END;
    {$IFDEF USEQWIK}
                    GOTORC(R1+VROW-1,C1+VCOLUMN-1);
    {$ELSE}
                    GOTOXY(VCOLUMN,VROW);
    {$ENDIF}
                  END;
                END;
             25:BEGIN                      { CTRL-Y / ERASE LINE }
                  GET_LINE_DATA(POSITION, b1,e1,col1);
                  IF E1>TEXTSIZE THEN E1:=E1-B1 ELSE E1:=E1-B1+1;
                  POSITION:=B1; { E1:=E1-B1+1; }
                  OFFSET:=0; VCOLUMN:=1;
                  DEL_CHARS(E1);
                  SHOW_TXT;
                  IF POSITION>TEXTSIZE THEN POSITION:=TEXTSIZE+1;
                END;
            ELSE BEGIN   {------------------ unspecific alphanumeric char }
              STUFF_TXT(CHR(C));           { store it }
              IF MAXCOLUMN=0 THEN BEGIN    { check for column boundaries }
                IF VCOLUMN>WIDTH THEN BEGIN
                  INC(OFFSET,SCRBUMP);
                  DEC(VCOLUMN,SCRBUMP);
                  SHOW_TXT;
                END;
              END ELSE BEGIN  { limited screen/line size }
                SHOW_LINE;
                IF ((VCOLUMN+OFFSET)>MAXCOLUMN+1) THEN BEGIN  { hit edge limit }
                  get_line_data(position,b1,e1,col1);
                  word_wrap(b1,textsize,MAXCOLUMN);
                END ELSE BEGIN
                  IF VCOLUMN>WIDTH THEN BEGIN { maxcolumn>width but set }
                    INC(OFFSET,SCRBUMP);
                    DEC(VCOLUMN,SCRBUMP);
                    SHOW_TXT;
                  END;
                END;
              END;
              SHOW_LINE;
            END;
          END; { case }
        END { alpha key }
      UNTIL LEAVING;
    END;

    (***************************************************************************)
    PROCEDURE SETUP_TEXT_SETTINGS(Row1,Column1,Row2,Column2:BYTE;DRAWBOX:BOOLEAN);
    { sets appropriate system values for text window }
    BEGIN
      R1:=ROW1; C1:=COLUMN1; R2:=ROW2; C2:=COLUMN2;  { set global position of win }
    { % these are arbitrary attribute values - tweak them to suit your tastes }
      NORMATTR:=37; { % }
      BACKATTR:=37; { % }
      BORDATTR:=36; { % }
      OFFSET:=0;
      INSERTON:=TRUE;
      XLINE:=FALSE;
      HEIGHT:=R2-R1+1;  { current height of text window }
      WIDTH:=C2-C1+1;   { current width (in columns) of text window }
      SCRBUMP:=WIDTH DIV 2;
      VROW:=1;          { virtual row and column of cursor inside text window }
      VCOLUMN:=1;

    { % maxcolumn sets automatic formatting and word wrapping !! }
    {  MAXCOLUMN:=WIDTH; { set to 0 to disable word wrap and line length limits }
      MAXCOLUMN:=0;   { no word wrapping }

      position:=1;
      wintop:=1;
      IF DRAWBOX THEN BEGIN
        DRAW_BOX(R1,C1,R2,C2);
        { DRAW_BOX must be prior to initialize window if not using qwik }
        INITIALIZE_WINDOW(R1,C1,R2,C2);
      END;
    END;
    (***************************************************************************)
    PROCEDURE EDIT(PT:POINTER;VAR RETURNCODE:BYTE);
    { Edit text; assumes text has already been initialized }
    BEGIN
      TXT:=PT;    { assign specified text pointer to working name }
      SHOW_TXT;

      RETURNCODE:=PARSE_INPUT;
    { RETURNCODE the following values based on keys pressed:
          1=ESC
          2=ALT-X
          3=F1
          4=F10
          5=F2
    }
    END;
    (***************************************************************************)
    END. { Unit MPEDITOR.PAS }

[Back to TEXTEDIT SWAG index] [Back to Main SWAG index] [Original]