1 {
2 *****************************************************************************
3 See the file COPYING.modifiedLGPL.txt, included in this distribution,
4 for details about the license.
5 *****************************************************************************
6
7 Author: Mattias Gaertner
8
9 Abstract:
10 Find in files dialog form.
11
12 }
13 unit FindInFilesDlg;
14
15 {$mode objfpc}{$H+}
16
17 interface
18
19 uses
20 Classes, SysUtils,
21 // LCL
22 LCLProc, Controls, StdCtrls, Forms, Buttons, ExtCtrls, Dialogs, ButtonPanel,
23 // Codetools
24 FileProcs,
25 // LazUtils
26 LazFileUtils,
27 // SynEdit
28 SynEditTypes, SynEdit,
29 // IdeIntf
30 MacroIntf, IDEWindowIntf, SrcEditorIntf, IDEHelpIntf, IDEDialogs,
31 ProjectGroupIntf,
32 // IDE
33 LazarusIDEStrConsts, InputHistory, InputhistoryWithSearchOpt, EditorOptions, Project,
34 IDEProcs, SearchFrm, SearchResultView, EnvironmentOpts;
35
36 type
37 { TLazFindInFilesDialog }
38
39 TLazFindInFilesDialog = class(TForm)
40 ButtonPanel1: TButtonPanel;
41 ReplaceCheckBox: TCheckBox;
42 ReplaceTextComboBox: TComboBox;
43 IncludeSubDirsCheckBox: TCheckBox;
44 FileMaskComboBox: TComboBox;
45 DirectoriesBrowse: TBitBtn;
46 DirectoriesComboBox: TComboBox;
47 DirectoriesLabel: TLabel;
48 FileMaskLabel: TLabel;
49 DirectoriesOptionsGroupBox: TGroupBox;
50 OptionsCheckGroupBox: TCheckGroup;
51 SelectDirectoryDialog: TSelectDirectoryDialog;
52 TextToFindComboBox: TComboBox;
53 TextToFindLabel: TLabel;
54 WhereRadioGroup: TRadioGroup;
55 procedure DirectoriesBrowseClick(Sender: TObject);
56 procedure FormClose(Sender: TObject; var {%H-}CloseAction: TCloseAction);
57 procedure FormCreate(Sender: TObject);
58 procedure FormShow(Sender: TObject);
59 procedure HelpButtonClick(Sender: TObject);
60 procedure OKButtonClick(Sender : TObject);
61 procedure ReplaceCheckBoxChange(Sender: TObject);
62 procedure WhereRadioGroupClick(Sender: TObject);
63 private
64 FProject: TProject;
GetFindTextnull65 function GetFindText: string;
GetOptionsnull66 function GetOptions: TLazFindInFileSearchOptions;
GetReplaceTextnull67 function GetReplaceText: string;
GetSynOptionsnull68 function GetSynOptions: TSynSearchOptions;
69 procedure SetFindText(const NewFindText: string);
70 procedure SetOptions(NewOptions: TLazFindInFileSearchOptions);
71 procedure SetReplaceText(const AValue: string);
72 procedure SetSynOptions(NewOptions: TSynSearchOptions);
73 procedure UpdateReplaceCheck;
74 procedure UpdateDirectoryOptions;
75 public
76 property Options: TLazFindInFileSearchOptions read GetOptions
77 write SetOptions;
78 property FindText: string read GetFindText write SetFindText;
79 property ReplaceText: string read GetReplaceText write SetReplaceText;
80 property SynSearchOptions: TSynSearchOptions read GetSynOptions
81 write SetSynOptions;
GetBaseDirectorynull82 function GetBaseDirectory: string;
83 procedure LoadHistory;
84 procedure SaveHistory;
85 procedure FindInSearchPath(SearchPath: string);
86 procedure FindInFilesPerDialog(AProject: TProject);
87 procedure InitFindText;
88 procedure InitFromLazSearch(Sender: TObject);
89 procedure FindInFiles(AProject: TProject; const AFindText: string);
GetResolvedDirectoriesnull90 function GetResolvedDirectories: string;
Executenull91 function Execute: boolean;
92 property LazProject: TProject read FProject write FProject;
93 end;
94
FindInFilesDialognull95 function FindInFilesDialog: TLazFindInFilesDialog;
96
97 implementation
98
99 var // WhereRadioGroup's ItemIndex in a more informative form.
100 ItemIndProject: integer = 0;
101 ItemIndProjectGroup: integer = -1;
102 ItemIndOpenFiles: integer = 1;
103 ItemIndDirectories: integer = 2;
104 ItemIndActiveFile: integer = 3;
105
106 var
107 FindInFilesDialogSingleton: TLazFindInFilesDialog = nil;
108
109 function FindInFilesDialog: TLazFindInFilesDialog;
110 begin
111 if FindInFilesDialogSingleton = nil then
112 FindInFilesDialogSingleton := TLazFindInFilesDialog.Create(Application);
113 Result := FindInFilesDialogSingleton;
114 end;
115
116 {$R *.lfm}
117
118 { TLazFindInFilesDialog }
119
120 procedure TLazFindInFilesDialog.SetFindText(const NewFindText: string);
121 begin
122 TextToFindComboBox.Text := NewFindText;
123 TextToFindComboBox.SelectAll;
124 ActiveControl := TextToFindComboBox;
125 end;
126
GetFindTextnull127 function TLazFindInFilesDialog.GetFindText: string;
128 begin
129 Result := TextToFindComboBox.Text;
130 end;
131
GetReplaceTextnull132 function TLazFindInFilesDialog.GetReplaceText: string;
133 begin
134 Result:=ReplaceTextComboBox.Text;
135 end;
136
137 procedure TLazFindInFilesDialog.WhereRadioGroupClick(Sender: TObject);
138 begin
139 UpdateDirectoryOptions;
140 end;
141
142 procedure TLazFindInFilesDialog.DirectoriesBrowseClick(Sender: TObject);
143 var
144 Dir: String;
145 OldDirs: String;
146 p: Integer;
147 begin
148 InitIDEFileDialog(SelectDirectoryDialog);
149 // use the first directory as initialdir for the dialog
150 OldDirs:=GetResolvedDirectories;
151 p:=1;
152 repeat
153 Dir:=GetNextDirectoryInSearchPath(OldDirs,p);
154 if Dir='' then break;
155 if DirectoryExistsUTF8(Dir) then break;
156 until false;
157 if Dir<>'' then
158 SelectDirectoryDialog.InitialDir := Dir
159 else
160 SelectDirectoryDialog.InitialDir := GetBaseDirectory;
161
162 if SelectDirectoryDialog.Execute then
163 DirectoriesComboBox.Text := AppendPathDelim(TrimFilename(SelectDirectoryDialog.FileName));
164 StoreIDEFileDialog(SelectDirectoryDialog);
165 end;
166
167 procedure TLazFindInFilesDialog.FormClose(Sender: TObject; var CloseAction: TCloseAction);
168 begin
169 IDEDialogLayoutList.SaveLayout(Self);
170 end;
171
172 procedure TLazFindInFilesDialog.FormCreate(Sender: TObject);
173 begin
174 Caption := srkmecFindInFiles;
175
176 TextToFindLabel.Caption := dlgTextToFind;
177 ReplaceCheckBox.Caption := dlgReplaceWith;
178
179 OptionsCheckGroupBox.Caption := lisOptions;
180 OptionsCheckGroupBox.Items[0] := dlgCaseSensitive;
181 OptionsCheckGroupBox.Items[1] := dlgWholeWordsOnly;
182 OptionsCheckGroupBox.Items[2] := dlgRegularExpressions;
183 OptionsCheckGroupBox.Items[3] := lisFindFileMultiLinePattern;
184
185 WhereRadioGroup.Caption:=lisFindFileWhere;
186 WhereRadioGroup.Items[ItemIndProject] := lisFindFilesearchAllFilesInProject;
187 if ProjectGroupManager<>nil then
188 begin
189 ItemIndProjectGroup:=1;
190 WhereRadioGroup.Items.Insert(ItemIndProjectGroup, lisFindFilesSearchInProjectGroup);
191 ItemIndOpenFiles:=2;
192 ItemIndDirectories:=3;
193 ItemIndActiveFile:=4;
194 end;
195 WhereRadioGroup.Items[ItemIndOpenFiles] := lisFindFilesearchAllOpenFiles;
196 WhereRadioGroup.Items[ItemIndDirectories] := lisFindFilesearchInDirectories;
197 WhereRadioGroup.Items[ItemIndActiveFile] := lisFindFilesearchInActiveFile;
198
199 DirectoriesOptionsGroupBox.Caption := lisDirectories;
200 DirectoriesComboBox.Hint:=lisMultipleDirectoriesAreSeparatedWithSemicolons;
201 DirectoriesLabel.Caption := lisFindFileDirectories;
202 FileMaskLabel.Caption := lisFindFileFileMask;
203
204 IncludeSubDirsCheckBox.Caption := lisFindFileIncludeSubDirectories;
205
206 ButtonPanel1.HelpButton.Caption := lisMenuHelp;
207 ButtonPanel1.CancelButton.Caption := lisCancel;
208
209 ReplaceCheckBox.Enabled:=true;
210
211 UpdateReplaceCheck;
212 UpdateDirectoryOptions;
213
214 AutoSize:=IDEDialogLayoutList.Find(Self,false)=nil;
215 IDEDialogLayoutList.ApplyLayout(Self);
216 end;
217
218 procedure TLazFindInFilesDialog.FormShow(Sender: TObject);
219 begin
220 TextToFindComboBox.DropDownCount:=EnvironmentOptions.DropDownCount;
221 ReplaceTextComboBox.DropDownCount:=EnvironmentOptions.DropDownCount;
222 DirectoriesComboBox.DropDownCount:=EnvironmentOptions.DropDownCount;
223 FileMaskComboBox.DropDownCount:=EnvironmentOptions.DropDownCount;
224 end;
225
226 procedure TLazFindInFilesDialog.HelpButtonClick(Sender: TObject);
227 begin
228 LazarusHelp.ShowHelpForIDEControl(Self);
229 end;
230
231 procedure TLazFindInFilesDialog.OKButtonClick(Sender : TObject);
232 var
233 Directories, Dir: String;
234 p: Integer;
235 begin
236 if (WhereRadioGroup.ItemIndex=ItemIndDirectories) then
237 begin
238 Directories:=GetResolvedDirectories;
239 p:=1;
240 repeat
241 Dir:=GetNextDirectoryInSearchPath(Directories,p);
242 if (Dir<>'') and not DirectoryExistsUTF8(Dir) then
243 begin
244 IDEMessageDialog(lisEnvOptDlgDirectoryNotFound,
245 Format(dlgSeachDirectoryNotFound,[Dir]),
246 mtWarning, [mbOk]);
247 ModalResult:=mrNone;
248 Break;
249 end;
250 until Dir='';
251 end;
252 end;
253
254 procedure TLazFindInFilesDialog.ReplaceCheckBoxChange(Sender: TObject);
255 begin
256 UpdateReplaceCheck;
257 end;
258
259 procedure TLazFindInFilesDialog.SetOptions(NewOptions: TLazFindInFileSearchOptions);
260 var
261 NewItemIndex: Integer;
262 begin
263 OptionsCheckGroupBox.Checked[0] := fifMatchCase in NewOptions;
264 OptionsCheckGroupBox.Checked[1] := fifWholeWord in NewOptions;
265 OptionsCheckGroupBox.Checked[2] := fifRegExpr in NewOptions;
266 OptionsCheckGroupBox.Checked[3] := fifMultiLine in NewOptions;
267 IncludeSubDirsCheckBox.Checked := fifIncludeSubDirs in NewOptions;
268 ReplaceCheckBox.Checked := [fifReplace,fifReplaceAll]*NewOptions<>[];
269
270 NewItemIndex:=ItemIndProject;
271 if fifSearchProject in NewOptions then
272 NewItemIndex := ItemIndProject;
273 if (fifSearchProjectGroup in NewOptions) and (ItemIndProjectGroup>=0) then
274 NewItemIndex := ItemIndProjectGroup;
275 if fifSearchOpen in NewOptions then NewItemIndex := ItemIndOpenFiles;
276 if fifSearchDirectories in NewOptions then NewItemIndex := ItemIndDirectories;
277 if fifSearchActive in NewOptions then NewItemIndex := ItemIndActiveFile;
278 WhereRadioGroup.ItemIndex:=NewItemIndex;
279
280 UpdateReplaceCheck;
281 UpdateDirectoryOptions;
282 end;
283
GetOptionsnull284 function TLazFindInFilesDialog.GetOptions: TLazFindInFileSearchOptions;
285 var
286 Where: Integer;
287 begin
288 Result := [];
289 if OptionsCheckGroupBox.Checked[0] then Include(Result, fifMatchCase);
290 if OptionsCheckGroupBox.Checked[1] then Include(Result, fifWholeWord);
291 if OptionsCheckGroupBox.Checked[2] then Include(Result, fifRegExpr);
292 if OptionsCheckGroupBox.Checked[3] then Include(Result, fifMultiLine);
293 if IncludeSubDirsCheckBox.Checked then Include(Result, fifIncludeSubDirs);
294 if ReplaceCheckBox.Checked then Include(Result, fifReplace);
295
296 Where:=WhereRadioGroup.ItemIndex;
297 if Where=ItemIndProject then Include(Result, fifSearchProject)
298 else if Where=ItemIndProjectGroup then Include(Result, fifSearchProjectGroup)
299 else if Where=ItemIndOpenFiles then Include(Result, fifSearchOpen)
300 else if Where=ItemIndDirectories then Include(Result, fifSearchDirectories)
301 else Include(Result, fifSearchActive);
302 end;
303
GetSynOptionsnull304 function TLazFindInFilesDialog.GetSynOptions: TSynSearchOptions;
305 begin
306 Result := [];
307 if OptionsCheckGroupBox.Checked[0] then Include(Result, ssoMatchCase);
308 if OptionsCheckGroupBox.Checked[1] then Include(Result, ssoWholeWord);
309 if OptionsCheckGroupBox.Checked[2] then Include(Result, ssoRegExpr);
310 if OptionsCheckGroupBox.Checked[3] then Include(Result, ssoRegExprMultiLine);
311 if ReplaceCheckBox.Checked then Include(Result, ssoReplace);
312 end;//GetSynOptions
313
314 procedure TLazFindInFilesDialog.SetReplaceText(const AValue: string);
315 begin
316 ReplaceTextComboBox.Text := AValue;
317 end;
318
319 procedure TLazFindInFilesDialog.SetSynOptions(NewOptions: TSynSearchOptions);
320 begin
321 OptionsCheckGroupBox.Checked[0] := ssoMatchCase in NewOptions;
322 OptionsCheckGroupBox.Checked[1] := ssoWholeWord in NewOptions;
323 OptionsCheckGroupBox.Checked[2] := ssoRegExpr in NewOptions;
324 OptionsCheckGroupBox.Checked[3] := ssoRegExprMultiLine in NewOptions;
325 ReplaceCheckBox.Checked := ([ssoReplace,ssoReplaceAll]*NewOptions <> []);
326
327 UpdateReplaceCheck;
328 end;//SetSynOptions
329
330 procedure TLazFindInFilesDialog.UpdateReplaceCheck;
331 begin
332 ReplaceTextComboBox.Enabled:=ReplaceCheckBox.Checked;
333 if ReplaceCheckBox.Checked then
334 ButtonPanel1.OKButton.Caption := lisBtnReplace
335 else
336 ButtonPanel1.OKButton.Caption := lisBtnFind;
337 end;
338
339 procedure TLazFindInFilesDialog.UpdateDirectoryOptions;
340 begin
341 if WhereRadioGroup.ItemIndex = ItemIndDirectories then
342 begin
343 DirectoriesOptionsGroupBox.Enabled := true;
344 DirectoriesBrowse.Enabled:=true;
345 DirectoriesComboBox.Enabled:=true;
346 end
347 else if WhereRadioGroup.ItemIndex = ItemIndProjectGroup then
348 begin
349 DirectoriesOptionsGroupBox.Enabled := true;
350 DirectoriesBrowse.Enabled:=false;
351 DirectoriesComboBox.Enabled:=false;
352 end else
353 DirectoriesOptionsGroupBox.Enabled := false;
354 end;
355
GetBaseDirectorynull356 function TLazFindInFilesDialog.GetBaseDirectory: string;
357 begin
358 Result:='';
359 if Project1<>nil then
360 Result:=Project1.Directory;
361 if Result='' then
362 Result:=GetCurrentDirUTF8;
363 end;
364
365 const
366 SharedOptions = [ssoMatchCase,ssoWholeWord,ssoRegExpr,ssoRegExprMultiLine];
367
368 procedure TLazFindInFilesDialog.LoadHistory;
369
370 procedure AssignToComboBox(AComboBox: TComboBox; Strings: TStrings);
371 begin
372 AComboBox.Items.Assign(Strings);
373 if AComboBox.Items.Count>0 then
374 AComboBox.ItemIndex := 0;
375 end;
376
377 procedure AddFileToComboBox(AComboBox: TComboBox; Filename: string);
378 var
379 i: Integer;
380 begin
381 if Filename='' then exit;
382 Filename:=AppendPathDelim(TrimFilename(Filename));
383 for i:=0 to AComboBox.Items.Count-1 do begin
384 if CompareFilenames(Filename,AComboBox.Items[i])=0 then begin
385 // move to front (but not top, top should be the last used directory)
386 if i>2 then
387 AComboBox.Items.Move(i,1);
388 exit;
389 end;
390 end;
391 // insert in front (but not top, top should be the last used directory)
392 if AComboBox.Items.Count>0 then
393 i:=1
394 else
395 i:=0;
396 AComboBox.Items.Insert(i,Filename);
397 end;
398
399 var
400 SrcEdit: TSourceEditorInterface;
401 begin
402 SrcEdit := SourceEditorManagerIntf.ActiveEditor;
403 //DebugLn('TSourceNotebook.LoadFindInFilesHistory ',dbgsName(TextToFindComboBox),' ',dbgsName(FindHistory));
404 TextToFindComboBox.Items.Assign(InputHistories.FindHistory);
405 ReplaceTextComboBox.Items.Assign(InputHistories.ReplaceHistory);
406 if not EditorOpts.FindTextAtCursor then begin
407 if TextToFindComboBox.Items.Count>0 then begin
408 //debugln('TSourceNotebook.LoadFindInFilesHistory A TextToFindComboBox.Text=',TextToFindComboBox.Text);
409 TextToFindComboBox.ItemIndex:=0;
410 TextToFindComboBox.SelectAll;
411 //debugln('TSourceNotebook.LoadFindInFilesHistory B TextToFindComboBox.Text=',TextToFindComboBox.Text);
412 end;
413 end;
414 // show last used directories and directory of current file
415 AssignToComboBox(DirectoriesComboBox, InputHistories.FindInFilesPathHistory);
416 if (SrcEdit<>nil) and (FilenameIsAbsolute(SrcEdit.FileName)) then
417 AddFileToComboBox(DirectoriesComboBox, ExtractFilePath(SrcEdit.FileName));
418 if DirectoriesComboBox.Items.Count>0 then
419 DirectoriesComboBox.Text:=DirectoriesComboBox.Items[0];
420 // show last used file masks
421 AssignToComboBox(FileMaskComboBox, InputHistories.FindInFilesMaskHistory);
422 Options := InputHistories.FindInFilesSearchOptions;
423 //share basic options with FindReplaceDlg
424 SynSearchOptions := InputHistoriesSO.FindOptions[False] * SharedOptions;
425 end;
426
427 procedure TLazFindInFilesDialog.SaveHistory;
428 var
429 Dir: String;
430 begin
431 if ReplaceCheckBox.Checked then
432 InputHistories.AddToReplaceHistory(ReplaceText);
433 InputHistories.AddToFindHistory(FindText);
434 Dir:=AppendPathDelim(TrimFilename(DirectoriesComboBox.Text));
435 if Dir<>'' then
436 InputHistories.AddToFindInFilesPathHistory(Dir);
437 InputHistories.AddToFindInFilesMaskHistory(FileMaskComboBox.Text);
438 InputHistories.FindInFilesSearchOptions:=Options;
439 //share basic options with FindReplaceDlg
440 InputHistoriesSO.FindOptions[False] := InputHistoriesSO.FindOptions[False] - SharedOptions
441 + (SynSearchOptions*SharedOptions);
442 InputHistories.Save;
443 end;
444
445 procedure TLazFindInFilesDialog.FindInSearchPath(SearchPath: string);
446 begin
447 debugln(['TLazFindInFilesDialog.FindInSearchPath ',SearchPath]);
448 InitFindText;
449 LoadHistory;
450 DirectoriesComboBox.Text:=SearchPath;
451 WhereRadioGroup.ItemIndex:=ItemIndDirectories;
452 // disable replace. Find in files is often called,
453 // but almost never to replace with the same parameters
454 Options := Options-[fifReplace,fifReplaceAll];
455 Execute;
456 end;
457
458 procedure TLazFindInFilesDialog.FindInFilesPerDialog(AProject: TProject);
459 begin
460 InitFindText;
461 FindInFiles(AProject, FindText);
462 end;
463
464 procedure TLazFindInFilesDialog.InitFindText;
465 var
466 TempEditor: TSourceEditorInterface;
467 NewFindText: String;
468 begin
469 NewFindText:='';
470 TempEditor := SourceEditorManagerIntf.ActiveEditor;
471 if TempEditor <> nil
472 then //with TempEditor.EditorComponent do
473 begin
474 if EditorOpts.FindTextAtCursor
475 then begin
476 if TempEditor.SelectionAvailable and (TempEditor.BlockBegin.Y = TempEditor.BlockEnd.Y)
477 then NewFindText := TempEditor.Selection
478 else NewFindText := TSynEdit(TempEditor.EditorControl).GetWordAtRowCol(TempEditor.CursorTextXY);
479 end else begin
480 if InputHistories.FindHistory.Count>0 then
481 NewFindText:=InputHistories.FindHistory[0];
482 end;
483 end;
484 FindText:=NewFindText;
485 end;
486
487 procedure TLazFindInFilesDialog.InitFromLazSearch(Sender: TObject);
488 var
489 Dir: String;
490 begin
491 Dir:=AppendPathDelim(TrimFilename(TLazSearch(Sender).SearchDirectories));
492 if Dir<>'' then
493 DirectoriesComboBox.Text:= Dir;
494 Options:= TLazSearch(Sender).SearchOptions;
495 FileMaskComboBox.Text:= TLazSearch(Sender).SearchMask;
496 end;
497
498 procedure TLazFindInFilesDialog.FindInFiles(AProject: TProject; const AFindText: string);
499 begin
500 LazProject:=AProject;
501 LoadHistory;
502
503 // if there is no FindText, use the most recently used FindText
504 FindText:= AFindText;
505 if (FindText = '') and (InputHistories.FindHistory.Count > 0) then
506 FindText := InputHistories.FindHistory[0];
507
508 // disable replace. Find in files is often called,
509 // but almost never to replace with the same parameters
510 Options := Options-[fifReplace,fifReplaceAll];
511 Execute;
512 end;
513
GetResolvedDirectoriesnull514 function TLazFindInFilesDialog.GetResolvedDirectories: string;
515 begin
516 Result:=DirectoriesComboBox.Text;
517 IDEMacros.SubstituteMacros(Result);
518 Result:=TrimSearchPath(Result,GetBaseDirectory,true,true);
519 end;
520
Executenull521 function TLazFindInFilesDialog.Execute: boolean;
522 var
523 SearchForm: TSearchProgressForm;
524 Where: Integer;
525 begin
526 if ShowModal=mrOk then
527 begin
528 Result:=true;
529 SaveHistory;
530
531 SearchForm:= TSearchProgressForm.Create(SearchResultsView);
532 with SearchForm do begin
533 SearchOptions := self.Options;
534 SearchText := self.FindText;
535 ReplaceText := self.ReplaceText;
536 SearchMask := self.FileMaskComboBox.Text;
537 SearchDirectories := self.GetResolvedDirectories;
538 end;
539
540 try
541 if FindText <> '' then
542 begin
543 Where:=WhereRadioGroup.ItemIndex;
544 if Where=ItemIndProject then
545 begin
546 if LazProject=nil then
547 SearchForm.DoSearchProject(Project1)
548 else
549 SearchForm.DoSearchProject(LazProject);
550 end else if Where=ItemIndProjectGroup then
551 begin
552 SearchForm.SearchOptions:=SearchForm.SearchOptions-[fifIncludeSubDirs];
553 SearchForm.DoSearchProjectGroup;
554 end
555 else if Where=ItemIndOpenFiles then
556 SearchForm.DoSearchOpenFiles
557 else if Where=ItemIndDirectories then
558 SearchForm.DoSearchDirs
559 else
560 SearchForm.DoSearchActiveFile;
561 end;
562 finally
563 FreeAndNil(SearchForm);
564 end;
565 end else
566 Result:=false;
567
568 FProject:=nil;
569 end;
570
571 end.
572
573