home *** CD-ROM | disk | FTP | other *** search
/ Delphi 5 for Professionals / DELPHI5.iso / AddOns / Components / Orpheus v3.02 / SETUP.EXE / %MAINDIR% / Ovceditp.pas < prev    next >
Encoding:
Pascal/Delphi Source File  |  1999-02-25  |  54.7 KB  |  2,012 lines

  1. {*********************************************************}
  2. {*                  OVCEDITP.PA2 3.00                    *}
  3. {*     Copyright 1995-99 (c) TurboPower Software Co      *}
  4. {*                 All rights reserved.                  *}
  5. {*********************************************************}
  6.  
  7. {$I OVC.INC}
  8.  
  9. {$B-} {Complete Boolean Evaluation}
  10. {$I+} {Input/Output-Checking}
  11. {$P+} {Open Parameters}
  12. {$T-} {Typed @ Operator}
  13. {$W-} {Windows Stack Frame}
  14. {$X+} {Extended Syntax}
  15. {$R-} {Range-Checking}
  16. {$S-} {Stack-Overflow Checking}
  17.  
  18. {$IFNDEF Win32}
  19. {$G+} {286 Instructions}
  20. {$N+} {Numeric Coprocessor}
  21.  
  22. {$C MOVEABLE,DEMANDLOAD,DISCARDABLE}
  23. {$ENDIF}
  24.  
  25. unit OvcEditP;
  26.   {-editor paragraph list and undo buffer classes}
  27.  
  28. interface
  29.  
  30. uses
  31.   Controls, Forms, SysUtils,
  32.   OvcData, OvcMisc, OvcEditN, OvcEditU;
  33.  
  34. const
  35.   edMaxMarkers = 10;
  36.  
  37. {.Z+}
  38. type
  39.   TOvcUndoBuffer = class;
  40.  
  41.   TOvcParaList = class(TObject)
  42.   public
  43.     Owner      : TOvcEditBase; {original owner of this object}
  44.     Head       : TParaNode;    {start of list}
  45.     Tail       : TParaNode;    {end of list}
  46.     LastNode   : TParaNode;    {last node found}
  47.     LastP      : LongInt;      {last paragraph found}
  48.     LastN      : LongInt;      {last line found}
  49.     LastO      : Integer;      {lastO is line offset into LastNode for LastN}
  50.     ParaCount  : LongInt;      {total number of paragraphs}
  51.     ByteCount  : LongInt;      {total number of bytes}
  52.     LineCount  : LongInt;      {total number of lines}
  53.     MaxParas   : LongInt;      {max number of paragraphs}
  54.     MaxBytes   : LongInt;      {max number of bytes}
  55.     MaxParaLen : Integer;      {max paragraph length}
  56.     WordWrap   : Boolean;      {if True, word wrap is on}
  57.     WrapColumn : Integer;      {column for word wrap}
  58.     TabSize    : Byte;         {size for tab stops}
  59.     Modified   : Boolean;      {if True, text stream has been modified}
  60.     FixMarkers : Boolean;      {should markers be adjusted yet?}
  61.     Markers    : TMarkerArray; {position markers}
  62.     UndoBuffer : TOvcUndoBuffer;  {buffer of insertions/deletions}
  63.     InUndo     : Boolean;      {are we in the Undo routine?}
  64.     FLine      : LongInt;      {first changed line}
  65.     LLine      : LongInt;      {last changed line}
  66.  
  67.     constructor Init(AOwner : TOvcEditBase; UndoSize : Word; Wrap : Boolean);
  68.       virtual;
  69.       {-initialize the paragraph list; POwner is original owner of list}
  70.     destructor Destroy;
  71.       override;
  72.       {-destroy the paragraph list}
  73.  
  74.     {undo facility}
  75.     procedure MakeUndoRec(UT : UndoType; P : LongInt; Pos : Integer;
  76.                           S : PAnsiChar; Len : Word);
  77.       {-create an undo record}
  78.     procedure MakeReplaceUndoRec(P : LongInt; Pos : Integer;
  79.                                  S : PAnsiChar; Len : Word;
  80.                                  R : PAnsiChar; RLen : Word);
  81.       {-create an undo record for a replace operation}
  82.     procedure Undo(Editor : TOvcEditBase; var P : LongInt; var Pos : Integer);
  83.       {-undo last insertion/deletion/replacement}
  84.     procedure Redo(Editor : TOvcEditBase; var P : LongInt; var Pos : Integer);
  85.       {-redo last undone operation}
  86.     procedure SetUndoSize(Size : Word);
  87.       {-set size of undo buffer}
  88.  
  89.     {adding and deleting nodes}
  90.     procedure Insert(PPN : TParaNode);
  91.       {-insert element PPN at beginning of list}
  92.     procedure Append(PPN : TParaNode);
  93.       {-add a node to the end of the list}
  94.     procedure Place(PPN, LPN : TParaNode);
  95.       {-place element PPN into list after existing element LPN}
  96.     procedure PlaceBefore(PPN, LPN : TParaNode);
  97.       {-place element PPN into list before existing element LPN}
  98.  
  99.     {finding lines/paragraphs}
  100.     procedure SetLastNode(PPN : TParaNode; P, N : LongInt; O : Integer);
  101.       {-set LastNode to PPN, LastP to P, LastN to N, LastO to O}
  102.     function  NthPara(P : LongInt) : TParaNode;
  103.       {-return pointer to Nth paragraph}
  104.     function  NthLine(N : LongInt; var S : PAnsiChar; var Len : Word) : TParaNode;
  105.       {-return pointer to Nth line and its length}
  106.     function  FindParaByLine(N : LongInt; var LinePos : Integer) : LongInt;
  107.       {-return the index of the paragraph containing line N}
  108.     function  FindLineByPara(P : LongInt; Pos : Integer;
  109.                              var Col : Integer) : LongInt;
  110.       {-return the Line,Col corresponding to paragraph P, position Pos}
  111.  
  112.     {settings}
  113.     procedure ResetPositionInfo;
  114.       {-tell all editors to reset their position info}
  115.     procedure Recalculate;
  116.       {-recalculate number of lines}
  117.     function  GetWrapColumn : Integer;
  118.       {-get the column for word wrap}
  119.     procedure SetWrapColumn(Value : Integer);
  120.       {-set the column for word wrap}
  121.     procedure SetTabSize(Value : Byte);
  122.       {-set the tab size}
  123.     procedure SetByteLimit(Value : LongInt);
  124.       {-set limit on total bytes}
  125.     procedure SetParaLimit(Value : LongInt);
  126.       {-set limit on total paragraphs}
  127.     procedure SetWordWrap(Value : Boolean);
  128.       {-turn word wrap on or off}
  129.  
  130.     {effective columns to actual columns, etc.}
  131.     function  ParaLength(Value : LongInt) : Integer;
  132.       {-return length of paragraph P}
  133.     function  LineLength(Value : LongInt) : Integer;
  134.       {-return length of line N}
  135.     function  EffStrLen(S : PAnsiChar; Len : Word) : Word;
  136.       {-compute effective length of S}
  137.     function  EffLen(N : LongInt) : Word;
  138.       {-compute effective length of line N}
  139.     function  EffCol(S : PAnsiChar; Len, Col : Word) : Word;
  140.       {-compute effective column}
  141.     function  ActualCol(S : PAnsiChar; Len, Col : Word) : Word;
  142.       {-given an effective column #, return an actual column #}
  143.  
  144.     {inserting and deleting text}
  145.     function  OkToInsert(P : LongInt; Paras, Bytes : Word) : Word;
  146.       {-is it OK to insert Bytes characters into paragraph P?}
  147.     function  AppendParaEof(S : PAnsiChar; SLen : Word; Trim : Boolean) : Word;
  148.       {-create a new paragraph from S and append to end of file}
  149.     procedure InsertParaNode(Editor : TOvcEditBase; P : LongInt; NPN : TParaNode);
  150.       {-insert a new paragraph before paragraph P}
  151.     function  InsertParaPrim(Editor : TOvcEditBase; P : LongInt;
  152.                              S : PAnsiChar; Len : Word) : Word;
  153.       {-create a new paragraph with text from S^ and insert before paragraph P}
  154.     procedure PlaceParaNode(Editor : TOvcEditBase; P : LongInt; NPN : TParaNode);
  155.       {-place a new paragraph after paragraph P}
  156.     function PlaceParaPrim(Editor : TOvcEditBase; P : LongInt;
  157.                            S : PAnsiChar; Len : Word) : Word;
  158.       {-create a new paragraph with text from S^ and place it after paragraph P}
  159.     function  InsertTextPrim(Editor : TOvcEditBase; P : LongInt; Pos : Integer;
  160.                              S : PAnsiChar; SLen : Word) : Word;
  161.       {-insert SLen characters from S^ in paragraph P at Pos}
  162.     function  InsertBlock(Editor : TOvcEditBase; var P : LongInt;
  163.                           var Pos : Integer; S : PAnsiChar) : Word;
  164.       {-insert a block of text}
  165.     procedure DeletePara(Editor : TOvcEditBase; P : LongInt);
  166.       {-delete paragraph P}
  167.     procedure DeleteText(Editor : TOvcEditBase; P : LongInt; Pos, Count : Integer);
  168.       {-delete Count characters from paragraph P at Pos}
  169.     procedure DeleteBlock(Editor : TOvcEditBase; Para1 : LongInt; Pos1 : Integer;
  170.                           Para2 : LongInt; Pos2 : Integer);
  171.       {-delete the block from Para1,Pos1 to Para2,Pos2}
  172.     function  JoinWithNext(Editor : TOvcEditBase; P : LongInt; Pos : Integer) : Word;
  173.       {-join paragraph P with the following paragraph at Pos}
  174.     function  BreakPara(Editor : TOvcEditBase; P : LongInt; Pos, TabSz : Integer;
  175.                         var Indent : Integer; Trim : Boolean) : Word;
  176.       {-break paragraph P at Pos and indent the new paragraph}
  177.     function  ReplaceText(Editor : TOvcEditBase; P : LongInt;
  178.                           Pos, Count : Integer;
  179.                           St : PAnsiChar; StLen : Integer) : Word;
  180.       {-replace the next Count characters at P,Pos with text of St^}
  181.  
  182.     {text markers}
  183.     procedure SetMarker(N : Byte; Para : LongInt; Pos : Integer);
  184.     procedure SetMarkerAt(N : Byte; Line : LongInt; Col : Integer);
  185.     procedure FixMarkerInsertedPara(var M : TMarker; N : LongInt; Pos, Indent : Integer);
  186.     procedure FixMarkersInsertedPara(Editor : TOvcEditBase; N : LongInt; Pos, Indent : Integer);
  187.     procedure FixMarkerInsertedText(var M : TMarker; N : LongInt; Pos, Count : Integer);
  188.     procedure FixMarkersInsertedText(Editor : TOvcEditBase; N : LongInt; Pos, Count : Integer);
  189.     procedure FixMarkDeletedPara(var M : TMarker; N : LongInt);
  190.     procedure FixMarkerDeletedPara(var M : TMarker; N : LongInt);
  191.     procedure FixMarkersDeletedPara(Editor : TOvcEditBase; N : LongInt);
  192.     procedure FixMarkDeletedText(var M : TMarker; N : LongInt; Pos, Count : Integer);
  193.     procedure FixMarkerDeletedText(var M : TMarker; N : LongInt; Pos, Count : Integer);
  194.     procedure FixMarkersDeletedText(Editor : TOvcEditBase; N : LongInt; Pos, Count : Integer);
  195.     procedure FixMarkerJoinedParas(var M : TMarker; N : LongInt; Pos : Integer);
  196.     procedure FixMarkersJoinedParas(Editor : TOvcEditBase; N : LongInt; Pos : Integer);
  197.   end;
  198.  
  199.   TOvcUndoBuffer = class(TObject)
  200.   public
  201.     Owner    : TOvcParaList;
  202.     Buffer   : Pointer;   {pointer to the undo/redo buffer}
  203.     BufSize  : Word;      {size of the undo/redo buffer}
  204.     BufAvail : Word;      {free space in the buffer}
  205.     Last     : PUndoRec;  {points to the last TUndoRec in the buffer}
  206.     Undos    : Word;      {number of undo entries}
  207.     Redos    : Word;      {number of redo entries}
  208.     CurLink  : Byte;
  209.     Linking  : Boolean;
  210.     Error    : Boolean;
  211.     GUN      : TUndoNode; {general undo node object}
  212.  
  213.     constructor Init(ParaList : TOvcParaList; Size : Word);
  214.       virtual;
  215.     destructor Destroy;
  216.       override;
  217.  
  218.     procedure Flush;
  219.     function  CheckSize(Bytes : LongInt) : Boolean;
  220.     function  SameOperation(UT : UndoType; var Before : Boolean; P : LongInt;
  221.                             Pos : Integer; DLen : Word) : Boolean;
  222.     procedure Append(D : PAnsiChar; DLen : Word);
  223.     procedure Prepend(D : PAnsiChar; DLen : Word);
  224.     procedure AppendReplace(D : PAnsiChar; DLen : Word; R : PAnsiChar; RLen : Word);
  225.     procedure Push(UT : UndoType; MF : Boolean; P : LongInt; Pos : Integer;
  226.                    D : PAnsiChar; DLen : Word);
  227.     procedure PushReplace(MF : Boolean; P : LongInt; Pos : Integer;
  228.                           D : PAnsiChar; DLen : Word;
  229.                           R : PAnsiChar; RLen : Word);
  230.     procedure GetUndo(var UT : UndoType; var Link : Byte; var MF : Boolean;
  231.                       var P : LongInt; var Pos : Integer;
  232.                       var D : PAnsiChar; var DLen : Word);
  233.     procedure GetRedo(var UT : UndoType; var Link : Byte; var MF : Boolean;
  234.                       var P : LongInt; var Pos : Integer;
  235.                       var D : PAnsiChar; var DLen : Word);
  236.     procedure PeekUndoLink(var Link : Byte);
  237.     procedure PeekRedoLink(var Link : Byte);
  238.     function  NthRec(N : Integer) : PUndoRec;
  239.     procedure SetModified;
  240.     procedure BeginComplexOp(var SaveLinking : Boolean);
  241.     procedure EndComplexOp(SaveLinking : Boolean);
  242.   end;
  243. {.Z-}
  244.  
  245.  
  246. implementation
  247.  
  248. uses
  249.   OvcEdit;
  250.  
  251.  
  252.  
  253. {*** TOvcParaList ***}
  254.  
  255. function TOvcParaList.ActualCol(S : PAnsiChar; Len, Col : Word) : Word;
  256.   {-given an effective column #, return an actual column #}
  257. var
  258.   ELen : Word;
  259.   HT   : Boolean;
  260. begin
  261.   if (Col <= 1) or (Len = 0) then
  262.     Result := Col
  263.   else begin
  264.     HT := edHaveTabs(S, Len);
  265.     if HT then
  266.       ELen := EffCol(S, Len, Len)
  267.     else
  268.       ELen := Len;
  269.     if Col > ELen then
  270.       Result := Len+(Col-ELen)
  271.     else if HT then
  272.       Result := edGetActualCol(S, Col, TabSize)
  273.     else
  274.       Result := Col;
  275.   end;
  276. end;
  277.  
  278. procedure TOvcParaList.Append(PPN : TParaNode);
  279.   {-add a node to the end of the list}
  280. begin
  281.   {Exit for bad input}
  282.   if PPN = nil then
  283.     Exit;
  284.   PPN.Prev := Tail;
  285.   PPN.Next := nil;
  286.   if Head = nil then begin
  287.     Head := PPN;
  288.     Tail := PPN;
  289.   end else begin
  290.     Tail.Next := PPN;
  291.     Tail := PPN;
  292.   end;
  293.   Inc(ParaCount);
  294. end;
  295.  
  296. function TOvcParaList.AppendParaEof(S : PAnsiChar; SLen : Word; Trim : Boolean) : Word;
  297.   {-create a new paragraph from S and append to end of file}
  298. var
  299.   PPN : TParaNode;
  300. begin
  301.   PPN := TParaNode.InitLen(S, SLen, GetWrapColumn, TabSize);
  302.   if PPN = nil then
  303.     Result := oeOutOfMemory
  304.   else begin
  305.     PlaceBefore(PPN, Tail);
  306.     SetLastNode(Head, 1, 1, 1);
  307.     Inc(ByteCount, SLen+2);
  308.     if Trim then
  309.       Dec(ByteCount, PPN.TrimWhiteSpace);
  310.     Inc(LineCount, PPN.LineCount);
  311.     Result := 0;
  312.   end;
  313. end;
  314.  
  315. function TOvcParaList.BreakPara(Editor : TOvcEditBase; P : LongInt;
  316.                              Pos, TabSz : Integer; var Indent : Integer;
  317.                              Trim : Boolean) : Word;
  318.   {-break paragraph P at Pos and indent the new paragraph}
  319. var
  320.   PPN         : TParaNode;
  321.   S, T        : PAnsiChar;
  322.   Len, WC     : Word;
  323.   Spaces      : Integer;
  324.   Tabs        : Integer;
  325.   SaveLinking : Boolean;
  326. begin
  327.   Result := 0;
  328.   PPN := NthPara(P);
  329.   if (PPN <> nil) then begin
  330.     {When ScrollPastEnd is on Pos can be > Len+1, So, set}
  331.     {Len to the end of the string during para break}
  332.     Len := PPN.SLen;
  333.     if Pos-1 > Len then
  334.       Pos := Len+1;
  335.  
  336.     if (Pos = 1) then
  337.       Result := InsertParaPrim(Editor, P, '', 0)
  338.     else begin
  339.       UndoBuffer.BeginComplexOp(SaveLinking);
  340.  
  341.       S := PPN.GetS;
  342.       MakeUndoRec(utSavePos, P, Pos, S, 0);
  343.       FixMarkers := False;
  344.  
  345.       {create the new paragraph and link it in after this one}
  346.       T := @S[Pos-1];
  347.       Result := PlaceParaPrim(Editor, P, T, StrLen(T));
  348.       if Result <> 0 then begin
  349.         UndoBuffer.EndComplexOp(SaveLinking);
  350.         Exit;
  351.       end;
  352.  
  353.       {delete any text we've broken off}
  354.       if Pos <= Len then begin
  355.         if Trim then
  356.           WC := PPN.CountWhiteSpace(Pos-1)
  357.         else
  358.           WC := 0;
  359.         DeleteText(Editor, P, Pos-WC, Succ(Len-(Pos-WC)));
  360.       end;
  361.  
  362.       {do we need to indent?}
  363.       if Indent <> 0 then begin
  364.         if TabSz = 0 then begin
  365.           Spaces := Indent;
  366.           Tabs := 0;
  367.         end else begin
  368.           Tabs := Indent div TabSz;
  369.           Spaces := Indent mod TabSz;
  370.         end;
  371.         Indent := Spaces+Tabs;
  372.  
  373.         GetMem(T, Indent+1);
  374.         if T <> nil then begin
  375.           FillChar(T[0], Tabs, ^I);
  376.           FillChar(T[Tabs], Spaces, ' ');
  377.           T[Indent] := #0;
  378.  
  379.           InsertTextPrim(Editor, P+1, 1, T, Indent);
  380.         end else
  381.           Indent := 0;
  382.       end;
  383.  
  384.       MakeUndoRec(utSavePos, P+1, Indent+1, S, 0);
  385.       FixMarkers := True;
  386.       FixMarkersInsertedPara(Editor, P, Pos, Indent);
  387.       UndoBuffer.EndComplexOp(SaveLinking);
  388.     end;
  389.   end;
  390. end;
  391.  
  392. procedure TOvcParaList.DeleteBlock(Editor : TOvcEditBase;
  393.                                 Para1 : LongInt; Pos1 : Integer;
  394.                                 Para2 : LongInt; Pos2 : Integer);
  395.   {-delete the block from Para1,Pos1 to Para2,Pos2}
  396. var
  397.   P           : LongInt;
  398.   I           : Integer;
  399.   SaveLinking : Boolean;
  400. begin
  401.   UndoBuffer.BeginComplexOp(SaveLinking);
  402.   if (Pos1 = 1) and (Pos2 = 1) then begin
  403.     {these are whole paragraphs that need to be deleted}
  404.     for I := Para2-1 downto Para1 do
  405.       DeletePara(Editor, Para1);
  406.   end else begin
  407.     {delete text in 1st paragraph}
  408.     I := Succ(ParaLength(Para1)-Pos1);
  409.     if I > 0 then
  410.       DeleteText(Editor, Para1, Pos1, I);
  411.  
  412.     {delete text in 2st paragraph}
  413.     if Pos2 > 1 then
  414.       DeleteText(Editor, Para2, 1, Pos2-1);
  415.  
  416.     {now scan and delete all intervening paragraphs}
  417.     for P := Para2-1 downto Para1+1 do
  418.       DeletePara(Editor, Para1+1);
  419.  
  420.     {splice}
  421.     JoinWithNext(Editor, Para1, ParaLength(Para1)+1);
  422.   end;
  423.   UndoBuffer.EndComplexOp(SaveLinking);
  424. end;
  425.  
  426. procedure TOvcParaList.DeletePara(Editor : TOvcEditBase; P : LongInt);
  427.   {-delete paragraph P}
  428. var
  429.   PPN : TParaNode;
  430. begin
  431.   PPN := NthPara(P);
  432.   if not InUndo then
  433.     MakeUndoRec(utDelPara, P, 1, @PPN, SizeOf(PPN));
  434.  
  435.   if P = 1 then
  436.     SetLastNode(PPN.Next, 1, 1, 1)
  437.   else
  438.     SetLastNode(PPN.Prev, P-1, LastN-PPN.Prev.LineCount, 1);
  439.  
  440.   with PPN do begin
  441.     {Fix pointers of surrounding nodes}
  442.     if Next <> nil then
  443.       Next.Prev := Prev;
  444.     if Prev <> nil then
  445.       Prev.Next := Next;
  446.   end;
  447.  
  448.   {Fix head and tail of list}
  449.   if Tail = PPN then
  450.     Tail := Tail.Prev;
  451.   if Head = PPN then
  452.     Head := Head.Next;
  453.  
  454.   {adjust counters}
  455.   Dec(ParaCount);
  456.   Dec(LineCount, PPN.LineCount);
  457.   Dec(ByteCount, PPN.SLen+2);
  458.  
  459.   {dispose of the node if we didn't create an undo record}
  460.   if not InUndo then
  461.     if UndoBuffer.Error then
  462.       PPN.Free;
  463.  
  464.   FixMarkersDeletedPara(Editor, P);
  465.   Modified := True;
  466. end;
  467.  
  468. procedure TOvcParaList.DeleteText(Editor : TOvcEditBase; P : LongInt;
  469.                                Pos, Count : Integer);
  470.   {-delete Count characters from paragraph P at Pos}
  471. var
  472.   PPN    : TParaNode;
  473.   LC     : LongInt;
  474.   FL, LL : Integer;
  475. begin
  476.   PPN := NthPara(P);
  477.  
  478.   {make sure that we have at least this many characters to delete}
  479.   if Count + Pos-1 > PPN.SLen then
  480.     Count := PPN.SLen-Pos+1;
  481.   if (Count < 0) or (PPN.S = nil) then
  482.     Count := 0;
  483.  
  484.   LC := PPN.LineCount;
  485.   MakeUndoRec(utDelete, P, Pos, @PPN.S[Pos-1], Count);
  486.   PPN.DeleteText(Pos, Count, GetWrapColumn, TabSize, FL, LL);
  487.   Dec(ByteCount, Count);
  488.   Dec(LineCount, LC-PPN.LineCount);
  489.   FLine := Pred(LastN)+FL;
  490.   if PPN.LineCount <> LC then
  491.     LLine := MaxLongInt
  492.   else
  493.     LLine := Pred(LastN)+LL;
  494.   FixMarkersDeletedText(Editor, P, Pos, Count);
  495.   Modified := True;
  496. end;
  497.  
  498. destructor TOvcParaList.Destroy;
  499.   {-destroy the paragraph list}
  500. var
  501.   N, P : TParaNode;
  502. begin
  503.   UndoBuffer.Free;
  504.   N := Tail;
  505.   while N <> nil do begin
  506.     {Get pointer to previous node}
  507.     P := N.Prev;
  508.     N.Free;
  509.     N := P;
  510.   end;
  511. end;
  512.  
  513. function TOvcParaList.EffCol(S : PAnsiChar; Len, Col : Word) : Word;
  514.   {-compute effective column}
  515. begin
  516.   if (Col <= 1) or (Len = 0) then
  517.     if Col = 0 then
  518.       Result := 1
  519.     else
  520.       Result := Col
  521.   else if Col > Len then
  522.     Result := EffStrLen(S, Len)+(Col-Len)
  523.   else
  524.     Result := EffStrLen(S, Col-1)+1;
  525. end;
  526.  
  527. function TOvcParaList.EffLen(N : LongInt) : Word;
  528.   {-compute effective length of line N}
  529. var
  530.   S   : PAnsiChar;
  531.   Len : Word;
  532. begin
  533.   NthLine(N, S, Len);
  534.   Result := EffStrLen(S, Len);
  535. end;
  536.  
  537. function TOvcParaList.EffStrLen(S : PAnsiChar; Len : Word) : Word;
  538.   {-compute effective length of S}
  539. begin
  540.   if (Len > 0) and edHaveTabs(S, Len) then
  541.     Result := edEffectiveLen(S, Len, TabSize)
  542.   else
  543.     Result := Len;
  544. end;
  545.  
  546. function TOvcParaList.FindLineByPara(P : LongInt; Pos : Integer;
  547.                                   var Col : Integer) : LongInt;
  548.   {-return the Line,Col corresponding to paragraph P, position Pos}
  549. var
  550.   PPN : TParaNode;
  551. begin
  552.   if P > ParaCount then
  553.     P := ParaCount;
  554.   if P < 1 then
  555.     P := 1;
  556.   PPN := NthPara(P);
  557.   Result := LastN+PPN.PosToLine(Pos, Col)-1
  558. end;
  559.  
  560. function TOvcParaList.FindParaByLine(N : LongInt; var LinePos : Integer) : LongInt;
  561.   {-return the index of the paragraph containing line N}
  562. var
  563.   PPN  : TParaNode;
  564.   S, T : PAnsiChar;
  565.   Len  : Word;
  566. begin
  567.   if not WordWrap then begin
  568.     LinePos := 0;
  569.     Result := N;
  570.   end else begin
  571.     PPN := NthLine(N, S, Len);
  572.     T := PPN.GetS;
  573.     LinePos := PtrDiff(S, T);
  574.     Result := LastP;
  575.   end;
  576. end;
  577.  
  578. procedure TOvcParaList.FixMarkDeletedPara(var M : TMarker; N : LongInt);
  579. begin
  580.   if M.Para = N then
  581.     M.Pos := 1
  582.   else if M.Para > N then
  583.     Dec(M.Para);
  584.   if M.Para > ParaCount then
  585.     M.Para := ParaCount;
  586. end;
  587.  
  588. procedure TOvcParaList.FixMarkDeletedText(var M : TMarker; N : LongInt;
  589.                                        Pos, Count : Integer);
  590. begin
  591.   if M.Para = N then
  592.     if M.Pos >= Pos then
  593.       if M.Pos < Pos+Count then
  594.         M.Pos := Pos
  595.       else
  596.         Dec(M.Pos, Count);
  597. end;
  598.  
  599. procedure TOvcParaList.FixMarkerDeletedPara(var M : TMarker; N : LongInt);
  600. begin
  601.   if M.Para = N then
  602.     FillChar(M, SizeOf(TMarker), 0)
  603.   else if M.Para > N then
  604.     Dec(M.Para);
  605. end;
  606.  
  607. procedure TOvcParaList.FixMarkerDeletedText(var M : TMarker; N : LongInt;
  608.                                          Pos, Count : Integer);
  609. begin
  610.   if M.Para = N then
  611.     if M.Pos >= Pos then
  612.       if M.Pos < Pos+Count then
  613.         FillChar(M, SizeOf(TMarker), 0)
  614.       else
  615.         Dec(M.Pos, Count);
  616. end;
  617.  
  618. procedure TOvcParaList.FixMarkerInsertedPara(var M : TMarker; N : LongInt;
  619.                                           Pos, Indent : Integer);
  620. begin
  621.   if M.Para > N then
  622.     Inc(M.Para)
  623.   else if M.Para = N then
  624.     if M.Pos >= Pos then begin
  625.       Inc(M.Para);
  626.       Inc(M.Pos, Indent-Pred(Pos));
  627.     end;
  628. end;
  629.  
  630. procedure TOvcParaList.FixMarkerInsertedText(var M : TMarker; N : LongInt;
  631.                                           Pos, Count : Integer);
  632. begin
  633.   if (M.Para = N) and (M.Pos >= Pos) then
  634.     Inc(M.Pos, Count);
  635. end;
  636.  
  637. procedure TOvcParaList.FixMarkerJoinedParas(var M : TMarker; N : LongInt; Pos : Integer);
  638. begin
  639.   if M.Para > N then
  640.     if M.Para = N+1 then begin
  641.       Dec(M.Para);
  642.       Inc(M.Pos, Pred(Pos));
  643.     end else
  644.       Dec(M.Para);
  645. end;
  646.  
  647. procedure TOvcParaList.FixMarkersDeletedPara(Editor : TOvcEditBase; N : LongInt);
  648. var
  649.   I : Integer;
  650. begin
  651.   if FixMarkers then begin
  652.     for I := 0 to edMaxMarkers-1 do
  653.       FixMarkerDeletedPara(Markers[I], N);
  654.     TOvcCustomEditor(Editor).edUpdateOnDeletedPara(N);
  655.   end;
  656. end;
  657.  
  658. procedure TOvcParaList.FixMarkersDeletedText(Editor : TOvcEditBase; N : LongInt;
  659.                                           Pos, Count : Integer);
  660. var
  661.   I : Integer;
  662. begin
  663.   if FixMarkers then begin
  664.     for I := 0 to edMaxMarkers-1 do
  665.       FixMarkerDeletedText(Markers[I], N, Pos, Count);
  666.     TOvcCustomEditor(Editor).edUpdateOnDeletedText(N, Pos, Count);
  667.   end;
  668. end;
  669.  
  670. procedure TOvcParaList.FixMarkersInsertedPara(Editor : TOvcEditBase; N : LongInt;
  671.                                            Pos, Indent : Integer);
  672. var
  673.   I : Integer;
  674. begin
  675.   if FixMarkers then begin
  676.     for I := 0 to edMaxMarkers-1 do
  677.       FixMarkerInsertedPara(Markers[I], N, Pos, Indent);
  678.     TOvcCustomEditor(Editor).edUpdateOnInsertedPara(N, Pos, Indent);
  679.   end;
  680. end;
  681.  
  682. procedure TOvcParaList.FixMarkersInsertedText(Editor : TOvcEditBase; N : LongInt;
  683.                                            Pos, Count : Integer);
  684. var
  685.   I : Integer;
  686. begin
  687.   if FixMarkers then begin
  688.     for I := 0 to edMaxMarkers-1 do
  689.       FixMarkerInsertedText(Markers[I], N, Pos, Count);
  690.     TOvcCustomEditor(Editor).edUpdateOnInsertedText(N, Pos, Count);
  691.   end;
  692. end;
  693.  
  694. procedure TOvcParaList.FixMarkersJoinedParas(Editor : TOvcEditBase; N : LongInt;
  695.                                           Pos : Integer);
  696. var
  697.   I : Integer;
  698. begin
  699.   if FixMarkers then begin
  700.     for I := 0 to edMaxMarkers-1 do
  701.       FixMarkerJoinedParas(Markers[I], N, Pos);
  702.     TOvcCustomEditor(Editor).edUpdateOnJoinedParas(N, Pos);
  703.   end;
  704. end;
  705.  
  706. function TOvcParaList.GetWrapColumn : Integer;
  707.   {-get the wrap column}
  708. begin
  709.   if WordWrap then
  710.     Result := WrapColumn
  711.   else
  712.     Result := High(SmallInt);
  713. end;
  714.  
  715. constructor TOvcParaList.Init(AOwner : TOvcEditBase; UndoSize : Word; Wrap : Boolean);
  716.   {-initialize the paragraph list; POwner is original owner of list}
  717. var
  718.   PPN : TParaNode;
  719. begin
  720.   inherited Create;
  721.  
  722.   Owner := AOwner;
  723.   Head := nil;
  724.   Tail := nil;
  725.   ParaCount := 0;
  726.   LineCount := 1;
  727.   ByteCount := 0;
  728.   MaxParas := MaxLongInt;
  729.   MaxBytes := MaxLongInt;
  730.   MaxParaLen := High(SmallInt);
  731.   WordWrap := Wrap;
  732.   WrapColumn := 80;
  733.   TabSize := 8;
  734.   Modified := False;
  735.   FixMarkers := True;
  736.   PPN := TParaNode.InitLen('', 0, 0, TabSize);
  737.   Append(PPN);
  738.   SetLastNode(PPN, 1, 1, 1);
  739.   FillChar(Markers, SizeOf(Markers), 0);
  740.   UndoBuffer := TOvcUndoBuffer.Init(@Self, UndoSize);
  741.   InUndo := False;
  742. end;
  743.  
  744. procedure TOvcParaList.Insert(PPN : TParaNode);
  745.   {-insert element PPN at beginning of list}
  746. begin
  747.   {Exit for bad input}
  748.   if (PPN = nil) then
  749.     Exit;
  750.   PPN.Prev := nil;
  751.   PPN.Next := Head;
  752.   if Head = nil then
  753.     {Special case for first node}
  754.     Tail := PPN
  755.   else
  756.     {Add at start of existing list}
  757.     Head.Prev := PPN;
  758.   Head := PPN;
  759.   Inc(ParaCount);
  760. end;
  761.  
  762. function TOvcParaList.InsertBlock(Editor : TOvcEditBase; var P : LongInt;
  763.                                var Pos : Integer; S : PAnsiChar) : Word;
  764.   {-insert a block of text}
  765. var
  766.   L, Len, Max : Word;
  767.   I, OPos : Integer;
  768.   SaveLinking : Boolean;
  769.   OP : LongInt;
  770. begin
  771.   Max := StrLen(S);
  772.   Len := edScanToEnd(S, Max);
  773.   L := Len;
  774.   while (L > 0) and ((S[L-1] = ^M) or (S[L-1] = ^J)) do
  775.     Dec(L);
  776.   if (Len = Max) and (L = Len) then begin
  777.     Result := InsertTextPrim(Editor, P, Pos, S, Len);
  778.     if Result = 0 then
  779.       Inc(Pos, Len);
  780.   end else begin
  781.     OP := P;
  782.     OPos := Pos;
  783.     Result := 0;
  784.     I := 0;
  785.     UndoBuffer.BeginComplexOp(SaveLinking);
  786.     while (Result = 0) and (S^ <> #0) do begin
  787.       if (Len > L) and (Pos = 1) then
  788.         Result := InsertParaPrim(Editor, P, S, L)
  789.       else begin
  790.         if L <> 0 then begin
  791.           Result := InsertTextPrim(Editor, P, Pos, S, L);
  792.           if Result = 0 then
  793.             Inc(Pos, L);
  794.         end;
  795.         if (Result = 0) and (Len > L) then
  796.           Result := BreakPara(Editor, P, Pos, 0, I, True);
  797.       end;
  798.       if (Result = 0) and (Len > L) then begin
  799.         Inc(P);
  800.         Pos := 1;
  801.       end;
  802.       if (Result = 0) then begin
  803.         Inc(S, Len);
  804.         Dec(Max, Len);
  805.       end;
  806.       if S^ <> #0 then begin
  807.         Len := edScanToEnd(S, Max);
  808.         L := Len;
  809.         while (L > 0) and ((S[L-1] = ^M) or (S[L-1] = ^J)) do
  810.           Dec(L);
  811.       end;
  812.     end;
  813.     UndoBuffer.EndComplexOp(SaveLinking);
  814.     FLine := FindLineByPara(OP, OPos, I);
  815.     if FLine > 1 then
  816.       Dec(FLine);
  817.     LLine := MaxLongInt;
  818.   end;
  819. end;
  820.  
  821. procedure TOvcParaList.InsertParaNode(Editor : TOvcEditBase; P : LongInt;
  822.                                    NPN : TParaNode);
  823.   {-insert a new paragraph before paragraph N}
  824. var
  825.   PPN : TParaNode;
  826. begin
  827.   PPN := NthPara(P);
  828.   Inc(ByteCount, NPN.SLen+2);
  829.   NPN.Recalc(GetWrapColumn, TabSize);
  830.   Inc(LineCount, NPN.LineCount);
  831.   if PPN = nil then
  832.     Append(NPN)
  833.   else
  834.     PlaceBefore(NPN, PPN);
  835.   SetLastNode(NPN, P, LastN, 1);
  836.   FixMarkersInsertedPara(Editor, P, 1, 0);
  837.   Modified := True;
  838. end;
  839.  
  840. function TOvcParaList.InsertParaPrim(Editor : TOvcEditBase; P : LongInt;
  841.                                   S : PAnsiChar; Len : Word) : Word;
  842.   {-create a new paragraph with text from S^ and insert before paragraph P}
  843. var
  844.   PPN, NPN : TParaNode;
  845.   Res : Word;
  846. begin
  847.   Result := 0;
  848.   PPN := NthPara(P);
  849.   if PPN = nil then
  850.     Exit;
  851.   Res := OkToInsert(0, 1, Len+2);
  852.   if Res = 0 then begin
  853.     NPN := TParaNode.InitLen(S, Len, GetWrapColumn, TabSize);
  854.     if NPN = nil then
  855.       Result := oeOutOfMemory
  856.     else begin
  857.       MakeUndoRec(utInsPara, P, 1, @NPN, SizeOf(NPN));
  858.       InsertParaNode(Editor, P, NPN);
  859.     end;
  860.   end else
  861.     Result := Res;
  862. end;
  863.  
  864. function TOvcParaList.InsertTextPrim(Editor : TOvcEditBase; P : LongInt; Pos : Integer;
  865.                                   S : PAnsiChar; SLen : Word) : Word;
  866.   {-insert SLen characters from S^ in paragraph P at Pos}
  867. var
  868.   PPN   : TParaNode;
  869.   I     : Word;
  870.   OLen  : Word;
  871.   NLen  : Word;
  872.   Delta : Word;
  873.   NS    : PAnsiChar;
  874.   LC    : LongInt;
  875.   FL    : Integer;
  876.   LL    : Integer;
  877. begin
  878.   Result := 0;
  879.   PPN := NthPara(P);
  880.   if PPN = nil then
  881.     Exit;
  882.   OLen := PPN.SLen;
  883.   LC := PPN.LineCount;
  884.   I := OkToInsert(P, 0, SLen);
  885.   if I = 0 then
  886.     I := PPN.InsertTextPrim(S, SLen, Pos, GetWrapColumn, TabSize, FL, LL);
  887.   if I = 0 then begin
  888.     NS := PPN.GetS;
  889.     NLen := PPN.SLen;
  890.     Delta := (NLen-OLen)-SLen;
  891.     if Delta > 0 then
  892.       MakeUndoRec(utInsert, P, Pos-Delta, @NS[OLen], Delta+SLen)
  893.     else
  894.       MakeUndoRec(utInsert, P, Pos, S, SLen);
  895.     Inc(ByteCount, NLen-OLen);
  896.     Inc(LineCount, PPN.LineCount-LC);
  897.     FLine := Pred(LastN)+FL;
  898.     if PPN.LineCount <> LC then
  899.       LLine := MaxLongInt
  900.     else
  901.       LLine := Pred(LastN)+LL;
  902.     FixMarkersInsertedText(Editor, P, Pos, SLen);
  903.     Modified := True;
  904.   end;
  905.   Result := I;
  906. end;
  907.  
  908. function TOvcParaList.JoinWithNext(Editor : TOvcEditBase; P : LongInt; Pos : Integer) : Word;
  909.   {-join paragraph P with the following paragraph at Pos}
  910. var
  911.   PPN         : TParaNode;
  912.   NPN         : TParaNode;
  913.   S           : PAnsiChar;
  914.   SLen        : Word;
  915.   D           : Word;
  916.   SaveLinking : Boolean;
  917. begin
  918.   Result := 0;
  919.  
  920.   PPN := NthPara(P);
  921.   if (PPN = nil) or (PPN.Next = nil) then
  922.     Exit;
  923.  
  924.   UndoBuffer.BeginComplexOp(SaveLinking);
  925.  
  926.   if (Pos = 1) then begin
  927.     FixMarkers := False;
  928.     DeletePara(Editor, P);
  929.     FixMarkers := True;
  930.     FixMarkersJoinedParas(Editor, P, Pos);
  931.   end else begin
  932.     NPN := PPN.Next;
  933.     S := NPN.GetS;
  934.     SLen := NPN.SLen;
  935.     D := Pred(Pos)-ParaLength(P);
  936.     Result := OkToInsert(P, 0, SLen+D);
  937.     if Result <> 0 then
  938.       Result := oeCannotJoin
  939.     else begin
  940.       FixMarkers := False;
  941.       if SLen > 0 then
  942.         Result := InsertTextPrim(Editor, P, Pos, S, SLen);
  943.       if Result = 0 then
  944.         DeletePara(Editor, P+1);
  945.       FixMarkers := True;
  946.       MakeUndoRec(utSavePos, P, Pos, S, 0);
  947.       if Result = 0 then
  948.         FixMarkersJoinedParas(Editor, P, Pos);
  949.     end;
  950.   end;
  951.  
  952.   UndoBuffer.EndComplexOp(SaveLinking);
  953. end;
  954.  
  955. function TOvcParaList.LineLength(Value : LongInt) : Integer;
  956.   {-return length of line N}
  957. var
  958.   S : PAnsiChar;
  959.   W : Word;
  960. begin
  961.   NthLine(Value, S, W);
  962.   Result := W;
  963. end;
  964.  
  965. procedure TOvcParaList.MakeReplaceUndoRec(P : LongInt; Pos : Integer;
  966.                                        S : PAnsiChar; Len : Word;
  967.                                        R : PAnsiChar; RLen : Word);
  968.   {-create an undo record for a replace operation}
  969. begin
  970.   if not InUndo then
  971.     UndoBuffer.PushReplace(Modified, P, Pos, S, Len, R, RLen);
  972. end;
  973.  
  974. procedure TOvcParaList.MakeUndoRec(UT : UndoType; P : LongInt; Pos : Integer;
  975.                                 S : PAnsiChar; Len : Word);
  976.   {-create an undo record}
  977. begin
  978.   if not InUndo then
  979.     UndoBuffer.Push(UT, Modified, P, Pos, S, Len);
  980. end;
  981.  
  982. function TOvcParaList.NthLine(N : LongInt; var S : PAnsiChar;
  983.                            var Len : Word) : TParaNode;
  984.   {-return pointer to Nth line and its length}
  985. var
  986.   I   : LongInt;
  987.   P   : LongInt;
  988.   O   : Integer;
  989.   PPN : TParaNode;
  990.   DF  : LongInt;
  991.   DL  : LongInt;
  992.   DE  : LongInt;
  993. begin
  994.   if not WordWrap then begin
  995.     PPN := NthPara(N);
  996.     if PPN = nil then begin
  997.       S := '';
  998.       Len := 0;
  999.       Result := nil;
  1000.       Exit;
  1001.     end;
  1002.     S := PPN.GetS;
  1003.     Len := PPN.SLen;
  1004.     Result := PPN;
  1005.     Exit;
  1006.   end;
  1007.  
  1008.   if N = LastN then begin
  1009.     S := LastNode.NthLine(LastO, Len);
  1010.     Result := LastNode;
  1011.   end else if N = 1 then begin
  1012.     S := Head.NthLine(1, Len);
  1013.     SetLastNode(Head, 1, 1, 1);
  1014.     Result := Head;
  1015.   end else if N = LineCount then begin
  1016.     S := Tail.NthLine(Tail.LineCount, Len);
  1017.     SetLastNode(Tail, ParaCount, N, Tail.LineCount);
  1018.     Result := Tail;
  1019.   end else begin
  1020.     {is it the next line after the last one we found?}
  1021.     if N = LastN+1 then begin
  1022.       if LastO < LastNode.LineCount then begin
  1023.         PPN := LastNode;
  1024.         P := LastP;
  1025.         O := LastO+1;
  1026.       end else begin
  1027.         PPN := LastNode.Next;
  1028.         P := LastP+1;
  1029.         O := 1;
  1030.       end;
  1031.     end else if N = LastN-1 then begin
  1032.       {the line before the last one we found?}
  1033.       if LastO > 1 then begin
  1034.         PPN := LastNode;
  1035.         P := LastP;
  1036.         O := LastO-1;
  1037.       end else begin
  1038.         PPN := LastNode.Prev;
  1039.         P := LastP-1;
  1040.         O := PPN.LineCount;
  1041.       end;
  1042.     end else begin  {we need to search}
  1043.       DF := N-1;
  1044.       DL := Abs(N-LastN);
  1045.       DE := LineCount-N;
  1046.  
  1047.       {is it closest to the first line?}
  1048.       if (DF <= DL) and (DF <= DE) then begin
  1049.         PPN := Head;
  1050.         P := 1;
  1051.         I := 1;
  1052.       end else if (DE <= DL) then begin
  1053.         {closest to the last line?}
  1054.         PPN := Tail;
  1055.         P := ParaCount;
  1056.         I := LineCount-Pred(PPN.LineCount);
  1057.       end else begin
  1058.         {closest to the last known line}
  1059.         PPN := LastNode;
  1060.         P := LastP;
  1061.         I := LastN-Pred(LastO);
  1062.       end;
  1063.  
  1064.       {go backward if we're too far}
  1065.       while I > N do begin
  1066.         PPN := PPN.Prev;
  1067.         Dec(P);
  1068.         Dec(I, PPN.LineCount);
  1069.       end;
  1070.  
  1071.       {go forward if we're not far enough}
  1072.       while (I+PPN.LineCount <= N) do begin
  1073.         Inc(I, PPN.LineCount);
  1074.         Inc(P);
  1075.         {stop if at the end of the text stream}
  1076.         if PPN.Next = nil then
  1077.           Break;
  1078.         PPN := PPN.Next;
  1079.       end;
  1080.  
  1081.       O := Succ(N-I);
  1082.     end;
  1083.  
  1084.     {we found the line}
  1085.     SetLastNode(PPN, P, N, O);
  1086.     S := PPN.NthLine(O, Len);
  1087.     Result := PPN;
  1088.   end;
  1089. end;
  1090.  
  1091. function TOvcParaList.NthPara(P : LongInt) : TParaNode;
  1092.   {-return pointer to Pth paragraph}
  1093. var
  1094.   I   : LongInt;
  1095.   N   : LongInt;
  1096.   PPN : TParaNode;
  1097.   DF  : LongInt;
  1098.   DL  : LongInt;
  1099.   DE  : LongInt;
  1100. begin
  1101.   if (P < 1) or (P > ParaCount) then begin
  1102.     Result := nil;
  1103.     Exit;
  1104.   end;
  1105.  
  1106.   Dec(LastN, Pred(LastO));
  1107.   LastO := 1;
  1108.   if P = LastP then
  1109.     Result := LastNode
  1110.   else if P = 1 then begin
  1111.     SetLastNode(Head, 1, 1, 1);
  1112.     Result := Head;
  1113.   end else if P = ParaCount then begin
  1114.     SetLastNode(Tail, P, LineCount-Pred(Tail.LineCount), 1);
  1115.     Result := Tail;
  1116.   end else begin
  1117.     if P = LastP+1 then begin
  1118.       N := LastN+LastNode.LineCount;
  1119.       PPN := LastNode.Next;
  1120.     end else if P = LastP-1 then begin
  1121.       PPN := LastNode.Prev;
  1122.       N := LastN-PPN.LineCount;
  1123.     end else begin
  1124.       DF := P-1;
  1125.       DL := Abs(P-LastP);
  1126.       DE := ParaCount-P;
  1127.  
  1128.       {is it closest to the first paragraph?}
  1129.       if (DF <= DL) and (DF <= DE) then begin
  1130.         PPN := Head;
  1131.         I := 1;
  1132.         N := 1;
  1133.       end else if (DE <= DL) then begin
  1134.         {closest to the last paragraph?}
  1135.         PPN := Tail;
  1136.         I := ParaCount;
  1137.         N := LineCount-Pred(PPN.LineCount);
  1138.       end else if (P > LastP) then begin
  1139.         {after the last known paragraph?}
  1140.         I := LastP;
  1141.         PPN := LastNode;
  1142.         N := LastN;
  1143.       end else begin
  1144.         {before the last known paragraph?}
  1145.         I := LastP;
  1146.         PPN := LastNode;
  1147.         N := LastN;
  1148.       end;
  1149.  
  1150.       {go backward as necessary}
  1151.       while (I > P) and (PPN <> nil) do begin
  1152.         PPN := PPN.Prev;
  1153.         Dec(N, PPN.LineCount);
  1154.         Dec(I);
  1155.       end;
  1156.  
  1157.       {go forward as necessary}
  1158.       while (I < P) and (PPN <> nil) do begin
  1159.         Inc(N, PPN.LineCount);
  1160.         PPN := PPN.Next;
  1161.         Inc(I);
  1162.       end;
  1163.     end;
  1164.  
  1165.     if PPN <> nil then
  1166.       SetLastNode(PPN, P, N, 1);
  1167.     Result := PPN;
  1168.   end;
  1169. end;
  1170.  
  1171. function TOvcParaList.OkToInsert(P : LongInt; Paras, Bytes : Word) : Word;
  1172.   {-is it OK to insert Bytes characters into paragraph P?}
  1173. var
  1174.   PPN : TParaNode;
  1175.   M   : LongInt;
  1176. begin
  1177.   Result := 0;
  1178.  
  1179.   {check for too many paragraphs}
  1180.   if (Paras <> 0) or (P > MaxParas) then begin
  1181.     if (P > MaxParas) or (ParaLength(ParaCount) <> 0) then
  1182.       M := MaxParas
  1183.     else if MaxParas = MaxLongInt then
  1184.       M := MaxLongInt
  1185.     else
  1186.       M := MaxParas+1;
  1187.     if (ParaCount+Paras > M) then begin
  1188.       Result := oeTooManyParas;
  1189.       Exit;
  1190.     end;
  1191.   end;
  1192.  
  1193.   {check for too many total bytes}
  1194.   if ByteCount+Bytes > MaxBytes then begin
  1195.     Result := oeTooManyBytes;
  1196.     Exit;
  1197.   end;
  1198.  
  1199.   {check for paragraph too long}
  1200.   if (P <> 0) and (Paras = 0) then begin
  1201.     PPN := NthPara(P);
  1202.     if PPN <> nil then
  1203.       if LongInt(Bytes)+PPN.SLen > MaxParaLen then
  1204.         Result := oeParaTooLong;
  1205.   end;
  1206. end;
  1207.  
  1208. function TOvcParaList.ParaLength(Value : LongInt) : Integer;
  1209.   {-return length of paragraph}
  1210. var
  1211.   PPN : TParaNode;
  1212. begin
  1213.   PPN := NthPara(Value);
  1214.   if PPN = nil then
  1215.     Result := 0
  1216.   else
  1217.     Result := PPN.SLen;
  1218. end;
  1219.  
  1220. procedure TOvcParaList.Place(PPN, LPN : TParaNode);
  1221.   {-place element PPN into list after existing element LPN}
  1222. begin
  1223.   {Exit for bad input}
  1224.   if (PPN = nil) or (PPN = LPN) then
  1225.     Exit;
  1226.   if LPN = nil then
  1227.     Insert(PPN)
  1228.   else if LPN = Tail then
  1229.     Append(PPN)
  1230.   else begin
  1231.     PPN.Prev := LPN;
  1232.     PPN.Next := LPN.Next;
  1233.     LPN.Next.Prev := PPN;
  1234.     LPN.Next := PPN;
  1235.     Inc(ParaCount);
  1236.   end;
  1237. end;
  1238.  
  1239. procedure TOvcParaList.PlaceBefore(PPN, LPN : TParaNode);
  1240.   {-place element PPN into list before existing element LPN}
  1241. begin
  1242.   {Exit for bad input}
  1243.   if (PPN = nil) or (PPN = LPN) then
  1244.     Exit;
  1245.   if (LPN = nil) or (LPN = Head) then
  1246.     {Place the new element at the start of the list}
  1247.     Insert(PPN)
  1248.   else begin
  1249.     {Patch in the new element}
  1250.     PPN.Next := LPN;
  1251.     PPN.Prev := LPN.Prev;
  1252.     LPN.Prev.Next := PPN;
  1253.     LPN.Prev := PPN;
  1254.     Inc(ParaCount);
  1255.   end;
  1256. end;
  1257.  
  1258. procedure TOvcParaList.PlaceParaNode(Editor : TOvcEditBase; P : LongInt;
  1259.                                   NPN : TParaNode);
  1260.   {-place a new paragraph after paragraph P}
  1261. var
  1262.   PPN : TParaNode;
  1263. begin
  1264.   PPN := NthPara(P);
  1265.   if PPN = nil then
  1266.     Exit;
  1267.  
  1268.   Inc(ByteCount, NPN.SLen+2);
  1269.   NPN.Recalc(GetWrapColumn, TabSize);
  1270.   Inc(LineCount, NPN.LineCount);
  1271.   Place(NPN, PPN);
  1272.   FixMarkersInsertedPara(Editor, P+1, 1, 0);
  1273.  
  1274.   Modified := True;
  1275. end;
  1276.  
  1277. function TOvcParaList.PlaceParaPrim(Editor : TOvcEditBase; P : LongInt;
  1278.                                  S : PAnsiChar; Len : Word) : Word;
  1279.   {-create a new paragraph with text from S^ and place it after paragraph P}
  1280. var
  1281.   PPN : TParaNode;
  1282.   NPN : TParaNode;
  1283. begin
  1284.   Result := 0;
  1285.   PPN := NthPara(P);
  1286.   if PPN = nil then
  1287.     Exit;
  1288.   NPN := TParaNode.InitLen(S, Len, GetWrapColumn, TabSize);
  1289.   if NPN = nil then
  1290.     Result := oeOutOfMemory
  1291.   else begin
  1292.     MakeUndoRec(utPlacePara, P, 1, @NPN, SizeOf(NPN));
  1293.     PlaceParaNode(Editor, P, NPN);
  1294.   end;
  1295. end;
  1296.  
  1297. procedure TOvcParaList.Recalculate;
  1298.   {-recalculate number of lines}
  1299. var
  1300.   PPN : TParaNode;
  1301.   LC  : LongInt;
  1302.   WC  : Integer;
  1303. begin
  1304.   {switch cursors if this is going to take a while}
  1305.   if ByteCount > 10000 then
  1306.     Screen.Cursor := crHourGlass;
  1307.   try
  1308.     {recalculate the total number of lines}
  1309.     WC := GetWrapColumn;
  1310.     LC := 0;
  1311.     PPN := Head;
  1312.     while PPN <> nil do begin
  1313.       PPN.Recalc(WC, TabSize);
  1314.       Inc(LC, PPN.LineCount);
  1315.       PPN := PPN.Next;
  1316.     end;
  1317.     LineCount := LC;
  1318.     SetLastNode(Head, 1, 1, 1);
  1319.   finally
  1320.     {restore cursor}
  1321.     if ByteCount > 10000 then
  1322.       Screen.Cursor := crDefault;
  1323.   end;
  1324. end;
  1325.  
  1326. procedure TOvcParaList.Redo(Editor : TOvcEditBase; var P : LongInt; var Pos : Integer);
  1327.   {-redo last undone operation}
  1328. var
  1329.   UT       : UndoType;
  1330.   D        : PAnsiChar;
  1331.   S        : PAnsiChar;
  1332.   DLen     : Word;
  1333.   OrigLink : Byte;
  1334.   Link     : Byte;
  1335.   PPPN     : ^TParaNode absolute D;
  1336. begin
  1337.   InUndo := True;
  1338.   UndoBuffer.PeekRedoLink(OrigLink);
  1339.   repeat
  1340.     UndoBuffer.GetRedo(UT, Link, Modified, P, Pos, D, DLen);
  1341.     case UT of
  1342.       utInsert :
  1343.         begin
  1344.           InsertTextPrim(Editor, P, Pos, D, DLen);
  1345.           Inc(Pos, DLen);
  1346.         end;
  1347.       utInsPara :
  1348.         begin
  1349.           InsertParaNode(Editor, P, PPPN^);
  1350.           Inc(P);
  1351.           Pos := 1;
  1352.         end;
  1353.       utPlacePara :
  1354.         begin
  1355.           PlaceParaNode(Editor, P, PPPN^);
  1356.           Inc(P);
  1357.           Pos := 1;
  1358.         end;
  1359.       utDelete :
  1360.         DeleteText(Editor, P, Pos, DLen);
  1361.       utDelPara :
  1362.         DeletePara(Editor, P);
  1363.       utReplace :
  1364.         begin
  1365.           S := D;
  1366.           Inc(S, StrLen(S)+1);
  1367.           ReplaceText(Editor, P, Pos, StrLen(D), S, StrLen(S));
  1368.           Inc(Pos, StrLen(S));
  1369.         end;
  1370.     end;
  1371.     UndoBuffer.PeekRedoLink(Link);
  1372.   until (Link <> OrigLink);
  1373.   InUndo := False;
  1374. end;
  1375.  
  1376. function TOvcParaList.ReplaceText(Editor : TOvcEditBase; P : LongInt;
  1377.                                Pos, Count : Integer;
  1378.                                St : PAnsiChar; StLen : Integer) : Word;
  1379.   {-replace the next Count characters at P,Pos with text of St^}
  1380. var
  1381.   S     : PAnsiChar;
  1382.   PPN   : TParaNode;
  1383.   LC    : LongInt;
  1384.   FL    : Integer;
  1385.   LL    : Integer;
  1386.   Delta : Integer;
  1387. begin
  1388.   Result := 0;
  1389.   PPN := NthPara(P);
  1390.   if PPN = nil then
  1391.     Exit;
  1392.  
  1393.   S := PPN.GetS;
  1394.   LC := PPN.LineCount;
  1395.  
  1396.   {create undo record}
  1397.   MakeReplaceUndoRec(P, Pos, @S[Pos-1], Count, St, StLen);
  1398.  
  1399.   {make the replacement}
  1400.   Delta := StLen-Count;
  1401.   Result := PPN.ReplaceText(Pos, Count, GetWrapColumn,
  1402.                             TabSize, St, StLen, FL, LL);
  1403.   {adjust text markers and line,byte count}
  1404.   if Result = 0 then begin
  1405.     Modified := True;
  1406.     Inc(LineCount, PPN.LineCount-LC);
  1407.     Inc(ByteCount, StLen-Count);
  1408.  
  1409.     FLine := Pred(LastN)+FL;
  1410.     if PPN.LineCount <> LC then
  1411.       LLine := MaxLongInt
  1412.     else
  1413.       LLine := Pred(LastN)+LL;
  1414.  
  1415.     if Delta = 0 then
  1416.       FixMarkersInsertedText(Editor, P, Pos, 0)
  1417.     else if Delta > 0 then
  1418.       FixMarkersInsertedText(Editor, P, Pos+Count, Delta)
  1419.     else
  1420.       FixMarkersDeletedText(Editor, P, Pos+Count, -Delta);
  1421.   end;
  1422. end;
  1423.  
  1424. procedure TOvcParaList.ResetPositionInfo;
  1425.   {-tell all editors to reset their position info}
  1426. var
  1427.   PE, SE : TOvcCustomEditor;
  1428. begin
  1429.   PE := TOvcCustomEditor(Owner);
  1430.   SE := PE;
  1431.   repeat
  1432.     PE.edResetPositionInfo;
  1433.     PE := PE.edNext;
  1434.   until PE = SE;
  1435. end;
  1436.  
  1437. procedure TOvcParaList.SetByteLimit(Value : LongInt);
  1438.   {-set limit on total bytes}
  1439. begin
  1440.   MaxBytes := Value;
  1441. end;
  1442.  
  1443. procedure TOvcParaList.SetLastNode(PPN : TParaNode; P, N : LongInt; O : Integer);
  1444.   {-set LastNode to PPN, LastP to P}
  1445. begin
  1446.   LastNode := PPN;
  1447.   LastP := P;
  1448.   LastN := N;
  1449.   LastO := O;
  1450. end;
  1451.  
  1452. procedure TOvcParaList.SetMarker(N : Byte; Para : LongInt; Pos : Integer);
  1453. begin
  1454.   if N < edMaxMarkers then begin
  1455.     Markers[N].Para := Para;
  1456.     Markers[N].Pos := Pos;
  1457.   end;
  1458. end;
  1459.  
  1460. procedure TOvcParaList.SetMarkerAt(N : Byte; Line : LongInt; Col : Integer);
  1461. var
  1462.   Para    : LongInt;
  1463.   LinePos : Integer;
  1464. begin
  1465.   if N < edMaxMarkers then begin
  1466.     Para := FindParaByLine(Line, LinePos);
  1467.     SetMarker(N, Para, LinePos+Col);
  1468.   end;
  1469. end;
  1470.  
  1471. procedure TOvcParaList.SetParaLimit(Value : LongInt);
  1472.   {-set limit on total paragraphs}
  1473. begin
  1474.   MaxParas := Value;
  1475. end;
  1476.  
  1477. procedure TOvcParaList.SetTabSize(Value : Byte);
  1478.   {-set the tab size}
  1479. begin
  1480.   if TabSize <> Value then begin
  1481.     TabSize := Value;
  1482.     {recalculate if word wrap is on}
  1483.     if WordWrap then begin
  1484.       {recalculate number of lines}
  1485.       Recalculate;
  1486.       {reset position info}
  1487.       ResetPositionInfo;
  1488.     end;
  1489.   end;
  1490. end;
  1491.  
  1492. procedure TOvcParaList.SetUndoSize(Size : Word);
  1493.   {-set size of undo buffer}
  1494. begin
  1495.   UndoBuffer.Free;
  1496.   UndoBuffer := TOvcUndoBuffer.Init(@Self, Size);
  1497. end;
  1498.  
  1499. procedure TOvcParaList.SetWordWrap(Value : Boolean);
  1500.   {-turn word wrap on or off}
  1501. begin
  1502.   if Value <> WordWrap then begin
  1503.     {turn word wrap on/off}
  1504.     WordWrap := Value;
  1505.     {recalculate number of lines}
  1506.     Recalculate;
  1507.     {reset position info}
  1508.     ResetPositionInfo;
  1509.   end;
  1510. end;
  1511.  
  1512. procedure TOvcParaList.SetWrapColumn(Value : Integer);
  1513.   {-set the wrap column}
  1514. begin
  1515.   if Value <> WrapColumn then begin
  1516.     {change the wrap column}
  1517.     WrapColumn := Value;
  1518.     {no need to recalculate if word wrap is off}
  1519.     if WordWrap then begin
  1520.       {recalculate number of lines}
  1521.       Recalculate;
  1522.       {reset position info}
  1523.       ResetPositionInfo;
  1524.     end;
  1525.   end;
  1526. end;
  1527.  
  1528. procedure TOvcParaList.Undo(Editor : TOvcEditBase; var P : LongInt; var Pos : Integer);
  1529.   {-undo last insertion/deletion/replacement}
  1530. var
  1531.   UT       : UndoType;
  1532.   D        : PAnsiChar;
  1533.   S        : PAnsiChar;
  1534.   PPPN     : ^TParaNode absolute D;
  1535.   DLen     : Word;
  1536.   MF       : Boolean;
  1537.   OrigLink : Byte;
  1538.   Link     : Byte;
  1539. begin
  1540.   InUndo := True;
  1541.   UndoBuffer.PeekUndoLink(OrigLink);
  1542.   repeat
  1543.     UndoBuffer.GetUndo(UT, Link, MF, P, Pos, D, DLen);
  1544.     case UT of
  1545.       utInsert :
  1546.         DeleteText(Editor, P, Pos, DLen);
  1547.       utInsPara :
  1548.         DeletePara(Editor, P);
  1549.       utPlacePara :
  1550.         DeletePara(Editor, P+1);
  1551.       utDelete :
  1552.         InsertTextPrim(Editor, P, Pos, D, DLen);
  1553.       utDelPara :
  1554.         InsertParaNode(Editor, P, PPPN^);
  1555.       utReplace :
  1556.         begin
  1557.           S := D;
  1558.           Inc(S, StrLen(S)+1);
  1559.           ReplaceText(Editor, P, Pos, StrLen(S), D, StrLen(D));
  1560.         end;
  1561.     end;
  1562.     UndoBuffer.PeekUndoLink(Link);
  1563.   until (Link <> OrigLink);
  1564.   Modified := MF;
  1565.   InUndo := False;
  1566. end;
  1567.  
  1568. {*** TOvcUndoBuffer ***}
  1569.  
  1570. procedure TOvcUndoBuffer.Append(D : PAnsiChar; DLen : Word);
  1571. var
  1572.   S : PAnsiChar;
  1573. begin
  1574.   {make sure there's room}
  1575.   if not CheckSize(DLen) then
  1576.     Exit;
  1577.  
  1578.   {append the data}
  1579.   S := @Last^.Data;
  1580.   Inc(S, Last^.DSize);
  1581.   Move(D^, S^, DLen);
  1582.  
  1583.   {adjust the record size}
  1584.   Inc(Last^.DSize, DLen);
  1585.  
  1586.   {adjust the BufAvail figure}
  1587.   Dec(BufAvail, DLen);
  1588. end;
  1589.  
  1590. procedure TOvcUndoBuffer.AppendReplace(D : PAnsiChar; DLen : Word;
  1591.                                     R : PAnsiChar; RLen : Word);
  1592. var
  1593.   S : PAnsiChar;
  1594.   Len : Word;
  1595. begin
  1596.   if not CheckSize(DLen+RLen) then
  1597.     Exit;
  1598.  
  1599.   {get a pointer to the existing data}
  1600.   S := @Last^.Data;
  1601.   Len := StrLen(S);
  1602.  
  1603.   {insert the D string}
  1604.   Move(S[Len], S[Len+DLen], Last^.DSize-Len);
  1605.   Move(D^, S[Len], DLen);
  1606.   Inc(Last^.DSize, DLen);
  1607.  
  1608.   {insert the R string}
  1609.   Inc(S, Len+DLen+1);
  1610.   Len := StrLen(S);
  1611.   Move(R^, S[Len], RLen);
  1612.   S[Len+RLen] := #0;
  1613.   Inc(Last^.DSize, RLen);
  1614.  
  1615.   {adjust the BufAvail figure}
  1616.   Dec(BufAvail, DLen+RLen);
  1617. end;
  1618.  
  1619. procedure TOvcUndoBuffer.BeginComplexOp(var SaveLinking : Boolean);
  1620. begin
  1621.   SaveLinking := Linking;
  1622.   if not Linking then begin
  1623.     Linking := True;
  1624.     Inc(CurLink);
  1625.   end;
  1626. end;
  1627.  
  1628. function TOvcUndoBuffer.CheckSize(Bytes : LongInt) : Boolean;
  1629. var
  1630.   MinAvail, I, Sz, LN : Word;
  1631.   PUR : PUndoRec;
  1632. begin
  1633.   Result := False;
  1634.   if Error then
  1635.     Exit;
  1636.  
  1637.   {is it too big to fit?}
  1638.   if (Bytes > BufSize) then begin
  1639.     {yes, flush and exit}
  1640.     Flush;
  1641.     Error := Linking;
  1642.     Exit;
  1643.   end;
  1644.  
  1645.   {will it fit without deleting anything?}
  1646.   Result := True;
  1647.   if Bytes <= BufAvail then
  1648.     Exit;
  1649.  
  1650.   {we'll need to delete some stuff}
  1651.   MinAvail := BufSize div 16;
  1652.   if Bytes > MinAvail then
  1653.     MinAvail := Bytes;
  1654.   I := 0;
  1655.   PUR := Buffer;
  1656.   while BufAvail < MinAvail do begin
  1657.     LN := PUR^.LinkNum;
  1658.     repeat
  1659.       {get rid of this group}
  1660.       Inc(I);
  1661.       GUN.Done(PUR);
  1662.       Sz := PUR^.DSize + UndoRecSize;
  1663.       Inc(BufAvail, Sz);
  1664.       PtrInc(PUR, Sz);
  1665.     until (I = Undos) or (PUR^.LinkNum <> LN);
  1666.   end;
  1667.  
  1668.   {do the deletion}
  1669.   if I = Undos then begin
  1670.     {nothing left, so clear everything}
  1671.     Undos := 0;
  1672.     Flush;
  1673.     Error := Linking;
  1674.     Result := not Linking;
  1675.   end else begin
  1676.     Sz := BufSize - PtrDiff(PUR, Buffer);
  1677.     Move(PUR^, Buffer^, Sz);
  1678.     PUndoRec(Buffer)^.PrevSize := 0;
  1679.     Dec(Undos, I);
  1680.     Redos := 0;
  1681.     Last := NthRec(Undos);
  1682.   end;
  1683. end;
  1684.  
  1685. destructor TOvcUndoBuffer.Destroy;
  1686. begin
  1687.   if BufSize <> 0 then begin
  1688.     {dispose of any memory associated with deleted paragraphs}
  1689.     Flush;
  1690.  
  1691.     {deallocate the buffer}
  1692.     FreeMem(Buffer, BufSize);
  1693.   end;
  1694.  
  1695.   {destroy the general undo object}
  1696.   GUN.Free;
  1697.  
  1698.   inherited Destroy;
  1699. end;
  1700.  
  1701. procedure TOvcUndoBuffer.EndComplexOp(SaveLinking : Boolean);
  1702. begin
  1703.   Linking := SaveLinking;
  1704.   if not Linking then
  1705.     Error := False;
  1706. end;
  1707.  
  1708. procedure TOvcUndoBuffer.Flush;
  1709. var
  1710.   PUR  : PUndoRec;
  1711.   I    : Word;
  1712. begin
  1713.   {destroy all the undo records, some of which have pointers to data}
  1714.   PUR := Buffer;
  1715.   for I := 1 to Undos do begin
  1716.     GUN.Done(PUR);
  1717.     PtrInc(PUR, PUR^.DSize + UndoRecSize);
  1718.   end;
  1719.  
  1720.   BufAvail := BufSize;
  1721.   Undos := 0;
  1722.   Redos := 0;
  1723.   Last := Buffer;
  1724.   CurLink := 0;
  1725. end;
  1726.  
  1727. procedure TOvcUndoBuffer.GetRedo(var UT : UndoType; var Link : Byte; var MF : Boolean;
  1728.                               var P : LongInt; var Pos : Integer;
  1729.                               var D : PAnsiChar; var DLen : Word);
  1730. var
  1731.   PUR : PUndoRec;
  1732. begin
  1733.   PUR := Last;
  1734.   if Undos > 0 then
  1735.     PtrInc(PUR, PUR^.DSize + UndoRecSize);
  1736.   with PUR^ do begin
  1737.     UT := GUN.GetUndoType(PUR);
  1738.     Link := LinkNum;
  1739.     P := PNum;
  1740.     Pos := PPos;
  1741.     D := @Data;
  1742.     DLen := DSize;
  1743.  
  1744.     {adjust the modified flag for this undo record}
  1745.     GUN.SetModFlag(PUR, MF);
  1746.   end;
  1747.   Dec(Redos);
  1748.   Inc(Undos);
  1749.   Dec(BufAvail, DLen + UndoRecSize);
  1750.   Last := PUR;
  1751. end;
  1752.  
  1753. procedure TOvcUndoBuffer.GetUndo(var UT : UndoType; var Link : Byte; var MF : Boolean;
  1754.                               var P : LongInt; var Pos : Integer;
  1755.                               var D : PAnsiChar; var DLen : Word);
  1756. begin
  1757.   with Last^ do begin
  1758.     UT := GUN.GetUndoType(Last);
  1759.     Link := LinkNum;
  1760.     MF := GUN.ModFlag(Last);
  1761.     P := PNum;
  1762.     Pos := PPos;
  1763.     D := @Data;
  1764.     DLen := DSize;
  1765.   end;
  1766.   Dec(Undos);
  1767.   Inc(Redos);
  1768.   Inc(BufAvail, DLen + UndoRecSize);
  1769.   PtrDec(Last, Last^.PrevSize);
  1770. end;
  1771.  
  1772. constructor TOvcUndoBuffer.Init(ParaList : TOvcParaList; Size : Word);
  1773. begin
  1774.   inherited Create;
  1775.  
  1776.   {assign owner of this undo buffer}
  1777.   Owner := ParaList;
  1778.  
  1779.   {allocate memory for the undo/redo buffer}
  1780.   if Size <> 0 then
  1781.     repeat
  1782.       {try to allocate the buffer}
  1783.       GetMem(Buffer, Size);
  1784.       if Buffer = nil then
  1785.         if Size <= UndoRecSize then
  1786.           Size := 0
  1787.         else
  1788.           Dec(Size, Size div 2);
  1789.     until (Buffer <> nil) or (Size = 0);
  1790.   BufSize := Size;
  1791.  
  1792.   {reset everything}
  1793.   BufAvail := BufSize;
  1794.   Undos := 0;
  1795.   Redos := 0;
  1796.   Last := Buffer;
  1797.   CurLink := 0;
  1798.   Linking := False;
  1799.   Error := False;
  1800.  
  1801.   {create one general undo object that is used to access all undo records}
  1802.   GUN := TUndoNode.Create;
  1803. end;
  1804.  
  1805. {$IFDEF Win32}
  1806. function TOvcUndoBuffer.NthRec(N : Integer) : PUndoRec; register;
  1807. asm
  1808.   push   esi                     {save}
  1809.  
  1810.   mov    esi,Self                {esi = this object}
  1811.   mov    esi,[esi].Buffer        {esi = Buffer}
  1812.   mov    ecx,edx                 {ecx = N}
  1813.   jecxz  @@2                     {done if CX is 0}
  1814.   dec    ecx                     {ecx = N-1}
  1815.   jecxz  @@2                     {done if CX is 0}
  1816.  
  1817.   xor    eax,eax                 {clear high word}
  1818. @@1:
  1819.   mov    ax,[esi].TUndoRec.DSize {size of next record}
  1820.   add    esi,eax                 {point to next record}
  1821.   add    esi,UndoRecSize         {undo record size}
  1822.   loop   @@1                     {until done}
  1823.  
  1824. @@2:
  1825.   mov eax,esi                    {return position in buffer}
  1826.  
  1827.   pop    esi                     {restore}
  1828. end;
  1829. {$ELSE}
  1830. function TOvcUndoBuffer.NthRec(N : Integer) : PUndoRec; assembler;
  1831. asm
  1832.   push ds                    {save DS}
  1833.   lds si,Self                {DS:SI = Buffer}
  1834.   lds si,[si].Buffer
  1835.   mov cx,N                   {CX = N}
  1836.   jcxz @2                    {done if CX is 0}
  1837.   dec cx                     {CX = N-1}
  1838.   jcxz @2                    {done if CX is 0}
  1839.   @1:
  1840.   add si,[si].TUndoRec.DSize {point to next record}
  1841.   add si,UndoRecSize         {undo record size}
  1842.   loop @1
  1843.   @2:
  1844.   mov dx,ds               {return DS:SI}
  1845.   mov ax,si
  1846.   pop ds                  {restore DS}
  1847. end;
  1848. {$ENDIF}
  1849.  
  1850. procedure TOvcUndoBuffer.PeekRedoLink(var Link : Byte);
  1851. var
  1852.   PUR : PUndoRec;
  1853. begin
  1854.   if Redos = 0 then
  1855.     Link := not Last^.LinkNum
  1856.   else begin
  1857.     PUR := Last;
  1858.     if Undos > 0 then
  1859.       PtrInc(PUR, PUR^.DSize + UndoRecSize);
  1860.     Link := PUR^.LinkNum;
  1861.   end;
  1862. end;
  1863.  
  1864. procedure TOvcUndoBuffer.PeekUndoLink(var Link : Byte);
  1865. begin
  1866.   if Undos = 0 then
  1867.     Link := not Last^.LinkNum
  1868.   else
  1869.     Link := Last^.LinkNum;
  1870. end;
  1871.  
  1872. procedure TOvcUndoBuffer.Prepend(D : PAnsiChar; DLen : Word);
  1873. var
  1874.   S : PAnsiChar;
  1875. begin
  1876.   {make sure there's room}
  1877.   if not CheckSize(DLen) then
  1878.     Exit;
  1879.  
  1880.   {prepend the data}
  1881.   S := @Last^.Data;
  1882.   Move(S[0], S[DLen], Last^.DSize);
  1883.   Move(D^, S^, DLen);
  1884.  
  1885.   {adjust the position}
  1886.   Dec(Last^.PPos, DLen);
  1887.  
  1888.   {adjust the record size}
  1889.   Inc(Last^.DSize, DLen);
  1890.  
  1891.   {adjust the BufAvail figure}
  1892.   Dec(BufAvail, DLen);
  1893. end;
  1894.  
  1895. procedure TOvcUndoBuffer.Push(UT : UndoType; MF : Boolean;
  1896.                            P : LongInt; Pos : Integer;
  1897.                            D : PAnsiChar; DLen : Word);
  1898. var
  1899.   PUR    : PUndoRec;
  1900.   PSize  : Word;
  1901.   Size   : LongInt;
  1902.   Before : Boolean;
  1903. begin
  1904.   if not Linking then
  1905.     if SameOperation(UT, Before, P, Pos, DLen) then begin
  1906.       Redos := 0;
  1907.       if Before then
  1908.         Prepend(D, DLen)
  1909.       else
  1910.         Append(D, DLen);
  1911.       Exit;
  1912.     end else
  1913.       Inc(CurLink);
  1914.  
  1915.   {make sure there's room}
  1916.   Size := LongInt(DLen) + UndoRecSize;
  1917.   if not CheckSize(Size) then
  1918.     Exit;
  1919.  
  1920.   {get pointer to the undo record and initialize it}
  1921.   Inc(Undos);
  1922.   PUR := Last;
  1923.   if Undos > 1 then begin
  1924.     PSize := PUR^.DSize + UndoRecSize;
  1925.     PtrInc(PUR, PSize);
  1926.   end else
  1927.     PSize := 0;
  1928.   GUN.Init(PUR, UT, CurLink, MF, PSize, P, Pos, D, DLen);
  1929.   Last := PUR;
  1930.   Redos := 0;
  1931.   Dec(BufAvail, Size);
  1932. end;
  1933.  
  1934. procedure TOvcUndoBuffer.PushReplace(MF : Boolean; P : LongInt; Pos : Integer;
  1935.                                   D : PAnsiChar; DLen : Word;
  1936.                                   R : PAnsiChar; RLen : Word);
  1937. var
  1938.   PUR    : PUndoRec;
  1939.   PSize  : Word;
  1940.   Size   : LongInt;
  1941.   Before : Boolean;
  1942. begin
  1943.   if not Linking then
  1944.     if SameOperation(utReplace, Before, P, Pos, DLen) then begin
  1945.       Redos := 0;
  1946.       AppendReplace(D, DLen, R, RLen);
  1947.       Exit;
  1948.     end else
  1949.       Inc(CurLink);
  1950.  
  1951.   {make sure there's room}
  1952.   Size := LongInt(DLen)+RLen+2 + UndoRecSize;
  1953.   if not CheckSize(Size) then
  1954.     Exit;
  1955.  
  1956.   {get pointer to the undo record and initialize it}
  1957.   Inc(Undos);
  1958.   PUR := Last;
  1959.   if Undos > 1 then begin
  1960.     PSize := PUR^.DSize + UndoRecSize;
  1961.     PtrInc(PUR, PSize);
  1962.   end else
  1963.     PSize := 0;
  1964.   GUN.InitReplace(PUR, CurLink, MF, PSize, P, Pos, D, DLen, R, RLen);
  1965.   Last := PUR;
  1966.   Redos := 0;
  1967.   Dec(BufAvail, Size);
  1968. end;
  1969.  
  1970. function TOvcUndoBuffer.SameOperation(UT : UndoType; var Before : Boolean;
  1971.                                    P : LongInt; Pos : Integer;
  1972.                                    DLen : Word) : Boolean;
  1973. var
  1974.   D : PAnsiChar;
  1975. begin
  1976.   Result := False;
  1977.   Before := False;
  1978.   if (Undos = 0) or (UT <> GUN.GetUndoType(Last)) then
  1979.     Exit;
  1980.   D := PAnsiChar(@Last^.Data);
  1981.   case UT of
  1982.     utInsert :
  1983.       Result := (Last^.PNum = P) and (Pos = Last^.PPos+Last^.DSize);
  1984.     utDelete :
  1985.       if (Last^.PNum = P) then
  1986.         if (Pos = Last^.PPos) then
  1987.           SameOperation := True
  1988.         else if (Pos+DLen = Last^.PPos) then begin
  1989.           Before := True;
  1990.           Result := True;
  1991.         end;
  1992.     utReplace :
  1993.       Result := (Last^.PNum = P) and (Pos = Last^.PPos+LongInt(StrLen(D)));
  1994.   end;
  1995. end;
  1996.  
  1997. procedure TOvcUndoBuffer.SetModified;
  1998. var
  1999.   PUR  : PUndoRec;
  2000.   I, J : Integer;
  2001. begin
  2002.   PUR := Buffer;
  2003.   J := Undos + Redos;
  2004.   for I := 1 to J do begin
  2005.     GUN.SetModFlag(PUR, True);
  2006.     PtrInc(PUR, PUR^.DSize + UndoRecSize);
  2007.   end;
  2008. end;
  2009.  
  2010.  
  2011. end.
  2012.