#include "ide.h" struct GatherLinksIterator : RichText::Iterator { Index link; virtual bool operator()(int pos, const RichPara& para) { for(int i = 0; i < para.GetCount(); i++) { String l = para[i].format.link; int q = l.ReverseFind('#'); if(q >= 0) l.Trim(q); if(!IsNull(l)) link.FindAdd(l); } return false; } }; void GatherLinks(Index& link, String topic) { int q = topic.ReverseFind('$'); if(q >= 0) topic.Set(q, '_'); if(link.Find(topic) < 0) { Topic p = GetTopic(topic); if(IsNull(p.text)) return; link.Add(topic); RichText txt = ParseQTF(p.text); SyncTopicFile(txt, topic, ":ide:", p.title); GatherLinksIterator ti; txt.Iterate(ti); for(int i = 0; i < ti.link.GetCount(); i++) GatherLinks(link, ti.link[i]); } } Index TopicCtrl::idelink; void TopicCtrl::ScanDirForTpp(const char *dir, const String& rel, Index& donepackage, Index& lang_list) { TopicLink tl; for(FindFile pff(AppendFileName(dir, "*.*")); pff; pff.Next()) { if(pff.IsFolder() && *pff.GetName() != '.') { String pdir = AppendFileName(dir, pff.GetName()); tl.package = rel + pff.GetName(); if(donepackage.Find(tl.package) < 0) { donepackage.Add(tl.package); for(FindFile ff(AppendFileName(pdir, "*.tpp")); ff; ff.Next()) if(ff.IsFolder()) { tl.group = GetFileTitle(ff.GetName()); String dir = AppendFileName(pdir, ff.GetName()); for(FindFile ft(AppendFileName(dir, "*.tpp")); ft; ft.Next()) if(ft.IsFile()) { tl.topic = GetFileTitle(ft.GetName()); int q = tl.topic.ReverseFind('_'); String l; if(q >= 0) { l = ToUpper(tl.topic.Mid(q + 1)); lang_list.FindAdd(l); } map.GetAdd(tl.package).GetAdd(tl.group).FindAdd(tl.topic); } } } ScanDirForTpp(pdir, tl.package + '/', donepackage, lang_list); } } } int map_serial, topic_serial; void TopicCtrl::LoadMap() { map.Clear(); lang_list.Clear(); Vector upp = GetUppDirs(); Index donepackage, lang_list; for(int i = 0; i < upp.GetCount(); i++) ScanDirForTpp(upp[i], String(), donepackage, lang_list); Vector l = lang_list.PickKeys(); Sort(l); String lng = ~lang; lang.Clear(); lang.Add("All"); for(int i = 0; i < l.GetCount(); i++) lang.Add(l[i]); if(lng.GetCount() && lang.Find(lng)) lang <<= lng; else if(lang.Find("EN-US")) lang <<= "EN-US"; else if(lang.GetCount()) lang.SetIndex(0); } static String sTopicHome = "topic://ide/app/index_en-us"; static String s_idehelp = "TheIDE help"; static String s_usedpackages = "Used packages"; static String s_otherpackages = "Other packages"; static String s_documents = "Documents"; static String s_reference = "Reference"; static String s_implementation = "Implementation"; inline int sFindN(const String& s) { if(s == s_idehelp) return 0; if(s == s_usedpackages) return 1; if(s == s_otherpackages) return 2; if(s == s_documents) return 3; if(s == s_reference) return 4; if(s == s_implementation) return 5; return 6; } int TopicSortOrder(const Value& k1, const Value& v1, const Value& k2, const Value& v2) { String s1 = v1; String s2 = v2; bool bk1 = IsNull(k1); bool bk2 = IsNull(k2); int q = (int)bk1 - (int)bk2; if(q) return q; if(bk1) { int q = sFindN(s1) - sFindN(s2); if(q) return q; } return StdValueCompare(v1, v2); } void TopicCtrl::SyncDocTree() { if(map_serial != topic_serial) { LoadMap(); map_serial = topic_serial; } Vector ss = Split((String)~search, ' '); if(ss.GetCount() && !SyncRefsFinished) { SyncRefsShowProgress = true; return; } Vector sdx; for(int i = 0; i < ss.GetCount(); i++) sdx.Add(ToUtf8(ToLower(ToUtf32(ss[i])))); ClearTree(); if(idelink.GetCount() == 0) GatherLinks(idelink, sTopicHome); int ide = 0; bool idefirst = true; if(MatchTopicLink(sTopicHome, sdx)) { ide = AddTree(0, IdeImg::book(), sTopicHome, s_idehelp); idefirst = false; } for(int i = 0; i < idelink.GetCount(); i++) { if(idelink[i] != sTopicHome && MatchTopicLink(idelink[i], sdx)) { if(idefirst) { ide = AddTree(0, IdeImg::Package(), sTopicHome, s_idehelp); idefirst = false; } AddTree(ide, TopicImg::Topic(), idelink[i], GetTopic(idelink[i]).title); } } Index used; const Workspace& wspc = GetIdeWorkspace(); for(int i = 0; i < wspc.GetCount(); i++) used.Add(wspc[i]); int usid = 0; bool usedfirst = true; int otid = 0; bool otherfirst = true; String lng = ~lang; for(int i = 0; i < map.GetCount(); i++) { TopicLink tl; tl.package = map.GetKey(i); bool packagefirst = true; int pid = 0; VectorMap >& group = map[i]; for(int i = 0; i < group.GetCount(); i++) { tl.group = group.GetKey(i); if(all || tl.group == "src" || tl.group == "srcdoc" || tl.group == "srcimp") { String n = tl.group; if(n == "src") n = s_reference; if(n == "srcdoc") n = s_documents; if(n == "srcimp") n = s_implementation; int gid = 0; bool groupfirst = true; const Index& topic = group[i]; for(int i = 0; i < topic.GetCount(); i++) { tl.topic = topic[i]; int q = tl.topic.ReverseFind('_'); String l; if(q >= 0) l = ToUpper(tl.topic.Mid(q + 1)); String link = TopicLinkString(tl); if(idelink.Find(link) < 0 && MatchTopicLink(link, sdx) && (lng == "All" || lng == l)) { int pd; if(used.Find(tl.package) >= 0) { if(usedfirst) { usid = AddTree(0, IdeImg::book(), Null, s_usedpackages); usedfirst = false; } pd = usid; } else { if(otherfirst) { otid = AddTree(0, IdeImg::book(), Null, s_otherpackages); otherfirst = false; } pd = otid; } if(packagefirst) { pid = AddTree(pd, TopicImg::Package(), Null, tl.package); packagefirst = false; } if(groupfirst) { gid = AddTree(pid, Null, Null, n); groupfirst = false; } String p = TopicLinkString(tl); String t = GetTopicTitle(p); AddTree(gid, TopicImg::Topic(), p, t); } } } } } SortTree(0, TopicSortOrder); FinishTree(); if(sdx.GetCount()) { OpenDeep(); CurrentOrHome(); } } Vector GetTypeRefLinks(const String& t, String &label) { const char *tp[] = { "", "::struct", "::class", "::union", "::typedef", "::enum" }; Vector f; for(int i = 0; i < __countof(tp); i++) { label = t + tp[i]; f = GetRefLinks(label); if(f.GetCount()) break; } return f; } String recent_topic; Topic TopicCtrl::AcquireTopic(const String& t) { String current = GetCurrent(); String topic = t; if(*topic == '#') topic = current + topic; recent_topic = topic; internal = (byte)*topic < 32; if(topic[0] == ':' && topic[1] == ':') { String lbl; Vector link = GetTypeRefLinks(topic, lbl); if(link.GetCount() == 0) return Topic(); if(link.GetCount() == 1) topic = link[0]; else { WithSimpleListLayout dlg; CtrlLayoutOKCancel(dlg, "Choose one of more link targets"); dlg.list.AddKey(); dlg.list.AddColumn("Topic"); for(int i = 0; i < link.GetCount(); i++) dlg.list.Add(link[i], GetTopicTitle(link[i])); dlg.list.SetCursor(0); if(dlg.Run() != IDOK || !dlg.list.IsCursor()) return Topic(); topic = dlg.list.GetKey(); } if(lbl.GetCount()) topic << '#' << lbl; } if(topic.StartsWith("topic://ide/app/")) return GetTopic(topic); TopicLink tl = ParseTopicLink(topic); if(!IsNull(tl.package)) { int q = tl.topic.ReverseFind('$'); if(q >= 0) tl.topic.Set(q, '_'); Topic t = ReadTopic(LoadFile(AppendFileName( AppendFileName(PackageDirectory(tl.package), tl.group + ".tpp"), tl.topic + ".tpp"))); t.label = tl.label; tl.label.Clear(); t.link = TopicLinkString(tl); return t; } return Topic(); } struct HighlightWords : RichText::Iterator { Vector words; struct Pos : Moveable { int pos, len; }; Vector pos; int FindPattern(const String& x) { for(int i = 0; i < words.GetCount(); i++) if(x.StartsWith(words[i])) return words[i].GetCount(); return -1; } virtual bool operator()(int tpos, const RichPara& para) { WString text = para.GetText(); const wchar *s = text; for(;;) { while(!IsAlpha(*s) && !IsDigit(*s) && *s) s++; if(*s == '\0') break; String wb; const wchar *b = s; while(IsAlpha(*s) || IsDigit(*s)) wb.Cat(ToUpper(*s++)); int q = FindPattern(wb); if(q >= 0) { Pos& p = pos.Add(); p.pos = int(b - ~text) + tpos; p.len = q; } } return false; } }; void TopicCtrl::FinishText(RichText& text) { spos.Clear(); if(!showwords) return; Vector ss = Split((String)~search, ' '); if(ss.GetCount() == 0) return; HighlightWords hw; hw.words = pick(ss); text.Iterate(hw); RichText::FormatInfo fi; fi.charvalid = RichText::PAPER|RichText::INK; fi.paravalid = 0; fi.paper = SColorHighlight(); fi.ink = SColorHighlightText(); for(int i = 0; i < hw.pos.GetCount(); i++) { text.ApplyFormatInfo(hw.pos[i].pos, fi, hw.pos[i].len); spos.Add(hw.pos[i].pos); } } void TopicCtrl::OpenTopic() { WhenTopic(GetCurrentLink()); } void TopicCtrl::Search() { int l, h; ClearCurrentLink(); search.GetSelection(l, h); SyncDocTree(); SetBar(); search.SetFocus(); search.SetSelection(l, h); } void TopicCtrl::SearchWord(const String& s) { search <<= ToUpper(s); Search(); } void TopicCtrl::ShowWords() { showwords = !showwords; SetBar(); GoTo(GetCurrent()); } void TopicCtrl::All() { all = !all; SyncDocTree(); SetBar(); } void TopicCtrl::Lang() { SyncDocTree(); SetBar(); } bool TopicCtrl::Key(dword key, int count) { if(key == K_ENTER && search.HasFocus()) { Search(); return true; } return HelpWindow::Key(key, count); } void TopicCtrl::FocusSearch() { search.SetFocus(); } void TopicCtrl::Prev() { if(!Up(spos)) HelpWindow::Prev(); } void TopicCtrl::Next() { if(!Down(spos)) HelpWindow::Next(); } void TopicCtrl::BarEx(Bar& bar) { bar.Gap(); bar.Add(lang, HorzLayoutZoom(60)); bar.Add("All topics", IdeImg::HelpAll(), THISBACK(All)) .Check(all); bar.Gap(HorzLayoutZoom(30)); bar.Add(search, HorzLayoutZoom(300)); bar.Add(search.GetLength(), "Previous", IdeImg::GoPrev(), THISBACK(Prev)); bar.Add(search.GetLength(), "Next", IdeImg::GoNext(), THISBACK(Next)); bar.AddKey(K_CTRL_F, THISBACK(FocusSearch)); /* bar.Add("Highlight search keywords in topic", IdeImg::ShowWords(), THISBACK(ShowWords)) .Check(showwords);*/ bar.Add(!internal && GetCurrent().StartsWith("topic:"), "Display on full screen", IdeImg::show(), THISBACK(SShow)); bar.GapRight(); if(WhenTopic) { bar.Separator(); bar.Add(!internal && GetCurrent().StartsWith("topic:"), "Edit topic", TopicImg::Topic(), THISBACK(OpenTopic)); } } void TopicCtrl::Serialize(Stream& s) { int version = 3; s / version; if(version < 3) { WithDropChoice dummy; dummy.SerializeList(s); } if(version >= 1) s % showwords; if(version >= 2) s % all; } struct HelpDes : public IdeDesigner { TopicCtrl *topic; virtual String GetFileName() const { return HELPNAME; } virtual void Save() {} virtual void EditMenu(Bar& menu) {} virtual Ctrl& DesignerCtrl() { return *topic; } virtual void SetFocus() { topic->SetFocus(); } ~HelpDes() { topic->Remove(); } }; bool IsHelpName(const char *path) { return strcmp(path, HELPNAME) == 0; } struct HelpModule : public IdeModule { virtual String GetID() { return "HelpModule"; } virtual Image FileIcon(const char *path) { return IsHelpName(path) ? CtrlImg::help() : Null; } virtual IdeDesigner *CreateDesigner(Ide *ide, const char *path, byte cs) { if(IsHelpName(path)) { topic_serial++; GetRefLinks(""); ide->doc.SyncDocTree(); ide->doc.GoTo(Nvl(recent_topic, sTopicHome)); HelpDes *d = new HelpDes; d->topic = &ide->doc; return d; } return NULL; } virtual void Serialize(Stream& s) { s % recent_topic; } }; INITBLOCK { RegisterIdeModule(Single()); } int CharFilterTopicSearch(int c) { if(c == ' ') return c; c = CharFilterDefaultToUpperAscii(c); return IsAlNum(c) ? c : 0; } TopicCtrl::TopicCtrl() { showwords = true; all = false; lang <<= THISBACK(Lang); lang.Tip("Language"), search.NullText("Search", StdFont().Italic(), SColorDisabled()); search.Tip("Full text search"); search <<= THISBACK(Search); search.SetFilter(CharFilterTopicSearch); internal = true; } void Ide::ShowTopics() { if(designer && designer->GetFileName() == HELPNAME) { CycleFiles(); tabi = 0; return; } if(doc_serial != TopicEditor::GetSerial()) { GetRefLinks(""); doc.SyncDocTree(); doc.GoTo(sTopicHome); doc_serial = TopicEditor::GetSerial(); } EditFile(HELPNAME); } void Ide::OpenHelp(const char *link, bool editable) { TopicCtrl& help = CreateNewWindow(); help.SetRect(0, 0, clamp(DPI(1300), 400, GetWorkArea().GetWidth() - DPI(20)), clamp(DPI(768), 400, GetWorkArea().GetHeight() - DPI(20))); if(editable) help.WhenTopic = [=](const String& topic) { OpenATopic(topic); }; help.Icon(CtrlImg::help()); topic_serial++; GetRefLinks(""); help.SyncDocTree(); String l = link; if(!l.StartsWith("topic://")) l = "topic://ide/app/" + l + "_en-us"; help.GoTo(l); help.OpenMain(); } void Ide::ShowTopicsWin() { String link = sTopicHome; if(designer) link = Nvl(designer->HelpLink(), link); OpenHelp(link, true); } void Ide::SearchTopics() { String s = editor.GetWord(); GetRefLinks(""); doc.SyncDocTree(); doc.GoTo(sTopicHome); EditFile(HELPNAME); if(s.GetLength()) doc.SearchWord(s); } void Ide::RefreshBrowser() { editor.SyncNavigator(); doc.SyncDocTree(); } void Ide::ViewIdeLogFile() { OpenLog(GetIdeLogPath()); } void IdeHelpButton(Button& help, const String& link) { help.SetImage(CtrlImg::help()); help.SetLabel(""); help << [=] { Ide *ide = TheIde(); if(ide) ide->OpenHelp(link); }; }