wip II
[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       const int stackSize = 1024;
651       int frame, treeTop = 0;
652       int globalFindCount = 0, filesSearchedCount = 0, filesMatchedCount = 0, dirsMatchedCount = 0;
653       //double lastTime = GetTime();
654       SearchStackFrame stack[stackSize];
655       FindInFilesMode mode = this.mode;
656       
657       EditBox replaceEdit = null;
658
659       abort = false;
660       abortNow = false;
661
662       app.Lock();
663          {
664             char substring[512];
665             char containing[512];
666             char * and;
667             char * filterName;
668             if(!strcmp(filter.name, "All files"))
669                filterName = "files";
670             else
671                filterName = filter.name;
672             if(nameCriteria[0])
673                sprintf(substring, $" with file name matching \"%s\"", nameCriteria);
674             else
675                substring[0] = '\0';
676             if(contentCriteria && contentCriteria[0])
677                sprintf(containing, $" containing \"%s\"", contentCriteria);
678             else
679                containing[0] = '\0';
680             if(substring[0] && containing[0])
681                and = " and";
682             else
683                and = "";
684             if(mode == directory)
685             {
686                char * s;
687                ide.outputView.findBox.Logf(
688                      $"Searching \"%s\"%s for %s%s%s%s\n\n",
689                      (s = CopySystemPath(dir)), subDirs ? $" and its sub directories" : "",
690                      filterName, substring, and, containing);
691                delete s;
692             }
693             else if(mode == workspace)
694                ide.outputView.findBox.Logf(
695                      $"Searching workspace files for %s%s%s%s\n\n",
696                      filterName, substring, and, containing);
697             else if(mode == project)
698                ide.outputView.findBox.Logf(
699                      $"Searching project %s files for %s%s%s%s\n\n",
700                      project.name, filterName, substring, and, containing);
701          }
702       app.Unlock();
703       
704       if(replaceMode)
705       {
706          replaceEdit = EditBox
707          {
708             multiLine = true,textHorzScroll = true,textVertScroll = true, 
709             text = $"Replacing Editbox", size = Size { 640,480 }/*,maxLineSize = 65536*/
710          };
711       }
712
713       if(mode == directory)
714       {
715          strcpy(stack[0].path, dir);
716          stack[0].fileList = FileListing { dir, extensions = filter.extensions };  // there should be a sorted = true/false 
717
718          for(frame = 0; frame >= 0 && frame < stackSize && !abort; )
719          {
720             if(stack[frame].fileList.Find())
721             {
722                bool match = true;
723                if(nameCriteria[0])
724                {
725                   char name[MAX_LOCATION];
726                   GetLastDirectory(stack[frame].fileList.path, name);
727                   if(!(bool)SearchString(name, 0, nameCriteria, false, false))
728                      match = false;
729                }
730                if(!stack[frame].fileList.stats.attribs.isDirectory)
731                {
732                   bool relative = false;
733                   char fileRelative[MAX_LOCATION];
734                   if(filter.ValidateFileName(stack[frame].fileList.name))
735                   {
736                      MakePathRelative(stack[frame].fileList.path, dir, fileRelative);
737                      relative = true;
738                      
739                      filesSearchedCount++;
740                      if(match && contentCriteria[0])
741                      {
742                         int ret;
743
744                         app.Lock();
745                            ide.outputView.findBox.Tellf(
746                                  $"Searching %s for %s", relative ? fileRelative : stack[frame].fileList.path, contentCriteria);
747                         app.Unlock();
748
749                         if(replaceMode)
750                            ret = SearchFileContentAndReplace(stack[frame].fileList.path, relative, fileRelative, replaceEdit);
751                         else
752                            ret = SearchFileContent(stack[frame].fileList.path, relative, fileRelative);
753                         if(ret > 0)
754                         {
755                            globalFindCount += ret;
756                            filesMatchedCount++;
757                         }
758                      }
759                      else if(match && nameCriteria[0])
760                      {
761                         filesMatchedCount++;
762                         app.Lock();
763                            ide.outputView.findBox.Logf(
764                                  "%s\n",
765                                  relative ? fileRelative : stack[frame].fileList.path);
766                         app.Unlock();
767                      }
768                   }
769                }
770                else
771                {
772                   bool relative = false;
773                   char fileRelative[MAX_LOCATION];
774                   MakePathRelative(stack[frame].fileList.path, dir, fileRelative);
775                   relative = true;
776                   app.Lock();
777                      if(match && nameCriteria[0])
778                      {
779                         dirsMatchedCount++;
780                         ide.outputView.findBox.Logf(
781                               "%s\n",
782                               relative ? fileRelative : stack[frame].fileList.path);
783                      }
784                      ide.outputView.findBox.Tellf(
785                            $"Searching %s", relative ? fileRelative : stack[frame].fileList.path);
786                   app.Unlock();
787                }
788
789                if(subDirs && stack[frame].fileList.stats.attribs.isDirectory && strcmp(stack[frame].fileList.name, ".git"))
790                {
791                   int lastFrame = frame;
792                   /*double thisTime = GetTime();
793                   app.Lock();
794                      if(thisTime - lastTime > 0.25)
795                      {
796                         findDialog.SearchUpdateLabel(stack[lastFrame].fileList.path);
797                         lastTime = thisTime;
798                      }
799                   app.Unlock();*/
800                   frame++;
801                   strcpy(stack[frame].path, stack[lastFrame].fileList.path);
802                   stack[frame].fileList = FileListing { stack[frame].path, extensions = stack[lastFrame].fileList.extensions };
803                }
804             }
805             else
806             {
807                frame--;
808             }
809          }
810          if(abort)
811             for( ; frame >= 0 ; frame--)
812                stack[frame].fileList.Stop();
813       }
814       else if(mode == workspace || mode == project)
815       {
816          int len;
817          char path[MAX_LOCATION];
818          bool firtIteration = true;
819          Project prj = project;
820          ProjectNode stack[1024];
821          Iterator<Project> it { ide.workspace.projects };
822
823          while(true)
824          {
825             if(mode == workspace)
826             {
827                if(!it.Next()) break;
828                prj = it.data;
829             }
830             stack[1] = projectNode ? projectNode : prj.topNode;
831
832             for(frame = 1; frame && !abort;)
833             {
834                switch(stack[frame].type)
835                {
836                   case project:
837                      if((subDirs || firtIteration) && stack[frame].files && stack[frame].files.count)
838                      {
839                         int lastFrame = frame;
840                         frame++;
841                         stack[frame] = stack[lastFrame].files.first;
842                         firtIteration = false;
843                      }
844                      break;
845                   case file:
846                   {   
847                      bool relative = true;
848                      char fileRelative[MAX_LOCATION];
849                      char filePath[MAX_LOCATION];
850                      filePath[0] = '\0';
851                      PathCat(filePath, prj.topNode.path);
852                      PathCat(filePath, stack[frame].path);
853                      PathCat(filePath, stack[frame].name);
854                      fileRelative[0] = '\0';
855                      PathCat(fileRelative, stack[frame].path);
856                      PathCat(fileRelative, stack[frame].name);
857                      if(relative && mode == workspace && prj != ide.project)
858                      {
859                         char special[MAX_LOCATION];
860                         sprintf(special, "(%s)%s", prj.name, fileRelative);
861                         strcpy(fileRelative, special);
862                      }
863                      if(filter.ValidateFileName(stack[frame].name))
864                      {
865                         filesSearchedCount++;
866                         if(!nameCriteria[0] || (bool)SearchString(stack[frame].name, 0, nameCriteria, false, false))
867                         {
868                            if(contentCriteria[0])
869                            {
870                               int ret;
871
872                               app.Lock();
873                                  ide.outputView.findBox.Tellf(
874                                        $"Searching %s for \"%s\"", relative ? fileRelative : filePath,
875                                        contentCriteria);
876                               app.Unlock();
877
878                               if(replaceMode)
879                                  ret = SearchFileContentAndReplace(filePath, relative, fileRelative, replaceEdit);
880                               else
881                                  ret = SearchFileContent(filePath, relative, fileRelative);
882                               if(ret > 0)
883                               {
884                                  globalFindCount += ret;
885                                  filesMatchedCount++;
886                               }
887                            }
888                            else if(nameCriteria[0])
889                            {
890                               filesMatchedCount++;
891                               app.Lock();
892                                  ide.outputView.findBox.Logf(
893                                        "%s\n", relative ? fileRelative : filePath);
894                               app.Unlock();
895                            }
896                         }
897                      }
898                      stack[frame] = stack[frame].next;
899                      break;
900                   }
901                   case folder:
902                   {
903                      bool relative = true;
904                      char fileRelative[MAX_LOCATION];
905                      char filePath[MAX_LOCATION];
906                      filePath[0] = '\0';
907                      PathCat(filePath, prj.topNode.path);
908                      PathCat(filePath, stack[frame].path);
909                      fileRelative[0] = '\0';
910                      PathCat(fileRelative, stack[frame].path);
911                      if(relative && mode == workspace && prj != ide.project)
912                      {
913                         char special[MAX_LOCATION];
914                         sprintf(special, "(%s)%s", prj.name, fileRelative);
915                         strcpy(fileRelative, special);
916                      }
917                      if(nameCriteria[0] && (bool)SearchString(stack[frame].name, 0, nameCriteria, false, false))
918                      {
919                         dirsMatchedCount++;
920                         app.Lock();
921                            ide.outputView.findBox.Logf(
922                                  "%s\n", relative ? fileRelative : filePath);
923                         app.Unlock();
924                      }
925                      if((subDirs || firtIteration) && stack[frame].files && stack[frame].files.count)
926                      {
927                         int lastFrame = frame;
928                         frame++;
929                         stack[frame] = stack[lastFrame].files.first;
930                         firtIteration = false;
931                      }
932                      else
933                         stack[frame] = stack[frame].next;
934                      break;
935                   }
936                   case resources:
937                      stack[frame] = stack[frame].next;
938                      break;
939                }
940                while(frame && !stack[frame])
941                {
942                   frame--;
943                   if(frame == 1 && projectNode && stack[frame] == projectNode)
944                      stack[frame] = null;
945                   else if(frame)
946                      stack[frame] = stack[frame].next;
947                }
948             }
949             if(mode == project) break;
950          }
951          if(abort)
952          {
953             for( ; frame ; frame--)
954                stack[frame] = null;
955          }
956       }
957       delete replaceEdit;
958
959       active = false;
960
961       app.Lock();
962          if(filesSearchedCount)
963          {
964             if(!contentCriteria[0] && (filesMatchedCount || dirsMatchedCount))
965                ide.outputView.findBox.Logf("\n");
966             if(globalFindCount)
967                ide.outputView.findBox.Logf(
968                      $"%s search %s a total of %d match%s in %d out of the %d file%s searched\n",
969                      abort ? $"Aborted" : $"Completed", replaceMode ? $"replaced" : $"found", globalFindCount, (globalFindCount > 1) ? $"es" : "",
970                      filesMatchedCount, filesSearchedCount, (filesSearchedCount > 1) ? $"s" : "");
971             else if(filesMatchedCount)
972                ide.outputView.findBox.Logf(
973                      $"%s search found a total of %d match%s in the %d file%s searched\n",
974                      abort ? $"Aborted" : $"Completed", filesMatchedCount, (filesMatchedCount > 1) ? $"es" : "",
975                      filesSearchedCount, (filesSearchedCount > 1) ? $"s" : "");
976             else
977                ide.outputView.findBox.Logf(
978                      $"%s search did not find any match in the %d files searched\n",
979                      abort ? $"Aborted" : $"Completed", filesSearchedCount);
980          }
981          else
982             ide.outputView.findBox.Logf(
983                   $"%s search did not find any file\n", abort ? $"Aborted" : $"Completed");
984          findDialog.SearchComplete();
985       app.Unlock();
986       return 0;
987    }
988
989    int SearchFileContent(char *filePath, bool relative, char *fileRelative)
990    {
991       int findCount = -1;
992       File f = FileOpen(filePath, read);
993       if(f)
994       {
995          int lineNum = 0;
996          char line[65536];
997          findCount = 0;
998          while(f.GetLine(line, 65536/* should there be a - 1 here? */) && !abortNow)
999          {
1000             int col = 0;
1001             uint start = 0;
1002             char * find = null;
1003             int inLineFindCount = 0;
1004             lineNum++;
1005             while((find = SearchString(line, start, contentCriteria, contentMatchCase, contentWholeWord)) && !abortNow)
1006             {
1007                if(!col)
1008                   col = find - &line[start] + 1;
1009                start += (find - &line[start]) / sizeof(char) + strlen(contentCriteria);
1010                inLineFindCount++;
1011             }
1012             if(inLineFindCount && !abortNow)
1013             {
1014                char s1[6] = "      ";
1015                char s2[4] = "    ";
1016                int len = strlen(line);
1017                s1[6 - HowManyDigits(lineNum)] = '\0';
1018                s2[4 - HowManyDigits(col)] = '\0';
1019                // Cut the line longer than 1024 because Logf prints to a buffer (and we don't want to output crazy long lines either)
1020                if(len > 1023)
1021                {
1022                   line[1023] = '\0';
1023                   line[1022] = '.';
1024                   line[1021] = '.';
1025                   line[1020] = '.';
1026                }
1027                app.Lock();
1028                   ide.outputView.findBox.Logf(
1029                         "   %s:%d:%d%s%s> %s\n", relative ? fileRelative : filePath,
1030                         lineNum, col, s1, s2, line);
1031                app.Unlock();
1032                findCount += inLineFindCount;
1033             }
1034             // todo
1035             /*else
1036                f.Seek(-strlen(contentCriteria), current);*/
1037          }
1038          delete f;
1039          if(findCount)
1040          {
1041             app.Lock();
1042                ide.outputView.findBox.Logf(
1043                      $"Found %d match%s in \"%s\"%s\n\n", findCount, (findCount > 1) ? "es" : "",
1044                      relative ? fileRelative : filePath, abortNow ? $" before search was aborted" : "");
1045             app.Unlock();
1046          }
1047       }
1048       else
1049       {
1050          app.Lock();
1051             ide.outputView.findBox.Logf($"Unable to open file %s\n\n", filePath);
1052          app.Unlock();
1053       }
1054       return findCount;
1055    }
1056
1057    int SearchFileContentAndReplace(char *filePath, bool relative, char *fileRelative, EditBox edit)
1058    {
1059       int replaceCount = -1;
1060       File f = FileOpen(filePath, read);
1061       if(f)
1062       {
1063          char line[65536];
1064          replaceCount = 0;
1065          while(f.GetLine(line, 65536/* should there be a - 1 here? */) && !abortNow)
1066          {
1067             int col = 0;
1068             char * find = null;
1069             int inLineFindCount = 0;
1070             if(SearchString(line, 0, contentCriteria, contentMatchCase, contentWholeWord) && !abortNow)
1071             {
1072                int lastLineNum = 0;
1073                f.Seek(0, start);
1074                edit.Load(f);
1075                delete f;
1076
1077                for(; edit.Find(contentCriteria, contentWholeWord, contentMatchCase, true) == found; replaceCount++)
1078                {
1079                   int lineNum = edit.lineNumber + 1;
1080                   edit.PutS(contentReplace);
1081                   if(lineNum != lastLineNum)
1082                   {
1083                      int len;
1084                      char line[1024];
1085                      char s1[6] = "      ";
1086                      char s2[4] = "    ";
1087                      //int len = strlen(line);
1088                      s1[6 - HowManyDigits(lineNum)] = '\0';
1089                      s2[4 - HowManyDigits(col)] = '\0';
1090                      strncpy(line, edit.line.text, 1023);
1091                      line[1023] = '\0';
1092                      len = strlen(line);
1093                      // Cut the line longer than 1024 because Logf prints
1094                      // to a buffer (and we don't want to output crazy long lines either)
1095                      if(len > 1023)
1096                      {
1097                         line[1022] = '.';
1098                         line[1021] = '.';
1099                         line[1020] = '.';
1100                      }
1101                      app.Lock();
1102                         ide.outputView.findBox.Logf(
1103                               "   %s:%d:%d%s%s> %s\n", relative ? fileRelative : filePath,
1104                               lineNum, col, s1, s2, line);
1105                      app.Unlock();
1106                   }
1107                }
1108                if(replaceCount)
1109                {
1110                   if((f = FileOpen(filePath, write)))
1111                   {
1112                      edit.Save(f, false);
1113                      delete f;
1114                   }
1115                }
1116                edit.Clear();
1117                break;
1118             }
1119             // todo
1120             /*else
1121                f.Seek(-strlen(contentCriteria), current);*/
1122                
1123          }
1124          delete f;
1125          if(replaceCount)
1126          {
1127             app.Lock();
1128                ide.outputView.findBox.Logf(
1129                      $"Replaced %d match%s in \"%s\"%s\n\n", replaceCount, (replaceCount > 1) ? $"es" : "",
1130                      relative ? fileRelative : filePath, abortNow ? $" before search was aborted" : "");
1131             app.Unlock();
1132          }
1133       }
1134       else
1135       {
1136          app.Lock();
1137             ide.outputView.findBox.Logf($"Unable to open file %s\n\n", filePath);
1138          app.Unlock();
1139       }
1140       return replaceCount;
1141    }
1142 }
1143
1144 static struct SearchStackFrame
1145 {
1146    int tag;
1147    char path[MAX_LOCATION];
1148    FileListing fileList;
1149 };
1150