1 {
2  ***************************************************************************
3  *                                                                         *
4  *   This source is free software; you can redistribute it and/or modify   *
5  *   it under the terms of the GNU General Public License as published by  *
6  *   the Free Software Foundation; either version 2 of the License, or     *
7  *   (at your option) any later version.                                   *
8  *                                                                         *
9  *   This code is distributed in the hope that it will be useful, but      *
10  *   WITHOUT ANY WARRANTY; without even the implied warranty of            *
11  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU     *
12  *   General Public License for more details.                              *
13  *                                                                         *
14  *   A copy of the GNU General Public License is available on the World    *
15  *   Wide Web at <http://www.gnu.org/copyleft/gpl.html>. You can also      *
16  *   obtain it by writing to the Free Software Foundation,                 *
17  *   Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1335, USA.   *
18  *                                                                         *
19  ***************************************************************************
20 
21  Abstract:
22    form for showing the diffs of editor files changed on disk
23 }
24 unit DiskDiffsDialog;
25 
26 {$mode objfpc}{$H+}
27 
28 interface
29 
30 uses
31   // RTL + FCL
32   Classes, SysUtils,
33   // LCL
34   LCLProc, LCLType, Forms, StdCtrls, ExtCtrls, CheckLst, ButtonPanel, Buttons,
35   // CodeTools
36   FileProcs, CodeCache,
37   // LazUtils
38   LazFileUtils, UITypes,
39   // IdeIntf
40   IDEImagesIntf,
41   // SynEdit
42   SynEdit, SynHighlighterDiff,
43   // IDE
44   Project, PackageDefs, DiffPatch, LazarusIDEStrConsts, EnvironmentOpts, EditorOptions;
45 
46 type
47   PDiffItem = ^TDiffItem;
48   TDiffItem = record
49     Valid: boolean;
50     Code: TCodeBuffer;
51     Owner: TObject;
52     Diff: string;
53     TxtOnDisk: string;
54   end;
55 
56   { TDiskDiffsDlg }
57 
58   TDiskDiffsDlg = class(TForm)
59     BtnPanel: TButtonPanel;
60     CheckDiskChangesWithLoadingCheckBox: TCheckBox;
61     DiffSynEdit: TSynEdit;
62     FilesListBox: TCheckListBox;
63     WarnImage: TPaintBox;
64     WarnLabel: TLabel;
65     Splitter: TSplitter;
66     SynDiffSyn1: TSynDiffSyn;
67     procedure FilesListBoxClick(Sender: TObject);
68     procedure FormClose(Sender: TObject; var {%H-}CloseAction: TCloseAction);
69     procedure WarnImagePaint(Sender: TObject);
70   private
71     FIgnoreList: TFPList;
72     FPackageList: TStringList;
73     FUnitList: TFPList;
74     FHasLocalModifications: Boolean;
75     FCachedDiffs: TFPList; // List of PDiffItem
76     procedure AddFile2Box(AInfo: TObject; AFileName: string; AModified: Boolean);
77     procedure FillFilesListBox;
78     procedure ApplyChecks;
79     procedure ShowDiff;
GetCachedDiffnull80     function GetCachedDiff(FileOwner: TObject; AltFilename: string): PDiffItem;
81     procedure ClearCache;
82   public
83     property UnitList: TFPList read FUnitList write FUnitList; // list of TUnitInfo
84     property PackageList: TStringList read FPackageList write FPackageList; // list of alternative filename and TLazPackage
85     property IgnoreList: TFPList read FIgnoreList write FIgnoreList;
86     constructor Create(TheOwner: TComponent); override;
87     destructor Destroy; override;
88   end;
89 
ShowDiskDiffsDialognull90 function ShowDiskDiffsDialog(AnUnitList: TFPList;
91   APackageList: TStringList; AnIgnoreList: TFPList): TModalResult;
92 
93 implementation
94 
95 {$R *.lfm}
96 
97 var
98   DiskDiffsDlg: TDiskDiffsDlg = nil;
99 
100 // Procedures used by ShowDiskDiffsDialog
101 
102 procedure CheckUnitsWithLoading(AnUnitList: TFPList);
103 var
104   i: Integer;
105   CurUnit: TUnitInfo;
106   CodeOk: Boolean;
107   MemCode: TCodeBuffer;
108   s, DiskEncoding, MemEncoding: String;
109   fs: TFileStream;
110 begin
111   for i:=AnUnitList.Count-1 downto 0 do
112   begin
113     CurUnit:=TUnitInfo(AnUnitList[i]);
114     MemCode:=CurUnit.Source;
115     CodeOk:=false;
116     try
117       fs := TFileStream.Create(MemCode.Filename, fmOpenRead or fmShareDenyNone);
118       try
119         SetLength(s{%H-}, fs.Size);
120         if s <> '' then
121           fs.Read(s[1], length(s));
122         DiskEncoding := '';
123         MemEncoding := '';
124         MemCode.CodeCache.OnDecodeLoaded(MemCode,MemCode.Filename,
125           s,DiskEncoding,MemEncoding);
126         //debugln(['CheckUnitsWithLoading ',MemCode.Filename,
127         //  ' ',length(s),'=',MemCode.SourceLength]);
128         if (MemEncoding=MemCode.MemEncoding)
129         and (DiskEncoding=MemCode.DiskEncoding)
130         and (length(s)=MemCode.SourceLength)
131         and (s=MemCode.Source) then begin
132           CodeOk:=true;
133         end;
134       finally
135         fs.Free;
136       end;
137     except
138       // unable to load
139     end;
140     if CodeOk then begin
141       if CurUnit.Source<>nil then
142         CurUnit.Source.MakeFileDateValid;
143       AnUnitList.Delete(i);
144     end;
145   end;
146 end;
147 
148 procedure CheckPackagesWithLoading(APackageList: TStringList);
149 var
150   i: Integer;
151   CurPackage: TLazPackage;
152   PackageOk: Boolean;
153   fs: TFileStream;
154   CurSource, DiskSource: string;
155   AltFilename: String;
156 begin
157   for i:=APackageList.Count-1 downto 0 do
158   begin
159     AltFilename:=APackageList[i];
160     CurPackage:=TLazPackage(APackageList.Objects[i]);
161     PackageOk:=false;
162     if CurPackage.LPKSource=nil then
163       continue; // this package was not loaded/saved
164     if CompareFilenames(CurPackage.Filename,AltFilename)<>0 then
165       continue; // lpk has vanished, an alternative lpk was found => show
166     try
167       CurPackage.SaveToString(CurSource);
168       fs:=TFileStream.Create(CurPackage.Filename,fmOpenRead);
169       try
170         if fs.Size=length(CurSource) then begin
171           // size has not changed => load to see difference
172           SetLength(DiskSource{%H-},fs.Size);
173           fs.Read(DiskSource[1],length(DiskSource));
174           if DiskSource=CurSource then
175             PackageOk:=true;
176         end;
177       finally
178         fs.Free;
179       end;
180     except
181       // unable to load
182       on E: Exception do
183         DebugLn(['CheckPackagesWithLoading Filename=',CurPackage.Filename,' Error=',E.Message]);
184     end;
185     if PackageOk then
186       APackageList.Delete(i);
187   end;
188 end;
189 
ShowDiskDiffsDialognull190 function ShowDiskDiffsDialog(AnUnitList: TFPList; APackageList: TStringList;
191   AnIgnoreList: TFPList): TModalResult;
192 
ListsAreEmptynull193   function ListsAreEmpty: boolean;
194   begin
195     Result:=((AnUnitList=nil) or (AnUnitList.Count=0))
196         and ((APackageList=nil) or (APackageList.Count=0));
197   end;
198 
199 begin
200   if (DiskDiffsDlg<>nil) or ListsAreEmpty then
201     exit(mrIgnore);
202   if EnvironmentOptions.CheckDiskChangesWithLoading then begin
203     if Assigned(AnUnitList) then
204       CheckUnitsWithLoading(AnUnitList);
205     if Assigned(APackageList) then
206       CheckPackagesWithLoading(APackageList);
207     if ListsAreEmpty then exit(mrIgnore);
208   end;
209   DiskDiffsDlg:=TDiskDiffsDlg.Create(nil);
210   DiskDiffsDlg.UnitList:=AnUnitList;
211   DiskDiffsDlg.PackageList:=APackageList;
212   DiskDiffsDlg.IgnoreList:=AnIgnoreList;
213   DiskDiffsDlg.FillFilesListBox;
214   Result:=DiskDiffsDlg.ShowModal;
215   case Result of
216     mrOK : DiskDiffsDlg.ApplyChecks;
217     mrCancel : Result:=mrIgnore;
218   end;
219   DiskDiffsDlg.Free;
220   DiskDiffsDlg:=nil;
221   Assert(Result in [mrOK,mrIgnore], 'ShowDiskDiffsDialog: Invalid result '+IntToStr(Result));
222 end;
223 
224 { TDiskDiffsDlg }
225 
226 procedure TDiskDiffsDlg.FilesListBoxClick(Sender: TObject);
227 begin
228   ShowDiff;
229 end;
230 
231 procedure TDiskDiffsDlg.FormClose(Sender: TObject; var CloseAction: TCloseAction);
232 begin
233   EnvironmentOptions.CheckDiskChangesWithLoading:=CheckDiskChangesWithLoadingCheckBox.Checked;
234 end;
235 
236 procedure TDiskDiffsDlg.WarnImagePaint(Sender: TObject);
237 var
238   ppi: Integer;
239   imgIndex: Integer;
240   paintbx: TPaintBox;
241 begin
242   paintbx := Sender as TPaintbox;
243   ppi := Font.PixelsPerInch;
244   imgIndex := IDEImages.GetImageIndex('state_warning');
245   IDEImages.Images_16.DrawForPPI(paintbx.Canvas, 0, 0, imgIndex, 16, ppi, GetCanvasScaleFactor);
246 end;
247 
248 procedure TDiskDiffsDlg.AddFile2Box(AInfo: TObject; AFileName: string; AModified: Boolean);
249 var
250   i: Integer;
251 begin
252   if AModified then
253     AFileName:='*'+AFileName;
254   i:=FilesListBox.Items.AddObject(AFileName,AInfo);
255   if AModified then
256     FHasLocalModifications:=True
257   else
258     FilesListBox.Checked[i]:=True;
259 end;
260 
261 procedure TDiskDiffsDlg.FillFilesListBox;
262 var
263   i: integer;
264   UInfo: TUnitInfo;
265   APackage: TLazPackage;
266 begin
267   FHasLocalModifications:=False;
268   FilesListBox.Items.BeginUpdate;
269   FilesListBox.Items.Clear;
270   if UnitList<>nil then
271   begin
272     for i:=0 to UnitList.Count-1 do begin
273       UInfo:=TUnitInfo(UnitList[i]);
274       AddFile2Box(UInfo, UInfo.ShortFilename, UInfo.Modified);
275     end;
276   end;
277   if PackageList<>nil then
278   begin
279     for i:=0 to PackageList.Count-1 do begin
280       APackage:=TLazPackage(PackageList.Objects[i]);
281       AddFile2Box(APackage, APackage.Filename, APackage.Modified);
282     end;
283   end;
284   FilesListBox.Items.EndUpdate;
285   WarnImage.Visible:=FHasLocalModifications;
286   WarnLabel.Visible:=FHasLocalModifications;
287 end;
288 
289 procedure TDiskDiffsDlg.ShowDiff;
290 var
291   i: integer;
292   DiffItem: PDiffItem;
293 begin
294   i:=FilesListBox.ItemIndex;
295   DiffItem:=nil;
296   if (i>=0) and (UnitList<>nil) then begin
297     if i<UnitList.Count then
298       DiffItem:=GetCachedDiff(TUnitInfo(UnitList[i]),'');
299     dec(i,UnitList.Count);
300   end;
301   if (i>=0) and (PackageList<>nil) then begin
302     if i<PackageList.Count then
303       DiffItem:=GetCachedDiff(TLazPackage(PackageList.Objects[i]),PackageList[i]);
304     dec(i,PackageList.Count);
305   end;
306   if DiffItem<>nil then begin
307     DiffSynEdit.Lines.Text:=DiffItem^.Diff;
308   end else begin
309     DiffSynEdit.Lines.Clear;
310   end;
311 end;
312 
TDiskDiffsDlg.GetCachedDiffnull313 function TDiskDiffsDlg.GetCachedDiff(FileOwner: TObject; AltFilename: string
314   ): PDiffItem;
315 var
316   i: integer;
317   fs: TFileStream;
318   Filename: String;
319   AnUnitInfo: TUnitInfo;
320   APackage: TLazPackage;
321   Source: String;
322   DiffOutput: TDiffOutput;
323 begin
324   if FCachedDiffs=nil then
325     FCachedDiffs:=TFPList.Create;
326   for i:=0 to FCachedDiffs.Count-1 do begin
327     Result:=PDiffItem(FCachedDiffs[i]);
328     if (Result<>nil) and (Result^.Owner=FileOwner) then exit;
329   end;
330   New(Result);
331   Result^.Owner:=FileOwner;
332   try
333     if FileOwner is TUnitInfo then begin
334       // compare disk and codetools
335       AnUnitInfo:=TUnitInfo(FileOwner);
336       Filename:=AnUnitInfo.Source.Filename;
337       Source:=AnUnitInfo.Source.Source;
338     end else if FileOwner is TLazPackage then begin
339       // compare disk and package
340       APackage:=TLazPackage(FileOwner);
341       if AltFilename<>'' then begin
342         if CompareFilenames(AltFilename,APackage.Filename)<>0 then
343           Result^.Diff+=Format(lisLpkHasVanishedOnDiskUsingAsAlternative,
344                                [LineEnding+AltFilename+LineEnding]);
345         Filename:=AltFilename;
346       end
347       else if APackage.LPKSource<>nil then
348         Filename:=APackage.LPKSource.Filename
349       else
350         Filename:=APackage.GetFullFilename(true);
351       APackage.SaveToString(Source);
352     end else begin
353       Filename:='';
354       Source:='';
355     end;
356     fs:=TFileStream.Create(Filename,fmOpenRead);
357     SetLength(Result^.TxtOnDisk,fs.Size);
358     if Result^.TxtOnDisk<>'' then
359       fs.Read(Result^.TxtOnDisk[1],length(Result^.TxtOnDisk));
360     fs.Free;
361 
362     DiffOutput:=TDiffOutput.Create(Source,Result^.TxtOnDisk, []);
363     try
364       Result^.Diff+=DiffOutput.CreateTextDiff;
365     finally
366       DiffOutput.Free;
367     end;
368   except
369     On E: Exception do
370       Result^.Diff+='\ '+Format(lisDiskDiffErrorReadingFile, [E.Message]);
371   end;
372   FCachedDiffs.Add(Result);
373 end;
374 
375 procedure TDiskDiffsDlg.ClearCache;
376 var
377   i: integer;
378   DiffItem: PDiffItem;
379 begin
380   if FCachedDiffs=nil then exit;
381   for i:=0 to FCachedDiffs.Count-1 do begin
382     DiffItem:=PDiffItem(FCachedDiffs[i]);
383     if DiffItem<>nil then begin
384       DiffItem^.TxtOnDisk:='';
385       DiffItem^.Diff:='';
386       Dispose(DiffItem);
387     end;
388   end;
389   FCachedDiffs.Clear;
390 end;
391 
392 constructor TDiskDiffsDlg.Create(TheOwner: TComponent);
393 begin
394   inherited Create(TheOwner);
395 
396   Caption:=lisDiskDiffSomeFilesHaveChangedOnDisk;
397   EditorOpts.GetSynEditSettings(DiffSynEdit);
398   DiffSynEdit.Lines.Text:=lisDiskDiffClickOnOneOfTheAboveItemsToSeeTheDiff;
399 
400   BtnPanel.OkButton.Caption:=lisDiskDiffReloadCheckedFilesFromDisk;
401   Assert(BtnPanel.OKButton.ModalResult=mrOK, 'OKButton.ModalResult<>mrOK');
402   // Cancel button now means Ignore All Disk Changes
403   BtnPanel.CancelButton.Caption:=lisDiskDiffIgnoreAllDiskChanges;
404 
405   WarnLabel.Caption:=lisDiskDiffSomeFilesHaveLocalChanges;
406   WarnLabel.Visible:=False;
407   WarnImage.Visible:=False;
408 
409   CheckDiskChangesWithLoadingCheckBox.Caption:=lisCheckForDiskFileChangesViaContent;
410   CheckDiskChangesWithLoadingCheckBox.Checked:=EnvironmentOptions.CheckDiskChangesWithLoading;
411 end;
412 
413 procedure TDiskDiffsDlg.ApplyChecks;
414 var
415   i: Integer;
416 begin
417   FIgnoreList.Clear;
418   for i := 0 to FilesListBox.Count-1 do
419     if not FilesListBox.Checked[i] then
420       FIgnoreList.Add(FilesListBox.Items.Objects[i]);
421 end;
422 
423 destructor TDiskDiffsDlg.Destroy;
424 begin
425   ClearCache;
426   FCachedDiffs.Free;
427   inherited Destroy;
428 end;
429 
430 end.
431 
432