{ *************************************************************************** * * * This source is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This code is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * A copy of the GNU General Public License is available on the World * * Wide Web at . You can also * * obtain it by writing to the Free Software Foundation, * * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * * * *************************************************************************** Author: Mattias Gaertner Abstract: Defines TSourceLog which manage a source (= an ansistring) and all changes like inserting, deleting and moving parts of it. } unit SourceLog; {$ifdef fpc}{$mode objfpc}{$endif}{$H+} interface {$I codetools.inc} uses {$IFDEF MEM_CHECK} MemCheck, {$ENDIF} Classes, SysUtils; type TSourceLog = class; TSourceLogEntryOperation = (sleoInsert, sleoDelete, sleoMove); TOnSourceLogInsert = procedure(Sender: TSourceLog; Pos: integer; const Txt: string) of object; TOnSourceLogDelete = procedure(Sender: TSourceLog; Pos, Len: integer) of object; TOnSourceLogMove = procedure(Sender: TSourceLog; Pos, Len, MoveTo: integer) of object; TSourceLogEntry = class private public Position: integer; Len: integer; MoveTo: integer; LineEnds: integer; LengthOfLastLine: integer; Operation: TSourceLogEntryOperation; procedure AdjustPosition(var APosition: integer); constructor Create(APos, ALength, AMoveTo: integer; const Txt: string; AnOperation: TSourceLogEntryOperation); end; TSourceLogMarker = class private public Position: integer; NewPosition: integer; Deleted: boolean; Data: Pointer; end; TLineRange = record StartPos, EndPos: integer; end; TSourceLog = class private FLineCount: integer; FLineRanges: {$ifdef fpc}^{$else}array of {$endif}TLineRange; // array of TLineRange FSrcLen: integer; FLog: TList; // list of TSourceLogEntry FMarkers: TList; // list of TSourceLogMarker; FModified: boolean; FOnInsert: TOnSourceLogInsert; FOnDelete: TOnSourceLogDelete; FOnMove: TOnSourceLogMove; FSource: string; FChangeStep: integer; FReadOnly: boolean; FWriteLock: integer; procedure SetSource(const NewSrc: string); function GetItems(Index: integer): TSourceLogEntry; procedure SetItems(Index: integer; AnItem: TSourceLogEntry); function GetMarkers(Index: integer): TSourceLogMarker; procedure BuildLineRanges; procedure IncreaseChangeStep; procedure SetReadOnly(const Value: boolean); public Data: Pointer; function LineCount: integer; function GetLine(Index: integer): string; property Items[Index: integer]: TSourceLogEntry read GetItems write SetItems; default; function Count: integer; // # Items property SourceLength: integer read fSrcLen; procedure ClearEntries; property ChangeStep: integer read FChangeStep; property Markers[Index: integer]: TSourceLogMarker read GetMarkers; function MarkerCount: integer; procedure AddMarker(Position: integer; SomeData: Pointer); procedure AddMarkerXY(Line, Column: integer; SomeData: Pointer); procedure AdjustPosition(var APosition: integer); procedure AdjustCursor(var Line, Column: integer); property Source: string read FSource write SetSource; property Modified: boolean read FModified write FModified; // Line and Column begin at 1 procedure LineColToPosition(Line, Column: integer; var Position: integer); procedure AbsoluteToLineCol(Position: integer; var Line, Column: integer); procedure Insert(Pos: integer; const Txt: string); procedure Delete(Pos, Len: integer); procedure Replace(Pos, Len: integer; const Txt: string); procedure Move(Pos, Len, MoveTo: integer); property OnInsert: TOnSourceLogInsert read FOnInsert write FOnInsert; property OnDelete: TOnSourceLogDelete read FOnDelete write FOnDelete; property OnMove: TOnSourceLogMove read FOnMove write FOnMove; function LoadFromFile(const Filename: string): boolean; virtual; function SaveToFile(const Filename: string): boolean; virtual; function IsEqual(sl: TStrings): boolean; procedure Assign(sl: TStrings); procedure AssignTo(sl: TStrings); procedure LoadFromStream(s: TStream); procedure SaveToStream(s: TStream); property ReadOnly: boolean read FReadOnly write SetReadOnly; property WriteLock: integer read FWriteLock; procedure IncWriteLock; procedure DecWriteLock; procedure Clear; virtual; function ConsistencyCheck: integer; constructor Create(const ASource: string); destructor Destroy; override; end; implementation { useful function } function LineEndCount(const Txt: string;var LengthOfLastLine: integer): integer; var i, LastLineEndPos, TxtLen: integer; begin i:=1; LastLineEndPos:=0; Result:=0; TxtLen:=length(Txt); while iTxt[i]) then inc(i); LastLineEndPos:=i; end else inc(i); end; LengthOfLastLine:=TxtLen-LastLineEndPos; end; { TSourceLogEntry } constructor TSourceLogEntry.Create(APos, ALength, AMoveTo: integer; const Txt: string; AnOperation: TSourceLogEntryOperation); begin Position:=APos; Len:=ALength; MoveTo:=AMoveTo; Operation:=AnOperation; LineEnds:=LineEndCount(Txt, LengthOfLastLine); end; procedure TSourceLogEntry.AdjustPosition(var APosition: integer); begin case Operation of sleoInsert: if APosition>=Position then inc(APosition,Len); sleoDelete: if (APosition>=Position) then begin if APosition>=Position+Len then dec(APosition,Len) else APosition:=Position; end; sleoMove: if Position=Position then begin if APosition=MoveTo then begin if APosition=0) and (Index0 then System.Move(fSource[fLineRanges[Index].StartPos],Result[1],LineLen); end else Result:=''; end; procedure TSourceLog.ClearEntries; var i: integer; begin for i:=0 to Count-1 do Items[i].Free; FLog.Clear; end; procedure TSourceLog.Clear; var i: integer; begin ClearEntries; for i:=0 to MarkerCount-1 do Markers[i].Free; FMarkers.Clear; FSource:=''; FSrcLen:=0; FModified:=false; if FLineRanges<>nil then begin FreeMem(FLineRanges); FLineRanges:=nil; end; FLineCount:=-1; IncreaseChangeStep; Data:=nil; FReadOnly:=false; end; function TSourceLog.GetItems(Index: integer): TSourceLogEntry; begin Result:=TSourceLogEntry(FLog[Index]); end; procedure TSourceLog.SetItems(Index: integer; AnItem: TSourceLogEntry); begin FLog[Index]:=AnItem; end; function TSourceLog.Count: integer; begin Result:=fLog.Count; end; function TSourceLog.GetMarkers(Index: integer): TSourceLogMarker; begin Result:=TSourceLogMarker(FMarkers[Index]); end; function TSourceLog.MarkerCount: integer; begin Result:=fMarkers.Count; end; procedure TSourceLog.SetSource(const NewSrc: string); begin //writeln('TSourceLog.SetSource ',length(NewSrc)); if NewSrc<>FSource then begin Clear; FSource:=NewSrc; FSrcLen:=length(FSource); FReadOnly:=false; end; end; procedure TSourceLog.Insert(Pos: integer; const Txt: string); var i: integer; NewSrcLogEntry: TSourceLogEntry; begin if Assigned(FOnInsert) then FOnInsert(Self,Pos,Txt); FSource:=copy(FSource,1,Pos-1) +Txt +copy(FSource,Pos,length(FSource)-Pos+1); FSrcLen:=length(FSource); writeln('TSourceLog.Insert ',fSrcLen); NewSrcLogEntry:=TSourceLogEntry.Create(Pos,length(Txt),-1,Txt,sleoInsert); FLog.Add(NewSrcLogEntry); for i:=0 to FMarkers.Count-1 do begin if (not Markers[i].Deleted) then NewSrcLogEntry.AdjustPosition(Markers[i].NewPosition); end; FLineCount:=-1; FModified:=true; IncreaseChangeStep; end; procedure TSourceLog.Delete(Pos, Len: integer); var i: integer; NewSrcLogEntry: TSourceLogEntry; begin if Assigned(FOnDelete) then FOnDelete(Self,Pos,Len); System.Delete(FSource,Pos,Len); FSrcLen:=length(FSource); writeln('TSourceLog.Delete ',fSrcLen,',',length(fSource)); NewSrcLogEntry:=TSourceLogEntry.Create(Pos,Len,-1,'',sleoDelete); FLog.Add(NewSrcLogEntry); for i:=0 to FMarkers.Count-1 do begin if (Markers[i].Deleted=false) then begin if (Markers[i].NewPosition<=Pos) and (Markers[i].NewPosition=Pos) and (MoveTo0 then begin AdjustPosition(p); AbsoluteToLineCol(p,Line,Column); end; end; procedure TSourceLog.BuildLineRanges; var p,line:integer; begin if FLineCount>=0 then exit; if FLineRanges<>nil then begin FreeMem(FLineRanges); FLineRanges:=nil; end; // count line ends FLineCount:=0; p:=1; while (p<=fSrcLen) do begin if (not (FSource[p] in [#10,#13])) then begin inc(p); end else begin // new line inc(FLineCount); inc(p); if (p<=fSrcLen) and (FSource[p] in [#10,#13]) and (FSource[p]<>FSource[p-1]) then inc(p); end; end; if (FSource<>'') and (not (FSource[fSrcLen] in [#10,#13])) then inc(FLineCount); // build line range list if FLineCount>0 then begin GetMem(FLineRanges,FLineCount*SizeOf(TLineRange)); p:=1; line:=0; FLineRanges[line].StartPos:=1; FLineRanges[FLineCount-1].EndPos:=fSrcLen+1; while (p<=fSrcLen) do begin if (not (FSource[p] in [#10,#13])) then begin inc(p); end else begin // new line FLineRanges[line].EndPos:=p; inc(line); inc(p); if (p<=fSrcLen) and (FSource[p] in [#10,#13]) and (FSource[p]<>FSource[p-1]) then inc(p); if line=1) and (Line<=FLineCount) and (Column>=1) then begin if (Line=fSrcLen) then begin Line:=-1; Column:=-1; exit; end; if (Position>=FLineRanges[FLineCount-1].StartPos) then begin Line:=FLineCount; Column:=Position-FLineRanges[Line-1].StartPos+1; exit; end; // binary search for the line l:=0; r:=FLineCount-1; repeat m:=(l+r) shr 1; if FLineRanges[m].StartPos>Position then begin // too high, search lower r:=m-1; end else if FLineRanges[m+1].StartPos<=Position then begin // too low, search higher l:=m+1; end else begin // line found Line:=m+1; Column:=Position-FLineRanges[Line-1].StartPos+1; exit; end; until false; end; function TSourceLog.LoadFromFile(const Filename: string): boolean; var s: string; fs: TFileStream; begin Result:=true; try fs:=TFileStream.Create(Filename, fmOpenRead); try SetLength(s,fs.Size); if s<>'' then fs.Read(s[1],length(s)); Source:=s; finally fs.Free; end; except Result:=false; end; end; procedure TSourceLog.IncreaseChangeStep; begin if FChangeStep=$7fffffff then FChangeStep:=-$7fffffff else inc(FChangeStep); end; function TSourceLog.SaveToFile(const Filename: string): boolean; var fs: TFileStream; begin Result:=true; try fs:=TFileStream.Create(Filename, fmCreate); try if fSrcLen>0 then fs.Write(FSource[1],length(FSource)); finally fs.Free; end; except Result:=false; end; end; function TSourceLog.IsEqual(sl: TStrings): boolean; var x,y,p,LineLen: integer; Line: string; begin Result:=false; if sl=nil then exit; p:=1; x:=1; y:=0; while (yfSource[p] then exit; inc(x); inc(p); end; if (p<=fSrcLen) and (not (fSource[p] in [#10,#13])) then exit; inc(p); if (p<=fSrcLen) and (fSource[p] in [#10,#13]) and (fSource[p]<>fSource[p-1]) then inc(p); inc(y); end; if p0 then begin System.Move(s[1],fSource[p],LineLen); inc(p,LineLen); end; fSource[p]:=#13; inc(p); fSource[p]:=#10; inc(p); end; end; procedure TSourceLog.AssignTo(sl: TStrings); var y: integer; s: string; begin if sl=nil then exit; if IsEqual(sl) then exit; sl.BeginUpdate; sl.Clear; BuildLineRanges; sl.Capacity:=fLineCount; for y:=0 to fLineCount-1 do begin s:=''; SetLength(s,fLineRanges[y].EndPos-fLineRanges[y].StartPos); if s<>'' then System.Move(fSource[fLineRanges[y].StartPos],s[1],length(s)); sl.Add(s); end; sl.EndUpdate; end; procedure TSourceLog.LoadFromStream(s: TStream); begin Clear; if s=nil then exit; s.Position:=0; fSrcLen:=s.Size; if fSrcLen>0 then begin SetLength(fSource,fSrcLen); s.Read(fSource[1],fSrcLen); end; end; procedure TSourceLog.SaveToStream(s: TStream); begin if fSource<>'' then s.Write(fSource[1],fSrcLen); end; procedure TSourceLog.SetReadOnly(const Value: boolean); begin FReadOnly := Value; end; procedure TSourceLog.IncWriteLock; begin inc(FWriteLock); end; procedure TSourceLog.DecWriteLock; begin if FWriteLock>0 then dec(FWriteLock); end; function TSourceLog.ConsistencyCheck: integer; begin if fSrcLen<>length(fSource) then begin Result:=-1; exit; end; Result:=0; end; end.