mirror of
https://gitlab.com/freepascal.org/lazarus/lazarus.git
synced 2025-04-07 23:18:45 +02:00
1109 lines
31 KiB
ObjectPascal
1109 lines
31 KiB
ObjectPascal
{
|
|
***************************************************************************
|
|
* *
|
|
* 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 <http://www.gnu.org/copyleft/gpl.html>. You can also *
|
|
* obtain it by writing to the Free Software Foundation, *
|
|
* Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1335, 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,
|
|
// LazUtils
|
|
LazFileUtils, LazUTF8, LazDbgLog, LazStringUtils;
|
|
|
|
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;
|
|
TOnSourceLogDecodeLoaded = procedure(Sender: TSourceLog; const Filename: string;
|
|
var Source, DiskEncoding, MemEncoding: string) of object;
|
|
TOnSourceLogEncodeSaving = procedure(Sender: TSourceLog;
|
|
const Filename: string; var Source: string) of object;
|
|
|
|
TSourceLogEntry = class
|
|
private
|
|
public
|
|
Position: integer;
|
|
Len: integer;
|
|
MoveTo: integer;
|
|
LineEnds: integer; // number of line ends in txt
|
|
LengthOfLastLine: integer;
|
|
Txt: string;
|
|
Operation: TSourceLogEntryOperation;
|
|
procedure AdjustPosition(var APosition: integer);
|
|
constructor Create(APos, ALength, AMoveTo: integer; const ATxt: string;
|
|
AnOperation: TSourceLogEntryOperation);
|
|
end;
|
|
|
|
TOnSourceChange = procedure(Sender: TSourceLog; Entry: TSourceLogEntry)
|
|
of object;
|
|
|
|
{ TSourceLogMarker }
|
|
|
|
TSourceLogMarker = class
|
|
private
|
|
public
|
|
Position: integer;
|
|
NewPosition: integer;
|
|
Deleted: boolean;
|
|
Data: Pointer;
|
|
Log: TSourceLog;
|
|
destructor Destroy; override;
|
|
end;
|
|
|
|
TLineRange = packed record
|
|
StartPos, EndPos: integer;
|
|
end;
|
|
PLineRange = ^TLineRange;
|
|
|
|
{ TSourceLog }
|
|
|
|
TSourceLog = class
|
|
private
|
|
FDiskEncoding: string;
|
|
FDiskLineEnding: string;
|
|
FLineCount: integer;
|
|
FLineRanges: PLineRange;
|
|
FMemEncoding: string;
|
|
FOnDecodeLoaded: TOnSourceLogDecodeLoaded;
|
|
FOnEncodeSaving: TOnSourceLogEncodeSaving;
|
|
// array of TLineRange
|
|
FSrcLen: integer;
|
|
FLog: TFPList; // list of TSourceLogEntry
|
|
FMarkers: TFPList; // list of TSourceLogMarker;
|
|
FModified: boolean;
|
|
FOnInsert: TOnSourceLogInsert;
|
|
FOnDelete: TOnSourceLogDelete;
|
|
FOnMove: TOnSourceLogMove;
|
|
FChangeHooks: {$ifdef fpc}^{$else}array of {$endif}TOnSourceChange;
|
|
FChangeHookCount: integer;
|
|
FChangeHookDelayed: boolean;
|
|
FSource: string;
|
|
FChangeStep: integer;
|
|
FReadOnly: boolean;
|
|
FWriteLock: integer;
|
|
FChangeHookLock: 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 SetReadOnly(const Value: boolean);
|
|
function IndexOfChangeHook(AChangeHook: TOnSourceChange): integer;
|
|
protected
|
|
procedure IncreaseChangeStep; virtual; // any change
|
|
procedure DoSourceChanged; virtual; // source change
|
|
procedure DecodeLoaded(const AFilename: string;
|
|
var ASource, ADiskEncoding, AMemEncoding: string); virtual;
|
|
procedure EncodeSaving(const AFilename: string; var ASource: string); virtual;
|
|
public
|
|
Data: Pointer;
|
|
LastError: string;
|
|
function LineCount: integer;
|
|
function GetLine(Index: integer; WithLineEnd: boolean = true): string; // 0-based
|
|
function GetLineLength(Index: integer): integer; // 0-based
|
|
procedure GetLineRange(Index: integer; out LineRange: TLineRange); // 0-based
|
|
function GetLineStart(Index: integer): integer; // 1-based
|
|
property Items[Index: integer]: TSourceLogEntry
|
|
read GetItems write SetItems; default;
|
|
function Count: integer; // # Items
|
|
property SourceLength: integer read fSrcLen;
|
|
function ClearEntries: boolean;
|
|
property ChangeStep: integer read FChangeStep;
|
|
property Markers[Index: integer]: TSourceLogMarker read GetMarkers;
|
|
function MarkerCount: integer;
|
|
function AddMarker(Position: integer; SomeData: Pointer): TSourceLogMarker;
|
|
function AddMarkerXY(Line, Column: integer; SomeData: Pointer): TSourceLogMarker;
|
|
procedure AdjustPosition(var APosition: integer);
|
|
procedure NotifyHooks(Entry: TSourceLogEntry);
|
|
procedure IncreaseHookLock;
|
|
procedure DecreaseHookLock;
|
|
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; out Position: integer);
|
|
procedure AbsoluteToLineCol(Position: integer; out Line, Column: integer);
|
|
function LineColIsOutside(Line, Column: integer): boolean;
|
|
function LineColIsSpace(Line, Column: integer): boolean;
|
|
function AbsoluteToLineColStr(Position: integer): string;
|
|
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);
|
|
function LoadFromFile(const Filename: string): boolean; virtual;
|
|
function SaveToFile(const Filename: string): boolean; virtual;
|
|
function GetLines(StartLine, EndLine: integer): string;
|
|
function IsEqual(sl: TStrings): boolean;
|
|
function OldIsEqual(sl: TStrings): boolean;
|
|
procedure Assign(sl: TStrings);
|
|
procedure AssignTo(sl: TStrings; UseAddStrings: Boolean);
|
|
procedure LoadFromStream(aStream: TStream);
|
|
procedure SaveToStream(aStream: TStream);
|
|
property ReadOnly: boolean read FReadOnly write SetReadOnly;
|
|
property DiskEncoding: string read FDiskEncoding write FDiskEncoding;
|
|
property MemEncoding: string read FMemEncoding write FMemEncoding;
|
|
property DiskLineEnding: string read FDiskLineEnding write FDiskLineEnding;
|
|
property WriteLock: integer read FWriteLock;
|
|
procedure IncWriteLock;
|
|
procedure DecWriteLock;
|
|
procedure Clear; virtual; // clear content, not Encoding, not LineEnding
|
|
function ConsistencyCheck: integer;
|
|
function CalcMemSize: PtrUInt; virtual;
|
|
constructor Create(const ASource: string);
|
|
destructor Destroy; override;
|
|
|
|
procedure AddChangeHook(AnOnSourceChange: TOnSourceChange);
|
|
procedure RemoveChangeHook(AnOnSourceChange: TOnSourceChange);
|
|
property OnInsert: TOnSourceLogInsert read FOnInsert write FOnInsert;
|
|
property OnDelete: TOnSourceLogDelete read FOnDelete write FOnDelete;
|
|
property OnMove: TOnSourceLogMove read FOnMove write FOnMove;
|
|
property OnDecodeLoaded: TOnSourceLogDecodeLoaded read FOnDecodeLoaded
|
|
write FOnDecodeLoaded;
|
|
property OnEncodeSaving: TOnSourceLogEncodeSaving read FOnEncodeSaving
|
|
write FOnEncodeSaving;
|
|
end;
|
|
|
|
implementation
|
|
|
|
{ TSourceLogEntry }
|
|
|
|
constructor TSourceLogEntry.Create(APos, ALength, AMoveTo: integer;
|
|
const ATxt: string; AnOperation: TSourceLogEntryOperation);
|
|
begin
|
|
Position:=APos;
|
|
Len:=ALength;
|
|
MoveTo:=AMoveTo;
|
|
Operation:=AnOperation;
|
|
LineEnds:=LineEndingCount(Txt, LengthOfLastLine);
|
|
Txt:=ATxt;
|
|
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<MoveTo then begin
|
|
if APosition>=Position then begin
|
|
if APosition<Position+Len then
|
|
inc(APosition,MoveTo-Position)
|
|
else if APosition<MoveTo then
|
|
dec(APosition,Len);
|
|
end;
|
|
end else begin
|
|
if APosition>=MoveTo then begin
|
|
if APosition<Position then
|
|
inc(APosition,Len)
|
|
else if APosition<Position+Len then
|
|
dec(APosition,Position-MoveTo);
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
{ TSourceLog }
|
|
|
|
constructor TSourceLog.Create(const ASource: string);
|
|
begin
|
|
inherited Create;
|
|
FModified:=false;
|
|
FSource:=ASource;
|
|
FSrcLen:=length(FSource);
|
|
FLog:=TFPList.Create;
|
|
FMarkers:=TFPList.Create;
|
|
FLineRanges:=nil;
|
|
FLineCount:=-1;
|
|
FChangeStep:=0;
|
|
Data:=nil;
|
|
FChangeHooks:=nil;
|
|
FChangeHookCount:=0;
|
|
FReadOnly:=false;
|
|
end;
|
|
|
|
destructor TSourceLog.Destroy;
|
|
var
|
|
i: Integer;
|
|
begin
|
|
if FChangeHooks<>nil then begin
|
|
FreeMem(FChangeHooks);
|
|
FChangeHooks:=nil;
|
|
end;
|
|
Clear;
|
|
for i:=FMarkers.Count-1 downto 0 do begin
|
|
Markers[i].Log:=nil;
|
|
Markers[i].Free;
|
|
end;
|
|
FMarkers.Free;
|
|
FLog.Free;
|
|
inherited Destroy;
|
|
end;
|
|
|
|
function TSourceLog.LineCount: integer;
|
|
begin
|
|
if fLineCount<0 then BuildLineRanges;
|
|
Result:=fLineCount;
|
|
end;
|
|
|
|
function TSourceLog.GetLine(Index: integer; WithLineEnd: boolean): string;
|
|
var LineLen: integer;
|
|
begin
|
|
BuildLineRanges;
|
|
if (Index>=0) and (Index<fLineCount) then begin
|
|
if WithLineEnd then begin
|
|
if Index<fLineCount-1 then
|
|
LineLen:=fLineRanges[Index+1].StartPos-fLineRanges[Index].StartPos
|
|
else
|
|
LineLen:=fSrcLen-fLineRanges[Index].StartPos+1;
|
|
end else begin
|
|
LineLen:=fLineRanges[Index].EndPos-fLineRanges[Index].StartPos
|
|
end;
|
|
SetLength(Result{%H-},LineLen);
|
|
if LineLen>0 then
|
|
System.Move(fSource[fLineRanges[Index].StartPos],Result[1],LineLen);
|
|
end else
|
|
Result:='';
|
|
end;
|
|
|
|
function TSourceLog.GetLineLength(Index: integer): integer;
|
|
begin
|
|
BuildLineRanges;
|
|
if (Index>=0) and (Index<fLineCount) then
|
|
Result:=fLineRanges[Index].EndPos-fLineRanges[Index].StartPos
|
|
else
|
|
Result:=0;
|
|
end;
|
|
|
|
procedure TSourceLog.GetLineRange(Index: integer; out LineRange: TLineRange);
|
|
begin
|
|
BuildLineRanges;
|
|
LineRange:=FLineRanges[Index];
|
|
end;
|
|
|
|
function TSourceLog.GetLineStart(Index: integer): integer;
|
|
begin
|
|
BuildLineRanges;
|
|
if Index<FLineCount then
|
|
Result:=FLineRanges[Index].StartPos
|
|
else
|
|
Result:=FSrcLen;
|
|
end;
|
|
|
|
function TSourceLog.ClearEntries: boolean;
|
|
var i: integer;
|
|
begin
|
|
if (Count=0) and (FLog.Count=0) then exit(false);
|
|
Result:=true;
|
|
for i:=0 to Count-1 do Items[i].Free;
|
|
FLog.Clear;
|
|
end;
|
|
|
|
procedure TSourceLog.Clear;
|
|
var i: integer;
|
|
m: TSourceLogMarker;
|
|
SourceChanged: Boolean;
|
|
begin
|
|
ClearEntries; // ignore if entries change
|
|
// markers are owned by someone else, do not free them
|
|
for i:=0 to FMarkers.Count-1 do begin
|
|
m:=Markers[i];
|
|
if m.Position>1 then
|
|
m.Deleted:=true;
|
|
end;
|
|
SourceChanged:=FSource<>'';
|
|
FSource:='';
|
|
FSrcLen:=0;
|
|
FModified:=false;
|
|
if FLineRanges<>nil then begin
|
|
FreeMem(FLineRanges);
|
|
FLineRanges:=nil;
|
|
end;
|
|
FLineCount:=-1;
|
|
Data:=nil;
|
|
FReadOnly:=false;
|
|
IncreaseChangeStep;
|
|
if SourceChanged then
|
|
DoSourceChanged;
|
|
NotifyHooks(nil);
|
|
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.NotifyHooks(Entry: TSourceLogEntry);
|
|
var i: integer;
|
|
begin
|
|
if (FChangeHooks=nil) or (FChangeHookLock>0) then begin
|
|
FChangeHookDelayed:=true;
|
|
exit;
|
|
end;
|
|
FChangeHookDelayed:=false;
|
|
for i:=0 to FChangeHookCount-1 do
|
|
FChangeHooks[i](Self,Entry);
|
|
end;
|
|
|
|
procedure TSourceLog.IncreaseHookLock;
|
|
begin
|
|
inc(FChangeHookLock);
|
|
end;
|
|
|
|
procedure TSourceLog.DecreaseHookLock;
|
|
begin
|
|
if FChangeHookLock<=0 then exit;
|
|
dec(FChangeHookLock);
|
|
if (FChangeHookLock=0) and FChangeHookDelayed then
|
|
NotifyHooks(nil);
|
|
end;
|
|
|
|
procedure TSourceLog.SetSource(const NewSrc: string);
|
|
begin
|
|
//DebugLn('TSourceLog.SetSource A ',length(NewSrc),' LineCount=',fLineCount,' SrcLen=',fSrcLen);
|
|
if NewSrc<>FSource then begin
|
|
inc(FChangeHookLock);
|
|
try
|
|
Clear;
|
|
FSource:=NewSrc;
|
|
FSrcLen:=length(FSource);
|
|
FLineCount:=-1;
|
|
FReadOnly:=false;
|
|
DoSourceChanged;
|
|
finally
|
|
dec(FChangeHookLock);
|
|
end;
|
|
NotifyHooks(nil);
|
|
end;
|
|
end;
|
|
|
|
procedure TSourceLog.Insert(Pos: integer; const Txt: string);
|
|
var i: integer;
|
|
NewSrcLogEntry: TSourceLogEntry;
|
|
begin
|
|
if Txt='' then exit;
|
|
if Assigned(FOnInsert) then FOnInsert(Self,Pos,Txt);
|
|
NewSrcLogEntry:=TSourceLogEntry.Create(Pos,length(Txt),-1,Txt,sleoInsert);
|
|
FLog.Add(NewSrcLogEntry);
|
|
NotifyHooks(NewSrcLogEntry);
|
|
FSource:=copy(FSource,1,Pos-1)
|
|
+Txt
|
|
+copy(FSource,Pos,length(FSource)-Pos+1);
|
|
FSrcLen:=length(FSource);
|
|
FLineCount:=-1;
|
|
for i:=0 to FMarkers.Count-1 do begin
|
|
if (not Markers[i].Deleted) then
|
|
NewSrcLogEntry.AdjustPosition(Markers[i].NewPosition);
|
|
end;
|
|
FModified:=true;
|
|
DoSourceChanged;
|
|
end;
|
|
|
|
procedure TSourceLog.Delete(Pos, Len: integer);
|
|
var i: integer;
|
|
NewSrcLogEntry: TSourceLogEntry;
|
|
begin
|
|
if Len=0 then exit;
|
|
if Assigned(FOnDelete) then FOnDelete(Self,Pos,Len);
|
|
NewSrcLogEntry:=TSourceLogEntry.Create(Pos,Len,-1,'',sleoDelete);
|
|
FLog.Add(NewSrcLogEntry);
|
|
NotifyHooks(NewSrcLogEntry);
|
|
System.Delete(FSource,Pos,Len);
|
|
FSrcLen:=length(FSource);
|
|
FLineCount:=-1;
|
|
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+Len) then
|
|
Markers[i].Deleted:=true
|
|
else begin
|
|
NewSrcLogEntry.AdjustPosition(Markers[i].NewPosition);
|
|
end;
|
|
end;
|
|
end;
|
|
FModified:=true;
|
|
DoSourceChanged;
|
|
end;
|
|
|
|
procedure TSourceLog.Replace(Pos, Len: integer; const Txt: string);
|
|
var i: integer;
|
|
DeleteSrcLogEntry, InsertSrcLogEntry: TSourceLogEntry;
|
|
begin
|
|
if (Len=0) and (Txt='') then exit;
|
|
if Len=length(Txt) then begin
|
|
i:=1;
|
|
while (i<=Len) and (FSource[Pos+i-1]=Txt[i]) do inc(i);
|
|
if i>Len then exit;
|
|
end;
|
|
if Assigned(FOnDelete) then FOnDelete(Self,Pos,Len);
|
|
if Assigned(FOnInsert) then FOnInsert(Self,Pos,Txt);
|
|
DeleteSrcLogEntry:=TSourceLogEntry.Create(Pos,Len,-1,'',sleoDelete);
|
|
FLog.Add(DeleteSrcLogEntry);
|
|
NotifyHooks(DeleteSrcLogEntry);
|
|
InsertSrcLogEntry:=TSourceLogEntry.Create(Pos,length(Txt),-1,Txt,sleoInsert);
|
|
FLog.Add(InsertSrcLogEntry);
|
|
NotifyHooks(InsertSrcLogEntry);
|
|
FSource:=copy(FSource,1,Pos-1)
|
|
+Txt
|
|
+copy(FSource,Pos+Len,length(FSource)-Pos-Len+1);
|
|
FSrcLen:=length(FSource);
|
|
FLineCount:=-1;
|
|
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+Len) then
|
|
Markers[i].Deleted:=true
|
|
else begin
|
|
DeleteSrcLogEntry.AdjustPosition(Markers[i].NewPosition);
|
|
InsertSrcLogEntry.AdjustPosition(Markers[i].NewPosition);
|
|
end;
|
|
end;
|
|
end;
|
|
FModified:=true;
|
|
DoSourceChanged;
|
|
end;
|
|
|
|
procedure TSourceLog.Move(Pos, Len, MoveTo: integer);
|
|
var i: integer;
|
|
NewSrcLogEntry: TSourceLogEntry;
|
|
begin
|
|
if Assigned(FOnMove) then FOnMove(Self,Pos,Len,MoveTo);
|
|
if (MoveTo>=Pos) and (MoveTo<Pos+Len) then exit;
|
|
NewSrcLogEntry:=TSourceLogEntry.Create(Pos,Len,MoveTo,'',sleoMove);
|
|
FLog.Add(NewSrcLogEntry);
|
|
NotifyHooks(NewSrcLogEntry);
|
|
if MoveTo<Pos then begin
|
|
FSource:=copy(FSource,1,MoveTo-1)
|
|
+copy(FSource,Pos,Len)
|
|
+copy(FSource,MoveTo,Pos-MoveTo)
|
|
+copy(FSource,Pos+Len,length(FSource)-Pos-Len+1);
|
|
end else begin
|
|
FSource:=copy(FSource,1,Pos-1)
|
|
+copy(FSource,Pos+Len,MoveTo-Pos-Len)
|
|
+copy(FSource,Pos,Len)
|
|
+copy(FSource,MoveTo,length(FSource)-MoveTo+1);
|
|
end;
|
|
FSrcLen:=length(FSource);
|
|
FLineCount:=-1;
|
|
for i:=0 to FMarkers.Count-1 do begin
|
|
if (Markers[i].Deleted=false) then
|
|
NewSrcLogEntry.AdjustPosition(Markers[i].NewPosition);
|
|
end;
|
|
FModified:=true;
|
|
DoSourceChanged;
|
|
end;
|
|
|
|
function TSourceLog.AddMarker(Position: integer; SomeData: Pointer
|
|
): TSourceLogMarker;
|
|
begin
|
|
Result:=TSourceLogMarker.Create;
|
|
Result.Position:=Position;
|
|
Result.NewPosition:=Result.Position;
|
|
Result.Data:=SomeData;
|
|
Result.Deleted:=false;
|
|
Result.Log:=Self;
|
|
FMarkers.Add(Result);
|
|
end;
|
|
|
|
function TSourceLog.AddMarkerXY(Line, Column: integer; SomeData: Pointer
|
|
): TSourceLogMarker;
|
|
begin
|
|
Result:=TSourceLogMarker.Create;
|
|
LineColToPosition(Line,Column,Result.Position);
|
|
Result.NewPosition:=Result.Position;
|
|
Result.Data:=SomeData;
|
|
Result.Deleted:=false;
|
|
Result.Log:=Self;
|
|
FMarkers.Add(Result);
|
|
end;
|
|
|
|
procedure TSourceLog.AdjustPosition(var APosition: integer);
|
|
var i: integer;
|
|
begin
|
|
for i:=0 to Count-1 do
|
|
Items[i].AdjustPosition(APosition);
|
|
end;
|
|
|
|
{$IFOPT R+}{$DEFINE RangeChecking}{$ENDIF}
|
|
{$R-}
|
|
procedure TSourceLog.BuildLineRanges;
|
|
var
|
|
line:integer;
|
|
Cap: Integer;
|
|
SrcEnd: PChar;
|
|
SrcStart: PChar;
|
|
p: PChar;
|
|
begin
|
|
//DebugLn(['[TSourceLog.BuildLineRanges] A Self=',DbgS(Self),',LineCount=',FLineCount,' Len=',SourceLength]);
|
|
if FLineCount>=0 then exit;
|
|
// build line range list
|
|
FLineCount:=0;
|
|
if FSource='' then begin
|
|
ReAllocMem(FLineRanges,0);
|
|
exit;
|
|
end;
|
|
Cap:=FSrcLen div 20+100;
|
|
ReAllocMem(FLineRanges,Cap*SizeOf(TLineRange));
|
|
line:=0;
|
|
FLineRanges[line].StartPos:=1;
|
|
SrcStart:=PChar(FSource);
|
|
SrcEnd:=SrcStart+FSrcLen;
|
|
p:=SrcStart;
|
|
repeat
|
|
if (not (p^ in [#10,#13])) then begin
|
|
if (p^=#0) and (p>=SrcEnd) then break;
|
|
inc(p);
|
|
end else begin
|
|
// new line
|
|
FLineRanges[line].EndPos:=p-SrcStart+1;
|
|
inc(line);
|
|
if line>=Cap then begin
|
|
Cap:=Cap*2;
|
|
ReAllocMem(FLineRanges,Cap*SizeOf(TLineRange));
|
|
end;
|
|
if (p[1] in [#10,#13]) and (p^<>p[1]) then
|
|
inc(p,2)
|
|
else
|
|
inc(p);
|
|
FLineRanges[line].StartPos:=p-SrcStart+1;
|
|
end;
|
|
until false;
|
|
FLineRanges[line].EndPos:=fSrcLen+1;
|
|
FLineCount:=line;
|
|
if not (FSource[FSrcLen] in [#10,#13]) then
|
|
inc(FLineCount);
|
|
ReAllocMem(FLineRanges,FLineCount*SizeOf(TLineRange));
|
|
//DebugLn('[TSourceLog.BuildLineRanges] END ',FLineCount);
|
|
end;
|
|
{$IFDEF RangeChecking}{$R+}{$ENDIF}
|
|
|
|
procedure TSourceLog.LineColToPosition(Line, Column: integer;
|
|
out Position: integer);
|
|
begin
|
|
BuildLineRanges;
|
|
if (Line>=1) and (Line<=FLineCount) and (Column>=1) then begin
|
|
if (Line<FLineCount) then begin
|
|
// not the last line
|
|
if (Column<=FLineRanges[Line-1].EndPos-FLineRanges[Line-1].StartPos+1)
|
|
then begin
|
|
Position:=FLineRanges[Line-1].StartPos+Column-1;
|
|
end else begin
|
|
Position:=FLineRanges[Line-1].EndPos;
|
|
end;
|
|
end else begin
|
|
// last line
|
|
if (Column<=fSrcLen-FLineRanges[Line-1].StartPos) then begin
|
|
Position:=FLineRanges[Line-1].StartPos+Column-1;
|
|
end else begin
|
|
Position:=FLineRanges[Line-1].EndPos;
|
|
end;
|
|
end;
|
|
end else begin
|
|
Position:=-1;
|
|
end;
|
|
end;
|
|
|
|
procedure TSourceLog.AbsoluteToLineCol(Position: integer;
|
|
out Line, Column: integer);
|
|
var l,r,m:integer;
|
|
begin
|
|
BuildLineRanges;
|
|
if (FLineCount=0) or (Position<1) or (Position>fSrcLen+1) 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.LineColIsOutside(Line, Column: integer): boolean;
|
|
begin
|
|
BuildLineRanges;
|
|
Result:=true;
|
|
if (Line<1) or (Column<1) then exit;
|
|
if (Line>LineCount+1) then exit;
|
|
if (Line<=fLineCount)
|
|
and (Column>fLineRanges[Line-1].EndPos-fLineRanges[Line-1].StartPos+1) then
|
|
exit;
|
|
// check if on empty last line
|
|
if (Line=FLineCount+1)
|
|
and ((Column>1) or (FSource='') or (not (FSource[FSrcLen] in [#10,#13]))) then
|
|
exit;
|
|
Result:=false;
|
|
end;
|
|
|
|
function TSourceLog.LineColIsSpace(Line, Column: integer): boolean;
|
|
// check if there is a non space character in front of or at Line,Column
|
|
var
|
|
p: PChar;
|
|
rg: PLineRange;
|
|
begin
|
|
BuildLineRanges;
|
|
Result:=true;
|
|
if (Line<1) or (Column<1) or (Line>LineCount) then exit;
|
|
rg:=@fLineRanges[Line-1];
|
|
if (Column>rg^.EndPos-rg^.StartPos+1) then
|
|
exit;
|
|
p:=@fSource[rg^.StartPos];
|
|
if (p[Column-1]>' ') then exit(false);
|
|
if (Column>1) and (p[Column-2]>' ') then exit(false);
|
|
end;
|
|
|
|
function TSourceLog.AbsoluteToLineColStr(Position: integer): string;
|
|
var
|
|
Line: integer;
|
|
Column: integer;
|
|
begin
|
|
AbsoluteToLineCol(Position,Line,Column);
|
|
Result:='p='+IntToStr(Position)+',line='+IntToStr(Line)+',col='+IntToStr(Column);
|
|
end;
|
|
|
|
function TSourceLog.LoadFromFile(const Filename: string): boolean;
|
|
var
|
|
s: string;
|
|
fs: TFileStream;
|
|
p: Integer;
|
|
begin
|
|
Result := False;
|
|
LastError:='';
|
|
try
|
|
fs := TFileStream.Create(Filename, fmOpenRead or fmShareDenyNone);
|
|
try
|
|
SetLength(s{%H-}, fs.Size);
|
|
if s <> '' then
|
|
fs.Read(s[1], length(s));
|
|
FDiskEncoding := '';
|
|
FMemEncoding := '';
|
|
DecodeLoaded(Filename, s, FDiskEncoding, FMemEncoding);
|
|
|
|
// get line ending
|
|
FDiskLineEnding:=LineEnding;
|
|
p:=1;
|
|
while p<=length(s) do begin
|
|
if s[p] in [#10,#13] then begin
|
|
if s[p]=#10 then fDiskLineEnding:=#10
|
|
else if (p<length(s)) and (s[p+1]=#10) then fDiskLineEnding:=#13#10
|
|
else fDiskLineEnding:=#13;
|
|
break;
|
|
end;
|
|
inc(p);
|
|
end;
|
|
|
|
Source := s;
|
|
finally
|
|
fs.Free;
|
|
end;
|
|
Result := True;
|
|
except
|
|
on E: Exception do
|
|
LastError:=E.Message;
|
|
end;
|
|
end;
|
|
|
|
procedure TSourceLog.IncreaseChangeStep;
|
|
begin
|
|
if FChangeStep<High(FChangeStep) then
|
|
inc(FChangeStep)
|
|
else
|
|
FChangeStep:=low(FChangeStep);
|
|
//DebugLn('[TSourceLog.IncreaseChangeStep] ',FChangeStep,',',DbgS(Self));
|
|
end;
|
|
|
|
procedure TSourceLog.DoSourceChanged;
|
|
begin
|
|
IncreaseChangeStep;
|
|
//debugln(['TSourceLog.DoSourceChanged ']);
|
|
end;
|
|
|
|
function TSourceLog.SaveToFile(const Filename: string): boolean;
|
|
var
|
|
fs: TFileStream;
|
|
s: String;
|
|
begin
|
|
{$IFDEF VerboseCTSave}
|
|
DebugLn(['TSourceLog.SaveToFile Self=',DbgS(Self),' ',Filename,' Size=',length(Source)]);
|
|
CTDumpStack;
|
|
{$ENDIF}
|
|
Result := False;
|
|
LastError:='';
|
|
try
|
|
s := Source;
|
|
// convert encoding
|
|
EncodeSaving(Filename, s);
|
|
// convert line ending to disk line ending
|
|
if (DiskLineEnding<>'') and (LineEnding <> DiskLineEnding) then
|
|
s := ChangeLineEndings(s, DiskLineEnding);
|
|
|
|
// keep filename case on disk
|
|
if FileExistsUTF8(Filename) then begin
|
|
InvalidateFileStateCache(Filename);
|
|
fs := TFileStream.Create(Filename, fmOpenWrite or fmShareDenyNone);
|
|
fs.Size := 0;
|
|
end else begin
|
|
InvalidateFileStateCache; // invalidate all (samba shares)
|
|
fs := TFileStream.Create(Filename, fmCreate);
|
|
end;
|
|
try
|
|
if s <> '' then
|
|
fs.Write(s[1], length(s));
|
|
finally
|
|
fs.Free;
|
|
end;
|
|
Result := True;
|
|
except
|
|
on E: Exception do
|
|
LastError:=E.Message;
|
|
end;
|
|
end;
|
|
|
|
function TSourceLog.GetLines(StartLine, EndLine: integer): string;
|
|
var
|
|
StartPos: Integer;
|
|
EndPos: Integer;
|
|
begin
|
|
BuildLineRanges;
|
|
if StartLine<1 then StartLine:=1;
|
|
if EndLine>LineCount then EndLine:=LineCount;
|
|
if StartLine<=EndLine then begin
|
|
StartPos:=FLineRanges[StartLine-1].StartPos;
|
|
if EndLine<LineCount then
|
|
EndPos:=FLineRanges[EndLine].StartPos
|
|
else
|
|
EndPos:=FLineRanges[EndLine-1].EndPos;
|
|
SetLength(Result{%H-},EndPos-StartPos);
|
|
System.Move(FSource[StartPos],Result[1],length(Result));
|
|
end else
|
|
Result:='';
|
|
end;
|
|
|
|
function TSourceLog.IsEqual(sl: TStrings): boolean;
|
|
var
|
|
p: PChar;
|
|
Line: String;
|
|
l: PChar;
|
|
y: Integer;
|
|
begin
|
|
Result:=false;
|
|
if sl=nil then exit;
|
|
if (FSrcLen=0) and (sl.Count>0) then exit;
|
|
if (FLineCount>=0) and (sl.Count<>FLineCount) then exit;
|
|
p:=PChar(FSource);
|
|
y:=0;
|
|
while (y<sl.Count) do begin
|
|
Line:=sl[y];
|
|
if (Line<>'') then begin
|
|
l:=PChar(Line);
|
|
while (l^=p^) do begin
|
|
if (l^=#0) then begin
|
|
if l-PChar(Line)=length(Line) then begin
|
|
// end of Line
|
|
if (p-PChar(FSource)=FSrcLen) then begin
|
|
// end of source
|
|
Result:=y=sl.Count-1;
|
|
exit;
|
|
end;
|
|
break;
|
|
end else if p-PChar(FSource)=FSrcLen then begin
|
|
// not at end of Line, end of source
|
|
exit;
|
|
end;
|
|
end;
|
|
inc(p);
|
|
inc(l);
|
|
end;
|
|
if l^<>#0 then exit;
|
|
end;
|
|
// at end of Line
|
|
if not (p^ in [#10,#13]) then begin
|
|
// not between two lines in Source
|
|
Result:=(y=sl.Count-1) and (p-PChar(FSource)=FSrcLen);
|
|
exit;
|
|
end;
|
|
// skip line end
|
|
if (p[1] in [#10,#13]) and (p^<>p[1]) then
|
|
inc(p,2)
|
|
else
|
|
inc(p);
|
|
inc(y);
|
|
end;
|
|
Result:=p-PChar(FSource)=FSrcLen;
|
|
end;
|
|
|
|
function TSourceLog.OldIsEqual(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 (y<sl.Count) do begin
|
|
Line:=sl[y];
|
|
LineLen:=length(Line);
|
|
if fSrcLen-p+1<LineLen then exit;
|
|
x:=1;
|
|
while (x<=LineLen) do begin
|
|
if Line[x]<>fSource[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 p<FSrcLen then exit;
|
|
Result:=true;
|
|
end;
|
|
|
|
procedure TSourceLog.Assign(sl: TStrings);
|
|
begin
|
|
if sl=nil then exit;
|
|
if IsEqual(sl) then exit;
|
|
IncreaseHookLock;
|
|
try
|
|
Clear;
|
|
fSource := sl.Text;
|
|
fSrcLen := Length(fSource);
|
|
DoSourceChanged;
|
|
NotifyHooks(nil);
|
|
finally
|
|
DecreaseHookLock;
|
|
end;
|
|
end;
|
|
|
|
procedure TSourceLog.AssignTo(sl: TStrings; UseAddStrings: Boolean);
|
|
var y: integer;
|
|
s: string;
|
|
TempList: TStringList;
|
|
begin
|
|
if sl=nil then exit;
|
|
if IsEqual(sl) then exit;
|
|
if UseAddStrings then begin
|
|
TempList:=TStringList.Create;
|
|
AssignTo(TempList,false);
|
|
sl.BeginUpdate;
|
|
sl.Clear;
|
|
sl.AddStrings(TempList);
|
|
sl.EndUpdate;
|
|
TempList.Free;
|
|
end else begin
|
|
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;
|
|
end;
|
|
|
|
procedure TSourceLog.LoadFromStream(aStream: TStream);
|
|
var
|
|
NewSrcLen: integer;
|
|
NewSource: String;
|
|
begin
|
|
IncreaseHookLock;
|
|
try
|
|
if aStream=nil then exit;
|
|
aStream.Position:=0;
|
|
NewSrcLen:=aStream.Size-aStream.Position;
|
|
NewSource:='';
|
|
if NewSrcLen>0 then begin
|
|
SetLength(NewSource,NewSrcLen);
|
|
aStream.Read(NewSource[1],NewSrcLen);
|
|
end;
|
|
Source:=NewSource;
|
|
finally
|
|
DecreaseHookLock;
|
|
end;
|
|
end;
|
|
|
|
procedure TSourceLog.SaveToStream(aStream: TStream);
|
|
begin
|
|
if fSource<>'' then aStream.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;
|
|
|
|
function TSourceLog.CalcMemSize: PtrUInt;
|
|
begin
|
|
Result:=PtrUInt(InstanceSize)
|
|
+MemSizeString(FDiskEncoding)
|
|
+MemSizeString(FDiskLineEnding)
|
|
+PtrUint(FLineCount)*SizeOf(TLineRange)
|
|
+MemSizeString(FMemEncoding)
|
|
+PtrUInt(FChangeHookCount)*SizeOf(TOnSourceChange)
|
|
+MemSizeString(FSource)
|
|
+PtrUint(FLog.Count)*SizeOf(TSourceLogEntry)
|
|
+PtrUInt(FMarkers.Count*TSourceLogMarker.InstanceSize);
|
|
end;
|
|
|
|
function TSourceLog.IndexOfChangeHook(AChangeHook: TOnSourceChange): integer;
|
|
begin
|
|
Result:=FChangeHookCount-1;
|
|
while (Result>=0) and (FChangeHooks[Result]<>AChangeHook) do dec(Result);
|
|
end;
|
|
|
|
procedure TSourceLog.DecodeLoaded(const AFilename: string;
|
|
var ASource, ADiskEncoding, AMemEncoding: string);
|
|
begin
|
|
if Assigned(OnDecodeLoaded) then
|
|
OnDecodeLoaded(Self,AFilename,ASource,ADiskEncoding,AMemEncoding);
|
|
end;
|
|
|
|
procedure TSourceLog.EncodeSaving(const AFilename: string; var ASource: string);
|
|
begin
|
|
if Assigned(OnEncodeSaving) then
|
|
OnEncodeSaving(Self,AFilename,ASource);
|
|
end;
|
|
|
|
procedure TSourceLog.AddChangeHook(AnOnSourceChange: TOnSourceChange);
|
|
var i: integer;
|
|
begin
|
|
i:=IndexOfChangeHook(AnOnSourceChange);
|
|
if i>=0 then exit;
|
|
inc(FChangeHookCount);
|
|
if FChangeHooks=nil then
|
|
GetMem(FChangeHooks, SizeOf(TOnSourceChange))
|
|
else
|
|
ReallocMem(FChangeHooks, SizeOf(TOnSourceChange) * FChangeHookCount);
|
|
FChangeHooks[FChangeHookCount-1]:=AnOnSourceChange;
|
|
end;
|
|
|
|
procedure TSourceLog.RemoveChangeHook(AnOnSourceChange: TOnSourceChange);
|
|
var i,j: integer;
|
|
begin
|
|
i:=IndexOfChangeHook(AnOnSourceChange);
|
|
if i<0 then exit;
|
|
dec(FChangeHookCount);
|
|
if FChangeHookCount=1 then
|
|
FreeMem(FChangeHooks)
|
|
else begin
|
|
for j:=i to FChangeHookCount-2 do
|
|
FChangeHooks[j]:=FChangeHooks[j+1];
|
|
ReAllocMem(FChangeHooks,SizeOf(TOnSourceChange) * FChangeHookCount);
|
|
end;
|
|
end;
|
|
|
|
{ TSourceLogMarker }
|
|
|
|
destructor TSourceLogMarker.Destroy;
|
|
begin
|
|
if Log<>nil then Log.FMarkers.Remove(Self);
|
|
Log:=nil;
|
|
inherited Destroy;
|
|
end;
|
|
|
|
end.
|
|
|