1 #include "ide.h"
2 #include "ide.h"
3 
sSD()4 FileSel& sSD()
5 {
6 	static bool b;
7 	static FileSel fs;
8 	if(!b) {
9 		fs.AllFilesType();
10 		b = true;
11 	}
12 	return fs;
13 }
14 
SerializeFindInFiles(Stream & s)15 void Ide::SerializeFindInFiles(Stream& s) {
16 	int version = 7;
17 	s / version;
18 	s % ff.files;
19 	ff.files.SerializeList(s);
20 	s % ff.folder;
21 	ff.folder.SerializeList(s);
22 	if(version >= 2)
23 	{
24 		s % ff.replace;
25 		ff.replace.SerializeList(s);
26 		s % ff.style;
27 	}
28 	if(version >= 1)
29 		s % sSD();
30 	if(version >= 3 && version < 7) {
31 		DropList dummy;
32 		s % dummy;
33 	}
34 
35 	if(version >= 4)
36 		s % ff.samecase;
37 	if(version >= 5)
38 		s % ff.regexp;
39 	if(version >= 6)
40 		s % ff.workspace;
41 }
42 
SearchForFiles(Index<String> & files,String dir,String mask,int readonly,Progress & pi)43 void SearchForFiles(Index<String>& files, String dir, String mask, int readonly, Progress& pi) {
44 	FindFile ff(AppendFileName(dir, "*.*"));
45 	while(ff) {
46 		if(ff.IsFolder() && *ff.GetName() != '.')
47 			SearchForFiles(files, AppendFileName(dir, ff.GetName()), mask, readonly, pi);
48 		else
49 		if(ff.IsFile() && PatternMatchMulti(mask, ff.GetName())) {
50 			if(IsNull(readonly) || !!readonly == !!ff.IsReadOnly()) {
51 				if(pi.StepCanceled()) return;
52 				files.FindAdd(AppendFileName(dir, ff.GetName()));
53 			}
54 		}
55 		ff.Next();
56 	}
57 }
58 
59 enum {
60 	WILDANY = 16,
61 	WILDONE,
62 	WILDSPACE,
63 	WILDNUMBER,
64 	WILDID,
65 };
66 
Match(const char * f,const char * s,bool we,bool ignorecase,int & count)67 bool Match(const char *f, const char *s, bool we, bool ignorecase, int& count) {
68 	const char *b = s;
69 	while(*f) {
70 		if(*f == WILDANY) {
71 			f++;
72 			for(;;) {
73 				if(Match(f, s, we, ignorecase, count)) {
74 					count += int(s - b);
75 					return true;
76 				}
77 				if(!*s++) break;
78 			}
79 			return false;
80 		}
81 		else
82 		if(*f == WILDONE) {
83 			if(!*s++) return false;
84 		}
85 		else
86 		if(*f == WILDSPACE) {
87 			if(*s != ' ' && *s != '\t') return false;
88 			s++;
89 			while(*s == ' ' || *s == '\t')
90 				s++;
91 		}
92 		else
93 		if(*f == WILDNUMBER) {
94 			if(*s < '0' || *s > '9') return false;
95 			s++;
96 			while(*s >= '0' && *s <= '9')
97 				s++;
98 		}
99 		else
100 		if(*f == WILDID) {
101 			if(!iscib(*s)) return false;
102 			s++;
103 			while(iscid(*s)) s++;
104 		}
105 		else {
106 			if(ignorecase ? ToUpper(*s) != ToUpper(*f) : *s != *f) return false;
107 			s++;
108 		}
109 		f++;
110 	}
111 	count = int(s - b);
112 	return we && iscid(*s) ? false : true;
113 }
114 
AddFoundFile(const String & fn,int ln,const String & line,int pos,int count)115 void Ide::AddFoundFile(const String& fn, int ln, const String& line, int pos, int count)
116 {
117 	ErrorInfo f;
118 	f.file = fn;
119 	f.lineno = ln;
120 	f.linepos = pos + 1;
121 	f.len = count;
122 	f.kind = 0;
123 	f.message = "\1" + EditorSyntax::GetSyntaxForFilename(fn) + "\1" +
124 	            AsString(pos) + "\1" + AsString(count) + "\1" + (line.GetCount() > 300 ? line.Mid(0, 300) : line);
125 	FFound().Add(fn, ln, f.message, RawToValue(f));
126 }
127 
SearchInFile(const String & fn,const String & pattern,bool wholeword,bool ignorecase,int & n,RegExp * regexp)128 bool Ide::SearchInFile(const String& fn, const String& pattern, bool wholeword, bool ignorecase,
129                        int& n, RegExp *regexp) {
130 	FileIn in(fn);
131 	if(!in) return true;
132 	int ln = 1;
133 	bool wb = wholeword ? iscid(*pattern) : false;
134 	bool we = wholeword ? iscid(*pattern.Last()) : false;
135 	int infile = 0;
136 	bool sync = false;
137 	while(!in.IsEof()) {
138 		String line = in.GetLine();
139 		bool bw = true;
140 		int  count;
141 		if(regexp) {
142 			if(regexp->Match(line)) {
143 				AddFoundFile(fn, ln, line, regexp->GetOffset(), regexp->GetLength());
144 				sync = true;
145 			}
146 		}
147 		else
148 			for(const char *s = line; *s; s++) {
149 				if(bw && Match(pattern, s, we, ignorecase, count)) {
150 					AddFoundFile(fn, ln, line, int(s - line), count);
151 					sync = true;
152 					infile++;
153 					n++;
154 					break;
155 				}
156 				if(wb) bw = !iscid(*s);
157 			}
158 		ln++;
159 	}
160 
161 	if(sync)
162 		FFound().Sync();
163 
164 	in.Close();
165 	int ffs = ~ff.style;
166 	if(infile && ffs != STYLE_NO_REPLACE)
167 	{
168 		EditFile(fn);
169 		if(!editor.IsReadOnly()) {
170 			bool doit = true;
171 			if(ffs == STYLE_CONFIRM_REPLACE)
172 			{
173 				editor.SetCursor(0);
174 				editor.Find(false, true);
175 				switch(PromptYesNoCancel(Format("Replace %d lines in [* \1%s\1]?", infile, fn)))
176 				{
177 				case 1:  break;
178 				case 0:  doit = false; break;
179 				case -1: return false;
180 				}
181 			}
182 			if(doit)
183 			{
184 				editor.SelectAll();
185 				editor.BlockReplace();
186 				SaveFile();
187 				FFound().Add(fn, Null, AsString(infile) + " replacements made");
188 				FFound().Sync();
189 			}
190 		}
191 	}
192 
193 	return true;
194 }
195 
FindInFiles(bool replace)196 void Ide::FindInFiles(bool replace) {
197 	CodeEditor::FindReplaceData d = editor.GetFindReplaceData();
198 	CtrlRetriever rf;
199 	ff.output <<= ffoundi_next;
200 	rf(ff.find, d.find)
201 	  (ff.replace, d.replace)
202 	  (ff.ignorecase, d.ignorecase)
203 	  (ff.samecase, d.samecase)
204 	  (ff.wholeword, d.wholeword)
205 	  (ff.wildcards, d.wildcards)
206 	  (ff.regexp, d.regexp)
207 	;
208 	WriteList(ff.find, d.find_list);
209 	WriteList(ff.replace, d.replace_list);
210 	ff.Sync();
211 	if(IsNull(~ff.folder))
212 		ff.folder <<= GetUppDir();
213 	ff.style <<= STYLE_NO_REPLACE;
214 	ff.Sync();
215 	ff.itext = editor.GetI();
216 	ff.Setup(replace);
217 
218 	int c = ff.Execute();
219 
220 	ff.find.AddHistory();
221 	ff.replace.AddHistory();
222 
223 	rf.Retrieve();
224 	d.find_list = ReadList(ff.find);
225 	d.replace_list = ReadList(ff.replace);
226 	editor.SetFindReplaceData(d);
227 
228 	if(c == IDOK) {
229 		SaveFile();
230 
231 		SetFFound(~ff.output);
232 
233 		FFound().HeaderTab(2).SetText("Source line");
234 		Renumber();
235 		ff.find.AddHistory();
236 		ff.files.AddHistory();
237 		ff.folder.AddHistory();
238 		ff.replace.AddHistory();
239 		Progress pi("Found %d files to search.");
240 		pi.AlignText(ALIGN_LEFT);
241 		Index<String> files;
242 		if(ff.workspace) {
243 			const Workspace& wspc = GetIdeWorkspace();
244 			for(int i = 0; i < wspc.GetCount(); i++)
245 				SearchForFiles(files, GetFileFolder(PackagePath(wspc[i])),
246 					           ~ff.files, ~ff.readonly, pi);
247 		}
248 		else
249 			SearchForFiles(files, NormalizePath(~~ff.folder, GetUppDir()), ~ff.files, ~ff.readonly, pi);
250 		if(!pi.Canceled()) {
251 			String pattern;
252 			RegExp rx, *regexp = NULL;
253 			if(ff.regexp) {
254 				rx.SetPattern(~ff.find);
255 				regexp = &rx;
256 				pattern = "dummy";
257 			}
258 			else
259 			if(ff.wildcards) {
260 				String q = ~ff.find;
261 				for(const char *s = q; *s; s++)
262 					if(*s == '\\') {
263 						s++;
264 						if(*s == '\0') break;
265 						pattern.Cat(*s);
266 					}
267 					else
268 					switch(*s) {
269 					case '*': pattern.Cat(WILDANY); break;
270 					case '?': pattern.Cat(WILDONE); break;
271 					case '%': pattern.Cat(WILDSPACE); break;
272 					case '#': pattern.Cat(WILDNUMBER); break;
273 					case '$': pattern.Cat(WILDID); break;
274 					default:  pattern.Cat(*s);
275 					}
276 			}
277 			else
278 				pattern = ~ff.find;
279 			pi.SetTotal(files.GetCount());
280 			FFound().Clear();
281 			pi.SetPos(0);
282 			int n = 0;
283 			for(int i = 0; i < files.GetCount(); i++) {
284 				pi.SetText(files[i]);
285 				if(pi.StepCanceled()) break;
286 				if(!IsNull(pattern)) {
287 					if(!SearchInFile(files[i], pattern, ff.wholeword, ff.ignorecase, n, regexp))
288 						break;
289 				}
290 				else {
291 					ErrorInfo f;
292 					f.file = files[i];
293 					f.lineno = 1;
294 					f.linepos = 0;
295 					f.kind = 0;
296 					f.message = files[i];
297 					FFound().Add(f.file, 1, f.message, RawToValue(f));
298 					FFound().Sync();
299 					n++;
300 				}
301 			}
302 			if(!IsNull(pattern))
303 				FFound().Add(Null, Null, AsString(n) + " occurrence(s) have been found.");
304 			else
305 				FFound().Add(Null, Null, AsString(n) + "  matching file(s) have been found.");
306 			FFound().HeaderTab(2).SetText(Format("Source line (%d)", FFound().GetCount()));
307 		}
308 	}
309 }
310 
311 
FindFileAll(const Vector<Tuple<int64,int>> & f)312 void Ide::FindFileAll(const Vector<Tuple<int64, int>>& f)
313 {
314 	SetFFound(ffoundi_next);
315 	FFound().Clear();
316 	for(auto pos : f) {
317 		editor.CachePos(pos.a);
318 		int linei = editor.GetLinePos64(pos.a);
319 		WString ln = editor.GetWLine(linei);
320 		AddFoundFile(editfile, linei + 1, ln.ToString(), lenAsUtf8(~ln, (int)pos.a), lenAsUtf8(~ln + pos.a, pos.b));
321 	}
322 	FFound().HeaderTab(2).SetText(Format("Source line (%d)", FFound().GetCount()));
323 	FFound().Add(Null, Null, AsString(f.GetCount()) + " occurrence(s) have been found.");
324 }
325 
FindString(bool back)326 void Ide::FindString(bool back)
327 {
328 	if(!editor.FindString(back))
329 		BeepMuteExclamation();
330 }
331 
TranslateString()332 void Ide::TranslateString()
333 {
334 	if(editor.IsReadOnly()) return;
335 	int l, h;
336 	if(editor.GetSelection(l, h)) {
337 		editor.Insert(l, "t_(");
338 		editor.Insert(h + 3, ")");
339 		editor.SetCursor(h + 4);
340 		FindString(false);
341 	}
342 }
343 
InsertWildcard(const char * s)344 void Ide::InsertWildcard(const char *s) {
345 	iwc = s;
346 }
347 
FindWildcard()348 void Ide::FindWildcard() {
349 	int l, h;
350 	ff.find.GetSelection(l, h);
351 	iwc = 0;
352 	FindWildcardMenu(THISBACK(InsertWildcard), ff.find.GetPushScreenRect().TopRight(), false, NULL, ff.regexp);
353 	if(iwc.GetCount()) {
354 		ff.wildcards = true;
355 		ff.find.SetFocus();
356 		ff.find.SetSelection(l, h);
357 		ff.find.RemoveSelection();
358 		ff.find.Insert(iwc);
359 	}
360 }
361 
FindSetStdDir(String n)362 void Ide::FindSetStdDir(String n)
363 {
364 	ff.folder <<= n;
365 }
366 
FindStdDir()367 void Ide::FindStdDir()
368 {
369 	String n = GetFileFolder(editfile);
370 	MenuBar menu;
371 	if(!IsNull(n))
372 		menu.Add(n, THISBACK1(FindSetStdDir, n));
373 	Vector<String> d = GetUppDirs();
374 	for(int i = 0; i < d.GetCount(); i++)
375 		menu.Add(d[i], THISBACK1(FindSetStdDir, d[i]));
376 	menu.Execute(&ff.folder, ff.folder.GetPushScreenRect().BottomLeft());
377 }
378 
FindFolder()379 void Ide::FindFolder()
380 {
381 	if(!sSD().ExecuteSelectDir()) return;
382 	ff.folder <<= ~sSD();
383 }
384 
SyncFindInFiles()385 void Ide::SyncFindInFiles()
386 {
387 	ff.samecase.Enable(ff.ignorecase);
388 }
389 
ConstructFindInFiles()390 void Ide::ConstructFindInFiles() {
391 	ff.find.AddButton().SetMonoImage(CtrlImg::smallright()).Tip("Wildcard") <<= THISBACK(FindWildcard);
392 	static const char *defs = "*.cpp *.h *.hpp *.c *.m *.C *.M *.cxx *.cc *.mm *.MM *.icpp *.sch *.lay *.rc";
393 	ff.files <<= String(defs);
394 	ff.files.AddList((String)defs);
395 	ff.files.AddList((String)"*.txt");
396 	ff.files.AddList((String)"*.*");
397 	ff.folder.AddButton().SetMonoImage(CtrlImg::smalldown()).Tip("Related folders") <<= THISBACK(FindStdDir);
398 	ff.folder.AddButton().SetMonoImage(CtrlImg::smallright()).Tip("Select folder") <<= THISBACK(FindFolder);
399 	editor.PutI(ff.find);
400 	editor.PutI(ff.replace);
401 	CtrlLayoutOKCancel(ff, "Find In Files");
402 	ff.ignorecase <<= THISBACK(SyncFindInFiles);
403 	ff.samecase <<= true;
404 	SyncFindInFiles();
405 }
406 
Sync()407 void FindInFilesDlg::Sync()
408 {
409 	replace.Enable((int)~style);
410 	bool b = !regexp;
411 	wildcards.Enable(b);
412 	ignorecase.Enable(b);
413 	wholeword.Enable(b);
414 	folder.Enable(!workspace);
415 	folder_lbl.Enable(!workspace);
416 }
417 
FindInFilesDlg()418 FindInFilesDlg::FindInFilesDlg()
419 {
420 	regexp <<= style <<= THISBACK(Sync);
421 	readonly.Add(Null, "All files");
422 	readonly.Add(0, "Writable");
423 	readonly.Add(1, "Read only");
424 	readonly <<= Null;
425 	workspace <<= THISBACK(Sync);
426 }
427 
Setup(bool replacing)428 void FindInFilesDlg::Setup(bool replacing)
429 {
430 	Title(replacing ? "Find and replace in files" : "Find in files");
431 	replace_lbl.Show(replacing);
432 	style.Show(replacing);
433 	replace_lbl2.Show(replacing);
434 	replace.Show(replacing);
435 	Size sz = GetLayoutSize();
436 	if(!replacing)
437 		sz.cy -= replace.GetRect().bottom - folder.GetRect().bottom;
438 	Rect r = GetRect();
439 	r.SetSize(sz);
440 	SetRect(r);
441 	ActiveFocus(find);
442 }
443 
Key(dword key,int count)444 bool FindInFilesDlg::Key(dword key, int count)
445 {
446 	if(key == K_CTRL_I) {
447 		if(find.HasFocus()) {
448 			find <<= itext;
449 			return true;
450 		}
451 		if(replace.HasFocus()) {
452 			replace <<= itext;
453 			return true;
454 		}
455 	}
456 	return TopWindow::Key(key, count);
457 }
458 
SetFFound(int ii)459 void Ide::SetFFound(int ii)
460 {
461 	ii = clamp(ii, 0, 2);
462 	SetBottom(BFINDINFILES1 + ii);
463 	ffoundi_next = (ii + 1) % 3;
464 }
465 
FFound()466 ArrayCtrl& Ide::FFound()
467 {
468 	int i = btabs.GetCursor() - BFINDINFILES1;
469 	return i >= 0 && i < 3 ? ffound[i] : ffound[0];
470 }
471 
CopyFound(bool all)472 void Ide::CopyFound(bool all)
473 {
474 	String txt;
475 	for(int i = 0; i < FFound().GetCount(); i++) {
476 		if(all)
477 			txt << FFound().Get(i, 0) << " (" << FFound().Get(i, 1) << "): ";
478 		String h = FFound().Get(i, 2);
479 		if(*h == '\1')
480 			h = Split(~h + 1, '\1', false).Top();
481 		txt << h << "\r\n";
482 	}
483 	WriteClipboardText(txt);
484 }
485 
FFoundMenu(Bar & bar)486 void Ide::FFoundMenu(Bar& bar)
487 {
488 	bar.Add("Copy text", THISBACK1(CopyFound, false));
489 	bar.Add("Copy all", THISBACK1(CopyFound, true));
490 }
491