lazarus/ide/diskdiffsdialog.pas

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.