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