home *** CD-ROM | disk | FTP | other *** search
/ Liren Large Software Subsidy 16 / 16.iso / w / w048 / 2.ddi / MSSRC.ARC / MSFILE.PAS < prev    next >
Encoding:
Pascal/Delphi Source File  |  1987-12-21  |  32.8 KB  |  1,151 lines

  1. {                            MSFILE.PAS
  2.                                MS 4.0
  3.                 Copyright (c) 1985, 87 by Borland International, Inc.         }
  4.  
  5. {$I msdirect.inc}
  6.  
  7. unit MsFile;
  8.   {-Perform MicroStar file operations}
  9.  
  10. interface
  11.  
  12. uses
  13.   Crt,                       {Basic video operations - standard unit}
  14.   Dos,                       {DOS interface - standard unit}
  15.   Errors,                    {Runtime error handler}
  16.   MsVars,                    {Global types and declarations}
  17.   MsScrn1,                   {Fast screen writing routines}
  18.   MsString,                  {String primitives}
  19.   MsPtrOp,                   {Pointer primitives}
  20.   EscSeq,                    {Returns text string for extended scan codes}
  21.   MsCmds,                    {Maps keystrokes to commands}
  22.   Int24,                     {DOS critical error handler}
  23.   Message,                   {Message system}
  24.   MsUser,                    {User keyboard input, line edit, error report, help}
  25.   MsMemOp,                   {Text buffer allocation and deallocation}
  26.   MsBack,                    {Background processes}
  27.   MsScrn2,                   {Editor screen updating}
  28.   MsMenu,                    {Pulldown and custom menu system}
  29.   MsDir,                     {Popup directory picker}
  30.   MsEdit;                    {Basic editing commands}
  31.  
  32. function EdExistFile(Fname : Filepath) : Boolean;
  33.   {-return true if file exists, false if non-existent or a device}
  34.  
  35. procedure EdShutWindow(ExitEditor : Boolean);
  36.   {-Shut the current window, set Rundown true if last one and ExitEditor true}
  37.  
  38. procedure EdMakeBakFile(Fname : Filepath);
  39.   {-Create a backup file based on fname}
  40.  
  41. procedure EdFileWrite(Fname : Filepath; Quitting : Boolean);
  42.   {-Save current text stream to specified file}
  43.  
  44. procedure EdLogDrive(NewPath : Filepath);
  45.   {-Select a new drive or directory}
  46.  
  47. function EdWriteNamedFile : Boolean;
  48.   {-Get file name, save current text stream to it, change stream names}
  49.  
  50. function CheckCurwinModified : boolean;
  51.   {-See if current window is modified, and if so, prompt to save it}
  52.  
  53. procedure EdAbandonFile(ExitEditor : Boolean);
  54.   {-Close file without saving}
  55.  
  56. function EdGetFileName(Prompt, DefExt : VarString;
  57.                        Row, Attr : Byte;
  58.                        var LastFname : Filepath; UseWild : Boolean) : Filepath;
  59.   {-Return a file name to use}
  60.  
  61. procedure EdDirectory;
  62.   {-Display and allow browsing of file directory}
  63.  
  64. procedure EdReadtextfile(Fname : Filepath; ReadingBlock : Boolean);
  65.   {-Read text file into current window}
  66.  
  67. procedure EdReadFile(Fname : Filepath);
  68.   {-Check and open a text file for editing}
  69.  
  70. procedure EdWriteBlock(Fname : Filepath; Exists, Appending : Boolean);
  71.   {-Write or append marked block to file}
  72.  
  73.   {==========================================================================}
  74.  
  75. implementation
  76.  
  77. const
  78.   EofMark : string[1] = ^Z;  {Indicates end of file}
  79.   BakFileExt : ExtString = 'BAK'; {Extension given to backup files}
  80.  
  81.   {$L MSFILE}
  82.  
  83.   procedure EdFastExpand(var B, T; var Tlen : Integer;
  84.                          Blen, TabSize, Maxlinelength : Integer); external;
  85.   {-Expand tabs in b to blanks in t, returning length in tlen}
  86.  
  87.   procedure EdStripHighBit(var B; Len : Integer); external;
  88.   {-Set bit 8 of each character in buffer b to zero}
  89.  
  90.  
  91.   function EdIsDevice(Fname : Filepath) : Boolean;
  92.     {-return true if fname is a DOS device}
  93.   var
  94.     regs : registers;
  95.     F : file;
  96.     Handle : Word absolute F;
  97.  
  98.   begin                      {EdIsDevice}
  99.     Assign(F, Fname);
  100.     Reset(F);
  101.     if EdINT24Result <> 0 then
  102.       {Probably a file}
  103.       EdIsDevice := False
  104.     else
  105.       with regs do begin
  106.         ax := $4400;
  107.         Bx := Handle;
  108.         intr($21, regs);
  109.         EdIsDevice := (Dx and $80 <> 0);
  110.       end;
  111.     Close(F);
  112.   end;                       {EdIsDevice}
  113.  
  114.   function EdExistFile(Fname : Filepath) : Boolean;
  115.     {-return true if file exists, false if non-existent or a device}
  116.   var
  117.     F : file;
  118.     I : Integer;
  119.  
  120.   begin                      {EdExistFile}
  121.     if EdIsDevice(Fname) then
  122.       {Return false so we don't try to back up devices or get their file size}
  123.       EdExistFile := False
  124.     else begin
  125.       Assign(F, Fname);
  126.       Reset(F);
  127.       EdExistFile := (EdINT24Result = 0);
  128.       Close(F);
  129.       {Clear IOresult}
  130.       I := EdINT24Result;
  131.     end;
  132.   end;                       {EdExistfile}
  133.  
  134.   function EdInsertbuffer(Ncols : Integer) : Boolean;
  135.     {-Insert buffer into text stream - after current line}
  136.   var
  137.     P : PlineDesc;
  138.     Len : Integer;
  139.  
  140.   begin                      {EdInsertbuffer}
  141.     with Curwin^ do begin
  142.  
  143.       {Get appropriate length of text buffer}
  144.       Len := EdBufferSize(Ncols);
  145.  
  146.       if not(EdMemAvail(Len+SizeOf(Linedesc), FreeListSpace)) then begin
  147.         {Margin for free list not available}
  148.         EdErrormsg(35);
  149.         EdInsertbuffer := False;
  150.         Exit;
  151.       end;
  152.  
  153.       {Make new text descriptor}
  154.       GetMem(P, SizeOf(Linedesc));
  155.  
  156.       with P^ do begin
  157.         GetMem(Txt, Len);
  158.         {Don't include first unused byte in size of text buffer}
  159.         Bufflen := Pred(Len);
  160.  
  161.         {We don't yet know anything about its font}
  162.         Flags := NewAttr;
  163.         Font := 0;
  164.       end;
  165.  
  166.       {Link new buffer into stream}
  167.       EdLinkbuffer(Curline, P);
  168.  
  169.       {Advance cursor to next line}
  170.       Curline := P;
  171.  
  172.       EdInsertbuffer := True;
  173.     end;
  174.   end;                       {EdInsertbuffer}
  175.  
  176.   function EdExpandTabs(var B; Blen : Integer; TabSize : Integer) : Boolean;
  177.     {-Convert tabs to spaces on "tabsize" character boundaries}
  178.     {-B is a buffer holding the new text, blen is the number of characters}
  179.     {-Return true if line successfully inserted}
  180.   var
  181.     Tlen, ActLen : Integer;
  182.     T : TextLine;
  183.  
  184.   begin                      {EdExpandTabs}
  185.  
  186.     {Expand tabs into the buffer T}
  187.     EdFastExpand(B, T, Tlen, Blen, TabSize, Maxlinelength);
  188.  
  189.     {Get length of line to be inserted}
  190.     if Tlen = 0 then
  191.       ActLen := Blen
  192.     else
  193.       ActLen := Tlen;
  194.  
  195.     {Insert it into text stream}
  196.     if EdInsertbuffer(Succ(ActLen)) then begin
  197.       with Curwin^.Curline^ do
  198.         if Tlen = 0 then begin
  199.           {No tabs found}
  200.           {Put original disk buffer into line buffer}
  201.           Move(B, Txt^[1], Blen);
  202.           FillChar(Txt^[Succ(Blen)], Bufflen-Blen, Blank);
  203.         end else begin
  204.           {Put expanded line into the line buffer}
  205.           Move(T[1], Txt^[1], Tlen);
  206.           FillChar(Txt^[Succ(Tlen)], Bufflen-Tlen, Blank);
  207.         end;
  208.       EdExpandTabs := True;
  209.     end else
  210.       EdExpandTabs := False;
  211.  
  212.   end;                       {EdExpandTabs}
  213.  
  214.   procedure EdConvertFormatChars(var P : PlineDesc);
  215.     {-Convert WordStar dot commands to MicroStar format commands}
  216.  
  217.   begin                      {EdConvertFormatChars}
  218.     with P^ do
  219.       if Txt^[1] = Period then begin
  220.         Txt^[1] := FormatChar;
  221.         if Txt^[2] = Period then
  222.           Txt^[2] := FormatChar;
  223.       end;
  224.   end;                       {EdConvertFormatChars}
  225.  
  226.   procedure EdReadtextfile(Fname : Filepath; ReadingBlock : Boolean);
  227.     {-Read text file into current window}
  228.   var
  229.     Infile : file;
  230.     TopSave, Cursave : PlineDesc;
  231.     EdError, Endoffile, GotEol : Boolean;
  232.     Colsave, BufOfs, BufPos, BufCnt, EolPos, EofPos, LineLen : Integer;
  233.  
  234.   begin                      {EdReadtextfile}
  235.  
  236.     if Abortcmd or EdStringEmpty(Fname) then
  237.       Exit;
  238.  
  239.     {Show "Reading" status}
  240.     EdForceMessage(EdGetMessage(325));
  241.  
  242.     Assign(Infile, Fname);
  243.     Reset(Infile, 1);
  244.     if EdFileerror then
  245.       Exit;
  246.  
  247.     with Curwin^ do begin
  248.  
  249.       {Reduce available memory so we have something left to link with}
  250.       FreeListSpace := FreeListPerm+(Maxlinelength shl 1);
  251.  
  252.       {Start inserting text at current cursor}
  253.       EdInsertLine;
  254.       if Goterror then begin
  255.         Close(Infile);
  256.         Exit;
  257.       end;
  258.  
  259.       {Save line position, which will be starting line of newly read text}
  260.       TopSave := TopLine;
  261.       Cursave := Curline;
  262.       Colsave := Colno;
  263.  
  264.       EdError := False;
  265.       BufOfs := 0;
  266.  
  267.       {Allocate buffers to the nearest byte while reading files}
  268.       ExactAllocation := True;
  269.  
  270.       if ReadingBlock then begin
  271.         Modified := True;
  272.         EdResetPageLine(Curwin);
  273.       end else begin
  274.         {A new file}
  275.         Modified := False;
  276.         Clineno := 1;
  277.       end;
  278.  
  279.       repeat
  280.  
  281.         {Check keyboard for abort and typeahead}
  282.         EdBreathe;
  283.         EdError := EdError or Abortcmd;
  284.  
  285.         if not(EdError) then begin
  286.  
  287.           {Get a new buffer full of characters}
  288.           EdBlockRead(Infile, WorkBuf[Succ(BufOfs)], Bufsize-BufOfs, BufCnt);
  289.           Inc(BufCnt, BufOfs);
  290.           {Bufcnt now holds count of characters in buffer}
  291.  
  292.           EdError := EdError or EdFileerror;
  293.  
  294.           {Adjust BufCnt for first ^Z found in buffer}
  295.           if BufCnt <> 0 then begin
  296.             EofPos := EdLongPosFwd(WorkBuf, 1, BufCnt, EofMark);
  297.             if EofPos <> 0 then
  298.               BufCnt := Pred(EofPos);
  299.           end;
  300.         end;
  301.  
  302.         {End of file if no active characters in buffer}
  303.         Endoffile := EdError or (BufCnt <= 0);
  304.  
  305.         if not(Endoffile) then begin
  306.  
  307.           if SaveStripMode then
  308.             {Set high bit off for WordStar DOC files}
  309.             EdStripHighBit(WorkBuf, BufCnt);
  310.  
  311.           {Scan the buffer, breaking it into <CR><LF> delimited lines}
  312.           BufOfs := 0;
  313.           BufPos := 1;
  314.  
  315.           repeat
  316.  
  317.             {Find next EOL in the buffer}
  318.             EolPos := EdLongPosFwd(WorkBuf, BufPos, BufCnt, EolMark);
  319.  
  320.             if (EolPos = 0) and (BufPos+Maxlinelength >= Bufsize) then begin
  321.  
  322.               {Eolmark not found in buffer}
  323.  
  324.               {Partial line, continue line into next buffer}
  325.               BufOfs := Succ(BufCnt-BufPos);
  326.               Move(WorkBuf[BufPos], WorkBuf[1], BufOfs);
  327.               {Force loop exit}
  328.               BufPos := Succ(BufCnt);
  329.  
  330.             end else begin
  331.  
  332.               {Eolmark found or linebreak forced}
  333.  
  334.               if (EolPos = 0) or ((EolPos-BufPos) >= Maxlinelength) then begin
  335.                 {Linebreak forced without finding a <CR><LF>}
  336.                 {Always leave at least one blank at end of line}
  337.                 EolPos := Pred(BufPos+Maxlinelength);
  338.                 if EolPos > BufCnt then
  339.                   EolPos := Succ(BufCnt);
  340.                 GotEol := False;
  341.               end else
  342.                 GotEol := True;
  343.  
  344.               if EolPos > BufPos then begin
  345.  
  346.                 {Nonempty line, store it}
  347.                 LineLen := EolPos-BufPos;
  348.                 if ReadExpandTabs then begin
  349.                   {Check for and expand any tab characters}
  350.  
  351.                   if not(EdExpandTabs(WorkBuf[BufPos], LineLen, SaveTabSize)) then begin
  352.                     {Insufficient memory}
  353.                     EdError := True;
  354.                     Goterror := False;
  355.                     Modified := True;
  356.                   end;
  357.                 end else begin
  358.                   {Not expanding tabs, create new line and fill it}
  359.  
  360.                   if EdInsertbuffer(Succ(LineLen)) then
  361.                     with Curline^ do begin
  362.                       Move(WorkBuf[BufPos], Txt^[1], LineLen);
  363.                       FillChar(Txt^[Succ(LineLen)], Bufflen-LineLen, Blank);
  364.                     end
  365.                   else begin
  366.                     EdError := True;
  367.                     Goterror := False;
  368.                     Modified := True;
  369.                   end;
  370.                 end;
  371.  
  372.                 if SaveStripMode then
  373.                   if not(EdError) then
  374.                     {Convert WordStar dot commands to MicroStar format commands}
  375.                     EdConvertFormatChars(Curline);
  376.  
  377.               end else begin
  378.                 {Empty line}
  379.                 if EdInsertbuffer(1) then
  380.                   with Curline^ do
  381.                     {Initialize buffer with blanks}
  382.                     FillChar(Txt^[1], Bufflen, Blank)
  383.                 else begin
  384.                   EdError := True;
  385.                   Goterror := False;
  386.                   Modified := True;
  387.                 end;
  388.               end;
  389.  
  390.               if GotEol then
  391.                 {Skip over <CR><LF>}
  392.                 BufPos := EolPos+Length(EolMark)
  393.               else
  394.                 {Start immediately after break}
  395.                 BufPos := EolPos;
  396.  
  397.             end;             {Eolmark found}
  398.           until EdError or (BufPos > BufCnt);
  399.         end;                 {Not endoffile}
  400.  
  401.       until EdError or Endoffile;
  402.  
  403.       Close(Infile);
  404.  
  405.       {Restore free list margin}
  406.       FreeListSpace := FreeListPerm;
  407.  
  408.       {Don't allocate on one byte boundaries any more}
  409.       ExactAllocation := False;
  410.  
  411.       if ReadingBlock then begin
  412.         {Set block markers around what we read in}
  413.         EdRightLine;
  414.         if GotEol then begin
  415.           EdDownLine;
  416.           Colno := 1;
  417.         end else
  418.           EdJoinline;
  419.         with Blockto do begin
  420.           Line := Curline;
  421.           Col := Colno;
  422.         end;
  423.         with Blockfrom do begin
  424.           Line := Cursave;
  425.           Col := Colsave;
  426.         end;
  427.         {Turn off old block marks}
  428.         EdOffblock;
  429.         {Prepare to display new ones}
  430.         Blockhide := False;
  431.       end;
  432.  
  433.       if Curline <> Cursave then begin
  434.         {Restore original line position}
  435.         TopLine := TopSave;
  436.         Curline := Cursave;
  437.         Colno := Colsave;
  438.         {Rejoin the left half of the line we split}
  439.         EdJoinline;
  440.       end;
  441.  
  442.       EdRealign;
  443.       Intrflag := Interr;
  444.       UpdateScreen := True;
  445.  
  446.       EdBufferCurrentLine;
  447.  
  448.     end;                     {With Curwin^}
  449.  
  450.     EdZapPromptLine;
  451.  
  452.   end;                       {EdReadtextfile}
  453.  
  454.   procedure EdMakeBakFile(Fname : Filepath);
  455.     {-Create a backup file based on fname}
  456.   var
  457.     I : Integer;
  458.     Bname : Filepath;
  459.     F : file;
  460.  
  461.   begin                      {EdMakeBakFile}
  462.  
  463.     if not(MakeBackups) then
  464.       Exit;
  465.  
  466.     {Build backup name}
  467.     if EdFileHasExtension(Fname, I) then
  468.       Bname := Copy(Fname, 1, I)+BakFileExt
  469.     else
  470.       Bname := Fname+Period+BakFileExt;
  471.  
  472.     {Erase existing backup}
  473.     if EdExistFile(Bname) then begin
  474.       Assign(F, Bname);
  475.       Erase(F);
  476.       if EdINT24Result <> 0 then
  477.         ;
  478.     end;
  479.  
  480.     {Rename existing file to backup}
  481.     Assign(F, Fname);
  482.     Rename(F, Bname);
  483.     if EdINT24Result <> 0 then
  484.       EdErrormsg(104);
  485.   end;                       {EdMakebakfile}
  486.  
  487.   procedure EdFlagExit;
  488.     {-If printing not in progress, set flag to exit editor}
  489.  
  490.   begin                      {EdFlagExit}
  491.     if Printing then begin
  492.       {Ask to abort print job}
  493.       if not(EdYesNo(EdGetMessage(388))) then
  494.         Exit;
  495.       if Abortcmd then
  496.         Exit;
  497.       {Close up the print job}
  498.       EdPrintExit;
  499.     end;
  500.     Rundown := True;
  501.     if CleanupAtExit then begin
  502.       {Clear off the heap and reset the window stack in case editor called in loop}
  503.       EdDeleteAllText(Curwin);
  504.       EdPushWindowStack(Curwin);
  505.     end;
  506.   end;                       {EdFlagExit}
  507.  
  508.   procedure EdShutWindow(ExitEditor : Boolean);
  509.     {-Shut the current window, set Rundown true if last one and ExitEditor true}
  510.   var
  511.     Rezoom : Boolean;
  512.  
  513.   begin                      {EdShutWindow}
  514.     {See if there is another window open}
  515.     if WindowCount <= 1 then begin
  516.       if ExitEditor then
  517.         {Exit the editor if printing not in progress}
  518.         EdFlagExit
  519.       else begin
  520.         if WindowCount = 1 then begin
  521.           {Clear the text from the window}
  522.           EdDeleteAllText(Curwin);
  523.           EdResetWindow(Curwin);
  524.         end;
  525.         {Stay in menu system}
  526.         Intrflag := NoInterr;
  527.         if EdPtrNotNil(CurrMenu) then
  528.           {Temporarily remove menu from screen}
  529.           EdEraseMenus;
  530.         {Return to original DOS background color}
  531.         ClrScr;
  532.         EdUserPush(MenuPrime+'F'); {+'FO' to prompt for new file}
  533.       end;
  534.     end else begin
  535.       {Undo zoom if needed}
  536.       Rezoom := Zoomed;
  537.       if Rezoom then
  538.         {Pull current lines up to fit into unzoomed windows}
  539.         EdZoomWindow(True);
  540.       {Delete current window and return in other one}
  541.       EdWindowUp;
  542.       EdWindowDelete(Succ(EdWindowNumber));
  543.       if Rezoom and ((WindowCount > 2) or SaveInitZoomState) then
  544.         EdZoomWindow(True);
  545.     end;
  546.     if WindowCount > 0 then
  547.       Dec(WindowCount);
  548.   end;                       {EdShutWindow}
  549.  
  550.   procedure EdConvertToTabs(var Sbuf, NsBuf; Slen : Integer; var NsLen : Integer);
  551.     {-Convert spaces to tabs in textbuffer s, returning textbuffer ns}
  552.   const
  553.     Tab = #9;
  554.     DoubleQuote = #34;
  555.     Apostrophe = #39;
  556.     BackSlash = #92;
  557.   var
  558.     S : TextLine absolute Sbuf;
  559.     Ns : TextLine absolute NsBuf;
  560.     Ipos, SpaceCount : Integer;
  561.     Ch : Char;
  562.     EndOfQuote : Boolean;
  563.  
  564.   begin                      {EdConvertToTabs}
  565.  
  566.     NsLen := 0;
  567.     Ipos := 1;
  568.     SpaceCount := 0;
  569.  
  570.     {Loop through all characters in input buffer}
  571.     while Ipos <= Slen do begin
  572.  
  573.       Ch := S[Ipos];
  574.  
  575.       {Insert tab characters if appropriate}
  576.       if SpaceCount > 1 then
  577.         if (Ipos mod SaveTabSize) = 1 then begin
  578.           NsLen := NsLen-SpaceCount;
  579.           for SpaceCount := 1 to (SpaceCount+Pred(SaveTabSize)) div SaveTabSize do begin
  580.             Inc(NsLen);
  581.             Ns[NsLen] := Tab;
  582.           end;
  583.           SpaceCount := 0;
  584.         end;
  585.  
  586.       {Keep count of contiguous spaces}
  587.       if (Ch = Blank) then
  588.         Inc(SpaceCount)
  589.       else
  590.         SpaceCount := 0;
  591.  
  592.       {See if the start of a quoted region}
  593.       if (Ch = Apostrophe) or (Ch = DoubleQuote) then begin
  594.  
  595.         {Pass quoted region through unchanged}
  596.         repeat
  597.  
  598.           {Store character in output}
  599.           Inc(NsLen);
  600.           Ns[NsLen] := S[Ipos];
  601.  
  602.           {Move to next input character}
  603.           Inc(Ipos);
  604.  
  605.           {Check for end of quote}
  606.           if Ipos >= Slen then
  607.             {End of line}
  608.             EndOfQuote := True
  609.           else if S[Ipos] = Ch then
  610.             {Perhaps end of quote}
  611.             case Ch of
  612.               Apostrophe : EndOfQuote := True;
  613.               DoubleQuote : EndOfQuote := (S[Pred(Ipos)] <> BackSlash);
  614.             end
  615.           else
  616.             EndOfQuote := False;
  617.  
  618.         until EndOfQuote;
  619.  
  620.       end;
  621.  
  622.       Inc(NsLen);
  623.       Ns[NsLen] := S[Ipos];
  624.       Inc(Ipos);
  625.  
  626.     end;
  627.   end;                       {EdConvertToTabs}
  628.  
  629.   procedure EdFileWrite(Fname : Filepath; Quitting : Boolean);
  630.     {-Save current text stream to specified file}
  631.   var
  632.     OutFile : file;
  633.     P : PlineDesc;
  634.     W : Pwindesc;
  635.     Len, BufPos, BufSiz, Olen : Integer;
  636.  
  637.   begin                      {EdFileWrite}
  638.     AbortEnable := True;
  639.     EdWait;
  640.  
  641.     if EdExistFile(Fname) then begin
  642.       {Create a .BAK file}
  643.       EdMakeBakFile(Fname);
  644.       if Goterror then
  645.         Exit;
  646.     end;
  647.  
  648.     Assign(OutFile, Fname);
  649.     Rewrite(OutFile, 1);
  650.     if EdFileerror then
  651.       Exit;
  652.  
  653.     {Find top of stream}
  654.     P := EdTopofStream(Curwin);
  655.  
  656.     BufPos := 0;
  657.     BufSiz := Bufsize-Length(EolMark);
  658.  
  659.     with Curwin^ do
  660.       repeat
  661.  
  662.         {Check for abort}
  663.         if not(Quitting) then
  664.           {If leaving the editor, let DOS buffer keystrokes}
  665.           EdBreathe;
  666.  
  667.         Len := EdTextLength(P);
  668.  
  669.         if BufPos+Len > BufSiz then begin
  670.           {Flush write buffer}
  671.           EdBlockWrite(OutFile, WorkBuf[1], BufPos);
  672.           BufPos := 0;
  673.         end;
  674.  
  675.         if Len <> 0 then
  676.           if WriteCompressTabs then begin
  677.             EdConvertToTabs(P^.Txt^, WorkBuf[BufPos], Len, Olen);
  678.             BufPos := BufPos+Olen;
  679.           end else begin
  680.             Move(P^.Txt^[1], WorkBuf[Succ(BufPos)], Len);
  681.             BufPos := BufPos+Len;
  682.           end;
  683.  
  684.         EdFwdPtr(P);
  685.         if EdPtrNotNil(P) then begin
  686.           {Add end of line marker}
  687.           Move(EolMark[1], WorkBuf[Succ(BufPos)], Length(EolMark));
  688.           BufPos := BufPos+Length(EolMark);
  689.         end;
  690.  
  691.       until Abortcmd or Goterror or EdPtrIsNil(P);
  692.  
  693.     if EdPtrIsNil(P) and (BufPos <> 0) then
  694.       {Flush the final chunk}
  695.       EdBlockWrite(OutFile, WorkBuf[1], BufPos);
  696.  
  697.     if EdPtrIsNil(P) and not(EdIsDevice(Fname)) then begin
  698.       {Write EOF marker}
  699.       if not(EdStringEmpty(EofMark)) then
  700.         EdBlockWrite(OutFile, EofMark[1], Length(EofMark));
  701.  
  702.       if not(Goterror) then begin
  703.         {Indicate that window and any linked to it are now saved completely}
  704.         W := Curwin;
  705.         repeat
  706.           if W^.Stream = Curwin^.Stream then
  707.             W^.Modified := False;
  708.           EdFwdPtr(W);
  709.         until W = Curwin;
  710.       end;
  711.  
  712.     end;
  713.  
  714.     Close(OutFile);
  715.     if EdFileerror then
  716.       ;
  717.   end;                       {EdFileWrite}
  718.  
  719.   procedure EdLogDrive(NewPath : Filepath);
  720.     {-Select a new drive or directory}
  721.   var
  722.     OldPath : Filepath;
  723.  
  724.   begin                      {EdLogdrive}
  725.     if Abortcmd or EdStringEmpty(NewPath) then
  726.       Exit;
  727.  
  728.     if (Length(NewPath) > 1) and
  729.     (NewPath[Length(NewPath)] = '\') and
  730.     (NewPath[Pred(Length(NewPath))] <> ':') then
  731.       {Remove trailing backslash}
  732.       Delete(NewPath, Length(NewPath), 1);
  733.  
  734.     {Get current path in case of error}
  735.     GetDir(0, OldPath);
  736.  
  737.     {Change to the requested directory}
  738.     ChDir(NewPath);
  739.     if EdINT24Result <> 0 then begin
  740.       {Invalid directory}
  741.       EdErrormsg(122);
  742.       {Change to the previous directory again (DOS quirk requires)}
  743.       ChDir(OldPath);
  744.     end;
  745.   end;                       {EdLogdrive}
  746.  
  747.   function EdWriteNamedFile : Boolean;
  748.     {-Get file name, save current text stream to it, change stream names}
  749.   var
  750.     Fname, Junk : Filepath;
  751.  
  752.   begin                      {EdWriteNamedFile}
  753.  
  754.     EdWriteNamedFile := False;
  755.  
  756.     {Get a file name}
  757.     EdClearString(Junk);
  758.     Fname := EdGetFileName(EdGetMessage(386), DefExtension, EdYcenterWindow(3), 0, Junk, False);
  759.     if Abortcmd or EdStringEmpty(Fname) then
  760.       Exit;
  761.  
  762.     if EdExistFile(Fname) then begin
  763.       {Prompt to overwrite}
  764.       if not(EdYesNo(EdGetMessage(319))) then
  765.         Exit;
  766.       if Abortcmd then
  767.         Exit;
  768.     end;
  769.  
  770.     EdFileWrite(Fname, False);
  771.     if Goterror then
  772.       Exit;
  773.  
  774.     {Change the names of all linked streams}
  775.     EdChangeStreamName(Fname);
  776.  
  777.     EdWriteNamedFile := True;
  778.  
  779.   end;                       {EdWriteNamedFile}
  780.  
  781.   function CheckCurwinModified : boolean;
  782.     {-See if current window is modified, and if so, prompt to save it}
  783.   var
  784.     SaveFirst : Boolean;
  785.  
  786.   begin                        {CheckCurwinModified}
  787.     CheckCurwinModified := false;
  788.     with Curwin^ do
  789.       if Modified then
  790.         {Prompt user to avoid loss of edits}
  791.         if EdLinkedWindow(Curwin) then
  792.           {Assure other modified flags are set, but abandon this window}
  793.           EdCloneModifiedFlags
  794.         else begin
  795.           {See if user wants to save it before quitting window}
  796.           SaveFirst := EdYesNo(Blank+EdEndOfPath(Filename)+EdGetMessage(306));
  797.           if Abortcmd then
  798.             Exit;
  799.           if SaveFirst then
  800.             if Filename = NoFile then begin
  801.               if not(EdWriteNamedFile) then
  802.                 exit;
  803.             end else
  804.               EdFileWrite(Filename, False);
  805.         end;
  806.     CheckCurwinModified := true;
  807.   end;                         {CheckCurwinModified}
  808.  
  809.   procedure EdAbandonFile(ExitEditor : Boolean);
  810.     {-Close file without saving}
  811.  
  812.   begin                      {EdAbandonFile}
  813.     if WindowCount >= 1 then
  814.       {If current window is modified, prompt to save it}
  815.       if not CheckCurwinModified then
  816.         {Exit if AbortCmd or <Esc> was entered}
  817.         exit;
  818.  
  819.     {Clearing the text stream from memory takes a little while}
  820.     EdWait;
  821.     EdShutWindow(ExitEditor);
  822.   end;                       {EdAbandonFile}
  823.  
  824.   procedure EdDirectory;
  825.     {-Display and allow browsing of file directory}
  826.   var
  827.     Fname : Filepath;
  828.  
  829.   begin                      {EdDirectory}
  830.     EdWritePromptLine('');
  831.     Fname := LastDirectory;
  832.     EdAskfor(EdGetMessage(303), 5, 20, 66, Fname);
  833.     if Abortcmd then
  834.       Exit;
  835.     LastDirectory := Fname;
  836.     EdCleanFileName(Fname);
  837.     {Use popup window when appropriate to allow browsing directory}
  838.     Fname := EdPickdir(Fname, 389, 0, False);
  839.   end;                       {EdDirectory}
  840.  
  841.   function EdGetFileName(Prompt, DefExt : VarString;
  842.                          Row, Attr : Byte;
  843.                          var LastFname : Filepath; UseWild : Boolean) : Filepath;
  844.     {-Return a file name to use}
  845.   var
  846.     Fname : Filepath;
  847.  
  848.   begin                      {EdGetFileName}
  849.  
  850.     EdGetFileName := '';
  851.     if EditUsercommandInput = 0 then
  852.       EdWritePromptLine(EdGetMessage(302));
  853.  
  854.     repeat
  855.       Abortcmd := False;
  856.       Goterror := False;
  857.       {Initialize default value}
  858.       if UseWild and EdStringEmpty(LastFname) then begin
  859.         if EdStringEmpty(DefExtension) then
  860.           Fname := '*.*'
  861.         else
  862.           Fname := '*.'+DefExtension;
  863.       end else
  864.         Fname := LastFname;
  865.       {Prompt for a new string}
  866.       EdAskfor(Prompt, 3, Row, 66, Fname);
  867.       if Abortcmd or EdStringEmpty(Fname) then
  868.         Exit;
  869.       {Clean up the file name}
  870.       EdCleanFileName(Fname);
  871.       {Store response for later use}
  872.       LastFname := Fname;
  873.       {Use popup window when appropriate to allow picking from directory}
  874.       Fname := EdPickdir(Fname, 304, Attr, True);
  875.       if Goterror then
  876.         Exit;
  877.       {Loop if no file selected from pick window}
  878.     until not(EdStringEmpty(Fname));
  879.  
  880.     if Attr = 0 then begin
  881.       {Strip blanks, uppercase, etc.}
  882.       EdCleanFileName(Fname);
  883.       EdDefaultExtension(DefExt, Fname);
  884.     end;
  885.  
  886.     EdGetFileName := Fname;
  887.   end;                       {EdGetfilename}
  888.  
  889.   procedure EdReadFile(Fname : Filepath);
  890.     {-Check and open a text file for editing}
  891.   var
  892.     Code : Integer;
  893.     F : file;
  894.  
  895.     function EdWindowLinked(Fname : Filepath) : Boolean;
  896.       {-Return true if and when window has been linked to another window}
  897.     var
  898.       IsLinked : Boolean;
  899.       Wthis, Wnext : Integer;
  900.       W : Pwindesc;
  901.  
  902.       procedure EdWindowLink(Wto : Byte; Wfrom : Byte);
  903.         {-Link one window to another}
  904.       var
  905.         Pto, Pfrom : Pwindesc;
  906.  
  907.       begin                  {EdWindowLink}
  908.  
  909.         Pto := EdFindWindesc(Wto);
  910.         Pfrom := EdFindWindesc(Wfrom);
  911.  
  912.         with Pfrom^ do begin
  913.  
  914.           {Clean out source window's text if no other windows point to it}
  915.           if not(EdLinkedWindow(Pfrom)) then
  916.             EdDeleteAllText(Pfrom);
  917.  
  918.           {Match streams}
  919.           Stream := Pto^.Stream;
  920.  
  921.           {Now equate the two}
  922.           Filename := Pto^.Filename;
  923.           TopLine := Pto^.TopLine;
  924.           Curline := Pto^.TopLine;
  925.           Lmargin := Pto^.Lmargin;
  926.           Wmargin := Pto^.Wmargin;
  927.           Rmargin := Pto^.Rmargin;
  928.           Modified := Pto^.Modified;
  929.           Leftedge := Pto^.Leftedge;
  930.           Lineno := 1;
  931.           Colno := 1;
  932.         end;
  933.  
  934.       end;                   {EdWindowLink}
  935.  
  936.     begin                    {EdWindowLinked}
  937.       IsLinked := False;
  938.       {If more than one window, then see if we should link to other window}
  939.       if (Fname <> NoFile) and (WindowCount > 0) then begin
  940.         {Get number of this window}
  941.         Wthis := EdWindowNumber;
  942.         Wnext := Wthis;
  943.         W := Curwin;
  944.         repeat
  945.           EdFwdPtr(W);
  946.           Inc(Wnext);
  947.           if Fname = W^.Filename then begin
  948.             IsLinked := True;
  949.             EdWindowLink(Wnext, Wthis);
  950.           end;
  951.         until IsLinked or (W = Curwin);
  952.       end;
  953.       EdWindowLinked := IsLinked;
  954.     end;                     {EdWindowLinked}
  955.  
  956.   begin                      {EdReadfile}
  957.  
  958.     if Abortcmd or Goterror then begin
  959.       Goterror := True;
  960.       Exit;
  961.     end;
  962.  
  963.     if EdStringEmpty(Fname) then
  964.       {Support editing of as yet unnamed files}
  965.       Fname := NoFile
  966.     else if EdIsDevice(Fname) then begin
  967.       {Can't read from a device}
  968.       EdErrormsg(34);
  969.       Exit;
  970.     end;
  971.  
  972.     {Erase the menu system if currently displayed}
  973.     if EdPtrNotNil(CurrMenu) then
  974.       EdEraseMenus;
  975.  
  976.     {Link text stream to existing window if appropriate}
  977.     if not(EdWindowLinked(Fname)) then begin
  978.  
  979.       if Fname <> NoFile then begin
  980.         {Try to open existing file}
  981.         Assign(F, Fname);
  982.         Reset(F);
  983.         Code := EdINT24Result;
  984.         if hi(Code) <> 0 then begin
  985.           {Drive not ready}
  986.           EdErrormsg(128);
  987.           Exit;
  988.         end;
  989.       end else
  990.         Code := 1;
  991.  
  992.       Curwin^.Filename := Fname;
  993.  
  994.       if Code <> 0 then begin
  995.  
  996.         if Fname <> NoFile then begin
  997.           {File was not found. See if illegal name, or just new file}
  998.           Rewrite(F);
  999.           if EdINT24Result <> 0 then begin
  1000.             {Illegal file name}
  1001.             EdErrormsg(5);
  1002.             Exit;
  1003.           end;
  1004.           Close(F);
  1005.           Erase(F);
  1006.         end;
  1007.  
  1008.         {New file}
  1009.         EdZapPromptLine;
  1010.         EdAppPromptLine(EdGetMessage(301));
  1011.         Curwin^.Modified := False;
  1012.         Intrflag := NoInterr;
  1013.         EdUpdateScreen;
  1014.         EdInterruptibleDelay(1500);
  1015.  
  1016.       end else begin
  1017.  
  1018.         {Update the screen while the file is read}
  1019.         Intrflag := NoInterr;
  1020.         EdUpdateScreen;
  1021.         {Read in existing file}
  1022.         Close(F);
  1023.         EdReadtextfile(Fname, False);
  1024.  
  1025.       end;
  1026.  
  1027.     end;
  1028.     EdZapPromptLine;
  1029.     UpdateScreen := True;
  1030.     UpdateCursor := True;
  1031.  
  1032.   end;                       {EdReadfile}
  1033.  
  1034.   procedure EdWriteBlock(Fname : Filepath; Exists, Appending : Boolean);
  1035.     {-Write or append marked block to file}
  1036.   var
  1037.     Stop, Start, Len, Olen, BufPos, BufSiz : Integer;
  1038.     P : PlineDesc;
  1039.     OutFile : file;
  1040.  
  1041.     procedure EdAddToFile(var OutFile : file);
  1042.       {-Append to an existing file terminated with up to 127 ^Z marks}
  1043.     var
  1044.       Fsize, LastSect : LongInt;
  1045.       LastBuf : array[0..128] of Char;
  1046.       Bufsize, EofPos, BytesRead : Integer;
  1047.  
  1048.     begin                    {EdAddToFile}
  1049.       Reset(OutFile, 1);
  1050.       Fsize := FileSize(OutFile);
  1051.       if Fsize = 0 then
  1052.         Rewrite(OutFile, 1)
  1053.       else begin
  1054.         {Seek last (possibly partial) 128 byte block and search for ^Z}
  1055.         LastSect := 128*(Fsize div 128);
  1056.         if Abs(LastSect-Fsize) < 1 then
  1057.           {File was on an exact 128 byte boundary}
  1058.           LastSect := Fsize-128;
  1059.         Seek(OutFile, LastSect);
  1060.         Bufsize := Fsize-LastSect;
  1061.         EdBlockRead(OutFile, LastBuf[1], Bufsize, BytesRead);
  1062.         if Goterror then
  1063.           Exit;
  1064.         EofPos := EdLongPosFwd(LastBuf, 1, BytesRead, EofMark);
  1065.         if EofPos <> 0 then
  1066.           Fsize := LastSect+LongInt(EofPos)-1;
  1067.         {Move file pointer to first ^Z}
  1068.         Seek(OutFile, Fsize);
  1069.       end;
  1070.     end;                     {EdAddToFile}
  1071.  
  1072.   begin                      {EdWriteBlock}
  1073.  
  1074.     {Append wait signal to command line}
  1075.     EdWait;
  1076.  
  1077.     Assign(OutFile, Fname);
  1078.     if Exists and Appending then begin
  1079.       EdAddToFile(OutFile);
  1080.       if Goterror then
  1081.         Exit
  1082.     end else begin
  1083.       Rewrite(OutFile, 1);
  1084.       if EdFileerror then
  1085.         Exit;
  1086.     end;
  1087.  
  1088.     P := Blockfrom.Line;
  1089.  
  1090.     BufPos := 0;
  1091.     BufSiz := Bufsize-Length(EolMark);
  1092.  
  1093.     repeat
  1094.  
  1095.       {Enable typeahead and abort during writing}
  1096.       EdBreathe;
  1097.  
  1098.       if P = Blockfrom.Line then
  1099.         Start := Blockfrom.Col
  1100.       else
  1101.         Start := 1;
  1102.  
  1103.       if P = Blockto.Line then
  1104.         Stop := Pred(Blockto.Col)
  1105.       else
  1106.         Stop := EdTextLength(P);
  1107.  
  1108.       Len := Succ(Stop-Start);
  1109.  
  1110.       if BufPos+Len > BufSiz then begin
  1111.         {Flush write buffer}
  1112.         EdBlockWrite(OutFile, WorkBuf[1], BufPos);
  1113.         BufPos := 0;
  1114.       end;
  1115.  
  1116.       if Len > 0 then
  1117.         if WriteCompressTabs then begin
  1118.           EdConvertToTabs(P^.Txt^[Pred(Start)], WorkBuf[BufPos], Len, Olen);
  1119.           BufPos := BufPos+Olen;
  1120.         end else begin
  1121.           Move(P^.Txt^[Start], WorkBuf[Succ(BufPos)], Len);
  1122.           BufPos := BufPos+Len;
  1123.         end;
  1124.  
  1125.       if P = Blockto.Line then
  1126.         EdSetPtrNil(P)
  1127.       else begin
  1128.         EdFwdPtr(P);
  1129.         Move(EolMark[1], WorkBuf[Succ(BufPos)], Length(EolMark));
  1130.         BufPos := BufPos+Length(EolMark);
  1131.       end;
  1132.  
  1133.     until Abortcmd or Goterror or EdPtrIsNil(P);
  1134.  
  1135.     if EdPtrIsNil(P) and (BufPos <> 0) then
  1136.       {Flush the final chunk}
  1137.       EdBlockWrite(OutFile, WorkBuf[1], BufPos);
  1138.  
  1139.     if EdPtrIsNil(P) and not(EdIsDevice(Fname)) then
  1140.       {Write EOF marker}
  1141.       if not(EdStringEmpty(EofMark)) then
  1142.         EdBlockWrite(OutFile, EofMark[1], Length(EofMark));
  1143.  
  1144.     Close(OutFile);
  1145.     if EdFileerror then
  1146.       ;
  1147.  
  1148.   end;                       {EdWriteBlock}
  1149.  
  1150. end.
  1151.