ecere/gui/Window: Prevent uninitialized values if base Window methods not overridden...
[sdk] / ide / src / dialogs / FindInFilesDialog.ec
1 import "ide"
2 import "layout"
3
4 enum FindInFilesMode { directory, workspace, project };
5
6 class FindInFilesDialog : Window
7 {
8    text = $"Find In Files";
9    background = formColor;
10    borderStyle = sizable;
11    minClientSize = { 440, 208 };
12    maxClientSize = { 640, 208 };
13    hasClose = true;
14    tabCycle = true;
15    size = { 440, 208 };
16    autoCreate = false;
17
18 public:
19    property const char * searchString { set { findContent.contents = value; } get { return findContent.contents; } };
20    property bool contentWholeWord { set { contentWholeWord.checked = value; } get { return contentWholeWord.checked; } };
21    property bool contentMatchCase { set { contentMatchCase.checked = value; } get { return contentMatchCase.checked; } };
22    property const char * currentDirectory
23    {
24       set
25       {
26          GetWorkingDir(currentDirectory, MAX_DIRECTORY);
27          PathCat(currentDirectory, value);
28          FileFixCase(currentDirectory);
29          findWhere.path = currentDirectory;
30       }
31       get { return (char *)currentDirectory; }
32    };
33    property FileFilter * filters { set { filters = value; } get { return filters; } };
34    // Replace with Array system
35    property int sizeFilters
36    {
37       set
38       {
39          int numFilters = value / sizeof(FileFilter);
40          int c;
41
42          sizeFilters = value;
43
44          // File Extension Filter
45          filterDrop.Clear();
46          // filterDrop.AddField(null);
47          fileFilter = 0;
48          if(filters)
49          {
50             for(c = 0; c<numFilters; c++)
51             {
52                DataRow row = filterDrop.AddString(filters[c].name);
53                if(filters[c].extensions && !strcmp(filters[c].extensions, "ec, eh, c, cpp, cc, cxx, h, hpp, hh, hxx"))
54                   fileFilter = c;
55                row.tag = c;
56             }
57          }
58          if(!numFilters)
59             filterDrop.AddString($"All files");
60
61          //if(fileFilter >= numFilters)
62          //  fileFilter = 0;
63          filterDrop.currentRow = filterDrop.FindRow(fileFilter);
64       }
65       get { return sizeFilters; }
66    };
67    property int filter { set { fileFilter = value; } get { return fileFilter; } };
68
69    property FindInFilesMode mode
70    {
71       set
72       {
73          switch(value)
74          {
75             case directory:
76                findIn.SelectRow(inDirectoryRow);
77                break;
78             case workspace:
79                findIn.SelectRow(inWorkspaceRow);
80                break;
81             case project:
82             {
83                DataRow row;
84                for(row = findIn.firstRow; row; row = row.next)
85                   if(row != inDirectoryRow && row != inWorkspaceRow)
86                      break;
87                if(row)
88                   findIn.SelectRow(row);
89                break;
90             }
91          }
92          lastSelectionMode = value;
93       }
94       get
95       {
96          if(findIn.currentRow == inDirectoryRow)
97             return directory;
98          else if(findIn.currentRow == inWorkspaceRow)
99             return workspace;
100          else
101             return project;
102       }
103    }
104
105    property char * findDir
106    {
107       get { return searchThread.mode == directory ? (char *)searchThread.dir : null; }
108    }
109       /*if(searchThread.mode == directory)
110          ide.outputView.findDir = CopyString(searchThread.dir);
111       else if(searchThread.mode == project)
112          ide.outputView.findProject = searchThread.project;*/
113    property Project findProject
114    {
115       get { return searchThread.mode == project ? searchThread.project : null; }
116    }
117
118    property bool replaceMode
119    {
120       set
121       {
122          if(value != replaceMode)
123          {
124             int h = value ? 236 : 208;
125             find.text = value ? $"Replace" : $"Find";
126             find.hotKey = value ? altF : altR; // tocheck: the hotkey keeps getting lost
127             llreplaceWith.visible = value;
128             text = value ? $"Replace In Files" : $"Find In Files";
129             minClientSize.h = h;
130             maxClientSize.h = h;
131             size.h = h;
132             replaceMode = value;
133          }
134       }
135    }
136
137    void AddProjectItem(Project project)
138    {
139       if(created)
140       {
141          char label[MAX_FILENAME];
142          DataRow row;
143          sprintf(label, $"%s Project", project.name);
144          row = findIn.AddString(label);
145          row.tag = (int64)(intptr)project;
146       }
147    }
148
149    void RemoveProjectItem(Project project)
150    {
151       DataRow row;
152       if(inWorkspaceRow)
153       {
154          for(row = inWorkspaceRow.next; row; row = row.next)
155          {
156             if((Project)(intptr)row.tag == project)
157             {
158                findIn.DeleteRow(row);
159                break;
160             }
161          }
162       }
163    }
164
165    void Show()
166    {
167       if(!created)
168          Modal();
169       else
170          Activate();
171    }
172
173 private:
174
175    char currentDirectory[MAX_DIRECTORY];
176    FileFilter * filters;
177    int sizeFilters;
178    int fileFilter;
179    DataRow inDirectoryRow;
180    DataRow inWorkspaceRow;
181    FindInFilesMode lastSelectionMode;
182    String lastSelectionProject;
183    String lastSelectionProjectNode;
184    bool replaceMode;
185    SelectorButton starDir;
186
187    FindInFilesDialog()
188    {
189       GetWorkingDir(currentDirectory, MAX_DIRECTORY);
190       FileFixCase(currentDirectory);
191       findWhere.path = currentDirectory;
192       findWherePrjNode.AddField(projectNodeField);
193    }
194
195    ~FindInFilesDialog()
196    {
197       SearchStop();
198       delete lastSelectionProject;
199       delete lastSelectionProjectNode;
200    }
201
202    LayoutPage layout
203    {
204       this, anchor = { left = 20, top = 4, right = 8, bottom = 0 };
205       direction = horizontal;
206       tabCycle = true;
207       reverse = true;
208       gap = 8;
209    };
210
211    LayoutPage lpmain
212    {
213       layout, this;
214       tabCycle = true;
215       anchor.left = 0;
216       anchor.top = 0, anchor.bottom = 0;
217       gap = 2;
218    };
219
220    LayoutLine llfindIn      { lpmain, this, size.h = 26 };
221    LayoutLine llfindWhere   { lpmain, this, size.h = 26 };
222    LayoutLine llsubDirs     { lpmain, this, size.h = 18 };
223    LayoutLine llfilter      { lpmain, this, size.h = 26 };
224    LayoutLine llfileName    { lpmain, this, size.h = 26 };
225    LayoutLine llfindWhat    { lpmain, this, size.h = 26 };
226    LayoutLine llwholeWord   { lpmain, this, size.h = 18 };
227    LayoutLine llmatchCase   { lpmain, this, size.h = 18 };
228    LayoutLine llreplaceWith { lpmain, this, size.h = 26, visible = false };
229
230    Label lfindIn { llfindIn, this, size.w = 72, labeledWindow = findIn };
231    DropBox findIn
232    {
233       llfindIn, this, $"Find in:", altI, anchor.right = 0;
234
235       bool NotifySelect(DropBox control, DataRow row, Modifiers mods)
236       {
237          FindInFilesMode mode = this.mode;
238          bool inDir = mode == directory;
239          bool inWrk = mode == workspace;
240          bool inPrj = mode == project;
241          if(inPrj)
242             lfindWhere.labeledWindow = findWherePrjNode;
243          else
244             lfindWhere.labeledWindow = findWhere;
245          findWhere.visible = !inPrj;
246          findWhere.disabled = !inDir;
247          findWherePrjNode.visible = inPrj;
248          subDirs.disabled = inWrk;
249          llfindWhere.size = { llfindWhere.size.w, llfindWhere.size.h };
250
251          if(row)
252          {
253             Project prj;
254             lastSelectionMode = mode;
255             prj = lastSelectionMode == project ? (Project)(intptr)row.tag : null;
256             delete lastSelectionProject;
257             if(prj)
258             {
259                DataRow r = null;
260                ProjectNode node = prj.topNode;
261                char filePath[MAX_LOCATION];
262                prj.topNode.GetFullFilePath(filePath, true);
263                lastSelectionProject = CopyString(filePath);
264                findWherePrjNode.Clear();
265                ListProjectNodeFolders(node, null);
266
267                if(lastSelectionProjectNode && !(node = prj.topNode.FindByFullPath(lastSelectionProjectNode, false)))
268                {
269                   node = prj.topNode;
270                   delete lastSelectionProjectNode;
271                }
272
273                for(r = findWherePrjNode.firstRow; r; r = r.next)
274                   if((ProjectNode)(intptr)r.tag == node)
275                      break;
276                if(r)
277                   findWherePrjNode.SelectRow(r);
278             }
279          }
280          return true;
281       }
282    };
283
284    void ListProjectNodeFolders(ProjectNode node, DataRow parentRow)
285    {
286       DataRow row;
287       if(parentRow)
288          row = findWherePrjNode/*parentRow*/.AddRow();
289       else
290          row = findWherePrjNode.AddRow();
291       row.tag = (int64)(intptr)node;
292       row.SetData(null, node);
293       if(node.files)
294       {
295          for(child : node.files; child.type == folder/* || child.type == file*//* || child.type == folderOpen*/)
296             ListProjectNodeFolders(child, row);
297       }
298    }
299
300    Label lfindWhere { llfindWhere, this, size.w = 72, labeledWindow = findWhere };
301    PathBox findWhere
302    {
303       llfindWhere, this, $"Find where:", altH, size.h = 24, anchor.right = 0;
304       typeExpected = directory, browseDialog = fileDialog;
305    };
306    DropBox findWherePrjNode
307    {
308       llfindWhere, this, $"Find where:", altH, size.h = 24, anchor.right = 0;
309       visible = false;
310       //collapseControl = true, treeBranches = true;
311
312       bool NotifySelect(DropBox control, DataRow row, Modifiers mods)
313       {
314          if(row)
315          {
316             ProjectNode node = (ProjectNode)(intptr)row.tag;
317             delete lastSelectionProjectNode;
318             if(node)
319             {
320                char filePath[MAX_LOCATION];
321                node.GetFullFilePath(filePath, true);
322                lastSelectionProjectNode = CopyString(filePath);
323             }
324          }
325          return true;
326       }
327    };
328    DataField projectNodeField { dataType = "ProjectNode", freeData = false };
329
330    Window spacerA { llsubDirs, this, size = { 72, 10 }, clickThrough = true, background = formColor, inactive = true };
331    Button subDirs
332    {
333       llsubDirs, this, $"Include Subdirectories", altU, isCheckbox = true, checked = true;
334    };
335
336    Label lfilter { llfilter, this, size.w = 72, labeledWindow = filterDrop };
337    DropBox filterDrop
338    {
339       llfilter, this, $"Filter:", altL, anchor.right = 0;
340
341       bool NotifySelect(DropBox control, DataRow row, Modifiers mods)
342       {
343          fileFilter = (int)(row ? row.tag : 0);
344          //ListFiles();
345          return true;
346       }
347    };
348
349    Label lfileName { llfileName, this, size.w = 72, labeledWindow = fileName };
350    EditBox fileName
351    {
352       llfileName, this, $"File name:", altN, size.h = 24, anchor.right = 0;
353    };
354
355    //Window spacerX { ll, this, size = { 72, 10 }, clickThrough = true, background = formColor, inactive = true };
356    //Button nameWholeWord { ll, this, "Whole word only", AltO, isCheckbox = true };
357    //Window spacerX { llwholeWord, this, size = { 72, 10 }, clickThrough = true, background = formColor, inactive = true };
358    //Button nameMatchCase { ll, this, "Match case", altA, isCheckbox = true };
359
360    Label lfindContent { llfindWhat, this, size.w = 72, labeledWindow = findContent };
361    EditBox findContent
362    {
363       llfindWhat, this, $"Find what:", altT, size.h = 24, anchor.right = 0;
364    };
365
366    Label lreplaceWith { llreplaceWith, this, size.w = 72, labeledWindow = replaceWith };
367    EditBox replaceWith
368    {
369       llreplaceWith, this, $"Replace with:", altE;
370       size.h = 24, anchor.right = 0;
371    };
372
373    Window spacerB { llwholeWord, this, size = { 72, 10 }, clickThrough = true, background = formColor, inactive = true };
374    Button contentWholeWord { llwholeWord, this, $"Whole word only", altW, isCheckbox = true };
375
376    Window spacerC { llmatchCase, this, size = { 72, 10 }, clickThrough = true, background = formColor, inactive = true };
377    Button contentMatchCase { llmatchCase, this, $"Match case", altC, isCheckbox = true };
378
379    LayoutPage lpbuttons
380    {
381       layout, this;
382       tabCycle = true;
383       size.w = 80;
384       anchor.top = 0, anchor.bottom = 0;
385       gap = 16;
386    };
387
388    LayoutLine llspace   { lpbuttons, this, size.h = 2 };
389    LayoutLine llfind    { lpbuttons, this, size.h = 30 };
390    LayoutLine llcancel  { lpbuttons, this, size.h = 30 };
391
392    Button find
393    {
394       llfind, this, $"Find", altF, isDefault = true, size = { 80, 24 }, anchor.horz = 0, anchor.vert = 0, keyRepeat = true;
395
396       bool NotifyClicked(Button control, int x, int y, Modifiers mods)
397       {
398          const String findPath = findWhere.path;
399          if(findIn.currentRow == inDirectoryRow && !findPath[0])
400          {
401             findWhere.Activate();
402             MessageBox { type = ok, master = parent,
403                   text = text, contents = $"You must specify a search location." }.Modal();
404          }
405          else if(findIn.currentRow == inDirectoryRow && !FileExists(findPath))
406          {
407             findWhere.Activate();
408             MessageBox { type = ok, master = parent,
409                   text = text, contents = $"Search location does not exist. Please provide a valid location." }.Modal();
410          }
411          else if(!fileName.contents[0] && !findContent.contents[0])
412          {
413             findContent.Activate();
414             MessageBox { type = ok, master = parent,
415                   text = text, contents = $"Nothing to be found. Please specify at least one criteria." }.Modal();
416          }
417          else
418          {
419             findContent.Activate();
420             SearchStop();
421             SearchStart();
422          }
423          return true;
424       }
425    };
426
427    Button cancel
428    {
429       llcancel, this, $"Cancel", hotKey = { escape }, size = { 80, 24 }, anchor.horz = 0, anchor.vert = 0;
430
431       bool NotifyClicked(Button control, int x, int y, Modifiers mods)
432       {
433          findContent.Activate();
434          Destroy(0);
435          return true;
436       }
437    };
438
439    SearchThread searchThread { findDialog = this };
440    FileDialog fileDialog { master = this, type = selectDir, text = $"Select Search Location..." };
441
442    void OnDestroy()
443    {
444       findIn.Clear();
445       inDirectoryRow = null;
446       inWorkspaceRow = null;
447    }
448
449    bool OnPostCreate()
450    {
451       bool withWorkspace = ide.workspace != null;
452       DataRow row;
453       if(!inDirectoryRow)
454          inDirectoryRow = findIn.AddString($"Directory");
455       if(withWorkspace)
456       {
457          if(!inWorkspaceRow)
458             inWorkspaceRow = findIn.AddString($"Workspace");
459          for(prj : ide.workspace.projects)
460             AddProjectItem(prj);
461          if(lastSelectionProject)
462          {
463             for(row = findIn.firstRow; row; row = row.next)
464             {
465                char filePath[MAX_LOCATION];
466                Project p = (Project)(intptr)row.tag;
467                if(p)
468                {
469                   p.topNode.GetFullFilePath(filePath, true);
470                   if(!fstrcmp(filePath, lastSelectionProject))
471                      break;
472                }
473             }
474             if(row)
475                findIn.SelectRow(row);
476             else
477                delete lastSelectionProject;
478          }
479          if(!lastSelectionProject)
480          {
481             if(lastSelectionMode == project)
482             {
483                Project prj = ide.workspace.projects.firstIterator.data;
484                for(row = findIn.firstRow; row; row = row.next)
485                   if((Project)(intptr)row.tag == prj)
486                      break;
487                if(row)
488                   findIn.SelectRow(row);
489             }
490             else
491                property::mode = lastSelectionMode;
492          }
493       }
494       else
495       {
496          findIn.SelectRow(inDirectoryRow);
497       }
498
499       findIn.disabled = !withWorkspace;
500       /*disabled = findIn.currentRow != inDirectoryRow;
501
502       findWhere.disabled = disabled;
503       subDirs.disabled = disabled;*/
504
505       findContent.Activate();
506       return true;
507    }
508
509    void SearchStart()
510    {
511       searchThread.active = true;
512       searchThread.project = null;
513       searchThread.projectNode = null;
514       searchThread.subDirs = subDirs.checked;
515
516       if(findIn.currentRow == inDirectoryRow)
517       {
518          searchThread.mode = directory;
519          strcpy(searchThread.dir, findWhere.path);
520       }
521       else if(findIn.currentRow == inWorkspaceRow)
522       {
523          searchThread.mode = workspace;
524          searchThread.subDirs = true;
525       }
526       else
527       {
528          searchThread.mode = project;
529          searchThread.project = (Project)(intptr)findIn.currentRow.tag;
530          searchThread.projectNode = (ProjectNode)(findWherePrjNode.currentRow ? (void *)(intptr)findWherePrjNode.currentRow.tag : null);
531       }
532       //searchThread.nameMatchCase = nameMatchCase.checked;
533       //searchThread.nameWholeWord = nameWholeWord.checked;
534       searchThread.contentMatchCase = contentMatchCase.checked;
535       searchThread.contentWholeWord = contentWholeWord.checked;
536
537       searchThread.filter = filters[fileFilter];
538
539       strcpy(searchThread.nameCriteria, fileName.contents);
540       strcpy(searchThread.contentCriteria, findContent.contents);
541       if(replaceMode)
542          strcpy(searchThread.contentReplace, replaceWith.contents);
543       searchThread.replaceMode = replaceMode;
544
545       //cancel.text = "Stop";
546
547       ide.outputView.ShowClearSelectTab(find);
548
549       Destroy(0);
550
551       searchThread.Create();
552    }
553
554    bool SearchAbort()
555    {
556       if(searchThread.active)
557       {
558          searchThread.Abort();
559          return true;
560       }
561       return false;
562    }
563
564    bool SearchStop()
565    {
566       if(searchThread.active)
567       {
568          searchThread.Abort();
569          searchThread.Abort();
570          app.Unlock();
571             searchThread.Wait();
572          app.Lock();
573          return true;
574       }
575       return false;
576    }
577
578    void SearchComplete()
579    {
580       //cancel.text = $"Cancel";
581    }
582
583    int GetSizeFilter()
584    {
585       return sizeFilters;
586    }
587
588    bool NotifyCharsAddedDeletedNameContent(EditBox editBox, BufferLocation before, BufferLocation after, bool pasteOperation)
589    {
590       find.disabled = (strlen(fileName.contents) == 0 && strlen(findContent.contents) == 0);
591       return true;
592    }
593
594    bool OnKeyHit(Key key, unichar ch)
595    {
596       if(ch && !key.alt && !key.ctrl && !key.shift && (contentMatchCase.active || contentWholeWord.active))
597       {
598          findContent.Activate();
599          return findContent.OnKeyHit(key, ch);
600       }
601       return true;
602    }
603 }
604
605 static define stackSize = 1024;
606
607 class SearchThread : Thread
608 {
609 public:
610    bool active, subDirs/*, nameMatchCase, nameWholeWord*/, contentMatchCase, contentWholeWord;
611    char dir[MAX_DIRECTORY], contentCriteria[1024], contentReplace[1024], nameCriteria[1024];
612    FileFilter filter;
613    FindInFilesDialog findDialog;
614    FindInFilesMode mode;
615    Project project;
616    ProjectNode projectNode;
617    bool replaceMode;
618
619    void Abort()
620    {
621       if(abort)
622          abortNow = true;
623       else
624          abort = true;
625    }
626
627 private:
628
629    bool abort, abortNow;
630
631    SearchThread()
632    {
633       active = false;
634       abort = false;
635       abortNow = false;
636    }
637
638    int HowManyDigits(int num)
639    {
640       int count = 1;
641       while(num > 9)
642       {
643          count++;
644          num /= 10;
645       }
646       return count;
647    }
648
649    unsigned int Main()
650    {
651       int frame;
652       int globalFindCount = 0, filesSearchedCount = 0, filesMatchedCount = 0, dirsMatchedCount = 0;
653       //double lastTime = GetTime();
654       FindInFilesMode mode = this.mode;
655
656       EditBox replaceEdit = null;
657
658       abort = false;
659       abortNow = false;
660
661       app.Lock();
662          {
663             char substring[512];
664             char containing[512];
665             const char * and;
666             const char * filterName;
667             if(!strcmp(filter.name, "All files"))
668                filterName = "files";
669             else
670                filterName = filter.name;
671             if(nameCriteria[0])
672                sprintf(substring, $" with file name matching \"%s\"", nameCriteria);
673             else
674                substring[0] = '\0';
675             if(contentCriteria && contentCriteria[0])
676                sprintf(containing, $" containing \"%s\"", contentCriteria);
677             else
678                containing[0] = '\0';
679             if(substring[0] && containing[0])
680                and = " and";
681             else
682                and = "";
683             if(mode == directory)
684             {
685                char * s;
686                ide.outputView.findBox.Logf(
687                      $"Searching \"%s\"%s for %s%s%s%s\n\n",
688                      (s = CopySystemPath(dir)), subDirs ? $" and its sub directories" : "",
689                      filterName, substring, and, containing);
690                delete s;
691             }
692             else if(mode == workspace)
693                ide.outputView.findBox.Logf(
694                      $"Searching workspace files for %s%s%s%s\n\n",
695                      filterName, substring, and, containing);
696             else if(mode == project)
697                ide.outputView.findBox.Logf(
698                      $"Searching project %s files for %s%s%s%s\n\n",
699                      project.name, filterName, substring, and, containing);
700          }
701       app.Unlock();
702
703       if(replaceMode)
704       {
705          replaceEdit = EditBox
706          {
707             multiLine = true,textHorzScroll = true,textVertScroll = true,
708             text = $"Replacing Editbox", size = Size { 640,480 }/*,maxLineSize = 65536*/
709          };
710       }
711
712       if(mode == directory)
713       {
714          SearchStackFrame * stack = new0 SearchStackFrame[stackSize];
715
716          strcpy(stack[0].path, dir);
717          stack[0].fileList = FileListing { dir, extensions = filter.extensions };  // there should be a sorted = true/false
718
719          for(frame = 0; frame >= 0 && frame < stackSize && !abort; )
720          {
721             if(stack[frame].fileList.Find())
722             {
723                bool match = true;
724                if(nameCriteria[0])
725                {
726                   char name[MAX_LOCATION];
727                   GetLastDirectory(stack[frame].fileList.path, name);
728                   if(SearchString(name, 0, nameCriteria, false, false) == null)
729                      match = false;
730                }
731                if(!stack[frame].fileList.stats.attribs.isDirectory)
732                {
733                   bool relative = false;
734                   char fileRelative[MAX_LOCATION];
735                   if(filter.ValidateFileName(stack[frame].fileList.name))
736                   {
737                      MakePathRelative(stack[frame].fileList.path, dir, fileRelative);
738                      relative = true;
739
740                      filesSearchedCount++;
741                      if(match && contentCriteria[0])
742                      {
743                         int ret;
744
745                         app.Lock();
746                            ide.outputView.findBox.Tellf(
747                                  $"Searching %s for %s", relative ? fileRelative : stack[frame].fileList.path, contentCriteria);
748                         app.Unlock();
749
750                         if(replaceMode)
751                            ret = SearchFileContentAndReplace(stack[frame].fileList.path, relative, fileRelative, replaceEdit);
752                         else
753                            ret = SearchFileContent(stack[frame].fileList.path, relative, fileRelative);
754                         if(ret > 0)
755                         {
756                            globalFindCount += ret;
757                            filesMatchedCount++;
758                         }
759                      }
760                      else if(match && nameCriteria[0])
761                      {
762                         filesMatchedCount++;
763                         app.Lock();
764                            ide.outputView.findBox.Logf(
765                                  "%s\n",
766                                  relative ? fileRelative : stack[frame].fileList.path);
767                         app.Unlock();
768                      }
769                   }
770                }
771                else
772                {
773                   bool relative = false;
774                   char fileRelative[MAX_LOCATION];
775                   MakePathRelative(stack[frame].fileList.path, dir, fileRelative);
776                   relative = true;
777                   app.Lock();
778                      if(match && nameCriteria[0])
779                      {
780                         dirsMatchedCount++;
781                         ide.outputView.findBox.Logf(
782                               "%s\n",
783                               relative ? fileRelative : stack[frame].fileList.path);
784                      }
785                      ide.outputView.findBox.Tellf(
786                            $"Searching %s", relative ? fileRelative : stack[frame].fileList.path);
787                   app.Unlock();
788                }
789
790                if(subDirs && stack[frame].fileList.stats.attribs.isDirectory && strcmp(stack[frame].fileList.name, ".git"))
791                {
792                   int lastFrame = frame;
793                   /*double thisTime = GetTime();
794                   app.Lock();
795                      if(thisTime - lastTime > 0.25)
796                      {
797                         findDialog.SearchUpdateLabel(stack[lastFrame].fileList.path);
798                         lastTime = thisTime;
799                      }
800                   app.Unlock();*/
801                   if(frame < stackSize-1)
802                   {
803                      frame++;
804                      strcpy(stack[frame].path, stack[lastFrame].fileList.path);
805                      stack[frame].fileList = FileListing { stack[frame].path, extensions = stack[lastFrame].fileList.extensions };
806                   }
807                   else
808                   {
809                      abort = true;
810                      for( ; frame >= 0 ; frame--)
811                         stack[frame].fileList.Stop();
812                      app.Lock();
813                         ide.outputView.findBox.Logf($"Error: aborting search!\n");
814                      app.Unlock();
815                   }
816                }
817             }
818             else
819             {
820                frame--;
821             }
822          }
823          if(abort)
824             for( ; frame >= 0 ; frame--)
825                if(frame < stackSize)
826                   stack[frame].fileList.Stop();
827          delete stack;
828       }
829       else if(mode == workspace || mode == project)
830       {
831          bool firstIteration = true;
832          Project prj = project;
833          ProjectNode * stack = new0 ProjectNode[stackSize];
834          Iterator<Project> it { ide.workspace.projects };
835
836          while(true)
837          {
838             if(mode == workspace)
839             {
840                if(!it.Next()) break;
841                prj = it.data;
842             }
843             stack[1] = projectNode ? projectNode : prj.topNode;
844
845             for(frame = 1; frame && !abort && frame < stackSize-1;)
846             {
847                switch(stack[frame].type)
848                {
849                   case project:
850                      if((subDirs || firstIteration) && stack[frame].files && stack[frame].files.count)
851                      {
852                         int lastFrame = frame;
853                         frame++;
854                         stack[frame] = stack[lastFrame].files.first;
855                         firstIteration = false;
856                      }
857                      break;
858                   case file:
859                   {
860                      bool relative = true;
861                      char fileRelative[MAX_LOCATION];
862                      char filePath[MAX_LOCATION];
863                      filePath[0] = '\0';
864                      PathCat(filePath, prj.topNode.path);
865                      PathCat(filePath, stack[frame].path);
866                      PathCat(filePath, stack[frame].name);
867                      fileRelative[0] = '\0';
868                      PathCat(fileRelative, stack[frame].path);
869                      PathCat(fileRelative, stack[frame].name);
870                      if(relative && mode == workspace && prj != ide.project)
871                      {
872                         char special[MAX_LOCATION];
873                         sprintf(special, "(%s)%s", prj.name, fileRelative);
874                         strcpy(fileRelative, special);
875                      }
876                      if(filter.ValidateFileName(stack[frame].name))
877                      {
878                         filesSearchedCount++;
879                         if(!nameCriteria[0] || SearchString(stack[frame].name, 0, nameCriteria, false, false) != null)
880                         {
881                            if(contentCriteria[0])
882                            {
883                               int ret;
884
885                               app.Lock();
886                                  ide.outputView.findBox.Tellf(
887                                        $"Searching %s for \"%s\"", relative ? fileRelative : filePath,
888                                        contentCriteria);
889                               app.Unlock();
890
891                               if(replaceMode)
892                                  ret = SearchFileContentAndReplace(filePath, relative, fileRelative, replaceEdit);
893                               else
894                                  ret = SearchFileContent(filePath, relative, fileRelative);
895                               if(ret > 0)
896                               {
897                                  globalFindCount += ret;
898                                  filesMatchedCount++;
899                               }
900                            }
901                            else if(nameCriteria[0])
902                            {
903                               filesMatchedCount++;
904                               app.Lock();
905                                  ide.outputView.findBox.Logf(
906                                        "%s\n", relative ? fileRelative : filePath);
907                               app.Unlock();
908                            }
909                         }
910                      }
911                      stack[frame] = stack[frame].next;
912                      break;
913                   }
914                   case folder:
915                   {
916                      bool relative = true;
917                      char fileRelative[MAX_LOCATION];
918                      char filePath[MAX_LOCATION];
919                      filePath[0] = '\0';
920                      PathCat(filePath, prj.topNode.path);
921                      PathCat(filePath, stack[frame].path);
922                      fileRelative[0] = '\0';
923                      PathCat(fileRelative, stack[frame].path);
924                      if(relative && mode == workspace && prj != ide.project)
925                      {
926                         char special[MAX_LOCATION];
927                         sprintf(special, "(%s)%s", prj.name, fileRelative);
928                         strcpy(fileRelative, special);
929                      }
930                      if(nameCriteria[0] && SearchString(stack[frame].name, 0, nameCriteria, false, false) != null)
931                      {
932                         dirsMatchedCount++;
933                         app.Lock();
934                            ide.outputView.findBox.Logf(
935                                  "%s\n", relative ? fileRelative : filePath);
936                         app.Unlock();
937                      }
938                      if((subDirs || firstIteration) && stack[frame].files && stack[frame].files.count)
939                      {
940                         int lastFrame = frame;
941                         frame++;
942                         stack[frame] = stack[lastFrame].files.first;
943                         firstIteration = false;
944                      }
945                      else
946                         stack[frame] = stack[frame].next;
947                      break;
948                   }
949                   case resources:
950                      stack[frame] = stack[frame].next;
951                      break;
952                }
953                while(frame && !stack[frame])
954                {
955                   frame--;
956                   if(frame == 1 && projectNode && stack[frame] == projectNode)
957                      stack[frame] = null;
958                   else if(frame)
959                      stack[frame] = stack[frame].next;
960                }
961             }
962             if(mode == project) break;
963          }
964          if(abort)
965          {
966             for( ; frame >= 0; frame--)
967                if(frame < stackSize)
968                   stack[frame] = null;
969          }
970          delete stack;
971       }
972       delete replaceEdit;
973
974       active = false;
975
976       app.Lock();
977          if(filesSearchedCount)
978          {
979             if(!contentCriteria[0] && (filesMatchedCount || dirsMatchedCount))
980                ide.outputView.findBox.Logf("\n");
981             if(globalFindCount)
982                ide.outputView.findBox.Logf(
983                      $"%s search %s a total of %d match%s in %d out of the %d file%s searched\n",
984                      abort ? $"Aborted" : $"Completed", replaceMode ? $"replaced" : $"found", globalFindCount, (globalFindCount > 1) ? $"es" : "",
985                      filesMatchedCount, filesSearchedCount, (filesSearchedCount > 1) ? $"s" : "");
986             else if(filesMatchedCount)
987                ide.outputView.findBox.Logf(
988                      $"%s search found a total of %d match%s in the %d file%s searched\n",
989                      abort ? $"Aborted" : $"Completed", filesMatchedCount, (filesMatchedCount > 1) ? $"es" : "",
990                      filesSearchedCount, (filesSearchedCount > 1) ? $"s" : "");
991             else
992                ide.outputView.findBox.Logf(
993                      $"%s search did not find any match in the %d files searched\n",
994                      abort ? $"Aborted" : $"Completed", filesSearchedCount);
995          }
996          else
997             ide.outputView.findBox.Logf(
998                   $"%s search did not find any file\n", abort ? $"Aborted" : $"Completed");
999          findDialog.SearchComplete();
1000       app.Unlock();
1001       return 0;
1002    }
1003
1004    int SearchFileContent(const char *filePath, bool relative, const char *fileRelative)
1005    {
1006       int findCount = -1;
1007       File f = FileOpen(filePath, read);
1008       if(f)
1009       {
1010          int lineNum = 0;
1011          char line[65536];
1012          findCount = 0;
1013          while(f.GetLine(line, 65536/* should there be a - 1 here? */) && !abortNow)
1014          {
1015             int col = 0;
1016             uint start = 0;
1017             char * find = null;
1018             int inLineFindCount = 0;
1019             lineNum++;
1020             while((find = SearchString(line, start, contentCriteria, contentMatchCase, contentWholeWord)) && !abortNow)
1021             {
1022                if(!col)
1023                   col = find - &line[start] + 1;
1024                start += (find - &line[start]) / sizeof(char) + strlen(contentCriteria);
1025                inLineFindCount++;
1026             }
1027             if(inLineFindCount && !abortNow)
1028             {
1029                char s1[7] = "      ";
1030                char s2[5] = "    ";
1031                int len = strlen(line);
1032                s1[6 - HowManyDigits(lineNum)] = '\0';
1033                s2[4 - HowManyDigits(col)] = '\0';
1034                // Cut the line longer than 1024 because Logf prints to a buffer (and we don't want to output crazy long lines either)
1035                if(len > 1023)
1036                {
1037                   line[1023] = '\0';
1038                   line[1022] = '.';
1039                   line[1021] = '.';
1040                   line[1020] = '.';
1041                }
1042                app.Lock();
1043                   ide.outputView.findBox.Logf(
1044                         "   %s:%d:%d%s%s> %s\n", relative ? fileRelative : filePath,
1045                         lineNum, col, s1, s2, line);
1046                app.Unlock();
1047                findCount += inLineFindCount;
1048             }
1049             // todo
1050             /*else
1051                f.Seek(-strlen(contentCriteria), current);*/
1052          }
1053          delete f;
1054          if(findCount)
1055          {
1056             app.Lock();
1057                ide.outputView.findBox.Logf(
1058                      $"Found %d match%s in \"%s\"%s\n\n", findCount, (findCount > 1) ? "es" : "",
1059                      relative ? fileRelative : filePath, abortNow ? $" before search was aborted" : "");
1060             app.Unlock();
1061          }
1062       }
1063       else
1064       {
1065          app.Lock();
1066             ide.outputView.findBox.Logf($"Unable to open file %s\n\n", filePath);
1067          app.Unlock();
1068       }
1069       return findCount;
1070    }
1071
1072    int SearchFileContentAndReplace(const char *filePath, bool relative, const char *fileRelative, EditBox edit)
1073    {
1074       int replaceCount = -1;
1075       File f = FileOpen(filePath, read);
1076       if(f)
1077       {
1078          char line[65536];
1079          replaceCount = 0;
1080          while(f.GetLine(line, 65536/* should there be a - 1 here? */) && !abortNow)
1081          {
1082             int col = 0;
1083             if(SearchString(line, 0, contentCriteria, contentMatchCase, contentWholeWord) && !abortNow)
1084             {
1085                int lastLineNum = 0;
1086                f.Seek(0, start);
1087                edit.Load(f);
1088                delete f;
1089
1090                for(; edit.Find(contentCriteria, contentWholeWord, contentMatchCase, true) == found; replaceCount++)
1091                {
1092                   int lineNum = edit.lineNumber + 1;
1093                   edit.PutS(contentReplace);
1094                   if(lineNum != lastLineNum)
1095                   {
1096                      int len;
1097                      char line[1024];
1098                      char s1[6] = "      ";
1099                      char s2[4] = "    ";
1100                      //int len = strlen(line);
1101                      s1[6 - HowManyDigits(lineNum)] = '\0';
1102                      s2[4 - HowManyDigits(col)] = '\0';
1103                      strncpy(line, edit.line.text, 1023);
1104                      line[1023] = '\0';
1105                      len = strlen(line);
1106                      // Cut the line longer than 1024 because Logf prints
1107                      // to a buffer (and we don't want to output crazy long lines either)
1108                      if(len > 1023)
1109                      {
1110                         line[1022] = '.';
1111                         line[1021] = '.';
1112                         line[1020] = '.';
1113                      }
1114                      app.Lock();
1115                         ide.outputView.findBox.Logf(
1116                               "   %s:%d:%d%s%s> %s\n", relative ? fileRelative : filePath,
1117                               lineNum, col, s1, s2, line);
1118                      app.Unlock();
1119                   }
1120                }
1121                if(replaceCount)
1122                {
1123                   if((f = FileOpen(filePath, write)))
1124                   {
1125                      edit.Save(f, false);
1126                      delete f;
1127                   }
1128                }
1129                edit.Clear();
1130                break;
1131             }
1132             // todo
1133             /*else
1134                f.Seek(-strlen(contentCriteria), current);*/
1135
1136          }
1137          delete f;
1138          if(replaceCount)
1139          {
1140             app.Lock();
1141                ide.outputView.findBox.Logf(
1142                      $"Replaced %d match%s in \"%s\"%s\n\n", replaceCount, (replaceCount > 1) ? $"es" : "",
1143                      relative ? fileRelative : filePath, abortNow ? $" before search was aborted" : "");
1144             app.Unlock();
1145          }
1146       }
1147       else
1148       {
1149          app.Lock();
1150             ide.outputView.findBox.Logf($"Unable to open file %s\n\n", filePath);
1151          app.Unlock();
1152       }
1153       return replaceCount;
1154    }
1155 }
1156
1157 static struct SearchStackFrame
1158 {
1159    int tag;
1160    char path[MAX_LOCATION];
1161    FileListing fileList;
1162 };