2b7856c616c00601786e564819bf5d24a0210948
[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 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 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)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)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    }
199
200    LayoutPage layout
201    {
202       this, anchor = { left = 20, top = 4, right = 8, bottom = 0 };
203       direction = horizontal;
204       tabCycle = true;
205       reverse = true;
206       gap = 8;
207    };
208
209    LayoutPage lpmain
210    {
211       layout, this;
212       tabCycle = true;
213       anchor.left = 0;
214       anchor.top = 0, anchor.bottom = 0;
215       gap = 2;
216    };
217
218    LayoutLine llfindIn      { lpmain, this, size.h = 26 };
219    LayoutLine llfindWhere   { lpmain, this, size.h = 26 };
220    LayoutLine llsubDirs     { lpmain, this, size.h = 18 };
221    LayoutLine llfilter      { lpmain, this, size.h = 26 };
222    LayoutLine llfileName    { lpmain, this, size.h = 26 };
223    LayoutLine llfindWhat    { lpmain, this, size.h = 26 };
224    LayoutLine llwholeWord   { lpmain, this, size.h = 18 };
225    LayoutLine llmatchCase   { lpmain, this, size.h = 18 };
226    LayoutLine llreplaceWith { lpmain, this, size.h = 26, visible = false };
227
228    Label lfindIn { llfindIn, this, size.w = 72, labeledWindow = findIn };
229    DropBox findIn
230    {
231       llfindIn, this, $"Find in:", altI, anchor.right = 0;
232
233       bool NotifySelect(DropBox control, DataRow row, Modifiers mods)
234       {
235          FindInFilesMode mode = this.mode;
236          bool inDir = mode == directory;
237          bool inWrk = mode == workspace;
238          bool inPrj = mode == project;
239          if(inPrj)
240             lfindWhere.labeledWindow = findWherePrjNode;
241          else
242             lfindWhere.labeledWindow = findWhere;
243          findWhere.visible = !inPrj;
244          findWhere.disabled = !inDir;
245          findWherePrjNode.visible = inPrj;
246          subDirs.disabled = inWrk;
247          llfindWhere.size = { llfindWhere.size.w, llfindWhere.size.h };
248
249          if(row)
250          {
251             Project prj;
252             lastSelectionMode = mode;
253             prj = lastSelectionMode == project ? (Project)row.tag : null;
254             delete lastSelectionProject;
255             if(prj)
256             {
257                DataRow r = null;
258                ProjectNode node = prj.topNode;
259                char filePath[MAX_LOCATION];
260                prj.topNode.GetFullFilePath(filePath);
261                lastSelectionProject = CopyString(filePath);
262                findWherePrjNode.Clear();
263                ListProjectNodeFolders(node, null);
264
265                if(lastSelectionProjectNode && !(node = prj.topNode.FindByFullPath(lastSelectionProjectNode, false)))
266                {
267                   node = prj.topNode;
268                   delete lastSelectionProjectNode;
269                }
270
271                for(r = findWherePrjNode.firstRow; r; r = r.next)
272                   if((ProjectNode)r.tag == node)
273                      break;
274                if(r)
275                   findWherePrjNode.SelectRow(r);
276             }
277          }
278          return true;
279       }
280    };
281
282    void ListProjectNodeFolders(ProjectNode node, DataRow parentRow)
283    {
284       DataRow row;
285       if(parentRow)
286          row = findWherePrjNode/*parentRow*/.AddRow();
287       else
288          row = findWherePrjNode.AddRow();
289       row.tag = (int64)node;
290       row.SetData(null, node);
291       if(node.files)
292       {
293          for(child : node.files; child.type == folder/* || child.type == file*//* || child.type == folderOpen*/)
294             ListProjectNodeFolders(child, row);
295       }
296    }
297
298    Label lfindWhere { llfindWhere, this, size.w = 72, labeledWindow = findWhere };
299    PathBox findWhere
300    {
301       llfindWhere, this, $"Find where:", altH, size.h = 24, anchor.right = 0;
302       typeExpected = directory, browseDialog = fileDialog;
303    };
304    DropBox findWherePrjNode
305    {
306       llfindWhere, this, $"Find where:", altH, size.h = 24, anchor.right = 0;
307       visible = false;
308       //collapseControl = true, treeBranches = true;
309
310       bool NotifySelect(DropBox control, DataRow row, Modifiers mods)
311       {
312          if(row)
313          {
314             ProjectNode node = (ProjectNode)row.tag;
315             delete lastSelectionProjectNode;
316             if(node)
317             {
318                char filePath[MAX_LOCATION];
319                node.GetFullFilePath(filePath);
320                lastSelectionProjectNode = CopyString(filePath);
321             }
322          }
323          return true;
324       }
325    };
326    DataField projectNodeField { dataType = "ProjectNode", freeData = false };
327
328    Window spacerA { llsubDirs, this, size = { 72, 10 }, clickThrough = true, background = formColor, inactive = true };
329    Button subDirs
330    {
331       llsubDirs, this, $"Include Subdirectories", altU, isCheckbox = true, checked = true;
332    };
333
334    Label lfilter { llfilter, this, size.w = 72, labeledWindow = filterDrop };
335    DropBox filterDrop
336    {
337       llfilter, this, $"Filter:", altL, anchor.right = 0;
338
339       bool NotifySelect(DropBox control, DataRow row, Modifiers mods)
340       {
341          fileFilter = (int)(row ? row.tag : 0);
342          //ListFiles();
343          return true;
344       }
345    };
346
347    Label lfileName { llfileName, this, size.w = 72, labeledWindow = fileName };
348    EditBox fileName
349    {
350       llfileName, this, $"File name:", altN, size.h = 24, anchor.right = 0;
351    };
352
353    //Window spacerX { ll, this, size = { 72, 10 }, clickThrough = true, background = formColor, inactive = true };
354    //Button nameWholeWord { ll, this, "Whole word only", AltO, isCheckbox = true };
355    //Window spacerX { llwholeWord, this, size = { 72, 10 }, clickThrough = true, background = formColor, inactive = true };
356    //Button nameMatchCase { ll, this, "Match case", altA, isCheckbox = true };
357
358    Label lfindContent { llfindWhat, this, size.w = 72, labeledWindow = findContent };
359    EditBox findContent
360    {
361       llfindWhat, this, $"Find what:", altT, size.h = 24, anchor.right = 0;
362    };
363
364    Label lreplaceWith { llreplaceWith, this, size.w = 72, labeledWindow = replaceWith };
365    EditBox replaceWith
366    {
367       llreplaceWith, this, $"Replace with:", altE;
368       size.h = 24, anchor.right = 0;
369    };
370
371    Window spacerB { llwholeWord, this, size = { 72, 10 }, clickThrough = true, background = formColor, inactive = true };
372    Button contentWholeWord { llwholeWord, this, $"Whole word only", altW, isCheckbox = true };
373
374    Window spacerC { llmatchCase, this, size = { 72, 10 }, clickThrough = true, background = formColor, inactive = true };
375    Button contentMatchCase { llmatchCase, this, $"Match case", altC, isCheckbox = true };
376
377    LayoutPage lpbuttons
378    {
379       layout, this;
380       tabCycle = true;
381       size.w = 80;
382       anchor.top = 0, anchor.bottom = 0;
383       gap = 16;
384    };
385
386    LayoutLine llspace   { lpbuttons, this, size.h = 2 };
387    LayoutLine llfind    { lpbuttons, this, size.h = 30 };
388    LayoutLine llcancel  { lpbuttons, this, size.h = 30 };
389
390    Button find
391    {
392       llfind, this, $"Find", altF, isDefault = true, size = { 80, 24 }, anchor.horz = 0, anchor.vert = 0, keyRepeat = true;
393
394       bool NotifyClicked(Button control, int x, int y, Modifiers mods)
395       {
396          String findPath = findWhere.path;
397          if(findIn.currentRow == inDirectoryRow && !findPath[0])
398          {
399             findWhere.Activate();
400             MessageBox { type = ok, master = parent,
401                   text = text, contents = $"You must specify a search location." }.Modal();
402          }
403          else if(findIn.currentRow == inDirectoryRow && !FileExists(findPath))
404          {
405             findWhere.Activate();
406             MessageBox { type = ok, master = parent,
407                   text = text, contents = $"Search location does not exist. Please provide a valid location." }.Modal();
408          }
409          else if(!fileName.contents[0] && !findContent.contents[0])
410          {
411             findContent.Activate();
412             MessageBox { type = ok, master = parent,
413                   text = text, contents = $"Nothing to be found. Please specify at least one criteria." }.Modal();
414          }
415          else
416          {
417             findContent.Activate();
418             SearchStop();
419             SearchStart();
420          }
421          return true;
422       }
423    };
424
425    Button cancel
426    {
427       llcancel, this, $"Cancel", hotKey = { escape }, size = { 80, 24 }, anchor.horz = 0, anchor.vert = 0;
428
429       bool NotifyClicked(Button control, int x, int y, Modifiers mods)
430       {
431          findContent.Activate();
432          Destroy(0);
433          return true;
434       }
435    };
436    
437    SearchThread searchThread { findDialog = this };
438    FileDialog fileDialog { master = this, type = selectDir, text = $"Select Search Location..." };
439
440    void OnDestroy()
441    {
442       findIn.Clear();
443       inDirectoryRow = null;
444       inWorkspaceRow = null;
445    }
446
447    bool OnPostCreate()
448    {
449       bool disabled;
450       bool withWorkspace = (bool)ide.workspace;
451       DataRow row;
452       if(!inDirectoryRow)
453          inDirectoryRow = findIn.AddString($"Directory");
454       if(withWorkspace)
455       {
456          if(!inWorkspaceRow)
457             inWorkspaceRow = findIn.AddString($"Workspace");
458          for(prj : ide.workspace.projects)
459             AddProjectItem(prj);
460          if(lastSelectionProject)
461          {
462             for(row = findIn.firstRow; row; row = row.next)
463             {
464                char filePath[MAX_LOCATION];
465                Project p = (Project)row.tag;
466                if(p)
467                {
468                   p.topNode.GetFullFilePath(filePath);
469                   if(!fstrcmp(filePath, lastSelectionProject))
470                      break;
471                }
472             }
473             if(row)
474                findIn.SelectRow(row);
475             else
476                delete lastSelectionProject;
477          }
478          if(!lastSelectionProject)
479          {
480             if(lastSelectionMode == project)
481             {
482                Project prj = ide.workspace.projects.firstIterator.data;
483                for(row = findIn.firstRow; row; row = row.next)
484                   if((Project)row.tag == prj)
485                      break;
486                if(row)
487                   findIn.SelectRow(row);
488             }
489             else
490                property::mode = lastSelectionMode;
491          }
492       }
493       else
494       {
495          findIn.SelectRow(inDirectoryRow);
496       }
497
498       findIn.disabled = !withWorkspace;
499       /*disabled = findIn.currentRow != inDirectoryRow;
500
501       findWhere.disabled = disabled;
502       subDirs.disabled = disabled;*/
503       
504       findContent.Activate();
505       return true;
506    }
507
508    void SearchStart()
509    {
510       char text[2048];
511       
512       searchThread.active = true;
513       searchThread.project = null;
514       searchThread.projectNode = null;
515       searchThread.subDirs = subDirs.checked;
516
517       if(findIn.currentRow == inDirectoryRow)
518       {
519          searchThread.mode = directory;
520          strcpy(searchThread.dir, findWhere.slashPath);
521       }
522       else if(findIn.currentRow == inWorkspaceRow)
523       {
524          searchThread.mode = workspace;
525          searchThread.subDirs = true;
526       }
527       else
528       {
529          searchThread.mode = project;
530          searchThread.project = (Project)findIn.currentRow.tag;
531          searchThread.projectNode = (ProjectNode)(findWherePrjNode.currentRow ? findWherePrjNode.currentRow.tag : null);
532       }
533       //searchThread.nameMatchCase = nameMatchCase.checked;
534       //searchThread.nameWholeWord = nameWholeWord.checked;
535       searchThread.contentMatchCase = contentMatchCase.checked;
536       searchThread.contentWholeWord = contentWholeWord.checked;
537       
538       searchThread.filter = filters[fileFilter];
539
540       strcpy(searchThread.nameCriteria, fileName.contents);
541       strcpy(searchThread.contentCriteria, findContent.contents);
542       if(replaceMode)
543          strcpy(searchThread.contentReplace, replaceWith.contents);
544       searchThread.replaceMode = replaceMode;
545
546       //cancel.text = "Stop";
547       
548       ide.outputView.ShowClearSelectTab(find);
549
550       Destroy(0);
551       
552       searchThread.Create();
553    }
554
555    bool SearchAbort()
556    {
557       if(searchThread.active)
558       {
559          searchThread.Abort();
560          return true;
561       }
562       return false;
563    }
564
565    bool SearchStop()
566    {
567       if(searchThread.active)
568       {
569          searchThread.Abort();
570          searchThread.Abort();
571          app.Unlock();
572             searchThread.Wait();
573          app.Lock();
574          return true;
575       }
576       return false;
577    }
578
579    void SearchComplete()
580    {
581       //cancel.text = $"Cancel";
582    }
583
584    int GetSizeFilter()
585    {
586       return sizeFilters;
587    }
588
589    bool NotifyCharsAddedDeletedNameContent(EditBox editBox, BufferLocation before, BufferLocation after, bool pasteOperation)
590    {
591       find.disabled = (strlen(fileName.contents) == 0 && strlen(findContent.contents) == 0);
592       return true;
593    }
594
595    bool OnKeyHit(Key key, unichar ch)
596    {
597       if(ch && !key.alt && !key.ctrl && !key.shift && (contentMatchCase.active || contentWholeWord.active))
598       {
599          findContent.Activate();
600          return findContent.OnKeyHit(key, ch);
601       }
602       return true;
603    }
604 }
605
606 class SearchThread : Thread
607 {
608 public:
609    bool active, subDirs/*, nameMatchCase, nameWholeWord*/, contentMatchCase, contentWholeWord;
610    char dir[MAX_DIRECTORY], contentCriteria[1024], contentReplace[1024], nameCriteria[1024];
611    FileFilter filter;
612    FindInFilesDialog findDialog;
613    FindInFilesMode mode;
614    Project project;
615    ProjectNode projectNode;
616    bool replaceMode;
617
618    void Abort()
619    {
620       if(abort)
621          abortNow = true;
622       else
623          abort = true;
624    }
625
626 private:
627
628    bool abort, abortNow;
629
630    SearchThread()
631    {
632       active = false;
633       abort = false;
634       abortNow = false;
635    }
636
637    int HowManyDigits(int num)
638    {
639       int count = 1;
640       while(num > 9)
641       {
642          count++;
643          num /= 10;
644       }
645       return count;
646    }
647
648    unsigned int Main()
649    {
650       int frame, treeTop = 0;
651       int globalFindCount = 0, filesSearchedCount = 0, filesMatchedCount = 0, dirsMatchedCount = 0;
652       //double lastTime = GetTime();
653       SearchStackFrame stack[1024];
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             char * and;
666             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          strcpy(stack[0].path, dir);
715          stack[0].fileList = FileListing { dir, extensions = filter.extensions };  // there should be a sorted = true/false 
716
717          for(frame = 0; frame >= 0 && !abort; )
718          {
719             if(stack[frame].fileList.Find())
720             {
721                bool match = true;
722                if(nameCriteria[0])
723                {
724                   char name[MAX_LOCATION];
725                   GetLastDirectory(stack[frame].fileList.path, name);
726                   if(!(bool)SearchString(name, 0, nameCriteria, false, false))
727                      match = false;
728                }
729                if(!stack[frame].fileList.stats.attribs.isDirectory)
730                {
731                   bool relative = false;
732                   char fileRelative[MAX_LOCATION];
733                   if(filter.ValidateFileName(stack[frame].fileList.name))
734                   {
735                      MakePathRelative(stack[frame].fileList.path, dir, fileRelative);
736                      relative = true;
737                      
738                      filesSearchedCount++;
739                      if(match && contentCriteria[0])
740                      {
741                         int ret;
742
743                         app.Lock();
744                            ide.outputView.findBox.Tellf(
745                                  $"Searching %s for %s", relative ? fileRelative : stack[frame].fileList.path, contentCriteria);
746                         app.Unlock();
747
748                         if(replaceMode)
749                            ret = SearchFileContentAndReplace(stack[frame].fileList.path, relative, fileRelative, replaceEdit);
750                         else
751                            ret = SearchFileContent(stack[frame].fileList.path, relative, fileRelative);
752                         if(ret > 0)
753                         {
754                            globalFindCount += ret;
755                            filesMatchedCount++;
756                         }
757                      }
758                      else if(match && nameCriteria[0])
759                      {
760                         filesMatchedCount++;
761                         app.Lock();
762                            ide.outputView.findBox.Logf(
763                                  "%s\n",
764                                  relative ? fileRelative : stack[frame].fileList.path);
765                         app.Unlock();
766                      }
767                   }
768                }
769                else
770                {
771                   bool relative = false;
772                   char fileRelative[MAX_LOCATION];
773                   MakePathRelative(stack[frame].fileList.path, dir, fileRelative);
774                   relative = true;
775                   app.Lock();
776                      if(match && nameCriteria[0])
777                      {
778                         dirsMatchedCount++;
779                         ide.outputView.findBox.Logf(
780                               "%s\n",
781                               relative ? fileRelative : stack[frame].fileList.path);
782                      }
783                      ide.outputView.findBox.Tellf(
784                            $"Searching %s", relative ? fileRelative : stack[frame].fileList.path);
785                   app.Unlock();
786                }
787
788                if(subDirs && stack[frame].fileList.stats.attribs.isDirectory && strcmp(stack[frame].fileList.name, ".git"))
789                {
790                   int lastFrame = frame;
791                   /*double thisTime = GetTime();
792                   app.Lock();
793                      if(thisTime - lastTime > 0.25)
794                      {
795                         findDialog.SearchUpdateLabel(stack[lastFrame].fileList.path);
796                         lastTime = thisTime;
797                      }
798                   app.Unlock();*/
799                   frame++;
800                   strcpy(stack[frame].path, stack[lastFrame].fileList.path);
801                   stack[frame].fileList = FileListing { stack[frame].path, extensions = stack[lastFrame].fileList.extensions };
802                }
803             }
804             else
805             {
806                frame--;
807             }
808          }
809          if(abort)
810             for( ; frame >= 0 ; frame--)
811                stack[frame].fileList.Stop();
812       }
813       else if(mode == workspace || mode == project)
814       {
815          int len;
816          char path[MAX_LOCATION];
817          bool firtIteration = true;
818          Project prj = project;
819          ProjectNode stack[1024];
820          Iterator<Project> it { ide.workspace.projects };
821
822          while(true)
823          {
824             if(mode == workspace)
825             {
826                if(!it.Next()) break;
827                prj = it.data;
828             }
829             stack[1] = projectNode ? projectNode : prj.topNode;
830
831             for(frame = 1; frame && !abort;)
832             {
833                switch(stack[frame].type)
834                {
835                   case project:
836                      if((subDirs || firtIteration) && stack[frame].files && stack[frame].files.count)
837                      {
838                         int lastFrame = frame;
839                         frame++;
840                         stack[frame] = stack[lastFrame].files.first;
841                         firtIteration = false;
842                      }
843                      break;
844                   case file:
845                   {   
846                      bool relative = true;
847                      char fileRelative[MAX_LOCATION];
848                      char filePath[MAX_LOCATION];
849                      filePath[0] = '\0';
850                      PathCat(filePath, prj.topNode.path);
851                      PathCat(filePath, stack[frame].path);
852                      PathCat(filePath, stack[frame].name);
853                      fileRelative[0] = '\0';
854                      PathCat(fileRelative, stack[frame].path);
855                      PathCat(fileRelative, stack[frame].name);
856                      if(relative && mode == workspace && prj != ide.project)
857                      {
858                         char special[MAX_LOCATION];
859                         sprintf(special, "(%s)%s", prj.name, fileRelative);
860                         strcpy(fileRelative, special);
861                      }
862                      if(filter.ValidateFileName(stack[frame].name))
863                      {
864                         filesSearchedCount++;
865                         if(!nameCriteria[0] || (bool)SearchString(stack[frame].name, 0, nameCriteria, false, false))
866                         {
867                            if(contentCriteria[0])
868                            {
869                               int ret;
870
871                               app.Lock();
872                                  ide.outputView.findBox.Tellf(
873                                        $"Searching %s for \"%s\"", relative ? fileRelative : filePath,
874                                        contentCriteria);
875                               app.Unlock();
876
877                               if(replaceMode)
878                                  ret = SearchFileContentAndReplace(filePath, relative, fileRelative, replaceEdit);
879                               else
880                                  ret = SearchFileContent(filePath, relative, fileRelative);
881                               if(ret > 0)
882                               {
883                                  globalFindCount += ret;
884                                  filesMatchedCount++;
885                               }
886                            }
887                            else if(nameCriteria[0])
888                            {
889                               filesMatchedCount++;
890                               app.Lock();
891                                  ide.outputView.findBox.Logf(
892                                        "%s\n", relative ? fileRelative : filePath);
893                               app.Unlock();
894                            }
895                         }
896                      }
897                      stack[frame] = stack[frame].next;
898                      break;
899                   }
900                   case folder:
901                   {
902                      bool relative = true;
903                      char fileRelative[MAX_LOCATION];
904                      char filePath[MAX_LOCATION];
905                      filePath[0] = '\0';
906                      PathCat(filePath, prj.topNode.path);
907                      PathCat(filePath, stack[frame].path);
908                      fileRelative[0] = '\0';
909                      PathCat(fileRelative, stack[frame].path);
910                      if(relative && mode == workspace && prj != ide.project)
911                      {
912                         char special[MAX_LOCATION];
913                         sprintf(special, "(%s)%s", prj.name, fileRelative);
914                         strcpy(fileRelative, special);
915                      }
916                      if(nameCriteria[0] && (bool)SearchString(stack[frame].name, 0, nameCriteria, false, false))
917                      {
918                         dirsMatchedCount++;
919                         app.Lock();
920                            ide.outputView.findBox.Logf(
921                                  "%s\n", relative ? fileRelative : filePath);
922                         app.Unlock();
923                      }
924                      if((subDirs || firtIteration) && stack[frame].files && stack[frame].files.count)
925                      {
926                         int lastFrame = frame;
927                         frame++;
928                         stack[frame] = stack[lastFrame].files.first;
929                         firtIteration = false;
930                      }
931                      else
932                         stack[frame] = stack[frame].next;
933                      break;
934                   }
935                   case resources:
936                      stack[frame] = stack[frame].next;
937                      break;
938                }
939                while(frame && !stack[frame])
940                {
941                   frame--;
942                   if(frame == 1 && projectNode && stack[frame] == projectNode)
943                      stack[frame] = null;
944                   else if(frame)
945                      stack[frame] = stack[frame].next;
946                }
947             }
948             if(mode == project) break;
949          }
950          if(abort)
951          {
952             for( ; frame ; frame--)
953                stack[frame] = null;
954          }
955       }
956       delete replaceEdit;
957
958       active = false;
959
960       app.Lock();
961          if(filesSearchedCount)
962          {
963             if(!contentCriteria[0] && (filesMatchedCount || dirsMatchedCount))
964                ide.outputView.findBox.Logf("\n");
965             if(globalFindCount)
966                ide.outputView.findBox.Logf(
967                      $"%s search %s a total of %d match%s in %d out of the %d file%s searched\n",
968                      abort ? $"Aborted" : $"Completed", replaceMode ? $"replaced" : $"found", globalFindCount, (globalFindCount > 1) ? $"es" : "",
969                      filesMatchedCount, filesSearchedCount, (filesSearchedCount > 1) ? $"s" : "");
970             else if(filesMatchedCount)
971                ide.outputView.findBox.Logf(
972                      $"%s search found a total of %d match%s in the %d file%s searched\n",
973                      abort ? $"Aborted" : $"Completed", filesMatchedCount, (filesMatchedCount > 1) ? $"es" : "",
974                      filesSearchedCount, (filesSearchedCount > 1) ? $"s" : "");
975             else
976                ide.outputView.findBox.Logf(
977                      $"%s search did not find any match in the %d files searched\n",
978                      abort ? $"Aborted" : $"Completed", filesSearchedCount);
979          }
980          else
981             ide.outputView.findBox.Logf(
982                   $"%s search did not find any file\n", abort ? $"Aborted" : $"Completed");
983          findDialog.SearchComplete();
984       app.Unlock();
985       return 0;
986    }
987
988    int SearchFileContent(char *filePath, bool relative, char *fileRelative)
989    {
990       int findCount = -1;
991       File f = FileOpen(filePath, read);
992       if(f)
993       {
994          int lineNum = 0;
995          char line[65536];
996          findCount = 0;
997          while(f.GetLine(line, 65536/* should there be a - 1 here? */) && !abortNow)
998          {
999             int col = 0;
1000             uint start = 0;
1001             char * find = null;
1002             int inLineFindCount = 0;
1003             lineNum++;
1004             while((find = SearchString(line, start, contentCriteria, contentMatchCase, contentWholeWord)) && !abortNow)
1005             {
1006                if(!col)
1007                   col = find - &line[start] + 1;
1008                start += (find - &line[start]) / sizeof(char) + strlen(contentCriteria);
1009                inLineFindCount++;
1010             }
1011             if(inLineFindCount && !abortNow)
1012             {
1013                char s1[6] = "      ";
1014                char s2[4] = "    ";
1015                int len = strlen(line);
1016                s1[6 - HowManyDigits(lineNum)] = '\0';
1017                s2[4 - HowManyDigits(col)] = '\0';
1018                // Cut the line longer than 1024 because Logf prints to a buffer (and we don't want to output crazy long lines either)
1019                if(len > 1023)
1020                {
1021                   line[1023] = '\0';
1022                   line[1022] = '.';
1023                   line[1021] = '.';
1024                   line[1020] = '.';
1025                }
1026                app.Lock();
1027                   ide.outputView.findBox.Logf(
1028                         "   %s:%d:%d%s%s> %s\n", relative ? fileRelative : filePath,
1029                         lineNum, col, s1, s2, line);
1030                app.Unlock();
1031                findCount += inLineFindCount;
1032             }
1033             // todo
1034             /*else
1035                f.Seek(-strlen(contentCriteria), current);*/
1036          }
1037          delete f;
1038          if(findCount)
1039          {
1040             app.Lock();
1041                ide.outputView.findBox.Logf(
1042                      $"Found %d match%s in \"%s\"%s\n\n", findCount, (findCount > 1) ? "es" : "",
1043                      relative ? fileRelative : filePath, abortNow ? $" before search was aborted" : "");
1044             app.Unlock();
1045          }
1046       }
1047       else
1048       {
1049          app.Lock();
1050             ide.outputView.findBox.Logf($"Unable to open file %s\n\n", filePath);
1051          app.Unlock();
1052       }
1053       return findCount;
1054    }
1055
1056    int SearchFileContentAndReplace(char *filePath, bool relative, char *fileRelative, EditBox edit)
1057    {
1058       int replaceCount = -1;
1059       File f = FileOpen(filePath, read);
1060       if(f)
1061       {
1062          char line[65536];
1063          replaceCount = 0;
1064          while(f.GetLine(line, 65536/* should there be a - 1 here? */) && !abortNow)
1065          {
1066             int col = 0;
1067             char * find = null;
1068             int inLineFindCount = 0;
1069             if(SearchString(line, 0, contentCriteria, contentMatchCase, contentWholeWord) && !abortNow)
1070             {
1071                int lastLineNum = 0;
1072                f.Seek(0, start);
1073                edit.Load(f);
1074                delete f;
1075
1076                for(; edit.Find(contentCriteria, contentWholeWord, contentMatchCase, true) == found; replaceCount++)
1077                {
1078                   int lineNum = edit.lineNumber + 1;
1079                   edit.PutS(contentReplace);
1080                   if(lineNum != lastLineNum)
1081                   {
1082                      int len;
1083                      char line[1024];
1084                      char s1[6] = "      ";
1085                      char s2[4] = "    ";
1086                      //int len = strlen(line);
1087                      s1[6 - HowManyDigits(lineNum)] = '\0';
1088                      s2[4 - HowManyDigits(col)] = '\0';
1089                      strncpy(line, edit.line.text, 1023);
1090                      line[1023] = '\0';
1091                      len = strlen(line);
1092                      // Cut the line longer than 1024 because Logf prints
1093                      // to a buffer (and we don't want to output crazy long lines either)
1094                      if(len > 1023)
1095                      {
1096                         line[1022] = '.';
1097                         line[1021] = '.';
1098                         line[1020] = '.';
1099                      }
1100                      app.Lock();
1101                         ide.outputView.findBox.Logf(
1102                               "   %s:%d:%d%s%s> %s\n", relative ? fileRelative : filePath,
1103                               lineNum, col, s1, s2, line);
1104                      app.Unlock();
1105                   }
1106                }
1107                if(replaceCount)
1108                {
1109                   if((f = FileOpen(filePath, write)))
1110                   {
1111                      edit.Save(f, false);
1112                      delete f;
1113                   }
1114                }
1115                edit.Clear();
1116                break;
1117             }
1118             // todo
1119             /*else
1120                f.Seek(-strlen(contentCriteria), current);*/
1121                
1122          }
1123          delete f;
1124          if(replaceCount)
1125          {
1126             app.Lock();
1127                ide.outputView.findBox.Logf(
1128                      $"Replaced %d match%s in \"%s\"%s\n\n", replaceCount, (replaceCount > 1) ? $"es" : "",
1129                      relative ? fileRelative : filePath, abortNow ? $" before search was aborted" : "");
1130             app.Unlock();
1131          }
1132       }
1133       else
1134       {
1135          app.Lock();
1136             ide.outputView.findBox.Logf($"Unable to open file %s\n\n", filePath);
1137          app.Unlock();
1138       }
1139       return replaceCount;
1140    }
1141 }
1142
1143 static struct SearchStackFrame
1144 {
1145    int tag;
1146    char path[MAX_LOCATION];
1147    FileListing fileList;
1148 };
1149