mirror of
https://gitlab.com/freepascal.org/lazarus/lazarus.git
synced 2025-04-05 13:18:08 +02:00
497 lines
15 KiB
ObjectPascal
497 lines
15 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. *
|
|
* *
|
|
***************************************************************************
|
|
|
|
Abstract:
|
|
form for showing the diffs of editor files changed on disk
|
|
}
|
|
unit DiskDiffsDialog;
|
|
|
|
{$mode objfpc}{$H+}
|
|
|
|
interface
|
|
|
|
uses
|
|
// RTL + FCL
|
|
Classes, SysUtils, System.UITypes,
|
|
// LCL
|
|
Forms, StdCtrls, ExtCtrls, CheckLst, ButtonPanel, Buttons,
|
|
// CodeTools
|
|
CodeCache,
|
|
// LazUtils
|
|
LazFileUtils, LazFileCache, LazLoggerBase,
|
|
// IdeIntf
|
|
IDEImagesIntf,
|
|
// SynEdit
|
|
SynEdit, SynHighlighterDiff,
|
|
// IdeConfig
|
|
EnvironmentOpts,
|
|
// IDE
|
|
Project, PackageDefs, DiffPatch, LazarusIDEStrConsts, EditorOptions;
|
|
|
|
type
|
|
PDiffItem = ^TDiffItem;
|
|
TDiffItem = record
|
|
Valid: boolean;
|
|
Code: TCodeBuffer;
|
|
Owner: TObject;
|
|
Diff: string;
|
|
TxtOnDisk: string;
|
|
end;
|
|
|
|
{ TDiskDiffsDlg }
|
|
|
|
TDiskDiffsDlg = class(TForm)
|
|
BtnPanel: TButtonPanel;
|
|
CheckDiskChangesWithLoadingCheckBox: TCheckBox;
|
|
DiffSynEdit: TSynEdit;
|
|
FilesListBox: TCheckListBox;
|
|
WarnImage: TPaintBox;
|
|
WarnLabel: TLabel;
|
|
Splitter: TSplitter;
|
|
SynDiffSyn1: TSynDiffSyn;
|
|
procedure FilesListBoxClick(Sender: TObject);
|
|
procedure FormClose(Sender: TObject; var {%H-}CloseAction: TCloseAction);
|
|
procedure WarnImagePaint(Sender: TObject);
|
|
private
|
|
FIgnoreList: TFPList;
|
|
FPackageList: TStringList;
|
|
FCodeList: TFPList;
|
|
FHasLocalModifications: Boolean;
|
|
FHasExistingFiles: Boolean;
|
|
FCachedDiffs: TFPList; // List of PDiffItem
|
|
function ShortenFilename(const aFilename: string): string;
|
|
procedure AddFile2Box(AInfo: TObject; AFileName: string; AModified: Boolean);
|
|
procedure FillFilesListBox;
|
|
procedure ApplyChecks;
|
|
procedure ShowDiff;
|
|
function GetCachedDiff(FileOwner: TObject; AltFilename: string): PDiffItem;
|
|
procedure ClearCache;
|
|
public
|
|
constructor Create(TheOwner: TComponent); override;
|
|
destructor Destroy; override;
|
|
property CodeList: TFPList read FCodeList write FCodeList; // list of TCodeBuffer
|
|
property PackageList: TStringList read FPackageList write FPackageList; // list of alternative filename and TLazPackage
|
|
property IgnoreList: TFPList read FIgnoreList write FIgnoreList; // list of TCodeBuffer or TLazPackage
|
|
end;
|
|
|
|
function ShowDiskDiffsDialog(
|
|
ACodeList: TFPList; // list of TCodeBuffer
|
|
APackageList: TStringList; // list of TLazPackage
|
|
AnIgnoreList: TFPList // ignore / mark modified
|
|
): TModalResult; // ok=reload, cancel=ignore all/mark all modified
|
|
|
|
implementation
|
|
|
|
{$R *.lfm}
|
|
|
|
var
|
|
DiskDiffsDlg: TDiskDiffsDlg = nil;
|
|
|
|
procedure CheckUnits(ACodeList: TFPList);
|
|
|
|
function AddChangedBuffer(Code: TCodeBuffer): boolean;
|
|
var
|
|
fs: TFileStream;
|
|
s, DiskEncoding, MemEncoding, aFilename: string;
|
|
begin
|
|
if (Code=nil) or Code.IsVirtual then
|
|
exit(false);
|
|
|
|
aFilename:=Code.Filename;
|
|
if EnvironmentOptions.CheckDiskChangesWithLoading then
|
|
begin
|
|
// load and compare
|
|
try
|
|
fs := TFileStream.Create(aFilename, fmOpenRead or fmShareDenyNone);
|
|
try
|
|
SetLength(s{%H-}, fs.Size);
|
|
if s <> '' then
|
|
fs.Read(s[1], length(s));
|
|
DiskEncoding := '';
|
|
MemEncoding := '';
|
|
Code.CodeCache.OnDecodeLoaded(Code,aFilename,
|
|
s,DiskEncoding,MemEncoding);
|
|
//debugln(['CheckUnitsWithLoading ',aFilename,
|
|
// ' ',length(s),'=',Code.SourceLength]);
|
|
if (MemEncoding=Code.MemEncoding)
|
|
and (DiskEncoding=Code.DiskEncoding)
|
|
and (length(s)=Code.SourceLength)
|
|
and (s=Code.Source) then begin
|
|
// same content -> no need to bother user
|
|
Code.MakeFileDateValid;
|
|
exit(false);
|
|
end;
|
|
finally
|
|
fs.Free;
|
|
end;
|
|
except
|
|
// unable to load, e.g. no longer exists or permission denied
|
|
end;
|
|
end;
|
|
|
|
// file has changed
|
|
Result:=true;
|
|
end;
|
|
|
|
var
|
|
i: Integer;
|
|
begin
|
|
for i:=ACodeList.Count-1 downto 0 do
|
|
begin
|
|
if not AddChangedBuffer(TCodeBuffer(ACodeList[i])) then
|
|
ACodeList.Delete(i);
|
|
end;
|
|
end;
|
|
|
|
procedure CheckPackages(APackageList: TStringList);
|
|
var
|
|
i: Integer;
|
|
CurPackage: TLazPackage;
|
|
PackageOk: Boolean;
|
|
fs: TFileStream;
|
|
CurSource, DiskSource: string;
|
|
AltFilename, LPKFilename: String;
|
|
begin
|
|
for i:=APackageList.Count-1 downto 0 do
|
|
begin
|
|
AltFilename:=APackageList[i];
|
|
CurPackage:=TLazPackage(APackageList.Objects[i]);
|
|
LPKFilename:=CurPackage.Filename;
|
|
PackageOk:=false;
|
|
if CurPackage.LPKSource=nil then
|
|
continue; // this package was not loaded/saved
|
|
if CompareFilenames(LPKFilename,AltFilename)<>0 then
|
|
continue; // lpk has vanished, an alternative lpk was found => show
|
|
try
|
|
CurPackage.SaveToString(CurSource);
|
|
fs:=TFileStream.Create(LPKFilename,fmOpenRead);
|
|
try
|
|
if fs.Size=length(CurSource) then begin
|
|
// size has not changed => load to see difference
|
|
SetLength(DiskSource{%H-},fs.Size);
|
|
fs.Read(DiskSource[1],length(DiskSource));
|
|
if DiskSource=CurSource then
|
|
PackageOk:=true;
|
|
end;
|
|
finally
|
|
fs.Free;
|
|
end;
|
|
except
|
|
// unable to load
|
|
on E: Exception do
|
|
DebugLn(['CheckPackagesWithLoading Filename=',CurPackage.Filename,' Error=',E.Message]);
|
|
end;
|
|
if PackageOk then
|
|
APackageList.Delete(i);
|
|
end;
|
|
end;
|
|
|
|
function ShowDiskDiffsDialog(ACodeList: TFPList; APackageList: TStringList;
|
|
AnIgnoreList: TFPList): TModalResult;
|
|
|
|
function ListsAreEmpty: boolean;
|
|
var
|
|
i: Integer;
|
|
begin
|
|
if ACodeList<>nil then
|
|
for i:=0 to ACodeList.Count-1 do
|
|
if AnIgnoreList.IndexOf(ACodeList[i])<0 then
|
|
exit(false);
|
|
if APackageList<>nil then
|
|
for i:=0 to APackageList.Count-1 do
|
|
if AnIgnoreList.IndexOf(APackageList.Objects[i])<0 then
|
|
exit(false);
|
|
Result:=true;
|
|
end;
|
|
|
|
begin
|
|
if (DiskDiffsDlg<>nil) or ListsAreEmpty then
|
|
exit(mrIgnore);
|
|
if Assigned(ACodeList) then
|
|
CheckUnits(ACodeList);
|
|
if Assigned(APackageList) then
|
|
CheckPackages(APackageList);
|
|
if ListsAreEmpty then
|
|
exit(mrIgnore);
|
|
DiskDiffsDlg:=TDiskDiffsDlg.Create(nil);
|
|
try
|
|
DiskDiffsDlg.CodeList:=ACodeList;
|
|
DiskDiffsDlg.PackageList:=APackageList;
|
|
DiskDiffsDlg.IgnoreList:=AnIgnoreList;
|
|
DiskDiffsDlg.FillFilesListBox;
|
|
Result:=DiskDiffsDlg.ShowModal;
|
|
case Result of
|
|
mrOK : DiskDiffsDlg.ApplyChecks;
|
|
mrCancel : Result:=mrIgnore;
|
|
end;
|
|
finally
|
|
DiskDiffsDlg.Free;
|
|
DiskDiffsDlg:=nil;
|
|
end;
|
|
Assert(Result in [mrOK,mrIgnore], 'ShowDiskDiffsDialog: Invalid result '+IntToStr(Result));
|
|
end;
|
|
|
|
{ TDiskDiffsDlg }
|
|
|
|
procedure TDiskDiffsDlg.FilesListBoxClick(Sender: TObject);
|
|
begin
|
|
ShowDiff;
|
|
end;
|
|
|
|
procedure TDiskDiffsDlg.FormClose(Sender: TObject; var CloseAction: TCloseAction);
|
|
begin
|
|
EnvironmentOptions.CheckDiskChangesWithLoading:=CheckDiskChangesWithLoadingCheckBox.Checked;
|
|
end;
|
|
|
|
procedure TDiskDiffsDlg.WarnImagePaint(Sender: TObject);
|
|
var
|
|
ppi: Integer;
|
|
imgIndex: Integer;
|
|
paintbx: TPaintBox;
|
|
begin
|
|
paintbx := Sender as TPaintbox;
|
|
ppi := Font.PixelsPerInch;
|
|
imgIndex := IDEImages.GetImageIndex('state_warning');
|
|
IDEImages.Images_16.DrawForPPI(paintbx.Canvas, 0, 0, imgIndex, 16, ppi, GetCanvasScaleFactor);
|
|
end;
|
|
|
|
function TDiskDiffsDlg.ShortenFilename(const aFilename: string): string;
|
|
begin
|
|
Result:=Project1.RemoveProjectPathFromFilename(aFilename);
|
|
end;
|
|
|
|
procedure TDiskDiffsDlg.AddFile2Box(AInfo: TObject; AFileName: string; AModified: Boolean);
|
|
var
|
|
i: Integer;
|
|
begin
|
|
if AModified then
|
|
AFileName:='*'+AFileName;
|
|
i:=FilesListBox.Items.AddObject(AFileName,AInfo);
|
|
if AModified then
|
|
FHasLocalModifications:=True
|
|
else
|
|
FilesListBox.Checked[i]:=True;
|
|
end;
|
|
|
|
procedure TDiskDiffsDlg.FillFilesListBox;
|
|
var
|
|
i: integer;
|
|
CurUnit: TUnitInfo;
|
|
APackage: TLazPackage;
|
|
aCode: TCodeBuffer;
|
|
aFilename, AltFilename: String;
|
|
CurModified: Boolean;
|
|
begin
|
|
FHasLocalModifications:=False;
|
|
FHasExistingFiles:=False;
|
|
FilesListBox.Items.BeginUpdate;
|
|
FilesListBox.Items.Clear;
|
|
if CodeList<>nil then
|
|
begin
|
|
for i:=0 to CodeList.Count-1 do begin
|
|
aCode:=TCodeBuffer(CodeList[i]);
|
|
if IgnoreList.IndexOf(aCode)>=0 then continue;
|
|
aFilename:=aCode.Filename;
|
|
CurUnit:=Project1.UnitInfoWithFilename(aFilename);
|
|
if CurUnit=nil then
|
|
CurUnit:=Project1.UnitInfoWithLFMFilename(aFilename);
|
|
CurModified:=(CurUnit<>nil) and CurUnit.Modified;
|
|
AddFile2Box(aCode, ShortenFilename(aFilename), CurModified);
|
|
if (not FHasExistingFiles) and FileExistsCached(aFilename) then
|
|
FHasExistingFiles:=true;
|
|
end;
|
|
end;
|
|
if PackageList<>nil then
|
|
begin
|
|
for i:=0 to PackageList.Count-1 do begin
|
|
APackage:=TLazPackage(PackageList.Objects[i]);
|
|
if IgnoreList.IndexOf(APackage)>=0 then continue;
|
|
AddFile2Box(APackage, APackage.Filename, APackage.Modified);
|
|
AltFilename:=PackageList[i];
|
|
if not FHasExistingFiles then
|
|
begin
|
|
if FileExistsCached(APackage.Filename)
|
|
or ((CompareFilenames(AltFilename,APackage.Filename)<>0)
|
|
and FileExistsCached(AltFilename)) then
|
|
FHasExistingFiles:=true;
|
|
end;
|
|
end;
|
|
end;
|
|
FilesListBox.Items.EndUpdate;
|
|
WarnImage.Visible:=FHasLocalModifications;
|
|
WarnLabel.Visible:=FHasLocalModifications;
|
|
BtnPanel.OkButton.Visible:=FHasExistingFiles;
|
|
end;
|
|
|
|
procedure TDiskDiffsDlg.ShowDiff;
|
|
var
|
|
i: integer;
|
|
DiffItem: PDiffItem;
|
|
AInfo: TObject;
|
|
aFilename: String;
|
|
aCode: TCodeBuffer;
|
|
begin
|
|
DiffItem:=nil;
|
|
i:=FilesListBox.ItemIndex;
|
|
if i>=0 then
|
|
begin
|
|
aFilename:=FilesListBox.Items[i];
|
|
if aFilename[1]='*' then
|
|
Delete(aFilename,1,1);
|
|
AInfo:=FilesListBox.Items.Objects[i];
|
|
if AInfo is TCodeBuffer then
|
|
begin
|
|
aCode:=TCodeBuffer(AInfo);
|
|
DiffItem:=GetCachedDiff(aCode,'');
|
|
end else if AInfo is TLazPackage then begin
|
|
for i:=0 to PackageList.Count-1 do
|
|
if PackageList.Objects[i]=AInfo then
|
|
begin
|
|
DiffItem:=GetCachedDiff(TLazPackage(AInfo),PackageList[i]);
|
|
break;
|
|
end;
|
|
end;
|
|
end;
|
|
if DiffItem<>nil then begin
|
|
DiffSynEdit.Lines.Text:=DiffItem^.Diff;
|
|
end else begin
|
|
DiffSynEdit.Lines.Clear;
|
|
end;
|
|
end;
|
|
|
|
function TDiskDiffsDlg.GetCachedDiff(FileOwner: TObject; AltFilename: string
|
|
): PDiffItem;
|
|
var
|
|
i: integer;
|
|
fs: TFileStream;
|
|
Filename: String;
|
|
APackage: TLazPackage;
|
|
Source: String;
|
|
DiffOutput: TDiffOutput;
|
|
Code: TCodeBuffer;
|
|
begin
|
|
if FCachedDiffs=nil then
|
|
FCachedDiffs:=TFPList.Create;
|
|
for i:=0 to FCachedDiffs.Count-1 do begin
|
|
Result:=PDiffItem(FCachedDiffs[i]);
|
|
if (Result<>nil) and (Result^.Owner=FileOwner) then exit;
|
|
end;
|
|
New(Result);
|
|
Result^.Owner:=FileOwner;
|
|
try
|
|
if FileOwner is TCodeBuffer then begin
|
|
// compare disk and codetools
|
|
Code:=TCodeBuffer(FileOwner);
|
|
Filename:=Code.Filename;
|
|
Source:=Code.Source;
|
|
end else if FileOwner is TLazPackage then begin
|
|
// compare disk and package
|
|
APackage:=TLazPackage(FileOwner);
|
|
if AltFilename<>'' then begin
|
|
if CompareFilenames(AltFilename,APackage.Filename)<>0 then
|
|
Result^.Diff+=Format(lisLpkHasVanishedOnDiskUsingAsAlternative,
|
|
[LineEnding+AltFilename+LineEnding]);
|
|
Filename:=AltFilename;
|
|
end
|
|
else if APackage.LPKSource<>nil then
|
|
Filename:=APackage.LPKSource.Filename
|
|
else
|
|
Filename:=APackage.GetFullFilename(true);
|
|
APackage.SaveToString(Source);
|
|
end else begin
|
|
Filename:='';
|
|
Source:='';
|
|
end;
|
|
fs:=TFileStream.Create(Filename,fmOpenRead);
|
|
SetLength(Result^.TxtOnDisk,fs.Size);
|
|
if Result^.TxtOnDisk<>'' then
|
|
fs.Read(Result^.TxtOnDisk[1],length(Result^.TxtOnDisk));
|
|
fs.Free;
|
|
|
|
DiffOutput:=TDiffOutput.Create(Source,Result^.TxtOnDisk, []);
|
|
try
|
|
Result^.Diff+=DiffOutput.CreateTextDiff;
|
|
finally
|
|
DiffOutput.Free;
|
|
end;
|
|
except
|
|
On E: Exception do
|
|
Result^.Diff+='\ '+Format(lisDiskDiffErrorReadingFile, [E.Message]);
|
|
end;
|
|
FCachedDiffs.Add(Result);
|
|
end;
|
|
|
|
procedure TDiskDiffsDlg.ClearCache;
|
|
var
|
|
i: integer;
|
|
DiffItem: PDiffItem;
|
|
begin
|
|
if FCachedDiffs=nil then exit;
|
|
for i:=0 to FCachedDiffs.Count-1 do begin
|
|
DiffItem:=PDiffItem(FCachedDiffs[i]);
|
|
if DiffItem<>nil then begin
|
|
DiffItem^.TxtOnDisk:='';
|
|
DiffItem^.Diff:='';
|
|
Dispose(DiffItem);
|
|
end;
|
|
end;
|
|
FCachedDiffs.Clear;
|
|
end;
|
|
|
|
constructor TDiskDiffsDlg.Create(TheOwner: TComponent);
|
|
begin
|
|
inherited Create(TheOwner);
|
|
|
|
Caption:=lisDiskDiffSomeFilesHaveChangedOnDisk;
|
|
EditorOpts.GetSynEditSettings(DiffSynEdit);
|
|
DiffSynEdit.Lines.Text:=lisDiskDiffClickOnOneOfTheAboveItemsToSeeTheDiff;
|
|
|
|
BtnPanel.OkButton.Caption:=lisDiskDiffReloadCheckedFilesFromDisk;
|
|
Assert(BtnPanel.OKButton.ModalResult=mrOK, 'OKButton.ModalResult<>mrOK');
|
|
// Cancel button now means Ignore All Disk Changes
|
|
BtnPanel.CancelButton.Caption:=lisDiskDiffIgnoreAllDiskChanges;
|
|
|
|
WarnLabel.Caption:=lisDiskDiffSomeFilesHaveLocalChanges;
|
|
WarnLabel.Visible:=False;
|
|
WarnImage.Visible:=False;
|
|
|
|
CheckDiskChangesWithLoadingCheckBox.Caption:=lisCheckForDiskFileChangesViaContent;
|
|
CheckDiskChangesWithLoadingCheckBox.Checked:=EnvironmentOptions.CheckDiskChangesWithLoading;
|
|
end;
|
|
|
|
procedure TDiskDiffsDlg.ApplyChecks;
|
|
var
|
|
i: Integer;
|
|
begin
|
|
for i := 0 to FilesListBox.Count-1 do
|
|
if not FilesListBox.Checked[i] then
|
|
FIgnoreList.Add(FilesListBox.Items.Objects[i]);
|
|
end;
|
|
|
|
destructor TDiskDiffsDlg.Destroy;
|
|
begin
|
|
ClearCache;
|
|
FCachedDiffs.Free;
|
|
inherited Destroy;
|
|
end;
|
|
|
|
end.
|
|
|