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 } 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 }