1 #include "ide.h"
2 
3 struct GatherLinksIterator : RichText::Iterator {
4 	Index<String> link;
5 
operator ()GatherLinksIterator6 	virtual bool operator()(int pos, const RichPara& para)
7 	{
8 		for(int i = 0; i < para.GetCount(); i++) {
9 			String l = para[i].format.link;
10 			int q = l.ReverseFind('#');
11 			if(q >= 0)
12 				l.Trim(q);
13 			if(!IsNull(l))
14 				link.FindAdd(l);
15 		}
16 		return false;
17 	}
18 };
19 
GatherLinks(Index<String> & link,String topic)20 void GatherLinks(Index<String>& link, String topic)
21 {
22 	int q = topic.ReverseFind('$');
23 	if(q >= 0)
24 		topic.Set(q, '_');
25 	if(link.Find(topic) < 0) {
26 		Topic p = GetTopic(topic);
27 		if(IsNull(p.text))
28 			return;
29 		link.Add(topic);
30 		RichText txt = ParseQTF(p.text);
31 		SyncTopicFile(txt, topic, ":ide:", p.title);
32 		GatherLinksIterator ti;
33 		txt.Iterate(ti);
34 		for(int i = 0; i < ti.link.GetCount(); i++)
35 			GatherLinks(link, ti.link[i]);
36 	}
37 }
38 
39 Index<String> TopicCtrl::idelink;
40 
ScanDirForTpp(const char * dir,const String & rel,Index<String> & donepackage,Index<String> & lang_list)41 void TopicCtrl::ScanDirForTpp(const char *dir, const String& rel,
42                               Index<String>& donepackage, Index<String>& lang_list)
43 {
44 	TopicLink tl;
45 	for(FindFile pff(AppendFileName(dir, "*.*")); pff; pff.Next()) {
46 		if(pff.IsFolder() && *pff.GetName() != '.') {
47 			String pdir = AppendFileName(dir, pff.GetName());
48 			tl.package = rel + pff.GetName();
49 			if(donepackage.Find(tl.package) < 0) {
50 				donepackage.Add(tl.package);
51 				for(FindFile ff(AppendFileName(pdir, "*.tpp")); ff; ff.Next())
52 					if(ff.IsFolder()) {
53 						tl.group = GetFileTitle(ff.GetName());
54 						String dir = AppendFileName(pdir, ff.GetName());
55 						for(FindFile ft(AppendFileName(dir, "*.tpp")); ft; ft.Next())
56 							if(ft.IsFile()) {
57 								tl.topic = GetFileTitle(ft.GetName());
58 								int q = tl.topic.ReverseFind('_');
59 								String l;
60 								if(q >= 0) {
61 									l = ToUpper(tl.topic.Mid(q + 1));
62 									lang_list.FindAdd(l);
63 								}
64 								map.GetAdd(tl.package).GetAdd(tl.group).FindAdd(tl.topic);
65 							}
66 					}
67 			}
68 			ScanDirForTpp(pdir, tl.package + '/', donepackage, lang_list);
69 		}
70 	}
71 }
72 
73 int map_serial, topic_serial;
74 
LoadMap()75 void TopicCtrl::LoadMap()
76 {
77 	map.Clear();
78 	lang_list.Clear();
79 	Vector<String> upp = GetUppDirs();
80 	Index<String> donepackage, lang_list;
81 	for(int i = 0; i < upp.GetCount(); i++)
82 		ScanDirForTpp(upp[i], String(), donepackage, lang_list);
83 	Vector<String> l = lang_list.PickKeys();
84 	Sort(l);
85 	String lng = ~lang;
86 	lang.Clear();
87 	lang.Add("All");
88 	for(int i = 0; i < l.GetCount(); i++)
89 		lang.Add(l[i]);
90 	if(lng.GetCount() && lang.Find(lng))
91 		lang <<= lng;
92 	else
93 	if(lang.Find("EN-US"))
94 		lang <<= "EN-US";
95 	else
96 	if(lang.GetCount())
97 		lang.SetIndex(0);
98 }
99 
100 static String sTopicHome = "topic://ide/app/index_en-us";
101 static String s_idehelp = "TheIDE help";
102 static String s_usedpackages = "Used packages";
103 static String s_otherpackages = "Other packages";
104 static String s_documents = "Documents";
105 static String s_reference = "Reference";
106 static String s_implementation = "Implementation";
107 
sFindN(const String & s)108 inline int sFindN(const String& s)
109 {
110 	if(s == s_idehelp) return 0;
111 	if(s == s_usedpackages) return 1;
112 	if(s == s_otherpackages) return 2;
113 	if(s == s_documents) return 3;
114 	if(s == s_reference) return 4;
115 	if(s == s_implementation) return 5;
116 	return 6;
117 }
118 
TopicSortOrder(const Value & k1,const Value & v1,const Value & k2,const Value & v2)119 int TopicSortOrder(const Value& k1, const Value& v1, const Value& k2, const Value& v2)
120 {
121 	String s1 = v1;
122 	String s2 = v2;
123 	bool bk1 = IsNull(k1);
124 	bool bk2 = IsNull(k2);
125 	int q = (int)bk1 - (int)bk2;
126 	if(q) return q;
127 	if(bk1) {
128 		int q = sFindN(s1) - sFindN(s2);
129 		if(q) return q;
130 	}
131 	return StdValueCompare(v1, v2);
132 }
133 
SyncDocTree()134 void TopicCtrl::SyncDocTree()
135 {
136 	if(map_serial != topic_serial) {
137 		LoadMap();
138 		map_serial = topic_serial;
139 	}
140 
141 	Vector<String> ss = Split((String)~search, ' ');
142 
143 	if(ss.GetCount() && !SyncRefsFinished) {
144 		SyncRefsShowProgress = true;
145 		return;
146 	}
147 
148 	Vector<String> sdx;
149 	for(int i = 0; i < ss.GetCount(); i++)
150 		sdx.Add(ToUtf8(ToLower(FromUtf8(ss[i]))));
151 
152 	ClearTree();
153 
154 	if(idelink.GetCount() == 0)
155 		GatherLinks(idelink, sTopicHome);
156 	int ide = 0;
157 	bool idefirst = true;
158 	if(MatchTopicLink(sTopicHome, sdx)) {
159 		ide = AddTree(0, IdeImg::book(), sTopicHome, s_idehelp);
160 		idefirst = false;
161 	}
162 	for(int i = 0; i < idelink.GetCount(); i++) {
163 		if(idelink[i] != sTopicHome && MatchTopicLink(idelink[i], sdx)) {
164 			if(idefirst) {
165 				ide = AddTree(0, IdeImg::Package(), sTopicHome, s_idehelp);
166 				idefirst = false;
167 			}
168 			AddTree(ide, TopicImg::Topic(), idelink[i], GetTopic(idelink[i]).title);
169 		}
170 	}
171 
172 	Index<String> used;
173 	const Workspace& wspc = GetIdeWorkspace();
174 	for(int i = 0; i < wspc.GetCount(); i++)
175 		used.Add(wspc[i]);
176 
177 	int usid = 0;
178 	bool usedfirst = true;
179 	int otid = 0;
180 	bool otherfirst = true;
181 
182 	String lng = ~lang;
183 	for(int i = 0; i < map.GetCount(); i++) {
184 		TopicLink tl;
185 		tl.package = map.GetKey(i);
186 		bool packagefirst = true;
187 		int pid = 0;
188 		VectorMap<String, Index<String> >& group = map[i];
189 		for(int i = 0; i < group.GetCount(); i++) {
190 			tl.group = group.GetKey(i);
191 			if(all || tl.group == "src" || tl.group == "srcdoc" || tl.group == "srcimp") {
192 				String n = tl.group;
193 				if(n == "src")
194 					n = s_reference;
195 				if(n == "srcdoc")
196 					n = s_documents;
197 				if(n == "srcimp")
198 					n = s_implementation;
199 				int gid = 0;
200 				bool groupfirst = true;
201 				const Index<String>& topic = group[i];
202 				for(int i = 0; i < topic.GetCount(); i++) {
203 					tl.topic = topic[i];
204 					int q = tl.topic.ReverseFind('_');
205 					String l;
206 					if(q >= 0)
207 						l = ToUpper(tl.topic.Mid(q + 1));
208 					String link = TopicLinkString(tl);
209 					if(idelink.Find(link) < 0 && MatchTopicLink(link, sdx) && (lng == "All" || lng == l)) {
210 						int pd;
211 						if(used.Find(tl.package) >= 0) {
212 							if(usedfirst) {
213 								usid = AddTree(0, IdeImg::book(), Null, s_usedpackages);
214 								usedfirst = false;
215 							}
216 							pd = usid;
217 						}
218 						else {
219 							if(otherfirst) {
220 								otid = AddTree(0, IdeImg::book(), Null, s_otherpackages);
221 								otherfirst = false;
222 							}
223 							pd = otid;
224 						}
225 						if(packagefirst) {
226 							pid = AddTree(pd, TopicImg::Package(), Null, tl.package);
227 							packagefirst = false;
228 						}
229 						if(groupfirst) {
230 							gid = AddTree(pid, Null, Null, n);
231 							groupfirst = false;
232 						}
233 						String p = TopicLinkString(tl);
234 						String t = GetTopicTitle(p);
235 						AddTree(gid, TopicImg::Topic(), p, t);
236 					}
237 				}
238 			}
239 		}
240 	}
241 	SortTree(0, TopicSortOrder);
242 	FinishTree();
243 	if(sdx.GetCount()) {
244 		OpenDeep();
245 		CurrentOrHome();
246 	}
247 }
248 
GetTypeRefLinks(const String & t,String & label)249 Vector<String> GetTypeRefLinks(const String& t, String &label)
250 {
251 	const char *tp[] = { "", "::struct", "::class", "::union", "::typedef", "::enum" };
252 	Vector<String> f;
253 	for(int i = 0; i < __countof(tp); i++) {
254 		label = t + tp[i];
255 		f = GetRefLinks(label);
256 		if(f.GetCount())
257 			break;
258 	}
259 	return f;
260 }
261 
262 String recent_topic;
263 
AcquireTopic(const String & t)264 Topic TopicCtrl::AcquireTopic(const String& t)
265 {
266 	String current = GetCurrent();
267 	String topic = t;
268 	if(*topic == '#')
269 		topic = current + topic;
270 	recent_topic = topic;
271 	internal = (byte)*topic < 32;
272 	if(topic[0] == ':' && topic[1] == ':') {
273 		String lbl;
274 		Vector<String> link = GetTypeRefLinks(topic, lbl);
275 		if(link.GetCount() == 0)
276 			return Topic();
277 		if(link.GetCount() == 1)
278 			topic = link[0];
279 		else {
280 			WithSimpleListLayout<TopWindow> dlg;
281 			CtrlLayoutOKCancel(dlg, "Choose one of more link targets");
282 			dlg.list.AddKey();
283 			dlg.list.AddColumn("Topic");
284 			for(int i = 0; i < link.GetCount(); i++)
285 				dlg.list.Add(link[i], GetTopicTitle(link[i]));
286 			dlg.list.SetCursor(0);
287 			if(dlg.Run() != IDOK || !dlg.list.IsCursor())
288 				return Topic();
289 			topic = dlg.list.GetKey();
290 		}
291 		if(lbl.GetCount())
292 			topic << '#' << lbl;
293 	}
294 	TopicLink tl = ParseTopicLink(topic);
295 	if(!IsNull(tl.package)) {
296 		int q = tl.topic.ReverseFind('$');
297 		if(q >= 0)
298 			tl.topic.Set(q, '_');
299 		Topic t;
300 		if(tl.topic.StartsWith("topic://ide/app/"))
301 			t = GetTopic(tl.topic);
302 		else
303 			t = ReadTopic(LoadFile(AppendFileName(
304 							AppendFileName(PackageDirectory(tl.package), tl.group + ".tpp"),
305 							tl.topic + ".tpp")));
306 		t.label = tl.label;
307 		tl.label.Clear();
308 		t.link = TopicLinkString(tl);
309 		return t;
310 	}
311 	return Topic();
312 }
313 
314 
315 
316 struct HighlightWords : RichText::Iterator {
317 	Vector<String> words;
318 	struct Pos : Moveable<Pos> { int pos, len; };
319 	Vector<Pos>   pos;
320 
FindPatternHighlightWords321 	int FindPattern(const String& x) {
322 		for(int i = 0; i < words.GetCount(); i++)
323 			if(x.StartsWith(words[i]))
324 				return words[i].GetCount();
325 		return -1;
326 	}
327 
operator ()HighlightWords328 	virtual bool operator()(int tpos, const RichPara& para) {
329 		WString text = para.GetText();
330 		const wchar *s = text;
331 		for(;;) {
332 			while(!IsAlpha(*s) && !IsDigit(*s) && *s)
333 				s++;
334 			if(*s == '\0')
335 				break;
336 			String wb;
337 			const wchar *b = s;
338 			while(IsAlpha(*s) || IsDigit(*s))
339 				wb.Cat(ToUpper(*s++));
340 			int q = FindPattern(wb);
341 			if(q >= 0) {
342 				Pos& p = pos.Add();
343 				p.pos = int(b - ~text) + tpos;
344 				p.len = q;
345 			}
346 		}
347 		return false;
348 	}
349 };
350 
FinishText(RichText & text)351 void TopicCtrl::FinishText(RichText& text)
352 {
353 	spos.Clear();
354 	if(!showwords)
355 		return;
356 	Vector<String> ss = Split((String)~search, ' ');
357 
358 	if(ss.GetCount() == 0)
359 		return;
360 	HighlightWords hw;
361 	hw.words = pick(ss);
362 	text.Iterate(hw);
363 	RichText::FormatInfo fi;
364 	fi.charvalid = RichText::PAPER|RichText::INK;
365 	fi.paravalid = 0;
366 	fi.paper = SColorHighlight();
367 	fi.ink = SColorHighlightText();
368 	for(int i = 0; i < hw.pos.GetCount(); i++) {
369 		text.ApplyFormatInfo(hw.pos[i].pos, fi, hw.pos[i].len);
370 		spos.Add(hw.pos[i].pos);
371 	}
372 }
373 
OpenTopic()374 void TopicCtrl::OpenTopic()
375 {
376 	WhenTopic();
377 }
378 
Search()379 void TopicCtrl::Search()
380 {
381 	int l, h;
382 	ClearCurrentLink();
383 	search.GetSelection(l, h);
384 	SyncDocTree();
385 	SetBar();
386 	search.SetFocus();
387 	search.SetSelection(l, h);
388 }
389 
SearchWord(const String & s)390 void TopicCtrl::SearchWord(const String& s)
391 {
392 	search <<= ToUpper(s);
393 	Search();
394 }
395 
ShowWords()396 void TopicCtrl::ShowWords()
397 {
398 	showwords = !showwords;
399 	SetBar();
400 	GoTo(GetCurrent());
401 }
402 
All()403 void TopicCtrl::All()
404 {
405 	all = !all;
406 	SyncDocTree();
407 	SetBar();
408 }
409 
Lang()410 void TopicCtrl::Lang()
411 {
412 	SyncDocTree();
413 	SetBar();
414 }
415 
Key(dword key,int count)416 bool TopicCtrl::Key(dword key, int count)
417 {
418 	if(key == K_ENTER && search.HasFocus()) {
419 		Search();
420 		return true;
421 	}
422 	return HelpWindow::Key(key, count);
423 }
424 
FocusSearch()425 void TopicCtrl::FocusSearch()
426 {
427 	search.SetFocus();
428 }
429 
Prev()430 void TopicCtrl::Prev()
431 {
432 	if(!Up(spos))
433 		HelpWindow::Prev();
434 }
435 
Next()436 void TopicCtrl::Next()
437 {
438 	if(!Down(spos))
439 		HelpWindow::Next();
440 }
441 
BarEx(Bar & bar)442 void  TopicCtrl::BarEx(Bar& bar)
443 {
444 	bar.Gap();
445 	bar.Add(lang, HorzLayoutZoom(60));
446 	bar.Add("All topics", IdeImg::HelpAll(), THISBACK(All))
447 	   .Check(all);
448 	bar.Gap(HorzLayoutZoom(30));
449 	bar.Add(search, HorzLayoutZoom(300));
450 	bar.Add(search.GetLength(), "Previous", IdeImg::GoPrev(), THISBACK(Prev));
451 	bar.Add(search.GetLength(), "Next", IdeImg::GoNext(), THISBACK(Next));
452 
453 	bar.AddKey(K_CTRL_F, THISBACK(FocusSearch));
454 /*	bar.Add("Highlight search keywords in topic", IdeImg::ShowWords(), THISBACK(ShowWords))
455 	   .Check(showwords);*/
456 	bar.Add(!internal && GetCurrent().StartsWith("topic:"), "Display on full screen",
457 	        IdeImg::show(), THISBACK(SShow));
458 	bar.GapRight();
459 	bar.Separator();
460 	bar.Add(!internal && GetCurrent().StartsWith("topic:"), "Edit topic",
461 	        TopicImg::Topic(), THISBACK(OpenTopic));
462 }
463 
Serialize(Stream & s)464 void TopicCtrl::Serialize(Stream& s)
465 {
466 	int version = 3;
467 	s / version;
468 	if(version < 3) {
469 		WithDropChoice<EditString> dummy;
470 		dummy.SerializeList(s);
471 	}
472 	if(version >= 1)
473 		s % showwords;
474 	if(version >= 2)
475 		s % all;
476 }
477 
478 struct HelpDes : public IdeDesigner {
479 	TopicCtrl *topic;
480 
GetFileNameHelpDes481 	virtual String GetFileName() const              { return HELPNAME; }
SaveHelpDes482 	virtual void   Save()                           {}
EditMenuHelpDes483 	virtual void   EditMenu(Bar& menu)              {}
DesignerCtrlHelpDes484 	virtual Ctrl&  DesignerCtrl()                   { return *topic; }
SetFocusHelpDes485 	virtual void   SetFocus()                       { topic->SetFocus(); }
486 
~HelpDesHelpDes487 	~HelpDes()                                      { topic->Remove(); }
488 };
489 
IsHelpName(const char * path)490 bool IsHelpName(const char *path)
491 {
492 	return strcmp(path, HELPNAME) == 0;
493 }
494 
495 struct HelpModule : public IdeModule {
GetIDHelpModule496 	virtual String       GetID() { return "HelpModule"; }
FileIconHelpModule497 	virtual Image FileIcon(const char *path) {
498 		return IsHelpName(path) ? IdeImg::help() : Null;
499 	}
CreateDesignerHelpModule500 	virtual IdeDesigner *CreateDesigner(Ide *ide, const char *path, byte cs) {
501 		if(IsHelpName(path)) {
502 			topic_serial++;
503 			GetRefLinks("");
504 			ide->doc.SyncDocTree();
505 			ide->doc.GoTo(Nvl(recent_topic, sTopicHome));
506 			HelpDes *d = new HelpDes;
507 			d->topic = &ide->doc;
508 			return d;
509 		}
510 		return NULL;
511 	}
SerializeHelpModule512 	virtual void Serialize(Stream& s) {
513 		s % recent_topic;
514 	}
515 };
516 
517 INITBLOCK {
518 	RegisterIdeModule(Single<HelpModule>());
519 }
520 
521 int CharFilterTopicSearch(int c)
522 {
523 	if(c == ' ') return c;
524 	c = CharFilterDefaultToUpperAscii(c);
525 	return IsAlNum(c) ? c : 0;
526 }
527 
TopicCtrl()528 TopicCtrl::TopicCtrl()
529 {
530 	showwords = true;
531 	all = false;
532 	lang <<= THISBACK(Lang);
533 	lang.Tip("Language"),
534 	search.NullText("Search", StdFont().Italic(), SColorDisabled());
535 	search.Tip("Full text search");
536 	search <<= THISBACK(Search);
537 	search.SetFilter(CharFilterTopicSearch);
538 	internal = true;
539 }
540 
ShowTopics()541 void Ide::ShowTopics()
542 {
543 	if(designer && designer->GetFileName() == HELPNAME) {
544 		CycleFiles();
545 		tabi = 0;
546 		return;
547 	}
548 	if(doc_serial != TopicEditor::GetSerial()) {
549 		GetRefLinks("");
550 		doc.SyncDocTree();
551 		doc.GoTo(sTopicHome);
552 		doc_serial = TopicEditor::GetSerial();
553 	}
554 	EditFile(HELPNAME);
555 }
556 
ShowTopicsWin()557 void Ide::ShowTopicsWin()
558 {
559 	windoc.Icon(IdeImg::help_win(), IdeImg::help_win_large());
560 	if(windoc.IsOpen())
561 		windoc.SetForeground();
562 	else {
563 		windoc.SyncDocTree();
564 		windoc.GoTo(sTopicHome);
565 		windoc.OpenMain();
566 	}
567 }
568 
SearchTopics()569 void Ide::SearchTopics()
570 {
571 	String s = editor.GetWord();
572 	GetRefLinks("");
573 	doc.SyncDocTree();
574 	doc.GoTo(sTopicHome);
575 	EditFile(HELPNAME);
576 	if(s.GetLength())
577 		doc.SearchWord(s);
578 }
579 
RefreshBrowser()580 void Ide::RefreshBrowser()
581 {
582 	editor.SyncNavigator();
583 	doc.SyncDocTree();
584 }
585 
ViewIdeLogFile()586 void Ide::ViewIdeLogFile()
587 {
588 	OpenLog(GetIdeLogPath());
589 }
590