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