diff --git a/Ide/Code/FindSymbol.cpp b/Ide/Code/FindSymbol.cpp --- a/Ide/Code/FindSymbol.cpp +++ b/Ide/Code/FindSymbol.cpp @@ -1,683 +1,683 @@ #include #include #include "lgi/common/Lgi.h" #include "lgi/common/List.h" #include "lgi/common/Token.h" #include "lgi/common/EventTargetThread.h" #include "lgi/common/TextFile.h" #include "LgiIde.h" #include "FindSymbol.h" #include "ParserCommon.h" #if 1 #include "lgi/common/ParseCpp.h" #endif #include "resdefs.h" #define MSG_TIME_MS 1000 #define DEBUG_FIND_SYMBOL 0 #define DEBUG_NO_THREAD 1 // #define DEBUG_FILE "RichTextEdit.h" int SYM_FILE_SENT = 0; class FindSymbolDlg : public LDialog { // AppWnd *App; LList *Lst; FindSymbolSystem *Sys; public: FindSymResult Result; FindSymbolDlg(LViewI *parent, FindSymbolSystem *sys); ~FindSymbolDlg(); int OnNotify(LViewI *v, LNotification n); void OnCreate(); bool OnViewKey(LView *v, LKey &k); LMessage::Result OnEvent(LMessage *m); }; int ScoreCmp(FindSymResult **a, FindSymResult **b) { return (*b)->Score - (*a)->Score; } #define USE_HASH 1 struct FindSymbolSystemPriv : public LEventTargetThread { struct FileSyms { LString Path; int Platforms; LString::Array *Inc; LArray Defs; bool IsSource; bool IsHeader; bool IsPython; bool IsJavascript; bool Parse(LAutoWString Source, bool Debug) { IsSource = false; IsHeader = false; IsPython = false; IsJavascript = false; Defs.Length(0); bool Status = false; char *Ext = LGetExtension(Path); if (Ext) { IsSource = !_stricmp(Ext, "c") || !_stricmp(Ext, "cpp") || !_stricmp(Ext, "cxx"); IsHeader = !_stricmp(Ext, "h") || !_stricmp(Ext, "hpp") || !_stricmp(Ext, "hxx"); IsPython = !_stricmp(Ext, "py"); IsJavascript = !_stricmp(Ext, "js"); if (IsSource || IsHeader) Status = BuildCppDefnList(Path, Source, Defs, DefnNone, Debug); else if (IsJavascript) Status = BuildJsDefnList(Path, Source, Defs, DefnNone, Debug); else if (IsPython) Status = BuildPyDefnList(Path, Source, Defs, DefnNone, Debug); } return Status; } }; int hApp; int MissingFiles = 0; LArray IncPaths; #if USE_HASH LHashTbl, FileSyms*> Files; #else LArray Files; #endif int Tasks = 0; uint64 MsgTs = 0; bool DoingProgress = false; FindSymbolSystemPriv(int appSinkHnd) : LEventTargetThread("FindSymbolSystemPriv"), hApp(appSinkHnd) { } ~FindSymbolSystemPriv() { // End the thread... EndThread(); // Clean up mem Files.DeleteObjects(); } void Log(const char *Fmt, ...) { va_list Arg; va_start(Arg, Fmt); LString s; s.Printf(Arg, Fmt); va_end(Arg); if (s.Length()) PostThreadEvent(hApp, M_APPEND_TEXT, (LMessage::Param)NewStr(s), AppWnd::BuildTab); } #if !USE_HASH int GetFileIndex(LString &Path) { for (unsigned i=0; iPath) return i; } return -1; } #endif bool AddFile(LString Path, int Platforms) { bool Debug = false; #ifdef DEBUG_FILE if ((Debug = Path.Find(DEBUG_FILE) >= 0)) printf("%s:%i - AddFile(%s)\n", _FL, Path.Get()); #endif // Already added? #if USE_HASH FileSyms *f = Files.Find(Path); if (f) { if (Platforms && f->Platforms == 0) f->Platforms = Platforms; return true; } #else int Idx = GetFileIndex(Path); if (Idx >= 0) { if (Platforms && Files[Idx]->Platforms == 0) Files[Idx]->Platforms = Platforms; return true; } FileSyms *f; #endif if (!LFileExists(Path)) { Log("Missing '%s'\n", Path.Get()); MissingFiles++; return false; } LAssert(!LIsRelativePath(Path)); // Setup the file sym data... f = new FileSyms; if (!f) return false; f->Path = Path; f->Platforms = Platforms; f->Inc = IncPaths.Length() ? IncPaths.Last() : NULL; #if USE_HASH Files.Add(Path, f); #else Files.Add(f); #endif // Parse for headers... LTextFile Tf; if (!Tf.Open(Path, O_READ) || Tf.GetSize() < 4) { LgiTrace("%s:%i - Error: LTextFile.Open(%s) failed.\n", _FL, Path.Get()); return false; } LAutoString Source = Tf.Read(); LArray Headers; LArray EmptyInc; if (BuildHeaderList(Source, Headers, f->Inc ? *f->Inc : EmptyInc, false)) { for (auto h: Headers) AddFile(h, 0); } Headers.DeleteArrays(); // Parse for symbols... #ifdef DEBUG_FILE if (Debug) printf("%s:%i - About to parse '%s'.\n", _FL, f->Path.Get()); #endif return f->Parse(LAutoWString(Utf8ToWide(Source)), Debug); } bool ReparseFile(LString Path) { #if USE_HASH FileSyms *f = Files.Find(Path); int Platform = f ? f->Platforms : 0; #else int Idx = GetFileIndex(Path); int Platform = Idx >= 0 ? Files[Idx]->Platforms : 0; #endif if (!RemoveFile(Path)) return false; return AddFile(Path, Platform); } bool RemoveFile(LString Path) { #if USE_HASH FileSyms *f = Files.Find(Path); if (!f) return false; Files.Delete(Path); delete f; #else int Idx = GetFileIndex(Path); if (Idx < 0) return false; delete Files[Idx]; Files.DeleteAt(Idx); #endif return true; } LMessage::Result OnEvent(LMessage *Msg) { if (IsCancelled()) return -1; switch (Msg->Msg()) { case M_FIND_SYM_REQUEST: { LAutoPtr Req((FindSymRequest*)Msg->A()); - int Platforms = Msg->B(); + auto Platforms = Msg->B(); if (Req && Req->SinkHnd >= 0) { LString::Array p = Req->Str.SplitDelimit(" \t"); if (p.Length() == 0) break; LArray ClassMatches; LArray HdrMatches; LArray SrcMatches; // For each file... #if USE_HASH for (auto it : Files) { FileSyms *fs = it.value; #else for (unsigned f=0; fPath.Find(DEBUG_FILE) >= 0; if (Debug) printf("%s:%i - Searching '%s' with %i syms...\n", _FL, fs->Path.Get(), (int)fs->Defs.Length()); #endif // Check platforms... if ((fs->Platforms & Platforms) == 0) { #ifdef DEBUG_FILE printf("%s:%i - '%s' doesn't match platform: %x %x\n", _FL, fs->Path.Get(), fs->Platforms, Platforms); #endif continue; } // For each symbol... for (unsigned i=0; iDefs.Length(); i++) { DefnInfo &Def = fs->Defs[i]; #ifdef DEBUG_FILE if (Debug) printf("%s:%i - '%s'\n", _FL, Def.Name.Get()); #endif // For each search term... bool Match = true; int ScoreSum = 0; for (unsigned n=0; nScore = ScoreSum; r->File = Def.File.Get(); r->Symbol = Def.Name.Get(); r->Line = Def.Line; if (Def.Type == DefnClass) ClassMatches.Add(r); else if (fs->IsHeader) HdrMatches.Add(r); else SrcMatches.Add(r); } } } } } ClassMatches.Sort(ScoreCmp); Req->Results.Add(ClassMatches); Req->Results.Add(HdrMatches); Req->Results.Add(SrcMatches); int Hnd = Req->SinkHnd; PostObject(Hnd, M_FIND_SYM_REQUEST, Req); } break; } case M_FIND_SYM_FILE: { LAutoPtr Params((FindSymbolSystem::SymFileParams*)Msg->A()); if (Params) { LString::Array Mime = LGetFileMimeType(Params->File).Split("/"); if (!Mime[0].Equals("image")) { if (Params->Action == FindSymbolSystem::FileAdd) AddFile(Params->File, Params->Platforms); else if (Params->Action == FindSymbolSystem::FileRemove) RemoveFile(Params->File); else if (Params->Action == FindSymbolSystem::FileReparse) ReparseFile(Params->File); } } SYM_FILE_SENT--; break; } case M_FIND_SYM_INC_PATHS: { LAutoPtr Paths((LString::Array*)Msg->A()); if (Paths) IncPaths.Add(Paths.Release()); break; } default: { LAssert(!"Implement handler for message."); break; } } auto Now = LCurrentTime(); // printf("Msg->Msg()=%i " LPrintfInt64 " %i\n", Msg->Msg(), MsgTs, (int)GetQueueSize()); if (Now - MsgTs > MSG_TIME_MS) { MsgTs = Now; DoingProgress = true; int Remaining = (int)(Tasks - GetQueueSize()); if (Remaining > 0) Log("FindSym: %i of %i (%.1f%%)\n", Remaining, Tasks, (double)Remaining * 100.0 / MAX(Tasks, 1)); } else if (GetQueueSize() == 0 && MsgTs) { if (DoingProgress) { Log("FindSym: Done.\n"); DoingProgress = false; } if (MissingFiles > 0) { Log("(%i files are missing)\n", MissingFiles); } MsgTs = 0; Tasks = 0; } return 0; } }; int AlphaCmp(LListItem *a, LListItem *b, NativeInt d) { return stricmp(a->GetText(0), b->GetText(0)); } FindSymbolDlg::FindSymbolDlg(LViewI *parent, FindSymbolSystem *sys) { Lst = NULL; Sys = sys; SetParent(parent); if (LoadFromResource(IDD_FIND_SYMBOL)) { MoveToCenter(); LViewI *f; if (GetViewById(IDC_STR, f)) f->Focus(true); GetViewById(IDC_RESULTS, Lst); } } FindSymbolDlg::~FindSymbolDlg() { } void FindSymbolDlg::OnCreate() { RegisterHook(this, LKeyEvents); } bool FindSymbolDlg::OnViewKey(LView *v, LKey &k) { switch (k.vkey) { case LK_UP: case LK_DOWN: case LK_PAGEDOWN: case LK_PAGEUP: { return Lst->OnKey(k); break; } } return false; } LMessage::Result FindSymbolDlg::OnEvent(LMessage *m) { switch (m->Msg()) { case M_FIND_SYM_REQUEST: { LAutoPtr Req((FindSymRequest*)m->A()); if (Req) { LString Str = GetCtrlName(IDC_STR); if (Str == Req->Str) { Lst->Empty(); List Ls; LString s; int CommonPathLen = 0; for (unsigned i=0; iResults.Length(); i++) { FindSymResult *r = Req->Results[i]; if (i) { char *a = s.Get(); char *a_end = strrchr(a, DIR_CHAR); char *b = r->File.Get(); char *b_end = strrchr(b, DIR_CHAR); int Common = 0; while ( *a && a <= a_end && *b && b <= b_end && ToLower(*a) == ToLower(*b)) { Common++; a++; b++; } if (i == 1) CommonPathLen = Common; else CommonPathLen = MIN(CommonPathLen, Common); } else s = r->File; } for (unsigned i=0; iResults.Length(); i++) { FindSymResult *r = Req->Results[i]; LListItem *it = new LListItem; if (it) { LString Ln; Ln.Printf("%i", r->Line); it->SetText(r->File.Get() + CommonPathLen, 0); it->SetText(Ln, 1); it->SetText(r->Symbol, 2); Ls.Insert(it); } } Lst->Insert(Ls); Lst->ResizeColumnsToContent(); } } break; } } return LDialog::OnEvent(m); } int FindSymbolDlg::OnNotify(LViewI *v, LNotification n) { switch (v->GetId()) { case IDC_STR: { if (n.Type != LNotifyReturnKey) { auto Str = v->Name(); if (Str && strlen(Str) > 2) { // Create a search Sys->Search(AddDispatch(), Str, true); } } break; } case IDC_RESULTS: { if (n.Type == LNotifyItemDoubleClick) { // Fall throu } else break; } case IDOK: { if (Lst) { LListItem *i = Lst->GetSelected(); if (i) { Result.File = i->GetText(0); Result.Line = atoi(i->GetText(1)); } } EndModal(true); break; } case IDCANCEL: { EndModal(false); break; } } return LDialog::OnNotify(v, n); } /////////////////////////////////////////////////////////////////////////// FindSymbolSystem::FindSymbolSystem(int AppHnd) { d = new FindSymbolSystemPriv(AppHnd); } FindSymbolSystem::~FindSymbolSystem() { delete d; } void FindSymbolSystem::OpenSearchDlg(LViewI *Parent, std::function Callback) { FindSymbolDlg *Dlg = new FindSymbolDlg(Parent, this); Dlg->DoModal([Dlg, Callback](auto d, auto code) { if (Callback) Callback(Dlg->Result); delete Dlg; }); } bool FindSymbolSystem::SetIncludePaths(LString::Array &Paths) { LString::Array *a = new LString::Array; if (!a) return false; *a = Paths; return d->PostEvent(M_FIND_SYM_INC_PATHS, (LMessage::Param)a); } bool FindSymbolSystem::OnFile(const char *Path, SymAction Action, int Platforms) { if (d->Tasks == 0) d->MsgTs = LCurrentTime(); d->Tasks++; LAutoPtr Params(new FindSymbolSystem::SymFileParams); Params->File = Path; Params->Action = Action; Params->Platforms = Platforms; if (d->PostObject(d->GetHandle(), M_FIND_SYM_FILE, Params)) { SYM_FILE_SENT++; } return false; } void FindSymbolSystem::Search(int ResultsSinkHnd, const char *SearchStr, int Platforms) { FindSymRequest *Req = new FindSymRequest(ResultsSinkHnd); if (Req) { Req->Str = SearchStr; d->PostEvent(M_FIND_SYM_REQUEST, (LMessage::Param)Req, (LMessage::Param)Platforms); } } diff --git a/Ide/Code/IdeDoc.cpp b/Ide/Code/IdeDoc.cpp --- a/Ide/Code/IdeDoc.cpp +++ b/Ide/Code/IdeDoc.cpp @@ -1,2127 +1,2126 @@ #include #include #include "lgi/common/Lgi.h" #include "lgi/common/Token.h" #include "lgi/common/Net.h" #include "lgi/common/ClipBoard.h" #include "lgi/common/DisplayString.h" #include "lgi/common/ScrollBar.h" #include "lgi/common/LgiRes.h" #include "lgi/common/Edit.h" #include "lgi/common/List.h" #include "lgi/common/PopupList.h" #include "lgi/common/TableLayout.h" #include "lgi/common/EventTargetThread.h" #include "lgi/common/CheckBox.h" #include "lgi/common/Http.h" #include "lgi/common/Menu.h" #include "lgi/common/FileSelect.h" #include "LgiIde.h" #include "ProjectNode.h" #include "SpaceTabConv.h" #include "DocEdit.h" #include "IdeDocPrivate.h" const char *Untitled = "[untitled]"; // static const char *White = " \r\t\n"; #define USE_OLD_FIND_DEFN 1 #define POPUP_WIDTH 700 // px #define POPUP_HEIGHT 350 // px enum { IDM_COPY_FILE = 1100, IDM_COPY_PATH, IDM_BROWSE }; int FileNameSorter(char **a, char **b) { char *A = strrchr(*a, DIR_CHAR); char *B = strrchr(*b, DIR_CHAR); return stricmp(A?A:*a, B?B:*b); } EditTray::EditTray(LTextView3 *ctrl, IdeDoc *doc) { Ctrl = ctrl; Doc = doc; Line = Col = 0; FuncBtn.ZOff(-1, -1); SymBtn.ZOff(-1, -1); int Ht = LSysFont->GetHeight() + 6; AddView(FileSearch = new LEdit(IDC_FILE_SEARCH, 0, 0, EDIT_CTRL_WIDTH, Ht)); AddView(FuncSearch = new LEdit(IDC_METHOD_SEARCH, 0, 0, EDIT_CTRL_WIDTH, Ht)); AddView(SymSearch = new LEdit(IDC_SYMBOL_SEARCH, 0, 0, EDIT_CTRL_WIDTH, Ht)); } EditTray::~EditTray() { } void EditTray::GotoSearch(int CtrlId, char *InitialText) { if (FileSearch && FileSearch->GetId() == CtrlId) { FileSearch->Name(InitialText); FileSearch->Focus(true); if (InitialText) FileSearch->SendNotify(LNotifyDocChanged); } if (FuncSearch && FuncSearch->GetId() == CtrlId) { FuncSearch->Name(InitialText); FuncSearch->Focus(true); if (InitialText) FuncSearch->SendNotify(LNotifyDocChanged); } if (SymSearch && SymSearch->GetId() == CtrlId) { SymSearch->Name(InitialText); SymSearch->Focus(true); if (InitialText) SymSearch->SendNotify(LNotifyDocChanged); } } void EditTray::OnCreate() { AttachChildren(); } int MeasureText(const char *s) { LDisplayString Ds(LSysFont, s); return Ds.X(); } #define HEADER_BTN_LABEL "h" #define FUNCTION_BTN_LABEL "{ }" #define SYMBOL_BTN_LABEL "s" void EditTray::OnPosChange() { LLayoutRect c(this, 2); c.Left(FileBtn, MeasureText(HEADER_BTN_LABEL)+10); if (FileSearch) c.Left(FileSearch, EDIT_CTRL_WIDTH); c.x1 += 8; c.Left(FuncBtn, MeasureText(FUNCTION_BTN_LABEL)+10); if (FuncSearch) c.Left(FuncSearch, EDIT_CTRL_WIDTH); c.x1 += 8; c.Left(SymBtn, MeasureText(SYMBOL_BTN_LABEL)+10); if (SymSearch) c.Left(SymSearch, EDIT_CTRL_WIDTH); c.x1 += 8; c.Remaining(TextMsg); } void EditTray::OnPaint(LSurface *pDC) { LRect c = GetClient(); pDC->Colour(L_MED); pDC->Rectangle(); LSysFont->Colour(L_TEXT, L_MED); LSysFont->Transparent(true); LString s; s.Printf("Cursor: %i,%i", Col, Line + 1); { LDisplayString ds(LSysFont, s); ds.Draw(pDC, TextMsg.x1, TextMsg.y1 + ((c.Y()-TextMsg.Y())/2), &TextMsg); } LRect f = FileBtn; LThinBorder(pDC, f, DefaultRaisedEdge); { LDisplayString ds(LSysFont, HEADER_BTN_LABEL); ds.Draw(pDC, f.x1 + 4, f.y1); } f = FuncBtn; LThinBorder(pDC, f, DefaultRaisedEdge); { LDisplayString ds(LSysFont, FUNCTION_BTN_LABEL); ds.Draw(pDC, f.x1 + 4, f.y1); } f = SymBtn; LThinBorder(pDC, f, DefaultRaisedEdge); { LDisplayString ds(LSysFont, SYMBOL_BTN_LABEL); ds.Draw(pDC, f.x1 + 4, f.y1); } } bool EditTray::Pour(LRegion &r) { LRect *c = FindLargest(r); if (c) { LRect n = *c; SetPos(n); return true; } return false; } void EditTray::OnHeaderList(LMouse &m) { // Header list button LArray Paths; if (Doc->BuildIncludePaths(Paths, PlatformCurrent, false)) { LArray Headers; if (Doc->BuildHeaderList(Ctrl->NameW(), Headers, Paths)) { // Sort them.. Headers.Sort(FileNameSorter); LSubMenu *s = new LSubMenu; if (s) { // Construct the menu LHashTbl, int> Map; int DisplayLines = GdcD->Y() / LSysFont->GetHeight(); if (Headers.Length() > (0.9 * DisplayLines)) { LArray Letters[26]; LArray Other; for (int i=0; i 1) { char *First = LGetLeaf(Letters[i][0]); char *Last = LGetLeaf(Letters[i].Last()); char Title[256]; sprintf_s(Title, sizeof(Title), "%s - %s", First, Last); LSubMenu *sub = s->AppendSub(Title); if (sub) { for (int n=0; n 0); sub->AppendItem(LGetLeaf(h), Id, true); } } } else if (Letters[i].Length() == 1) { char *h = Letters[i][0]; int Id = Map.Find(h); LAssert(Id > 0); s->AppendItem(LGetLeaf(h), Id, true); } } if (Other.Length() > 0) { for (int n=0; n 0); s->AppendItem(LGetLeaf(h), Id, true); } } } else { for (int i=0; i 0) s->AppendItem(LGetLeaf(h), Id, true); else LgiTrace("%s:%i - Failed to get id for '%s' (map.len=%i)\n", _FL, h, Map.Length()); } if (!Headers.Length()) { s->AppendItem("(none)", 0, false); } } // Show the menu LPoint p(m.x, m.y); PointToScreen(p); int Goto = s->Float(this, p.x, p.y, true); if (Goto > 0) { char *File = Headers[Goto-1]; if (File) { // Open the selected file Doc->GetProject()->GetApp()->OpenFile(File); } } DeleteObj(s); } } // Clean up memory Headers.DeleteArrays(); } else { LgiTrace("%s:%i - No include paths set.\n", _FL); } } void EditTray::OnFunctionList(LMouse &m) { LArray Funcs; if (BuildDefnList(Doc->GetFileName(), (char16*)Ctrl->NameW(), Funcs, DefnNone /*DefnFunc | DefnClass*/)) { LSubMenu s; LArray a; int ScreenHt = GdcD->Y(); int ScreenLines = ScreenHt / LSysFont->GetHeight(); float Ratio = ScreenHt ? (float)(LSysFont->GetHeight() * Funcs.Length()) / ScreenHt : 0.0f; bool UseSubMenus = Ratio > 0.9f; int Buckets = UseSubMenus ? (int)(ScreenLines * 0.9) : 1; int BucketSize = MAX(2, (int)Funcs.Length() / Buckets); LSubMenu *Cur = NULL; for (unsigned n=0; nType != DefnEnumValue) { for (char *k = i->Name; *k && o < Buf+sizeof(Buf)-8; k++) { if (*k == '&') { *o++ = '&'; *o++ = '&'; } else if (*k == '\t') { *o++ = ' '; } else { *o++ = *k; } } *o++ = 0; a[n] = i; if (UseSubMenus) { if (!Cur || n % BucketSize == 0) { LString SubMsg; SubMsg.Printf("%s...", Buf); Cur = s.AppendSub(SubMsg); } if (Cur) Cur->AppendItem(Buf, n+1, true); } else { s.AppendItem(Buf, n+1, true); } } } LPoint p(m.x, m.y); PointToScreen(p); int Goto = s.Float(this, p.x, p.y, true); if (Goto) { DefnInfo *Info = a[Goto-1]; if (Info) { Ctrl->SetLine(Info->Line); } } } else { LgiTrace("%s:%i - No functions in input.\n", _FL); } } void EditTray::OnSymbolList(LMouse &m) { LAutoString s(Ctrl->GetSelection()); if (s) { LAutoWString sw(Utf8ToWide(s)); if (sw) { #if USE_OLD_FIND_DEFN List Matches; Doc->FindDefn(sw, Ctrl->NameW(), Matches); #else LArray Matches; Doc->GetApp()->FindSymbol(s, Matches); #endif LSubMenu *s = new LSubMenu; if (s) { // Construct the menu int n=1; #if USE_OLD_FIND_DEFN for (auto Def: Matches) { char m[512]; char *d = strrchr(Def->File, DIR_CHAR); sprintf(m, "%s (%s:%i)", Def->Name.Get(), d ? d + 1 : Def->File.Get(), Def->Line); s->AppendItem(m, n++, true); } #else for (int i=0; iAppendItem(m, n++, true); } #endif if (!Matches.Length()) { s->AppendItem("(none)", 0, false); } // Show the menu LPoint p(m.x, m.y); PointToScreen(p); int Goto = s->Float(this, p.x, p.y, true); if (Goto) { #if USE_OLD_FIND_DEFN DefnInfo *Def = Matches[Goto-1]; #else FindSymResult *Def = &Matches[Goto-1]; #endif { // Open the selected symbol if (Doc->GetProject() && Doc->GetProject()->GetApp()) { AppWnd *App = Doc->GetProject()->GetApp(); IdeDoc *Doc = App->OpenFile(Def->File); if (Doc) { Doc->SetLine(Def->Line, false); } else { char *f = Def->File; LgiTrace("%s:%i - Couldn't open doc '%s'\n", _FL, f); } } else { LgiTrace("%s:%i - No project / app ptr.\n", _FL); } } } DeleteObj(s); } } } else { LSubMenu *s = new LSubMenu; if (s) { s->AppendItem("(No symbol currently selected)", 0, false); LPoint p(m.x, m.y); PointToScreen(p); s->Float(this, p.x, p.y, true); DeleteObj(s); } } } void EditTray::OnMouseClick(LMouse &m) { if (m.Left() && m.Down()) { if (FileBtn.Overlap(m.x, m.y)) { OnHeaderList(m); } else if (FuncBtn.Overlap(m.x, m.y)) { OnFunctionList(m); } else if (SymBtn.Overlap(m.x, m.y)) { OnSymbolList(m); } } } class ProjMethodPopup : public LPopupList { AppWnd *App; public: LArray All; ProjMethodPopup(AppWnd *app, LViewI *target) : LPopupList(target, PopupAbove, POPUP_WIDTH) { App = app; } LString ToString(DefnInfo *Obj) { return Obj->Name; } void OnSelect(DefnInfo *Obj) { App->GotoReference(Obj->File, Obj->Line, false); } bool Name(const char *s) { LString InputStr = s; LString::Array p = InputStr.SplitDelimit(" \t"); LArray Matching; for (unsigned i=0; iName, p[n])) { Match = false; break; } } if (Match) Matching.Add(Def); } return SetItems(Matching); } int OnNotify(LViewI *Ctrl, LNotification n) { if (Lst && Ctrl == Edit && (n.Type == LNotifyValueChanged || n.Type == LNotifyDocChanged)) { Name(Edit->Name()); } return LPopupList::OnNotify(Ctrl, n); } }; class ProjSymPopup : public LPopupList { AppWnd *App; IdeDoc *Doc; int CommonPathLen; public: LArray All; ProjSymPopup(AppWnd *app, IdeDoc *doc, LViewI *target) : LPopupList(target, PopupAbove, POPUP_WIDTH) { App = app; Doc = doc; CommonPathLen = 0; } void FindCommonPathLength() { LString s; for (unsigned i=0; iFile.Get(); char *b_end = strrchr(b, DIR_CHAR); int Common = 0; while ( *a && a <= a_end && *b && b <= b_end && ToLower(*a) == ToLower(*b)) { Common++; a++; b++; } if (i == 1) CommonPathLen = Common; else CommonPathLen = MIN(CommonPathLen, Common); } else s = All[i]->File; } } LString ToString(FindSymResult *Obj) { LString s; s.Printf("%s:%i - %s", CommonPathLen < Obj->File.Length() ? Obj->File.Get() + CommonPathLen : Obj->File.Get(), Obj->Line, Obj->Symbol.Get()); return s; } void OnSelect(FindSymResult *Obj) { App->GotoReference(Obj->File, Obj->Line, false); } int OnNotify(LViewI *Ctrl, LNotification n) { if (Lst && Ctrl == Edit && (n.Type == LNotifyValueChanged || n.Type == LNotifyDocChanged)) { // Kick off search... LString s = Ctrl->Name(); s = s.Strip(); if (s.Length() > 2) App->FindSymbol(Doc->AddDispatch(), s); } return LPopupList::OnNotify(Ctrl, n); } }; void FilterFiles(LArray &Perfect, LArray &Nodes, LString InputStr, int Platforms) { LString::Array p = InputStr.SplitDelimit(" \t"); auto InputLen = InputStr.RFind("."); if (InputLen < 0) InputLen = InputStr.Length(); LArray Partial; auto Start = LCurrentTime(); for (unsigned i=0; i 400) { break; } ProjectNode *Pn = Nodes[i]; if (! (Pn->GetPlatforms() & Platforms) ) continue; auto Fn = Pn->GetFileName(); if (Fn) { char *Dir = strchr(Fn, '/'); if (!Dir) Dir = strchr(Fn, '\\'); auto Leaf = Dir ? strrchr(Fn, *Dir) : Fn; bool Match = true; for (unsigned n=0; n { AppWnd *App; public: LArray Nodes; ProjFilePopup(AppWnd *app, LViewI *target) : LPopupList(target, PopupAbove, POPUP_WIDTH) { App = app; } LString ToString(ProjectNode *Obj) { return LString(Obj->GetFileName()); } void OnSelect(ProjectNode *Obj) { auto Fn = Obj->GetFileName(); if (LIsRelativePath(Fn)) { IdeProject *Proj = Obj->GetProject(); LAutoString Base = Proj->GetBasePath(); LFile::Path p(Base); p += Fn; App->GotoReference(p, 1, false); } else { App->GotoReference(Fn, 1, false); } } void Update(LString InputStr) { LArray Matches; FilterFiles(Matches, Nodes, InputStr, App->GetPlatform()); SetItems(Matches); } int OnNotify(LViewI *Ctrl, LNotification n) { if (Lst && Ctrl == Edit && (n.Type == LNotifyValueChanged || n.Type == LNotifyDocChanged)) { auto s = Ctrl->Name(); if (ValidStr(s)) Update(s); } return LPopupList::OnNotify(Ctrl, n); } }; class LStyleThread : public LEventTargetThread { public: LStyleThread() : LEventTargetThread("StyleThread") { } LMessage::Result OnEvent(LMessage *Msg) { switch (Msg->Msg()) { } return 0; } } StyleThread; //////////////////////////////////////////////////////////////////////////////////////////// IdeDocPrivate::IdeDocPrivate(IdeDoc *d, AppWnd *a, NodeSource *src, const char *file) : NodeView(src), LMutex("IdeDocPrivate.Lock") { FilePopup = NULL; MethodPopup = NULL; SymPopup = NULL; App = a; Doc = d; Project = 0; FileName = file; LFontType Font, *Use = 0; if (Font.Serialize(App->GetOptions(), OPT_EditorFont, false)) { Use = &Font; } Doc->AddView(Edit = new DocEdit(Doc, Use)); Doc->AddView(Tray = new EditTray(Edit, Doc)); } void IdeDocPrivate::OnDelete() { IdeDoc *Temp = Doc; DeleteObj(Temp); } void IdeDocPrivate::UpdateName() { char n[MAX_PATH_LEN+30]; LString Dsp = GetDisplayName(); char *File = Dsp; #if MDI_TAB_STYLE char *Dir = File ? strrchr(File, DIR_CHAR) : NULL; if (Dir) File = Dir + 1; #endif strcpy_s(n, sizeof(n), File ? File : Untitled); if (Edit->IsDirty()) { strcat(n, " (changed)"); } Doc->Name(n); } LString IdeDocPrivate::GetDisplayName() { if (nSrc) { auto Fn = nSrc->GetFileName(); if (Fn) { if (stristr(Fn, "://")) { LUri u(nSrc->GetFileName()); if (u.sPass) { u.sPass = "******"; } return u.ToString(); } else if (*Fn == '.') { return nSrc->GetFullPath(); } } return LString(Fn); } return LString(FileName); } bool IdeDocPrivate::IsFile(const char *File) { LString Mem; char *f = NULL; if (nSrc) { Mem = nSrc->GetFullPath(); f = Mem; } else { f = FileName; } if (!f) return false; LToken doc(f, DIR_STR); LToken in(File, DIR_STR); ssize_t in_pos = (ssize_t)in.Length() - 1; ssize_t doc_pos = (ssize_t)doc.Length() - 1; while (in_pos >= 0 && doc_pos >= 0) { char *i = in[in_pos--]; char *d = doc[doc_pos--]; if (!i || !d) { return false; } if (!strcmp(i, ".") || !strcmp(i, "..")) { continue; } if (stricmp(i, d)) { return false; } } return true; } const char *IdeDocPrivate::GetLocalFile() { if (nSrc) { if (nSrc->IsWeb()) return nSrc->GetLocalCache(); auto fp = nSrc->GetFullPath(); if (_stricmp(fp.Get()?fp.Get():"", Buffer.Get()?Buffer.Get():"")) Buffer = fp; return Buffer; } return FileName; } void IdeDocPrivate::SetFileName(const char *f) { nSrc = NULL; FileName = f; Edit->IsDirty(true); } bool IdeDocPrivate::Load() { bool Status = false; if (nSrc) { Status = nSrc->Load(Edit, this); } else if (FileName) { if (LFileExists(FileName)) { Status = Edit->Open(FileName); } else LgiTrace("%s:%i - '%s' doesn't exist.\n", _FL, FileName.Get()); } if (Status) ModTs = GetModTime(); return Status; } bool IdeDocPrivate::Save() { bool Status = false; if (nSrc) { Status = nSrc->Save(Edit, this); } else if (FileName) { Status = Edit->Save(FileName); if (!Status) { const char *Err = Edit->GetLastError(); LgiMsg(App, "%s", AppName, MB_OK, Err ? Err : "$unknown_error"); } OnSaveComplete(Status); } else { Edit->IsDirty(false); } if (Status) ModTs = GetModTime(); return Status; } void IdeDocPrivate::OnSaveComplete(bool Status) { if (Status) Edit->IsDirty(false); UpdateName(); ProjectNode *Node = dynamic_cast(nSrc); if (Node) { auto Full = nSrc->GetFullPath(); App->OnNode(Full, Node, FindSymbolSystem::FileReparse); } } void IdeDocPrivate::CheckModTime() { if (!ModTs.IsValid()) return; LDateTime Ts = GetModTime(); if (Ts.IsValid() && Ts > ModTs) { static bool InCheckModTime = false; if (!InCheckModTime) { InCheckModTime = true; if (!Edit->IsDirty() || LgiMsg(Doc, "Do you want to reload modified file from\ndisk and lose your changes?", AppName, MB_YESNO) == IDYES) { auto Ln = Edit->GetLine(); Load(); Edit->IsDirty(false); UpdateName(); Edit->SetLine((int)Ln); } InCheckModTime = false; } } } /////////////////////////////////////////////////////////////////////////////////////////// LString IdeDoc::CurIpDoc; int IdeDoc::CurIpLine = -1; IdeDoc::IdeDoc(AppWnd *a, NodeSource *src, const char *file) { d = new IdeDocPrivate(this, a, src, file); d->UpdateName(); /* if (src || file) d->Load(); */ } IdeDoc::~IdeDoc() { d->App->OnDocDestroy(this); DeleteObj(d); } class WebBuild : public LThread { IdeDocPrivate *d; LString Uri; int64 SleepMs; LStream *Log; LCancel Cancel; public: WebBuild(IdeDocPrivate *priv, LString uri, int64 sleepMs) : LThread("WebBuild"), d(priv), Uri(uri), SleepMs(sleepMs) { Log = d->App->GetBuildLog(); Run(); } ~WebBuild() { Cancel.Cancel(); while (!IsExited()) { LSleep(1); } } int Main() { if (SleepMs > 0) { // Sleep for a number of milliseconds to allow the file to upload/save to the website uint64 Ts = LCurrentTime(); while (!Cancel.IsCancelled() && (LCurrentTime()-Ts) < SleepMs) LSleep(1); } // Download the file... LStringPipe Out; LString Error; bool r = LgiGetUri(&Cancel, &Out, &Error, Uri, NULL/*InHdrs*/, NULL/*Proxy*/); if (r) { // Parse through it and extract any errors... } else { // Show the download error in the build log... Log->Print("%s:%i - Web build download failed: %s\n", _FL, Error.Get()); } return 0; } }; bool IdeDoc::Build() { if (!d->Edit) return false; int64 SleepMs = -1; LString s = d->Edit->Name(), Uri; LString::Array Lines = s.Split("\n"); for (auto Ln : Lines) { s = Ln.Strip(); if (s.Find("//") == 0) { LString::Array p = s(2,-1).Strip().Split(":", 1); if (p.Length() == 2) { if (p[0].Equals("build-sleep")) { SleepMs = p[1].Strip().Int(); } else if (p[0].Equals("build-uri")) { Uri = p[1].Strip(); break; } } } } if (Uri) { if (d->Build && !d->Build->IsExited()) { // Already building... LStream *Log = d->App->GetBuildLog(); if (Log) Log->Print("%s:%i - Already building...\n"); return false; } return d->Build.Reset(new WebBuild(d, Uri, SleepMs)); } return false; } void IdeDoc::OnLineChange(int Line) { d->App->OnLocationChange(d->GetLocalFile(), Line); } void IdeDoc::OnMarginClick(int Line) { LString Dn = d->GetDisplayName(); d->App->ToggleBreakpoint(Dn, Line); } void IdeDoc::OnTitleClick(LMouse &m) { LMdiChild::OnTitleClick(m); if (m.IsContextMenu()) { char Full[MAX_PATH_LEN] = "", sFile[MAX_PATH_LEN] = "", sFull[MAX_PATH_LEN] = "", sBrowse[MAX_PATH_LEN] = ""; const char *Fn = GetFileName(), *Dir = NULL; IdeProject *p = GetProject(); if (Fn) { strcpy_s(Full, sizeof(Full), Fn); if (LIsRelativePath(Fn) && p) { LAutoString Base = p->GetBasePath(); if (Base) LMakePath(Full, sizeof(Full), Base, Fn); } Dir = strrchr(Full, DIR_CHAR); if (Dir) sprintf_s(sFile, sizeof(sFile), "Copy '%s'", Dir + 1); sprintf_s(sFull, sizeof(sFull), "Copy '%s'", Full); sprintf_s(sBrowse, sizeof(sBrowse), "Browse to '%s'", Dir ? Dir + 1 : Full); } LSubMenu s; s.AppendItem("Save", IDM_SAVE, d->Edit->IsDirty()); s.AppendItem("Close", IDM_CLOSE, true); if (Fn) { s.AppendSeparator(); if (Dir) s.AppendItem(sFile, IDM_COPY_FILE, true); s.AppendItem(sFull, IDM_COPY_PATH, true); s.AppendItem(sBrowse, IDM_BROWSE, true); s.AppendItem("Show In Project", IDM_SHOW_IN_PROJECT, true); } if (p) { s.AppendSeparator(); s.AppendItem("Properties", IDM_PROPERTIES, true); } m.ToScreen(); int Cmd = s.Float(this, m.x, m.y, m.Left()); switch (Cmd) { case IDM_SAVE: { SetClean(NULL); break; } case IDM_CLOSE: { if (OnRequestClose(false)) Quit(); break; } case IDM_COPY_FILE: { if (Dir) { LClipBoard c(this); c.Text(Dir + 1); } break; } case IDM_COPY_PATH: { LClipBoard c(this); c.Text(Full); break; } case IDM_BROWSE: { LBrowseToFile(Full); break; } case IDM_SHOW_IN_PROJECT: { d->App->ShowInProject(Fn); break; } case IDM_PROPERTIES: { p->ShowFileProperties(Full); break; } } } } AppWnd *IdeDoc::GetApp() { return d->App; } bool IdeDoc::IsFile(const char *File) { return File ? d->IsFile(File) : false; } bool IdeDoc::AddBreakPoint(ssize_t Line, bool Add) { if (Add) d->BreakPoints.Add(Line, true); else d->BreakPoints.Delete(Line); if (d->Edit) d->Edit->Invalidate(); return true; } void IdeDoc::GotoSearch(int CtrlId, char *InitialText) { LString File; if (CtrlId == IDC_SYMBOL_SEARCH) { // Check if the cursor is on a #include line... in which case we // should look up the header and go to that instead of looking for // a symbol in the code. if (d->Edit) { // Get current line LString Ln = (*d->Edit)[d->Edit->GetLine()]; if (Ln.Find("#include") >= 0) { LString::Array a = Ln.SplitDelimit(" \t", 1); if (a.Length() == 2) { File = a[1].Strip("\'\"<>"); InitialText = File; CtrlId = IDC_FILE_SEARCH; } } } } if (d->Tray) d->Tray->GotoSearch(CtrlId, InitialText); } #define IsVariableChar(ch) \ ( \ IsAlpha(ch) \ || \ IsDigit(ch) \ || \ strchr("-_~", ch) != NULL \ ) void IdeDoc::SearchSymbol() { if (!d->Edit || !d->Tray) { LAssert(0); return; } ssize_t Cur = d->Edit->GetCaret(); auto Txt = d->Edit->NameW(); if (Cur >= 0 && Txt != NULL) { ssize_t Start = Cur; while ( Start > 0 && IsVariableChar(Txt[Start-1])) Start--; ssize_t End = Cur; while ( Txt[End] && IsVariableChar(Txt[End])) End++; LString Word(Txt + Start, End - Start); GotoSearch(IDC_SYMBOL_SEARCH, Word); } } void IdeDoc::UpdateControl() { if (d->Edit) d->Edit->Invalidate(); } void IdeDoc::SearchFile() { GotoSearch(IDC_FILE_SEARCH, NULL); } bool IdeDoc::IsCurrentIp() { auto Fn = GetFileName(); bool DocMatch = CurIpDoc && Fn && !_stricmp(Fn, CurIpDoc); return DocMatch; } void IdeDoc::ClearCurrentIp() { CurIpDoc.Empty(); CurIpLine = -1; } void IdeDoc::SetCrLf(bool CrLf) { if (d->Edit) d->Edit->SetCrLf(CrLf); } bool IdeDoc::OpenFile(const char *File) { if (!d->Edit) return false; auto Cs = d->GetSrc() ? d->GetSrc()->GetCharset() : NULL; return d->Edit->Open(File, Cs); } void IdeDoc::SetEditorParams(int IndentSize, int TabSize, bool HardTabs, bool ShowWhiteSpace) { if (d->Edit) { d->Edit->SetIndentSize(IndentSize > 0 ? IndentSize : 4); d->Edit->SetTabSize(TabSize > 0 ? TabSize : 4); d->Edit->SetHardTabs(HardTabs); d->Edit->SetShowWhiteSpace(ShowWhiteSpace); d->Edit->Invalidate(); } } bool IdeDoc::HasFocus(int Set) { if (!d->Edit) return false; if (Set) d->Edit->Focus(Set); return d->Edit->Focus(); } void IdeDoc::SplitSelection(LString Sep) { if (!d->Edit) return; auto r = d->Edit->GetSelectionRange(); LString s = d->Edit->GetSelection(); if (!s) return; auto parts = s.SplitDelimit(Sep); auto joined = LString("\n").Join(parts); LAutoWString w(Utf8ToWide(joined)); d->Edit->DeleteSelection(); d->Edit->Insert(r.Start, w, StrlenW(w)); } void IdeDoc::JoinSelection(LString Sep) { if (!d->Edit) return; auto r = d->Edit->GetSelectionRange(); LString s = d->Edit->GetSelection(); if (!s) return; LAutoWString w(Utf8ToWide(s.Replace("\n", Sep))); d->Edit->DeleteSelection(); d->Edit->Insert(r.Start, w, StrlenW(w)); } void IdeDoc::EscapeSelection(bool ToEscaped) { if (!d->Edit) return; LString s = d->Edit->GetSelection(); if (!s) return; auto ReplaceSelection = [this](LString s) { auto r = d->Edit->GetSelectionRange(); LAutoWString w(Utf8ToWide(s)); d->Edit->DeleteSelection(); d->Edit->Insert(r.Start, w, StrlenW(w)); }; if (ToEscaped) { LMouse m; GetMouse(m); LString Delim = "\r\n\\"; if (m.Ctrl()) { auto Inp = new LInput(this, LString::Escape(Delim, -1, "\\"), "Delimiter chars:", "Escape"); Inp->DoModal([this, ReplaceSelection, s, Inp](auto d, auto code) { if (code) { auto Delim = LString::UnEscape(Inp->GetStr().Get(), -1); auto str = LString::Escape(s, -1, Delim); ReplaceSelection(str); } delete d; }); return; } s = LString::Escape(s, -1, Delim); } else { s = LString::UnEscape(s.Get(), -1); } ReplaceSelection(s); } void IdeDoc::ConvertWhiteSpace(bool ToTabs) { if (!d->Edit) return; LAutoString Sp( ToTabs ? SpacesToTabs(d->Edit->Name(), d->Edit->GetTabSize()) : TabsToSpaces(d->Edit->Name(), d->Edit->GetTabSize()) ); if (Sp) { d->Edit->Name(Sp); SetDirty(); } } ssize_t IdeDoc::GetLine() { return d->Edit ? d->Edit->GetLine() : -1; } void IdeDoc::SetLine(int Line, bool CurIp) { if (CurIp) { LString CurDoc = GetFileName(); if (ValidStr(CurIpDoc) ^ ValidStr(CurDoc) || (CurIpDoc && CurDoc && strcmp(CurDoc, CurIpDoc) != 0) || Line != CurIpLine) { bool Cur = IsCurrentIp(); if (d->Edit && Cur && CurIpLine >= 0) { // Invalidate the old IP location d->Edit->InvalidateLine(CurIpLine - 1); } CurIpLine = Line; CurIpDoc = CurDoc; // LgiTrace("%s:%i - CurIpLine=%i\n", _FL, CurIpLine); d->Edit->InvalidateLine(CurIpLine - 1); } } if (d->Edit) { d->Edit->SetLine(Line); } } IdeProject *IdeDoc::GetProject() { return d->Project; } void IdeDoc::SetProject(IdeProject *p) { d->Project = p; if (d->Project->GetApp() && d->BreakPoints.Length() == 0) d->Project->GetApp()->LoadBreakPoints(this); } const char *IdeDoc::GetFileName() { return d->GetLocalFile(); } void IdeDoc::SetFileName(const char *f, bool Write) { d->SetFileName(f); if (Write) d->Edit->Save(d->GetLocalFile()); } void IdeDoc::Focus(bool f) { d->Edit->Focus(f); } LMessage::Result IdeDoc::OnEvent(LMessage *Msg) { switch (Msg->Msg()) { case M_FIND_SYM_REQUEST: { LAutoPtr Resp((FindSymRequest*)Msg->A()); if (Resp && d->SymPopup) { LViewI *SymEd; if (GetViewById(IDC_SYMBOL_SEARCH, SymEd)) { LString Input = SymEd->Name(); if (Input == Resp->Str) // Is the input string still the same? { /* The problem with this is that the user it still typing something and the cursor / focus jumps to the document now they are typing a search string into the document, which is not what they intended. if (Resp->Results.Length() == 1) { FindSymResult *r = Resp->Results[0]; d->SymPopup->Visible(false); d->App->GotoReference(r->File, r->Line, false); } else */ { d->SymPopup->All = Resp->Results; Resp->Results.Length(0); d->SymPopup->FindCommonPathLength(); d->SymPopup->SetItems(d->SymPopup->All); d->SymPopup->Visible(true); } } } } break; } } return LMdiChild::OnEvent(Msg); } void IdeDoc::OnPulse() { d->CheckModTime(); if (d->Lock(_FL)) { if (d->WriteBuf.Length()) { bool Pour = d->Edit->SetPourEnabled(false); for (auto s: d->WriteBuf) { LAutoWString w(Utf8ToWide(s, s.Length())); d->Edit->Insert(d->Edit->Length(), w, Strlen(w.Get())); } d->Edit->SetPourEnabled(Pour); d->WriteBuf.Empty(); d->Edit->Invalidate(); } d->Unlock(); } } LString IdeDoc::Read() { return d->Edit->Name(); } ssize_t IdeDoc::Write(const void *Ptr, ssize_t Size, int Flags) { if (d->Lock(_FL)) { d->WriteBuf.New().Set((char*)Ptr, Size); d->Unlock(); } return 0; } void IdeDoc::OnProjectChange() { DeleteObj(d->FilePopup); DeleteObj(d->MethodPopup); DeleteObj(d->SymPopup); } int IdeDoc::OnNotify(LViewI *v, LNotification n) { // printf("IdeDoc::OnNotify(%i, %i)\n", v->GetId(), f); switch (v->GetId()) { case IDC_EDIT: { switch (n.Type) { case LNotifyDocChanged: { d->UpdateName(); break; } case LNotifyCursorChanged: { if (d->Tray) { LPoint Pt; if (d->Edit->GetLineColumnAtIndex(Pt, d->Edit->GetCaret())) { d->Tray->Col = Pt.x; d->Tray->Line = Pt.y; d->Tray->Invalidate(); } } break; } default: break; } break; } case IDC_FILE_SEARCH: { if (n.Type == LNotifyEscapeKey) { d->Edit->Focus(true); break; } auto SearchStr = v->Name(); if (ValidStr(SearchStr)) { if (!d->FilePopup) { if ((d->FilePopup = new ProjFilePopup(d->App, v))) { // Populate with files from the project... // Find the root project... IdeProject *p = d->Project; while (p && p->GetParentProject()) p = p->GetParentProject(); if (p) { // Get all the nodes List All; p->GetChildProjects(All); All.Insert(p); for (auto p: All) { p->GetAllNodes(d->FilePopup->Nodes); } } } } if (d->FilePopup) { // Update list elements... d->FilePopup->OnNotify(v, n); } } else if (d->FilePopup) { DeleteObj(d->FilePopup); } break; } case IDC_METHOD_SEARCH: { if (n.Type == LNotifyEscapeKey) { printf("%s:%i Got LNotifyEscapeKey\n", _FL); d->Edit->Focus(true); break; } auto SearchStr = v->Name(); if (ValidStr(SearchStr)) { if (!d->MethodPopup) d->MethodPopup = new ProjMethodPopup(d->App, v); if (d->MethodPopup) { // Populate with symbols d->MethodPopup->All.Length(0); BuildDefnList(GetFileName(), (char16*)d->Edit->NameW(), d->MethodPopup->All, DefnFunc); // Update list elements... d->MethodPopup->OnNotify(v, n); } } else if (d->MethodPopup) { DeleteObj(d->MethodPopup); } break; } case IDC_SYMBOL_SEARCH: { if (n.Type == LNotifyEscapeKey) { printf("%s:%i Got LNotifyEscapeKey\n", _FL); d->Edit->Focus(true); break; } auto SearchStr = v->Name(); if (ValidStr(SearchStr)) { if (!d->SymPopup) d->SymPopup = new ProjSymPopup(d->App, this, v); if (d->SymPopup) d->SymPopup->OnNotify(v, n); } else if (d->SymPopup) { DeleteObj(d->SymPopup); } break; } } return 0; } void IdeDoc::SetDirty() { d->Edit->IsDirty(true); d->UpdateName(); } bool IdeDoc::GetClean() { return !d->Edit->IsDirty(); } LString IdeDoc::GetFullPath() { LAutoString Base; if (GetProject()) Base = GetProject()->GetBasePath(); LString LocalPath; if (d->GetLocalFile() && LIsRelativePath(LocalPath) && Base) { char p[MAX_PATH_LEN]; LMakePath(p, sizeof(p), Base, d->GetLocalFile()); LocalPath = p; } else { LocalPath = d->GetLocalFile(); } if (d->Project) d->Project->CheckExists(LocalPath); return LocalPath; } void IdeDoc::SetClean(std::function Callback) { static bool Processing = false; - bool Status = false; if (!Processing) { Processing = true; LAutoString Base; if (GetProject()) Base = GetProject()->GetBasePath(); LString LocalPath = GetFullPath(); // printf("OnSave setup d=%p\n", d); auto OnSave = [this, Callback](bool ok) { // printf("OnSave %i d=%p\n", ok, d); if (ok) d->Save(); // printf("OnSave cb\n"); if (Callback) Callback(ok); // printf("OnSave done\n"); }; if (d->Edit->IsDirty() && !LFileExists(LocalPath)) { // We need a valid filename to save to... auto s = new LFileSelect; s->Parent(this); if (Base) s->InitialDir(Base); s->Save([this, OnSave](auto fileSel, auto ok) { // printf("Doc.Save.Cb ok=%i d=%p\n", ok, d); if (ok) d->SetFileName(fileSel->Name()); // printf("Doc.Save.Cb onsave\n", ok); OnSave(ok); // printf("Doc.Save.Cb del\n", ok); delete fileSel; }); } else OnSave(true); Processing = false; } } void IdeDoc::OnPaint(LSurface *pDC) { LMdiChild::OnPaint(pDC); #if !MDI_TAB_STYLE LRect c = GetClient(); LWideBorder(pDC, c, SUNKEN); #endif } void IdeDoc::OnPosChange() { LMdiChild::OnPosChange(); } bool IdeDoc::OnRequestClose(bool OsShuttingDown) { if (d->Edit->IsDirty()) { LString Dsp = d->GetDisplayName(); int a = LgiMsg(this, "Save '%s'?", AppName, MB_YESNOCANCEL, Dsp ? Dsp.Get() : Untitled); switch (a) { case IDYES: { SetClean([](bool ok) { if (ok) { LAssert(!"Impl close doc."); } }); // This is returned immediately... before the user has decided to save or not return false; } case IDNO: { break; } default: case IDCANCEL: { return false; } } } return true; } /* LTextView3 *IdeDoc::GetEdit() { return d->Edit; } */ bool IdeDoc::BuildIncludePaths(LArray &Paths, IdePlatform Platform, bool IncludeSysPaths) { if (!GetProject()) { LgiTrace("%s:%i - GetProject failed.\n", _FL); return false; } bool Status = GetProject()->BuildIncludePaths(Paths, true, IncludeSysPaths, Platform); if (Status) { if (IncludeSysPaths) GetApp()->GetSystemIncludePaths(Paths); } else { LgiTrace("%s:%i - GetProject()->BuildIncludePaths failed.\n", _FL); } return Status; } bool IdeDoc::BuildHeaderList(const char16 *Cpp, LArray &Headers, LArray &IncPaths) { LAutoString c8(WideToUtf8(Cpp)); if (!c8) return false; return ::BuildHeaderList(c8, Headers, IncPaths, true); } bool MatchSymbol(DefnInfo *Def, char16 *Symbol) { static char16 Dots[] = {':', ':', 0}; LBase o; o.Name(Def->Name); auto Name = o.NameW(); auto Sep = StristrW((char16*)Name, Dots); auto Start = Sep ? Sep : Name; // char16 *End = StrchrW(Start, '('); ssize_t Len = StrlenW(Symbol); char16 *Match = StristrW((char16*)Start, Symbol); if (Match) // && Match + Len <= End) { if (Match > Start && isword(Match[-1])) { return false; } char16 *e = Match + Len; if (*e && isword(*e)) { return false; } return true; } return false; } bool IdeDoc::FindDefn(char16 *Symbol, const char16 *Source, List &Matches) { if (!Symbol || !Source) { LgiTrace("%s:%i - Arg error.\n", _FL); return false; } #if DEBUG_FIND_DEFN LStringPipe Dbg; LgiTrace("FindDefn(%S)\n", Symbol); #endif LString::Array Paths; LArray Headers; if (!BuildIncludePaths(Paths, PlatformCurrent, true)) { LgiTrace("%s:%i - BuildIncludePaths failed.\n", _FL); // return false; } char Local[MAX_PATH_LEN]; strcpy_s(Local, sizeof(Local), GetFileName()); LTrimDir(Local); Paths.New() = Local; if (!BuildHeaderList(Source, Headers, Paths)) { LgiTrace("%s:%i - BuildHeaderList failed.\n", _FL); // return false; } { LArray Defns; for (int i=0; i Defns; if (BuildDefnList(h, c16, Defns, DefnNone, false )) { bool Found = false; for (unsigned n=0; n 0; } diff --git a/Ide/Code/LgiIde.cpp b/Ide/Code/LgiIde.cpp --- a/Ide/Code/LgiIde.cpp +++ b/Ide/Code/LgiIde.cpp @@ -1,4742 +1,4740 @@ #include #include #include #include "lgi/common/Lgi.h" #include "lgi/common/Mdi.h" #include "lgi/common/Token.h" #include "lgi/common/XmlTree.h" #include "lgi/common/Panel.h" #include "lgi/common/Button.h" #include "lgi/common/TabView.h" #include "lgi/common/ClipBoard.h" #include "lgi/common/Box.h" #include "lgi/common/TextLog.h" #include "lgi/common/Edit.h" #include "lgi/common/TableLayout.h" #include "lgi/common/TextLabel.h" #include "lgi/common/Combo.h" #include "lgi/common/CheckBox.h" #include "lgi/common/LgiRes.h" #include "lgi/common/Box.h" #include "lgi/common/SubProcess.h" #include "lgi/common/About.h" #include "lgi/common/Menu.h" #include "lgi/common/ToolBar.h" #include "lgi/common/FileSelect.h" #include "lgi/common/SubProcess.h" #include "LgiIde.h" #include "FtpThread.h" #include "FindSymbol.h" #include "Debugger.h" #include "ProjectNode.h" #define IDM_RECENT_FILE 1000 #define IDM_RECENT_PROJECT 1100 #define IDM_WINDOWS 1200 #define IDM_MAKEFILE_BASE 1300 #define USE_HAIKU_PULSE_HACK 0 #define OPT_ENTIRE_SOLUTION "SearchSolution" #define OPT_SPLIT_PX "SplitPos" #define OPT_OUTPUT_PX "OutputPx" #define OPT_FIX_RENAMED "FixRenamed" #define OPT_RENAMED_SYM "RenamedSym" #define OPT_PLATFORM "Platform" #define IsSymbolChar(c) ( IsDigit(c) || IsAlpha(c) || strchr("-_", c) ) ////////////////////////////////////////////////////////////////////////////////////////// class FindInProject : public LDialog { AppWnd *App; LList *Lst; public: FindInProject(AppWnd *app) { Lst = NULL; App = app; if (LoadFromResource(IDC_FIND_PROJECT_FILE)) { MoveSameScreen(App); LViewI *v; if (GetViewById(IDC_TEXT, v)) v->Focus(true); if (!GetViewById(IDC_FILES, Lst)) return; RegisterHook(this, LKeyEvents, 0); } } bool OnViewKey(LView *v, LKey &k) { switch (k.vkey) { case LK_UP: case LK_DOWN: case LK_PAGEDOWN: case LK_PAGEUP: { return Lst->OnKey(k); break; } case LK_RETURN: { if (k.Down()) { LListItem *i = Lst->GetSelected(); if (i) { const char *Ref = i->GetText(0); App->GotoReference(Ref, 1, false); } EndModal(1); return true; } break; } case LK_ESCAPE: { if (k.Down()) { EndModal(0); return true; } break; } } return false; } void Search(const char *s) { IdeProject *p = App->RootProject(); if (!p || !s) return; LArray Matches, Nodes; List All; p->GetChildProjects(All); All.Insert(p); for (auto p: All) { p->GetAllNodes(Nodes); } FilterFiles(Matches, Nodes, s, App->GetPlatform()); Lst->Empty(); for (auto m: Matches) { LListItem *li = new LListItem; LString Fn = m->GetFileName(); #ifdef WINDOWS Fn = Fn.Replace("/","\\"); #else Fn = Fn.Replace("\\","/"); #endif m->GetProject()->CheckExists(Fn); li->SetText(Fn); Lst->Insert(li); } Lst->ResizeColumnsToContent(); } int OnNotify(LViewI *c, LNotification n) { switch (c->GetId()) { case IDC_FILES: if (n.Type == LNotifyItemDoubleClick) { LListItem *i = Lst->GetSelected(); if (i) { App->GotoReference(i->GetText(0), 1, false); EndModal(1); } } break; case IDC_TEXT: if (n.Type != LNotifyReturnKey) Search(c->Name()); break; case IDCANCEL: EndModal(0); break; } return 0; } }; ////////////////////////////////////////////////////////////////////////////////////////// const char *AppName = "LgiIde"; char *dirchar(char *s, bool rev = false) { if (rev) { char *last = 0; while (s && *s) { if (*s == '/' || *s == '\\') last = s; s++; } return last; } else { while (s && *s) { if (*s == '/' || *s == '\\') return s; s++; } } return 0; } ////////////////////////////////////////////////////////////////////////////////////////// class AppDependency : public LTreeItem { char *File; bool Loaded; LTreeItem *Fake; public: AppDependency(const char *file) { File = NewStr(file); char *d = strrchr(File, DIR_CHAR); Loaded = false; Insert(Fake = new LTreeItem); if (LFileExists(File)) { SetText(d?d+1:File); } else { char s[256]; sprintf(s, "%s (missing)", d?d+1:File); SetText(s); } } ~AppDependency() { DeleteArray(File); } char *GetFile() { return File; } void Copy(LStringPipe &p, int Depth = 0) { { char s[1024]; ZeroObj(s); memset(s, ' ', Depth * 4); sprintf(s+(Depth*4), "[%c] %s\n", Expanded() ? '-' : '+', GetText(0)); p.Push(s); } if (Loaded) { for (LTreeItem *i=GetChild(); i; i=i->GetNext()) { ((AppDependency*)i)->Copy(p, Depth+1); } } } char *Find(const char *Paths, char *e) { LToken Path(Paths, LGI_PATH_SEPARATOR); for (int p=0; pSunken(true); Root = new AppDependency(File); if (Root) { t->Insert(Root); Root->Expanded(true); } Children.Insert(t); Children.Insert(new LButton(IDC_COPY, 10, t->LView::GetPos().y2 + 10, 60, 20, "Copy")); Children.Insert(new LButton(IDOK, 80, t->LView::GetPos().y2 + 10, 60, 20, "Ok")); } DoModal(NULL); } int OnNotify(LViewI *c, LNotification n) { switch (c->GetId()) { case IDC_COPY: { if (Root) { LStringPipe p; Root->Copy(p); char *s = p.NewStr(); if (s) { LClipBoard c(this); c.Text(s); DeleteArray(s); } break; } break; } case IDOK: { EndModal(0); break; } } return 0; } }; ////////////////////////////////////////////////////////////////////////////////////////// class DebugTextLog : public LTextLog { public: DebugTextLog(int id) : LTextLog(id) { } void PourText(size_t Start, ssize_t Len) override { auto Ts = LCurrentTime(); LTextView3::PourText(Start, Len); auto Dur = LCurrentTime() - Ts; if (Dur > 1500) { // Yo homes, too much text bro... Name(NULL); } else { for (auto l: Line) { char16 *t = Text + l->Start; if (l->Len > 5 && !StrnicmpW(t, L"(gdb)", 5)) { l->c.Rgb(0, 160, 0); } else if (l->Len > 1 && t[0] == '[') { l->c.Rgb(192, 192, 192); } } } } }; WatchItem::WatchItem(IdeOutput *out, const char *Init) { Out = out; Expanded(false); if (Init) SetText(Init); Insert(PlaceHolder = new LTreeItem); } WatchItem::~WatchItem() { } bool WatchItem::SetValue(LVariant &v) { char *Str = v.CastString(); if (ValidStr(Str)) SetText(Str, 2); else LTreeItem::SetText(NULL, 2); return true; } bool WatchItem::SetText(const char *s, int i) { if (ValidStr(s)) { LTreeItem::SetText(s, i); if (i == 0 && Tree && Tree->GetWindow()) { LViewI *Tabs = Tree->GetWindow()->FindControl(IDC_DEBUG_TAB); if (Tabs) Tabs->SendNotify(LNotifyValueChanged); } return true; } if (i == 0) delete this; return false; } void WatchItem::OnExpand(bool b) { if (b && PlaceHolder) { // Do something } } class BuildLog : public LTextLog { public: BuildLog(int id) : LTextLog(id) { } void PourStyle(size_t Start, ssize_t Length) { List::I it = LTextView3::Line.begin(); for (LTextLine *ln = *it; ln; ln = *++it) { if (!ln->c.IsValid()) { char16 *t = Text + ln->Start; char16 *Err = Strnistr(t, L"error", ln->Len); char16 *Undef = Strnistr(t, L"undefined reference", ln->Len); char16 *Warn = Strnistr(t, L"warning", ln->Len); if ( (Err && strchr(":[", Err[5])) || (Undef != NULL) ) ln->c.Rgb(222, 0, 0); else if (Warn && strchr(":[", Warn[7])) ln->c.Rgb(255, 128, 0); else ln->c = LColour(L_TEXT); } } } }; class IdeOutput : public LTabView { public: AppWnd *App; LTabPage *Build; LTabPage *Output; LTabPage *Debug; LTabPage *Find; LTabPage *Ftp; LList *FtpLog; LTextLog *Txt[AppWnd::Channels::ChannelMax]; LArray Buf[AppWnd::Channels::ChannelMax]; LFont Small; LFont Fixed; LTabView *DebugTab; LBox *DebugBox; LBox *DebugLog; LList *Locals, *CallStack, *Threads; LTree *Watch; LTextLog *ObjectDump, *MemoryDump, *Registers; LTableLayout *MemTable; LEdit *DebugEdit; LTextLog *DebuggerLog; IdeOutput(AppWnd *app) { ZeroObj(Txt); App = app; Build = Output = Debug = Find = Ftp = 0; FtpLog = 0; DebugBox = NULL; Locals = NULL; Watch = NULL; DebugLog = NULL; DebugEdit = NULL; DebuggerLog = NULL; CallStack = NULL; ObjectDump = NULL; MemoryDump = NULL; MemTable = NULL; Threads = NULL; Registers = NULL; Small = *LSysFont; Small.PointSize(Small.PointSize()-1); Small.Create(); LAssert(Small.Handle()); LFontType Type; if (Type.GetSystemFont("Fixed")) { Type.SetPointSize(LSysFont->PointSize()-1); Fixed.Create(&Type); } else { Fixed.PointSize(LSysFont->PointSize()-1); Fixed.Face("Courier"); Fixed.Create(); } GetCss(true)->MinHeight("60px"); Build = Append("Build"); Output = Append("Output"); Find = Append("Find"); Ftp = Append("Ftp"); Debug = Append("Debug"); SetFont(&Small); Build->SetFont(&Small); Output->SetFont(&Small); Find->SetFont(&Small); Ftp->SetFont(&Small); Debug->SetFont(&Small); if (Build) Build->Append(Txt[AppWnd::BuildTab] = new BuildLog(IDC_BUILD_LOG)); if (Output) Output->Append(Txt[AppWnd::OutputTab] = new LTextLog(IDC_OUTPUT_LOG)); if (Find) Find->Append(Txt[AppWnd::FindTab] = new LTextLog(IDC_FIND_LOG)); if (Ftp) Ftp->Append(FtpLog = new LList(104, 0, 0, 100, 100)); if (Debug) { Debug->Append(DebugBox = new LBox); if (DebugBox) { DebugBox->SetVertical(false); if ((DebugTab = new LTabView(IDC_DEBUG_TAB))) { DebugTab->GetCss(true)->Padding("0px"); DebugTab->SetFont(&Small); DebugBox->AddView(DebugTab); LTabPage *Page; if ((Page = DebugTab->Append("Locals"))) { Page->SetFont(&Small); if ((Locals = new LList(IDC_LOCALS_LIST, 0, 0, 100, 100, "Locals List"))) { Locals->SetFont(&Small); Locals->AddColumn("", 30); Locals->AddColumn("Type", 50); Locals->AddColumn("Name", 50); Locals->AddColumn("Value", 1000); Locals->SetPourLargest(true); Page->Append(Locals); } } if ((Page = DebugTab->Append("Object"))) { Page->SetFont(&Small); if ((ObjectDump = new LTextLog(IDC_OBJECT_DUMP))) { ObjectDump->SetFont(&Fixed); ObjectDump->SetPourLargest(true); Page->Append(ObjectDump); } } if ((Page = DebugTab->Append("Watch"))) { Page->SetFont(&Small); if ((Watch = new LTree(IDC_WATCH_LIST, 0, 0, 100, 100, "Watch List"))) { Watch->SetFont(&Small); Watch->ShowColumnHeader(true); Watch->AddColumn("Watch", 80); Watch->AddColumn("Type", 100); Watch->AddColumn("Value", 600); Watch->SetPourLargest(true); Page->Append(Watch); LXmlTag *w = App->GetOptions()->LockTag("watches", _FL); if (!w) { App->GetOptions()->CreateTag("watches"); w = App->GetOptions()->LockTag("watches", _FL); } if (w) { for (auto c: w->Children) { if (c->IsTag("watch")) { Watch->Insert(new WatchItem(this, c->GetContent())); } } App->GetOptions()->Unlock(); } } } if ((Page = DebugTab->Append("Memory"))) { Page->SetFont(&Small); if ((MemTable = new LTableLayout(IDC_MEMORY_TABLE))) { LCombo *cbo; LCheckBox *chk; LTextLabel *txt; LEdit *ed; MemTable->SetFont(&Small); int x = 0, y = 0; auto *c = MemTable->GetCell(x++, y); if (c) { c->VerticalAlign(LCss::VerticalMiddle); c->Add(txt = new LTextLabel(IDC_STATIC, 0, 0, -1, -1, "Address:")); txt->SetFont(&Small); } c = MemTable->GetCell(x++, y); if (c) { c->PaddingRight(LCss::Len("1em")); c->Add(ed = new LEdit(IDC_MEM_ADDR, 0, 0, 60, 20)); ed->SetFont(&Small); } c = MemTable->GetCell(x++, y); if (c) { c->PaddingRight(LCss::Len("1em")); c->Add(cbo = new LCombo(IDC_MEM_SIZE, 0, 0, 60, 20)); cbo->SetFont(&Small); cbo->Insert("1 byte"); cbo->Insert("2 bytes"); cbo->Insert("4 bytes"); cbo->Insert("8 bytes"); } c = MemTable->GetCell(x++, y); if (c) { c->VerticalAlign(LCss::VerticalMiddle); c->Add(txt = new LTextLabel(IDC_STATIC, 0, 0, -1, -1, "Page width:")); txt->SetFont(&Small); } c = MemTable->GetCell(x++, y); if (c) { c->PaddingRight(LCss::Len("1em")); c->Add(ed = new LEdit(IDC_MEM_ROW_LEN, 0, 0, 60, 20)); ed->SetFont(&Small); } c = MemTable->GetCell(x++, y); if (c) { c->VerticalAlign(LCss::VerticalMiddle); c->Add(chk = new LCheckBox(IDC_MEM_HEX, 0, 0, -1, -1, "Show Hex")); chk->SetFont(&Small); chk->Value(true); } int cols = x; x = 0; c = MemTable->GetCell(x++, ++y, true, cols); if ((MemoryDump = new LTextLog(IDC_MEMORY_DUMP))) { MemoryDump->SetFont(&Fixed); MemoryDump->SetPourLargest(true); c->Add(MemoryDump); } Page->Append(MemTable); } } if ((Page = DebugTab->Append("Threads"))) { Page->SetFont(&Small); if ((Threads = new LList(IDC_THREADS, 0, 0, 100, 100, "Threads"))) { Threads->SetFont(&Small); Threads->AddColumn("", 20); Threads->AddColumn("Thread", 1000); Threads->SetPourLargest(true); Threads->MultiSelect(false); Page->Append(Threads); } } if ((Page = DebugTab->Append("Call Stack"))) { Page->SetFont(&Small); if ((CallStack = new LList(IDC_CALL_STACK, 0, 0, 100, 100, "Call Stack"))) { CallStack->SetFont(&Small); CallStack->AddColumn("", 20); CallStack->AddColumn("Call Stack", 1000); CallStack->SetPourLargest(true); CallStack->MultiSelect(false); Page->Append(CallStack); } } if ((Page = DebugTab->Append("Registers"))) { Page->SetFont(&Small); if ((Registers = new LTextLog(IDC_REGISTERS))) { Registers->SetFont(&Small); Registers->SetPourLargest(true); Page->Append(Registers); } } } if ((DebugLog = new LBox)) { DebugLog->SetVertical(true); DebugBox->AddView(DebugLog); DebugLog->AddView(DebuggerLog = new DebugTextLog(IDC_DEBUGGER_LOG)); DebuggerLog->SetFont(&Small); DebugLog->AddView(DebugEdit = new LEdit(IDC_DEBUG_EDIT, 0, 0, 60, 20)); DebugEdit->GetCss(true)->Height(LCss::Len(LCss::LenPx, (float)(LSysFont->GetHeight() + 8))); } } } if (FtpLog) { FtpLog->SetPourLargest(true); FtpLog->Sunken(true); FtpLog->AddColumn("Entry", 1000); FtpLog->ShowColumnHeader(false); } for (int n=0; nSetTabSize(8); Txt[n]->Sunken(true); } } ~IdeOutput() { } const char *GetClass() { return "IdeOutput"; } void Save() { if (Watch) { LXmlTag *w = App->GetOptions()->LockTag("watches", _FL); if (!w) { App->GetOptions()->CreateTag("watches"); w = App->GetOptions()->LockTag("watches", _FL); } if (w) { w->EmptyChildren(); for (LTreeItem *ti = Watch->GetChild(); ti; ti = ti->GetNext()) { LXmlTag *t = new LXmlTag("watch"); if (t) { t->SetContent(ti->GetText(0)); w->InsertTag(t); } } App->GetOptions()->Unlock(); } } } void OnCreate() { SetPulse(1000); AttachChildren(); } void RemoveAnsi(LArray &a) { char *s = a.AddressOf(); char *e = s + a.Length(); while (s < e) { if (*s == 0x7) { a.DeleteAt(s - a.AddressOf(), true); s--; } else if ( *s == 0x1b && s[1] >= 0x40 && s[1] <= 0x5f ) { // ANSI seq char *end; if (s[1] == '[' && s[2] == '0' && s[3] == ';') end = s + 4; else { end = s + 2; while (end < e && !IsAlpha(*end)) { end++; } if (*end) end++; } auto len = end - s; memmove(s, end, e - end); a.Length(a.Length() - len); s--; } s++; } } void OnPulse() { int Changed = -1; for (int Channel = 0; Channel w; if (!LIsUtf8(Utf, (ssize_t)Size)) { LgiTrace("Ch %i not utf len=" LPrintfInt64 "\n", Channel, Size); // Clear out the invalid UTF? uint8_t *u = (uint8_t*) Utf, *e = u + Size; ssize_t len = Size; LArray out; while (u < e) { int32 u32 = LgiUtf8To32(u, len); if (u32) { out.Add(u32); } else { out.Add(0xFFFD); u++; } } out.Add(0); w.Reset(out.Release()); } else { RemoveAnsi(Buf[Channel]); w.Reset(Utf8ToWide(Utf, (ssize_t)Size)); } // auto OldText = Txt[Channel]->NameW(); ssize_t OldLen = Txt[Channel]->Length(); auto Cur = Txt[Channel]->GetCaret(); Txt[Channel]->Insert(OldLen, w, StrlenW(w)); if (Cur > OldLen - 1) Txt[Channel]->SetCaret(OldLen + StrlenW(w), false); else printf("Caret move: %i, %i = %i\n", (int)Cur, (int)OldLen, Cur > OldLen - 1); Changed = Channel; Buf[Channel].Length(0); Txt[Channel]->Invalidate(); } } /* if (Changed >= 0) Value(Changed); */ } }; int DocSorter(IdeDoc *a, IdeDoc *b, NativeInt d) { auto A = a->GetFileName(); auto B = b->GetFileName(); if (A && B) { auto Af = strrchr(A, DIR_CHAR); auto Bf = strrchr(B, DIR_CHAR); return stricmp(Af?Af+1:A, Bf?Bf+1:B); } return 0; } struct FileLoc { LAutoString File; int Line; void Set(const char *f, int l) { File.Reset(NewStr(f)); Line = l; } }; class AppWndPrivate { public: AppWnd *App; int Platform = 0; LMdiParent *Mdi; LOptionsFile Options; LBox *HBox, *VBox; List Docs; List Projects; LImageList *Icons; LTree *Tree; IdeOutput *Output; bool Debugging; bool Running; bool Building; bool FixBuildWait = false; int RebuildWait = 0; LSubMenu *WindowsMenu; LSubMenu *CreateMakefileMenu; LAutoPtr FindSym; LArray SystemIncludePaths; LArray BreakPoints; // Debugging LDebugContext *DbgContext; // Cursor history tracking int HistoryLoc; LArray CursorHistory; bool InHistorySeek; void SeekHistory(int Direction) { if (CursorHistory.Length()) { int Loc = HistoryLoc + Direction; if (Loc >= 0 && Loc < CursorHistory.Length()) { HistoryLoc = Loc; FileLoc &Loc = CursorHistory[HistoryLoc]; App->GotoReference(Loc.File, Loc.Line, false, false); App->DumpHistory(); } } } // Find in files LAutoPtr FindParameters; LAutoPtr Finder; int AppHnd; // Mru LString::Array RecentFiles; LSubMenu *RecentFilesMenu = NULL; LString::Array RecentProjects; LSubMenu *RecentProjectsMenu = NULL; // Object AppWndPrivate(AppWnd *a) : Options(LOptionsFile::DesktopMode, AppName), AppHnd(LEventSinkMap::Dispatch.AddSink(a)) { FindSym.Reset(new FindSymbolSystem(AppHnd)); HistoryLoc = 0; InHistorySeek = false; WindowsMenu = 0; App = a; HBox = VBox = NULL; Tree = 0; Mdi = NULL; DbgContext = NULL; Output = 0; Debugging = false; Running = false; Building = false; Icons = LLoadImageList("icons.png", 16, 16); Options.SerializeFile(false); App->SerializeState(&Options, "WndPos", true); SerializeStringList("RecentFiles", &RecentFiles, false); SerializeStringList("RecentProjects", &RecentProjects, false); } ~AppWndPrivate() { FindSym.Reset(); Finder.Reset(); if (Output) Output->Save(); App->SerializeState(&Options, "WndPos", false); SerializeStringList("RecentFiles", &RecentFiles, true); SerializeStringList("RecentProjects", &RecentProjects, true); Options.SerializeFile(true); while (Docs.Length()) { auto len = Docs.Length(); delete Docs[0]; LAssert(Docs.Length() != len); // doc must delete itself... } auto root = App->RootProject(); if (root) { // printf("Deleting proj %s\n", root->GetFileName()); delete root; } LAssert(!Projects.Length()); DeleteObj(Icons); } bool FindSource(LAutoString &Full, char *File, char *Context) { if (!LIsRelativePath(File)) { Full.Reset(NewStr(File)); } char *ContextPath = 0; if (Context && !Full) { char *Dir = strrchr(Context, DIR_CHAR); for (auto p: Projects) { ContextPath = p->FindFullPath(Dir?Dir+1:Context); if (ContextPath) break; } if (ContextPath) { LTrimDir(ContextPath); char p[300]; LMakePath(p, sizeof(p), ContextPath, File); if (LFileExists(p)) { Full.Reset(NewStr(p)); } } else { LgiTrace("%s:%i - Context '%s' not found in project.\n", _FL, Context); } } if (!Full) { List::I Projs = Projects.begin(); for (IdeProject *p=*Projs; p; p=*++Projs) { LAutoString Base = p->GetBasePath(); if (Base) { char Path[MAX_PATH_LEN]; LMakePath(Path, sizeof(Path), Base, File); if (LFileExists(Path)) { Full.Reset(NewStr(Path)); break; } } } } if (!Full) { char *Dir = dirchar(File, true); for (auto p: Projects) { if (Full.Reset(p->FindFullPath(Dir?Dir+1:File))) break; } if (!Full) { if (LFileExists(File)) { Full.Reset(NewStr(File)); } } } return ValidStr(Full); } void ViewMsg(char *File, int Line, char *Context) { LAutoString Full; if (FindSource(Full, File, Context)) { App->GotoReference(Full, Line, false); } } #if 1 #define LOG_SEEK_MSG(...) printf(__VA_ARGS__) #else #define LOG_SEEK_MSG(...) #endif void GetContext(const char16 *Txt, ssize_t &i, char16 *&Context) { static char16 NMsg[] = L"In file included "; static char16 FromMsg[] = L"from "; auto NMsgLen = StrlenW(NMsg); if (Txt[i] != '\n') return; if (StrncmpW(Txt + i + 1, NMsg, NMsgLen)) return; i += NMsgLen + 1; while (Txt[i]) { // Skip whitespace while (Txt[i] && strchr(" \t\r\n", Txt[i])) i++; // Check for 'from' if (StrncmpW(FromMsg, Txt + i, 5)) break; i += 5; auto Start = Txt + i; // Skip to end of doc or line const char16 *Colon = 0; while (Txt[i] && Txt[i] != '\n') { if (Txt[i] == ':' && Txt[i+1] != '\n') { Colon = Txt + i; } i++; } if (Colon) { DeleteArray(Context); Context = NewStrW(Start, Colon-Start); } } } template bool IsTimeStamp(T *s, ssize_t i) { while (i > 0 && s[i-1] != '\n') i--; auto start = i; while (s[i] && (IsDigit(s[i]) || strchr(" :-", s[i]))) i++; LString txt(s + start, i - start); auto parts = txt.SplitDelimit(" :-"); return parts.Length() == 6 && parts[0].Length() == 4; } template bool IsContext(T *s, ssize_t i) { auto key = L"In file included"; auto end = i; while (i > 0 && s[i-1] != '\n') i--; if (Strnistr(s + i, key, end - i)) return true; return false; } #define PossibleLineSep(ch) \ ( (ch) == ':' || (ch) == '(' ) void SeekMsg(int Direction) { LString Comp; IdeProject *p = App->RootProject(); if (p) p ->GetSettings()->GetStr(ProjCompiler); // bool IsIAR = Comp.Equals("IAR"); if (!Output) return; int64 Current = Output->Value(); LTextView3 *o = Current < CountOf(Output->Txt) ? Output->Txt[Current] : 0; if (!o) return; auto Txt = o->NameW(); if (!Txt) return; ssize_t Cur = o->GetCaret(); char16 *Context = NULL; // Scan forward to the end of file for the next filename/line number separator. ssize_t i; for (i=Cur; Txt[i]; i++) { GetContext(Txt, i, Context); if ( PossibleLineSep(Txt[i]) && isdigit(Txt[i+1]) && !IsTimeStamp(Txt, i) && !IsContext(Txt, i) ) { break; } } // If not found then scan from the start of the file for the next filename/line number separator. if (!PossibleLineSep(Txt[i])) { for (i=0; i 0 && !strchr("\n>", Txt[Line-1])) { Line--; } // Store the filename LString File(Txt+Line, i-Line); if (!File) return; #if DIR_CHAR == '\\' File = File.Replace("/", "\\"); #else File = File.Replace("\\", "/"); #endif // Scan over the line number.. auto NumIndex = ++i; while (isdigit(Txt[NumIndex])) NumIndex++; // Store the line number LString NumStr(Txt + i, NumIndex - i); if (!NumStr) return; // Convert it to an integer auto LineNumber = (int)NumStr.Int(); o->SetCaret(Line, false); o->SetCaret(NumIndex + 1, true); LString Context8 = Context; ViewMsg(File, LineNumber, Context8); } void UpdateMenus() { static const char *None = "(none)"; if (!App->GetMenu()) return; // This happens in GTK during window destruction if (RecentFilesMenu) { RecentFilesMenu->Empty(); if (RecentFiles.Length() == 0) RecentFilesMenu->AppendItem(None, 0, false); else { int n=0; char *f; for (auto It = RecentFiles.begin(); (f = *It); f=*(++It)) { for (; f; f=*(++It)) { if (LIsUtf8(f)) RecentFilesMenu->AppendItem(f, IDM_RECENT_FILE+n++, true); else RecentFiles.Delete(It); } } } } if (RecentProjectsMenu) { RecentProjectsMenu->Empty(); if (RecentProjects.Length() == 0) RecentProjectsMenu->AppendItem(None, 0, false); else { int n=0; char *f; for (auto It = RecentProjects.begin(); (f = *It); f=*(++It)) { if (LIsUtf8(f)) RecentProjectsMenu->AppendItem(f, IDM_RECENT_PROJECT+n++, true); else RecentProjects.Delete(It); } } } if (WindowsMenu) { WindowsMenu->Empty(); Docs.Sort(DocSorter); int n=0; for (auto d: Docs) { const char *File = d->GetFileName(); if (!File) File = "(untitled)"; char *Dir = strrchr((char*)File, DIR_CHAR); WindowsMenu->AppendItem(Dir?Dir+1:File, IDM_WINDOWS+n++, true); } if (!Docs.Length()) { WindowsMenu->AppendItem(None, 0, false); } } } void Dump(LString::Array &a) { for (auto i: a) printf(" %s\n", i.Get()); } void OnFile(const char *File, bool IsProject = false) { if (!File) return; auto *Recent = IsProject ? &RecentProjects : &RecentFiles; for (auto &f: *Recent) { if (f && LFileCompare(f, File) == 0) { f = File; UpdateMenus(); return; } } Recent->AddAt(0, File); if (Recent->Length() > 10) Recent->Length(10); UpdateMenus(); } void RemoveRecent(const char *File) { if (File) { LString::Array *Recent[3] = { &RecentProjects, &RecentFiles, 0 }; for (int i=0; Recent[i]; i++) { auto &a = *Recent[i]; for (size_t n=0; nIsFile(File)) { return Doc; } } // LgiTrace("%s:%i - '%s' not found in %i docs.\n", _FL, File, Docs.Length()); return 0; } IdeProject *IsProjectOpen(const char *File) { if (File) { for (auto p: Projects) { if (p->GetFileName() && stricmp(p->GetFileName(), File) == 0) { return p; } } } return 0; } void SerializeStringList(const char *Opt, LString::Array *Lst, bool Write) { LVariant v; LString Sep = OptFileSeparator; if (Write) { if (Lst->Length() > 0) { auto s = Sep.Join(*Lst); Options.SetValue(Opt, v = s.Get()); // printf("Saving '%s' to %s\n", s.Get(), Opt); } else Options.DeleteValue(Opt); } else if (Options.GetValue(Opt, v)) { auto files = LString(v.Str()).Split(Sep); Lst->Length(0); for (auto f: files) if (f.Length() > 0) Lst->Add(f); // printf("Reading '%s' to %s, file.len=%i %s\n", v.Str(), Opt, (int)files.Length(), v.Str()); } // else printf("%s:%i - No option '%s' to read.\n", _FL, Opt); } }; AppWnd::AppWnd() { #ifdef __GTK_H__ LgiGetResObj(true, AppName); #endif LRect r(0, 0, 1000, 760); SetPos(r); MoveToCenter(); d = new AppWndPrivate(this); Name(AppName); SetQuitOnClose(true); #if WINNATIVE SetIcon((char*)MAKEINTRESOURCE(IDI_APP)); #else SetIcon("icon64.png"); #endif if (!Attach(0)) { LgiTrace("%s:%i - Attach failed.\n", _FL); return; } if ((Menu = new LMenu)) { Menu->Attach(this); bool Loaded = Menu->Load(this, "IDM_MENU"); LAssert(Loaded); if (Loaded) { Menu->SetPrefAndAboutItems(IDM_OPTIONS, IDM_ABOUT); d->RecentFilesMenu = Menu->FindSubMenu(IDM_RECENT_FILES); d->RecentProjectsMenu = Menu->FindSubMenu(IDM_RECENT_PROJECTS); d->WindowsMenu = Menu->FindSubMenu(IDM_WINDOW_LST); d->CreateMakefileMenu = Menu->FindSubMenu(IDM_CREATE_MAKEFILE); if (d->CreateMakefileMenu) { d->CreateMakefileMenu->Empty(); for (int i=0; PlatformNames[i]; i++) { d->CreateMakefileMenu->AppendItem(PlatformNames[i], IDM_MAKEFILE_BASE + i); } } else LgiTrace("%s:%i - FindSubMenu failed.\n", _FL); LMenuItem *Debug = GetMenu()->FindItem(IDM_DEBUG_MODE); if (Debug) Debug->Checked(true); else LgiTrace("%s:%i - FindSubMenu failed.\n", _FL); d->UpdateMenus(); } } LToolBar *Tools = NULL; if (GdcD->Y() > 1200) Tools = LgiLoadToolbar(this, "cmds-32px.png", 32, 32); else Tools = LgiLoadToolbar(this, "cmds-16px.png", 16, 16); if (Tools) { Tools->AppendButton("New", IDM_NEW, TBT_PUSH, true, CMD_NEW); Tools->AppendButton("Open", IDM_OPEN, TBT_PUSH, true, CMD_OPEN); Tools->AppendButton("Save", IDM_SAVE_ALL, TBT_PUSH, true, CMD_SAVE_ALL); Tools->AppendSeparator(); Tools->AppendButton("Cut", IDM_CUT, TBT_PUSH, true, CMD_CUT); Tools->AppendButton("Copy", IDM_COPY, TBT_PUSH, true, CMD_COPY); Tools->AppendButton("Paste", IDM_PASTE, TBT_PUSH, true, CMD_PASTE); Tools->AppendSeparator(); Tools->AppendButton("Compile", IDM_COMPILE, TBT_PUSH, true, CMD_COMPILE); Tools->AppendButton("Build", IDM_BUILD, TBT_PUSH, true, CMD_BUILD); Tools->AppendButton("Stop", IDM_STOP_BUILD, TBT_PUSH, true, CMD_STOP_BUILD); // Tools->AppendButton("Execute", IDM_EXECUTE, TBT_PUSH, true, CMD_EXECUTE); Tools->AppendSeparator(); Tools->AppendButton("Debug", IDM_START_DEBUG, TBT_PUSH, true, CMD_DEBUG); Tools->AppendButton("Pause", IDM_PAUSE_DEBUG, TBT_PUSH, true, CMD_PAUSE); Tools->AppendButton("Restart", IDM_RESTART_DEBUGGING, TBT_PUSH, true, CMD_RESTART); Tools->AppendButton("Kill", IDM_STOP_DEBUG, TBT_PUSH, true, CMD_KILL); Tools->AppendButton("Step Into", IDM_STEP_INTO, TBT_PUSH, true, CMD_STEP_INTO); Tools->AppendButton("Step Over", IDM_STEP_OVER, TBT_PUSH, true, CMD_STEP_OVER); Tools->AppendButton("Step Out", IDM_STEP_OUT, TBT_PUSH, true, CMD_STEP_OUT); Tools->AppendButton("Run To", IDM_RUN_TO, TBT_PUSH, true, CMD_RUN_TO); Tools->AppendSeparator(); Tools->AppendButton("Find In Files", IDM_FIND_IN_FILES, TBT_PUSH, true, CMD_FIND_IN_FILES); Tools->GetCss(true)->Padding("4px"); Tools->Attach(this); } else LgiTrace("%s:%i - No tools obj?", _FL); LVariant SplitPx = 270, OutPx = 250, v; d->Options.GetValue(OPT_SPLIT_PX, SplitPx); d->Options.GetValue(OPT_OUTPUT_PX, OutPx); if (d->Options.GetValue(OPT_PLATFORM, v)) SetPlatform(v.CastInt32()); else SetPlatform(PLATFORM_CURRENT); AddView(d->VBox = new LBox); d->VBox->SetVertical(true); d->HBox = new LBox; d->VBox->AddView(d->HBox); d->VBox->AddView(d->Output = new IdeOutput(this)); d->HBox->AddView(d->Tree = new IdeTree); if (d->Tree) { d->Tree->SetImageList(d->Icons, false); d->Tree->Sunken(false); } d->HBox->AddView(d->Mdi = new LMdiParent); if (d->Mdi) { d->Mdi->HasButton(true); } d->HBox->Value(MAX(SplitPx.CastInt32(), 20)); LRect c = GetClient(); if (c.Y() > OutPx.CastInt32()) { auto Px = OutPx.CastInt32(); LCss::Len y(LCss::LenPx, (float)MAX(Px, 120)); d->Output->GetCss(true)->Height(y); } AttachChildren(); OnPosChange(); UpdateState(); Visible(true); DropTarget(true); SetPulse(1000); #ifdef LINUX LFinishXWindowsStartup(this); #endif #if USE_HAIKU_PULSE_HACK if (d->Output) d->Output->SetPulse(1000); #endif OnCommand(IDM_NEW, 0, NULL); } AppWnd::~AppWnd() { LAssert(IsClean()); #ifdef HAIKU WaitThread(); #endif LVariant v; if (d->HBox) d->Options.SetValue(OPT_SPLIT_PX, v = d->HBox->Value()); if (d->Output) d->Options.SetValue(OPT_OUTPUT_PX, v = d->Output->Y()); d->Options.SetValue(OPT_PLATFORM, v = d->Platform); ShutdownFtpThread(); LAppInst->AppWnd = NULL; DeleteObj(d); } void AppWnd::OnPulse() { IdeDoc *Top = TopDoc(); if (Top) Top->OnPulse(); if (d->FixBuildWait) { d->FixBuildWait = false; if (OnFixBuildErrors() > 0) d->RebuildWait = 3; } else if (d->RebuildWait > 0) { if (--d->RebuildWait == 0) Build(); } for (auto n: NeedsPulse) n->OnPulse(); } LDebugContext *AppWnd::GetDebugContext() { return d->DbgContext; } struct DumpBinThread : public LThread { LStream *Out; LString InFile; bool IsLib; public: DumpBinThread(LStream *out, LString file) : LThread("DumpBin.Thread") { Out = out; InFile = file; DeleteOnExit = true; auto Ext = LGetExtension(InFile); IsLib = Ext && !stricmp(Ext, "lib"); Run(); } bool DumpBin(LString Args, LStream *Str) { char Buf[256]; ssize_t Rd; const char *Prog = "c:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\bin\\dumpbin.exe"; LSubProcess s(Prog, Args); if (!s.Start(true, false)) { Out->Print("%s:%i - '%s' doesn't exist.\n", _FL, Prog); return false; } while ((Rd = s.Read(Buf, sizeof(Buf))) > 0) Str->Write(Buf, Rd); return true; } LString::Array Dependencies(const char *Executable, int Depth = 0) { LString Args; LStringPipe p; Args.Printf("/dependents \"%s\"", Executable); DumpBin(Args, &p); char Spaces[256]; int Len = Depth * 2; memset(Spaces, ' ', Len); Spaces[Len] = 0; LString::Array Files; auto Parts = p.NewLStr().Replace("\r", "").Split("\n\n"); if (Parts.Length() > 0) { Files = Parts[4].Strip().Split("\n"); auto Path = LGetPath(); for (size_t i=0; i= 0) { auto p = Ln.Strip().Split(Key); if (p.Length() == 2) { Arch = p[1].Strip("()"); Machine = p[0].Int(16); } } } if (Machine == 0x14c) Arch += " 32bit"; else if (Machine == 0x200) Arch += " 64bit Itanium"; else if (Machine == 0x8664) Arch += " 64bit"; return Arch; } LString GetExports() { LString Args; LStringPipe p; /* if (IsLib) Args.Printf("/symbols \"%s\"", InFile.Get()); else */ Args.Printf("/exports \"%s\"", InFile.Get()); DumpBin(Args, &p); LString Exp; auto Raw = p.NewLStr().Replace("\r", ""); auto Sect = Raw.Split("\n\n"); #if 0 // Debug output Exp = Raw; #else if (IsLib) { for (auto &s : Sect) { if (s.Find("COFF/PE Dumper") >= 0) continue; auto ln = s.Split("\n"); if (ln.Length() == 1) continue; for (auto &l: ln) l = l.LStrip(); Exp = LString("\n").Join(ln); break; } } else { bool Ord = false; for (auto &s : Sect) { if (s.Strip().Find("ordinal") == 0) Ord = true; else if (Ord) { Exp = s; break; } else Ord = false; } } #endif return Exp; } int Main() { if (!IsLib) { auto Deps = Dependencies(InFile); if (Deps.Length()) Out->Print("Dependencies:\n\t%s\n\n", LString("\n\t").Join(Deps).Get()); } auto Arch = GetArch(); if (Arch) Out->Print("Arch: %s\n\n", Arch.Get()); auto Exp = GetExports(); if (Arch) Out->Print("Exports:\n%s\n\n", Exp.Get()); return 0; } }; void AppWnd::OnReceiveFiles(LArray &Files) { for (int i=0; iSetFileName(Docs, false); new DumpBinThread(Doc, Files[i]); } } else { OpenFile(f); } } Raise(); if (LAppInst->GetOption("createMakeFiles")) { IdeProject *p = RootProject(); if (p) { p->CreateMakefile(PlatformCurrent, false); } } } void AppWnd::OnDebugState(bool Debugging, bool Running) { // Make sure this event is processed in the GUI thread. #if DEBUG_SESSION_LOGGING LgiTrace("AppWnd::OnDebugState(%i,%i) InThread=%i\n", Debugging, Running, InThread()); #endif PostEvent(M_DEBUG_ON_STATE, Debugging, Running); } bool IsVarChar(LString &s, ssize_t pos) { if (pos < 0) return false; if (pos >= s.Length()) return false; char i = s[pos]; return IsAlpha(i) || IsDigit(i) || i == '_'; } bool ReplaceWholeWord(LString &Ln, LString Word, LString NewWord) { ssize_t Pos = 0; bool Status = false; while (Pos >= 0) { Pos = Ln.Find(Word, Pos); if (Pos < 0) return Status; ssize_t End = Pos + Word.Length(); if (!IsVarChar(Ln, Pos-1) && !IsVarChar(Ln, End)) { LString NewLn = Ln(0,Pos) + NewWord + Ln(End,-1); Ln = NewLn; Status = true; } Pos++; } return Status; } struct LFileInfo { LString Path; LString::Array Lines; bool Dirty; LFileInfo() { Dirty = false; } bool Save() { LFile f; if (!f.Open(Path, O_WRITE)) return false; LString NewFile = LString("\n").Join(Lines); f.SetSize(0); f.Write(NewFile); f.Close(); return true; } }; int AppWnd::OnFixBuildErrors() { LHashTbl, LString> Map; LVariant v; if (GetOptions()->GetValue(OPT_RENAMED_SYM, v)) { auto Lines = LString(v.Str()).Split("\n"); for (auto Ln: Lines) { auto p = Ln.SplitDelimit(); if (p.Length() == 2) Map.Add(p[0], p[1]); } } LString Raw = d->Output->Txt[AppWnd::BuildTab]->Name(); LString::Array Lines = Raw.Split("\n"); auto *Log = d->Output->Txt[AppWnd::OutputTab]; Log->Name(NULL); Log->Print("Parsing errors...\n"); int Replacements = 0; LArray Files; LHashTbl,bool> FixHistory; for (int Idx=0; Idx= 0) { #ifdef WINDOWS LString::Array p = Ln.SplitDelimit(">()"); #else LString::Array p = Ln(0, ErrPos).Strip().SplitDelimit(":"); #endif if (p.Length() <= 2) { Log->Print("Error: Only %i parts? '%s'\n", (int)p.Length(), Ln.Get()); } else { #ifdef WINDOWS int Base = p[0].IsNumeric() ? 1 : 0; LString Fn = p[Base]; if (Fn.Find("Program Files") >= 0) { Log->Print("Is prog file\n"); continue; } auto LineNo = p[Base+1].Int(); bool FileNotFound = Ln.Find("Cannot open include file:") > 0; #else LString Fn = p[0]; auto LineNo = p[1].Int(); bool FileNotFound = false; // fixme #endif LAutoString Full; if (!d->FindSource(Full, Fn, NULL)) { Log->Print("Error: Can't find Fn='%s' Line=%i\n", Fn.Get(), (int)LineNo); continue; } LFileInfo *Fi = NULL; for (auto &i: Files) { if (i.Path.Equals(Full)) { Fi = &i; break; } } if (!Fi) { LFile f(Full, O_READ); if (f.IsOpen()) { Fi = &Files.New(); Fi->Path = Full.Get(); auto OldFile = f.Read(); Fi->Lines = OldFile.SplitDelimit("\n", -1, false); } else { Log->Print("Error: Can't open '%s'\n", Full.Get()); } } if (Fi) { LString Loc; Loc.Printf("%s:%i", Full.Get(), (int)LineNo); if (FixHistory.Find(Loc)) { // Log->Print("Already fixed %s\n", Loc.Get()); } else if (LineNo <= Fi->Lines.Length()) { FixHistory.Add(Loc, true); if (FileNotFound) { auto n = p.Last().SplitDelimit("\'"); auto wrongName = n[1]; LFile f(Full, O_READ); auto Lines = f.Read().SplitDelimit("\n", -1, false); f.Close(); if (LineNo <= Lines.Length()) { auto &errLine = Lines[LineNo-1]; auto Pos = errLine.Find(wrongName); /* if (Pos < 0) { for (int i=0; iPrint("[%i]=%s\n", i, Lines[i].Get()); } */ if (Pos > 0) { // Find where it went... LString newPath; for (auto p: d->Projects) { const char *SubStr[] = { ".", "lgi/common" }; LArray IncPaths; if (p->BuildIncludePaths(IncPaths, true, false, PlatformCurrent)) { for (auto &inc: IncPaths) { for (int sub=0; !newPath && subPrint("Already changed '%s'.\n", wrongName.Get()); } else { LString backup = LString(Full.Get()) + ".orig"; if (LFileExists(backup)) FileDev->Delete(backup); LError Err; if (FileDev->Move(Full, backup, &Err)) { errLine = newLine; LString newLines = LString("\n").Join(Lines); LFile out(Full, O_WRITE); out.Write(newLines); Log->Print("Fixed '%s'->'%s' on ln %i in %s\n", wrongName.Get(), newPath.Get(), (int)LineNo, Full.Get()); Replacements++; } else Log->Print("Error: moving '%s' to backup (%s).\n", Full.Get(), Err.GetMsg().Get()); } } else Log->Print("Error: Missing header '%s'.\n", wrongName.Get()); } else { Log->Print("Error: '%s' not found in line %i of '%s' -> '%s'\n", wrongName.Get(), (int)LineNo, Fn.Get(), Full.Get()); // return; } } else Log->Print("Error: Line %i is beyond file lines: %i\n", (int)LineNo, (int)Lines.Length()); } else { auto OldReplacements = Replacements; for (auto i: Map) { for (int Offset = 0; (LineNo + Offset >= 1) && Offset >= -1; Offset--) { LString &s = Fi->Lines[LineNo+Offset-1]; if (ReplaceWholeWord(s, i.key, i.value)) { Log->Print("Renamed '%s' -> '%s' at %s:%i\n", i.key, i.value.Get(), Full.Get(), LineNo+Offset); Fi->Dirty = true; Replacements++; Offset = -2; } } } if (OldReplacements == Replacements && Ln.Find("syntax error: id") > 0) { Log->Print("Unhandled: %s\n", Ln.Get()); } } } else { Log->Print("Error: Invalid line %i\n", (int)LineNo); } } else { Log->Print("Error: Fi is NULL\n"); } } } } for (auto &Fi : Files) { if (Fi.Dirty) Fi.Save(); } Log->Print("%i replacements made.\n", Replacements); if (Replacements > 0) d->Output->Value(AppWnd::OutputTab); return Replacements; } void AppWnd::OnBuildStateChanged(bool NewState) { LVariant v; if (!NewState && GetOptions()->GetValue(OPT_FIX_RENAMED, v) && v.CastInt32()) { d->FixBuildWait = true; } } void AppWnd::UpdateState(int Debugging, int Building) { // printf("UpdateState %i %i\n", Debugging, Building); if (Debugging >= 0) d->Debugging = Debugging; if (Building >= 0) { if (d->Building != (Building != 0)) OnBuildStateChanged(Building); d->Building = Building; } SetCtrlEnabled(IDM_COMPILE, !d->Building); SetCtrlEnabled(IDM_BUILD, !d->Building); SetCtrlEnabled(IDM_STOP_BUILD, d->Building); // SetCtrlEnabled(IDM_RUN, !d->Building); // SetCtrlEnabled(IDM_TOGGLE_BREAKPOINT, !d->Building); SetCtrlEnabled(IDM_START_DEBUG, !d->Debugging && !d->Building); SetCtrlEnabled(IDM_PAUSE_DEBUG, d->Debugging); SetCtrlEnabled(IDM_RESTART_DEBUGGING, d->Debugging); SetCtrlEnabled(IDM_STOP_DEBUG, d->Debugging); SetCtrlEnabled(IDM_STEP_INTO, d->Debugging); SetCtrlEnabled(IDM_STEP_OVER, d->Debugging); SetCtrlEnabled(IDM_STEP_OUT, d->Debugging); SetCtrlEnabled(IDM_RUN_TO, d->Debugging); } void AppWnd::AppendOutput(char *Txt, AppWnd::Channels Channel) { if (!d->Output) { LgiTrace("%s:%i - No output panel.\n", _FL); return; } if (Channel < 0 || Channel >= CountOf(d->Output->Txt)) { LgiTrace("%s:%i - Channel range: %i, %i.\n", _FL, Channel, CountOf(d->Output->Txt)); return; } if (!d->Output->Txt[Channel]) { LgiTrace("%s:%i - No log for channel %i.\n", _FL, Channel); return; } if (Txt) { d->Output->Buf[Channel].Add(Txt, strlen(Txt)); } else { auto Ctrl = d->Output->Txt[Channel]; Ctrl->UnSelectAll(); Ctrl->Name(""); } } int AppWnd::GetPlatform() { return d->Platform; } bool AppWnd::SetPlatform(int p) { if (p == 0) p = PLATFORM_CURRENT; if (d->Platform == p) return false; d->Platform = p; if (auto m = GetMenu()) { bool all = d->Platform == PLATFORM_ALL; #define SET_CHK(id, flag) \ { auto mi = m->FindItem(id); \ if (mi) mi->Checked((d->Platform & flag) && !all); } SET_CHK(IDM_WIN, PLATFORM_WIN32); SET_CHK(IDM_LINUX, PLATFORM_LINUX); SET_CHK(IDM_MAC, PLATFORM_MAC); SET_CHK(IDM_HAIKU, PLATFORM_HAIKU); auto mi = m->FindItem(IDM_ALL_OS); if (mi) mi->Checked(all); } return true; } bool AppWnd::IsClean() { for (auto Doc: d->Docs) { if (!Doc->GetClean()) return false; } for (auto Proj: d->Projects) { if (!Proj->GetClean()) return false; } return true; } struct SaveState { AppWndPrivate *d = NULL; LArray Docs; LArray Projects; std::function Callback; bool Status = true; bool CloseDirty = false; void Iterate() { if (Docs.Length()) { auto doc = Docs[0]; Docs.DeleteAt(0); // printf("Saving doc...\n"); doc->SetClean([this, doc](bool ok) { // printf("SetClean cb ok=%i\n", ok); if (ok) d->OnFile(doc->GetFileName()); else { if (CloseDirty) delete doc; Status = false; } // printf("SetClean cb iter\n", ok); Iterate(); }); } else if (Projects.Length()) { auto proj = Projects[0]; Projects.DeleteAt(0); // printf("Saving proj...\n"); proj->SetClean([this, proj](bool ok) { if (ok) d->OnFile(proj->GetFileName(), true); else { if (CloseDirty) delete proj; Status = false; } Iterate(); }); } else { // printf("Doing callback...\n"); if (Callback) Callback(Status); // printf("Deleting...\n"); delete this; } } }; void AppWnd::SaveAll(std::function Callback, bool CloseDirty) { auto ss = new SaveState; ss->d = d; ss->Callback = Callback; ss->CloseDirty = CloseDirty; for (auto Doc: d->Docs) { if (!Doc->GetClean()) ss->Docs.Add(Doc); } for (auto Proj: d->Projects) { if (!Proj->GetClean()) ss->Projects.Add(Proj); } ss->Iterate(); } void AppWnd::CloseAll() { SaveAll([&](auto status) { if (!status) { LgiTrace("%s:%i - status=%i\n", _FL, status); return; } while (d->Docs[0]) delete d->Docs[0]; IdeProject *p = RootProject(); if (p) DeleteObj(p); while (d->Projects[0]) delete d->Projects[0]; DeleteObj(d->DbgContext); }); } bool AppWnd::OnRequestClose(bool IsOsQuit) { if (!IsClean()) { SaveAll([](bool status) { LCloseApp(); }, true); return false; } else { return LWindow::OnRequestClose(IsOsQuit); } } bool AppWnd::OnBreakPoint(LDebugger::BreakPoint &b, bool Add) { List::I it = d->Docs.begin(); for (IdeDoc *doc = *it; doc; doc = *++it) { auto fn = doc->GetFileName(); bool Match = !Stricmp(fn, b.File.Get()); if (Match) doc->AddBreakPoint(b.Line, Add); } if (d->DbgContext) d->DbgContext->OnBreakPoint(b, Add); return true; } bool AppWnd::LoadBreakPoints(IdeDoc *doc) { if (!doc) return false; auto fn = doc->GetFileName(); for (int i=0; iBreakPoints.Length(); i++) { LDebugger::BreakPoint &b = d->BreakPoints[i]; if (!_stricmp(fn, b.File)) { doc->AddBreakPoint(b.Line, true); } } return true; } bool AppWnd::LoadBreakPoints(LDebugger *db) { if (!db) return false; for (int i=0; iBreakPoints.Length(); i++) { LDebugger::BreakPoint &bp = d->BreakPoints[i]; db->SetBreakPoint(&bp); } return true; } bool AppWnd::ToggleBreakpoint(const char *File, ssize_t Line) { bool DeleteBp = false; for (int i=0; iBreakPoints.Length(); i++) { LDebugger::BreakPoint &b = d->BreakPoints[i]; if (!_stricmp(File, b.File) && b.Line == Line) { OnBreakPoint(b, false); d->BreakPoints.DeleteAt(i); DeleteBp = true; break; } } if (!DeleteBp) { LDebugger::BreakPoint &b = d->BreakPoints.New(); b.File = File; b.Line = Line; OnBreakPoint(b, true); } return true; } void AppWnd::DumpHistory() { #if 0 LgiTrace("History %i of %i\n", d->HistoryLoc, d->CursorHistory.Length()); for (int i=0; iCursorHistory.Length(); i++) { FileLoc &p = d->CursorHistory[i]; LgiTrace(" [%i] = %s, %i %s\n", i, p.File.Get(), p.Line, d->HistoryLoc == i ? "<-----":""); } #endif } /* void CheckHistory(LArray &CursorHistory) { if (CursorHistory.Length() > 0) { FileLoc *loc = &CursorHistory[0]; for (unsigned i=CursorHistory.Length(); iInHistorySeek) { if (d->CursorHistory.Length() > 0) { FileLoc &Last = d->CursorHistory.Last(); if (_stricmp(File, Last.File) == 0 && abs(Last.Line - Line) <= 1) { // Previous or next line... just update line number Last.Line = Line; DumpHistory(); return; } // Add new entry d->HistoryLoc++; FileLoc &loc = d->CursorHistory[d->HistoryLoc]; #ifdef WIN64 if ((NativeInt)loc.File.Get() == 0xcdcdcdcdcdcdcdcd) LAssert(0); // wtf? else #endif loc.Set(File, Line); } else { // Add new entry d->CursorHistory[0].Set(File, Line); } // Destroy any history after the current... d->CursorHistory.Length(d->HistoryLoc+1); DumpHistory(); } } void AppWnd::OnFile(char *File, bool IsProject) { d->OnFile(File, IsProject); } IdeDoc *AppWnd::NewDocWnd(const char *FileName, NodeSource *Src) { IdeDoc *Doc = new IdeDoc(this, Src, 0); if (Doc) { d->Docs.Insert(Doc); LRect p = d->Mdi->NewPos(); Doc->LView::SetPos(p); Doc->Attach(d->Mdi); Doc->Focus(true); Doc->Raise(); if (FileName) d->OnFile(FileName); } return Doc; } IdeDoc *AppWnd::GetCurrentDoc() { if (d->Mdi) return dynamic_cast(d->Mdi->GetTop()); return NULL; } IdeDoc *AppWnd::GotoReference(const char *File, int Line, bool CurIp, bool WithHistory) { if (!WithHistory) d->InHistorySeek = true; IdeDoc *Doc = File ? OpenFile(File) : GetCurrentDoc(); if (Doc) { Doc->SetLine(Line, CurIp); Doc->Focus(true); } if (!WithHistory) d->InHistorySeek = false; return Doc; } IdeDoc *AppWnd::FindOpenFile(char *FileName) { List::I it = d->Docs.begin(); for (IdeDoc *i=*it; i; i=*++it) { auto f = i->GetFileName(); if (f) { IdeProject *p = i->GetProject(); if (p) { LAutoString Base = p->GetBasePath(); if (Base) { char Path[MAX_PATH_LEN]; if (*f == '.') LMakePath(Path, sizeof(Path), Base, f); else strcpy_s(Path, sizeof(Path), f); if (stricmp(Path, FileName) == 0) return i; } } else { if (stricmp(f, FileName) == 0) return i; } } } return 0; } IdeDoc *AppWnd::OpenFile(const char *FileName, NodeSource *Src) { static bool DoingProjectFind = false; IdeDoc *Doc = 0; const char *File = Src ? Src->GetFileName() : FileName; if (!Src && !ValidStr(File)) { LgiTrace("%s:%i - No source or file?\n", _FL); return NULL; } LString FullPath; if (LIsRelativePath(File)) { IdeProject *Proj = Src && Src->GetProject() ? Src->GetProject() : RootProject(); if (Proj) { List Projs; Projs.Insert(Proj); Proj->CollectAllSubProjects(Projs); for (auto p: Projs) { auto ProjPath = p->GetBasePath(); char s[MAX_PATH_LEN]; LMakePath(s, sizeof(s), ProjPath, File); LString Path = s; if (p->CheckExists(Path)) { FullPath = Path; File = FullPath; break; } } } } // Sniff type... bool probablyLgiProj = false; if (!Stricmp(LGetExtension(File), "xml")) { LFile f(File, O_READ); if (f) { char buf[256]; auto rd = f.Read(buf, sizeof(buf)); if (rd > 0) probablyLgiProj = Strnistr(buf, "IsFileOpen(File); if (!Doc) { if (Src) { Doc = NewDocWnd(File, Src); } else if (!DoingProjectFind) { DoingProjectFind = true; List::I Proj = d->Projects.begin(); for (IdeProject *p=*Proj; p && !Doc; p=*++Proj) { p->InProject(LIsRelativePath(File), File, true, &Doc); } DoingProjectFind = false; d->OnFile(File); } } if (!Doc && LFileExists(File)) { Doc = new IdeDoc(this, 0, File); if (Doc) { Doc->OpenFile(File); LRect p = d->Mdi->NewPos(); Doc->LView::SetPos(p); d->Docs.Insert(Doc); d->OnFile(File); } } if (Doc) { Doc->SetEditorParams(4, 4, true, false); if (!Doc->IsAttached()) { Doc->Attach(d->Mdi); } Doc->Focus(true); Doc->Raise(); } return Doc; } IdeProject *AppWnd::RootProject() { for (auto p: d->Projects) if (!p->GetParentProject()) return p; return NULL; } IdeProject *AppWnd::OpenProject(const char *FileName, IdeProject *ParentProj, bool Create, bool Dep) { if (!FileName) { LgiTrace("%s:%i - Error: No filename.\n", _FL); return NULL; } if (d->IsProjectOpen(FileName)) { LgiTrace("%s:%i - Warning: Project already open.\n", _FL); return NULL; } IdeProject *p = new IdeProject(this); if (!p) { LgiTrace("%s:%i - Error: mem alloc.\n", _FL); return NULL; } LString::Array Inc; p->BuildIncludePaths(Inc, false, false, PlatformCurrent); d->FindSym->SetIncludePaths(Inc); p->SetParentProject(ParentProj); ProjectStatus Status = p->OpenFile(FileName); if (Status == OpenOk) { d->Projects.Insert(p); d->OnFile(FileName, true); if (!Dep) { auto d = strrchr(FileName, DIR_CHAR); if (d++) { char n[256]; sprintf(n, "%s [%s]", AppName, d); Name(n); } } } else { LgiTrace("%s:%i - Failed to open '%s'\n", _FL, FileName); DeleteObj(p); if (Status == OpenError) d->RemoveRecent(FileName); } if (!GetTree()->Selection()) { GetTree()->Select(GetTree()->GetChild()); } GetTree()->Focus(true); return p; } LMessage::Result AppWnd::OnEvent(LMessage *m) { switch (m->Msg()) { case M_MAKEFILES_CREATED: { IdeProject *p = (IdeProject*)m->A(); if (p) p->OnMakefileCreated(); break; } case M_LAST_MAKEFILE_CREATED: { if (LAppInst->GetOption("exit")) LCloseApp(); break; } case M_START_BUILD: { IdeProject *p = RootProject(); if (p) p->Build(true, GetBuildMode()); else printf("%s:%i - No root project.\n", _FL); break; } case M_BUILD_DONE: { UpdateState(-1, false); IdeProject *p = RootProject(); if (p) p->StopBuild(); break; } case M_BUILD_ERR: { char *Msg = (char*)m->B(); if (Msg) { d->Output->Txt[AppWnd::BuildTab]->Print("Build Error: %s\n", Msg); DeleteArray(Msg); } break; } case M_APPEND_TEXT: { LAutoString Text((char*) m->A()); Channels Ch = (Channels) m->B(); AppendOutput(Text, Ch); break; } case M_SELECT_TAB: { if (!d->Output) break; d->Output->Value(m->A()); break; } case M_DEBUG_ON_STATE: { bool Debugging = m->A(); bool Running = m->B(); if (d->Running != Running) { bool RunToNotRun = d->Running && !Running; d->Running = Running; if (RunToNotRun && d->Output && d->Output->DebugTab) { d->Output->DebugTab->SendNotify(LNotifyValueChanged); } } if (d->Debugging != Debugging) { d->Debugging = Debugging; if (!Debugging) { IdeDoc::ClearCurrentIp(); IdeDoc *c = GetCurrentDoc(); if (c) c->UpdateControl(); // Shutdown the debug context and free the memory DeleteObj(d->DbgContext); } } SetCtrlEnabled(IDM_START_DEBUG, !Debugging || !Running); SetCtrlEnabled(IDM_PAUSE_DEBUG, Debugging && Running); SetCtrlEnabled(IDM_RESTART_DEBUGGING, Debugging); SetCtrlEnabled(IDM_STOP_DEBUG, Debugging); SetCtrlEnabled(IDM_STEP_INTO, Debugging && !Running); SetCtrlEnabled(IDM_STEP_OVER, Debugging && !Running); SetCtrlEnabled(IDM_STEP_OUT, Debugging && !Running); SetCtrlEnabled(IDM_RUN_TO, Debugging && !Running); break; } default: { if (d->DbgContext) d->DbgContext->OnEvent(m); break; } } return LWindow::OnEvent(m); } bool AppWnd::OnNode(const char *Path, ProjectNode *Node, FindSymbolSystem::SymAction Action) { // This takes care of adding/removing files from the symbol search engine. if (!Path || !Node) return false; if (d->FindSym) d->FindSym->OnFile(Path, Action, Node->GetPlatforms()); return true; } LOptionsFile *AppWnd::GetOptions() { return &d->Options; } class Options : public LDialog { AppWnd *App; LFontType Font; public: Options(AppWnd *a) { SetParent(App = a); if (LoadFromResource(IDD_OPTIONS)) { SetCtrlEnabled(IDC_FONT, false); MoveToCenter(); if (!Font.Serialize(App->GetOptions(), OPT_EditorFont, false)) { Font.GetSystemFont("Fixed"); } char s[256]; if (Font.GetDescription(s, sizeof(s))) { SetCtrlName(IDC_FONT, s); } LVariant v; if (App->GetOptions()->GetValue(OPT_Jobs, v)) SetCtrlValue(IDC_JOBS, v.CastInt32()); else SetCtrlValue(IDC_JOBS, 2); } } int OnNotify(LViewI *c, LNotification n) { switch (c->GetId()) { case IDOK: { LVariant v; Font.Serialize(App->GetOptions(), OPT_EditorFont, true); App->GetOptions()->SetValue(OPT_Jobs, v = GetCtrlValue(IDC_JOBS)); // Fall through.. } case IDCANCEL: { EndModal(c->GetId()); break; } case IDC_SET_FONT: { Font.DoUI(this, [&](auto ui) { char s[256]; if (Font.GetDescription(s, sizeof(s))) { SetCtrlName(IDC_FONT, s); } }); break; } } return 0; } }; void AppWnd::UpdateMemoryDump() { if (d->DbgContext) { const char *sWord = GetCtrlName(IDC_MEM_SIZE); int iWord = sWord ? atoi(sWord) : 1; int64 RowLen = GetCtrlValue(IDC_MEM_ROW_LEN); bool InHex = GetCtrlValue(IDC_MEM_HEX) != 0; d->DbgContext->FormatMemoryDump(iWord, (int)RowLen, InHex); } } int AppWnd::OnNotify(LViewI *Ctrl, LNotification n) { switch (Ctrl->GetId()) { case IDC_PROJECT_TREE: { if (n.Type == LNotifyDeleteKey) { ProjectNode *n = dynamic_cast(d->Tree->Selection()); if (n) n->Delete(); } break; } case IDC_DEBUG_EDIT: { if (n.Type == LNotifyReturnKey && d->DbgContext) { const char *Cmd = Ctrl->Name(); if (Cmd) { d->DbgContext->OnUserCommand(Cmd); Ctrl->Name(NULL); } } break; } case IDC_MEM_ADDR: { if (n.Type == LNotifyReturnKey) { if (d->DbgContext) { const char *s = Ctrl->Name(); if (s) { auto sWord = GetCtrlName(IDC_MEM_SIZE); int iWord = sWord ? atoi(sWord) : 1; d->DbgContext->OnMemoryDump(s, iWord, (int)GetCtrlValue(IDC_MEM_ROW_LEN), GetCtrlValue(IDC_MEM_HEX) != 0); } else if (d->DbgContext->MemoryDump) { d->DbgContext->MemoryDump->Print("No address specified."); } else { LAssert(!"No MemoryDump."); } } else LAssert(!"No debug context."); } break; } case IDC_MEM_ROW_LEN: { if (n.Type == LNotifyReturnKey) UpdateMemoryDump(); break; } case IDC_MEM_HEX: case IDC_MEM_SIZE: { UpdateMemoryDump(); break; } case IDC_DEBUG_TAB: { if (d->DbgContext && n.Type == LNotifyValueChanged) { switch (Ctrl->Value()) { case AppWnd::LocalsTab: { d->DbgContext->UpdateLocals(); break; } case AppWnd::WatchTab: { d->DbgContext->UpdateWatches(); break; } case AppWnd::RegistersTab: { d->DbgContext->UpdateRegisters(); break; } case AppWnd::CallStackTab: { d->DbgContext->UpdateCallStack(); break; } case AppWnd::ThreadsTab: { d->DbgContext->UpdateThreads(); break; } default: break; } } break; } case IDC_LOCALS_LIST: { if (d->Output->Locals && n.Type == LNotifyItemDoubleClick && d->DbgContext) { LListItem *it = d->Output->Locals->GetSelected(); if (it) { const char *Var = it->GetText(2); const char *Val = it->GetText(3); if (Var) { if (d->Output->DebugTab) d->Output->DebugTab->Value(AppWnd::ObjectTab); d->DbgContext->DumpObject(Var, Val); } } } break; } case IDC_CALL_STACK: { if (n.Type == LNotifyValueChanged) { if (d->Output->DebugTab) d->Output->DebugTab->Value(AppWnd::CallStackTab); } else if (n.Type == LNotifyItemSelect) { // This takes the user to a given call stack reference if (d->Output->CallStack && d->DbgContext) { LListItem *item = d->Output->CallStack->GetSelected(); if (item) { LAutoString File; int Line; if (d->DbgContext->ParseFrameReference(item->GetText(1), File, Line)) { LAutoString Full; if (d->FindSource(Full, File, NULL)) { GotoReference(Full, Line, false); const char *sFrame = item->GetText(0); if (sFrame && IsDigit(*sFrame)) d->DbgContext->SetFrame(atoi(sFrame)); } } } } } break; } case IDC_WATCH_LIST: { WatchItem *Edit = NULL; switch (n.Type) { case LNotifyDeleteKey: { LArray Sel; for (LTreeItem *c = d->Output->Watch->GetChild(); c; c = c->GetNext()) { if (c->Select()) Sel.Add(c); } Sel.DeleteObjects(); break; } case LNotifyItemClick: { Edit = dynamic_cast(d->Output->Watch->Selection()); break; } case LNotifyContainerClick: { // Create new watch. Edit = new WatchItem(d->Output); if (Edit) d->Output->Watch->Insert(Edit); break; } default: break; } if (Edit) Edit->EditLabel(0); break; } case IDC_THREADS: { if (n.Type == LNotifyItemSelect) { // This takes the user to a given thread if (d->Output->Threads && d->DbgContext) { LListItem *item = d->Output->Threads->GetSelected(); if (item) { LString sId = item->GetText(0); int ThreadId = (int)sId.Int(); if (ThreadId > 0) { d->DbgContext->SelectThread(ThreadId); } } } } break; } } return 0; } bool AppWnd::Build() { SaveAll([&](bool status) { if (!status) { LgiTrace("%s:%i - status=%i\n", _FL); return; } IdeDoc *Top; IdeProject *p = RootProject(); if (p) { UpdateState(-1, true); p->Build(false, GetBuildMode()); } else if ((Top = TopDoc())) { Top->Build(); } }); return false; } class RenameDlg : public LDialog { AppWnd *App; public: static RenameDlg *Inst; RenameDlg(AppWnd *a) { Inst = this; SetParent(App = a); MoveSameScreen(a); if (LoadFromResource(IDC_RENAME)) { LVariant v; if (App->GetOptions()->GetValue(OPT_FIX_RENAMED, v)) SetCtrlValue(IDC_FIX_RENAMED, v.CastInt32()); if (App->GetOptions()->GetValue(OPT_RENAMED_SYM, v)) SetCtrlName(IDC_SYM, v.Str()); SetAlwaysOnTop(true); DoModeless(); } } ~RenameDlg() { Inst = NULL; } int OnNotify(LViewI *c, LNotification n) { switch (c->GetId()) { case IDC_APPLY: { LVariant v; App->GetOptions()->SetValue(OPT_RENAMED_SYM, v = GetCtrlName(IDC_SYM)); App->GetOptions()->SetValue(OPT_FIX_RENAMED, v = GetCtrlValue(IDC_FIX_RENAMED)); App->GetOptions()->SerializeFile(true); break; } case IDC_CLOSE: { EndModeless(); break; } } return 0; } }; void AppWnd::SeekHistory(int Offset) { d->SeekHistory(Offset); } RenameDlg *RenameDlg::Inst = NULL; bool AppWnd::ShowInProject(const char *Fn) { if (!Fn) return false; for (auto p: d->Projects) { ProjectNode *Node = NULL; if (p->FindFullPath(Fn, &Node)) { for (LTreeItem *i = Node->GetParent(); i; i = i->GetParent()) { i->Expanded(true); } Node->Select(true); Node->ScrollTo(); return true; } } return false; } int AppWnd::OnCommand(int Cmd, int Event, OsView Wnd) { switch (Cmd) { case IDM_EXIT: { LCloseApp(); break; } case IDM_OPTIONS: { auto dlg = new Options(this); dlg->DoModal(NULL); break; } case IDM_HELP: { LExecute(APP_URL); break; } case IDM_ABOUT: { auto dlg = new LAbout( this, AppName, APP_VER, "\nLGI Integrated Development Environment", "icon128.png", APP_URL, "fret@memecode.com"); dlg->DoModal(NULL); break; } case IDM_NEW: { IdeDoc *Doc; d->Docs.Insert(Doc = new IdeDoc(this, 0, 0)); if (Doc && d->Mdi) { LRect p = d->Mdi->NewPos(); Doc->LView::SetPos(p); Doc->Attach(d->Mdi); Doc->Focus(true); } break; } case IDM_OPEN: { LFileSelect *s = new LFileSelect; s->Parent(this); // printf("File open dlg from thread=%u\n", GetCurrentThreadId()); s->Open([&](auto s, auto ok) { // printf("open handler start... ok=%i thread=%u\n", ok, GetCurrentThreadId()); if (ok) OpenFile(s->Name()); // printf("open handler deleting...\n"); delete s; // printf("open handler deleted...\n"); }); break; } case IDM_SAVE_ALL: { SaveAll(NULL); break; } case IDM_SAVE: { IdeDoc *Top = TopDoc(); if (Top) Top->SetClean(NULL); break; } case IDM_SAVEAS: { IdeDoc *Top = TopDoc(); if (Top) { LFileSelect *s = new LFileSelect; s->Parent(this); s->Save([&](auto s, auto ok) { Top->SetFileName(s->Name(), true); d->OnFile(s->Name()); delete s; }); } break; } case IDM_CLOSE: { IdeDoc *Top = TopDoc(); if (Top) { if (Top->OnRequestClose(false)) { Top->Quit(); } } DeleteObj(d->DbgContext); break; } case IDM_CLOSE_ALL: { CloseAll(); Name(AppName); break; } // // Editor // case IDM_UNDO: { LTextView3 *Doc = FocusEdit(); if (Doc) { Doc->Undo(); } else LgiTrace("%s:%i - No focus doc.\n", _FL); break; } case IDM_REDO: { LTextView3 *Doc = FocusEdit(); if (Doc) { Doc->Redo(); } else LgiTrace("%s:%i - No focus doc.\n", _FL); break; } case IDM_FIND: { LTextView3 *Doc = FocusEdit(); if (Doc) { Doc->DoFind(NULL); } else LgiTrace("%s:%i - No focus doc.\n", _FL); break; } case IDM_FIND_NEXT: { LTextView3 *Doc = FocusEdit(); if (Doc) { Doc->DoFindNext(NULL); } else LgiTrace("%s:%i - No focus doc.\n", _FL); break; } case IDM_REPLACE: { LTextView3 *Doc = FocusEdit(); if (Doc) { Doc->DoReplace(NULL); } else LgiTrace("%s:%i - No focus doc.\n", _FL); break; } case IDM_GOTO: { LTextView3 *Doc = FocusEdit(); if (Doc) Doc->DoGoto(NULL); else { LInput *Inp = new LInput(this, NULL, LLoadString(L_TEXTCTRL_GOTO_LINE, "Goto [file:]line:"), "Goto"); Inp->DoModal([Inp,this](auto dlg, auto code) { LString s = Inp->GetStr(); LString::Array p = s.SplitDelimit(":,"); if (p.Length() == 2) { LString file = p[0]; int line = (int)p[1].Int(); GotoReference(file, line, false, true); } else LgiMsg(this, "Error: Needs a file name as well.", AppName); delete dlg; }); } break; } case IDM_CUT: { LTextView3 *Doc = FocusEdit(); if (Doc) Doc->PostEvent(M_CUT); break; } case IDM_COPY: { LTextView3 *Doc = FocusEdit(); if (Doc) Doc->PostEvent(M_COPY); break; } case IDM_PASTE: { LTextView3 *Doc = FocusEdit(); if (Doc) Doc->PostEvent(M_PASTE); break; } case IDM_FIND_IN_FILES: { if (!d->Finder) { d->Finder.Reset(new FindInFilesThread(d->AppHnd)); } if (d->Finder) { if (!d->FindParameters && d->FindParameters.Reset(new FindParams)) { LVariant var; if (GetOptions()->GetValue(OPT_ENTIRE_SOLUTION, var)) d->FindParameters->Type = var.CastInt32() ? FifSearchSolution : FifSearchDirectory; } auto Dlg = new FindInFiles(this, d->FindParameters); LViewI *Focus = GetFocus(); if (Focus) { LTextView3 *Edit = dynamic_cast(Focus); if (Edit && Edit->HasSelection()) { LAutoString a(Edit->GetSelection()); Dlg->Params->Text = a; } } IdeProject *p = RootProject(); if (p) { LAutoString Base = p->GetBasePath(); if (Base) Dlg->Params->Dir = Base; } Dlg->DoModal([this, Dlg, p](auto dlg, auto code) { if (p && Dlg->Params->Type == FifSearchSolution) { Dlg->Params->ProjectFiles.Length(0); List Projects; Projects.Insert(p); p->GetChildProjects(Projects); LArray Nodes; for (auto p: Projects) p->GetAllNodes(Nodes); for (unsigned i=0; iGetFullPath(); if (s) Dlg->Params->ProjectFiles.Add(s); } } LVariant var = d->FindParameters->Type == FifSearchSolution; GetOptions()->SetValue(OPT_ENTIRE_SOLUTION, var); d->Finder->Stop(); if (d->FindParameters->Text) d->Finder->PostEvent(FindInFilesThread::M_START_SEARCH, (LMessage::Param) new FindParams(d->FindParameters)); delete Dlg; }); } break; } case IDM_FIND_SYMBOL: { IdeDoc *Doc = FocusDoc(); if (Doc) { Doc->GotoSearch(IDC_SYMBOL_SEARCH); } else { d->FindSym->OpenSearchDlg(this, [&](auto r) { if (r.File) GotoReference(r.File, r.Line, false); }); } break; } case IDM_GOTO_SYMBOL: { IdeDoc *Doc = FocusDoc(); if (Doc) { Doc->SearchSymbol(); } break; } case IDM_FIND_PROJECT_FILE: { IdeDoc *Doc = FocusDoc(); if (Doc) { Doc->SearchFile(); } else { auto d = new FindInProject(this); d->DoModal([](auto dlg, auto ctrlId){ delete dlg; }); } break; } case IDM_FIND_REFERENCES: { LViewI *f = LAppInst->GetFocus(); LDocView *doc = dynamic_cast(f); if (!doc) break; ssize_t c = doc->GetCaret(); if (c < 0) break; LString Txt = doc->Name(); char *s = Txt.Get() + c; char *e = s; while ( s > Txt.Get() && IsSymbolChar(s[-1])) s--; while (*e && IsSymbolChar(*e)) e++; if (e <= s) break; LString Word(s, e - s); if (!d->Finder) d->Finder.Reset(new FindInFilesThread(d->AppHnd)); if (!d->Finder) break; IdeProject *p = RootProject(); if (!p) break; List Projects; Projects.Insert(p); p->GetChildProjects(Projects); LArray Nodes; for (auto p: Projects) p->GetAllNodes(Nodes); LAutoPtr Params(new FindParams); Params->Type = FifSearchSolution; Params->MatchWord = true; Params->Text = Word; for (unsigned i = 0; i < Nodes.Length(); i++) { Params->ProjectFiles.New() = Nodes[i]->GetFullPath(); } d->Finder->Stop(); d->Finder->PostEvent(FindInFilesThread::M_START_SEARCH, (LMessage::Param) Params.Release()); break; } case IDM_PREV_LOCATION: { d->SeekHistory(-1); break; } case IDM_NEXT_LOCATION: { d->SeekHistory(1); break; } // // Project // case IDM_NEW_PROJECT: { CloseAll(); IdeProject *p; d->Projects.Insert(p = new IdeProject(this)); if (p) { p->CreateProject(); } break; } case IDM_NEW_PROJECT_TEMPLATE: { NewProjectFromTemplate(this); break; } case IDM_OPEN_PROJECT: { LFileSelect *s = new LFileSelect; s->Parent(this); s->Type("Projects", "*.xml"); s->Open([&](auto s, auto ok) { if (ok) { CloseAll(); OpenProject(s->Name(), NULL, Cmd == IDM_NEW_PROJECT); if (d->Tree) { d->Tree->Focus(true); } } delete s; }); break; } case IDM_IMPORT_DSP: { IdeProject *p = RootProject(); if (p) { LFileSelect *s = new LFileSelect; s->Parent(this); s->Type("Developer Studio Project", "*.dsp"); s->Open([&](auto s, auto ok) { if (ok) p->ImportDsp(s->Name()); delete s; }); } break; } case IDM_RUN: { SaveAll([&](bool status) { if (!status) { LgiTrace("%s:%i - status=%i\n", _FL, status); return; } IdeProject *p = RootProject(); if (p) p->Execute(); }); break; } case IDM_VALGRIND: { SaveAll([&](bool status) { if (!status) { LgiTrace("%s:%i - status=%i\n", _FL, status); return; } IdeProject *p = RootProject(); if (p) p->Execute(ExeValgrind); }); break; } case IDM_FIX_MISSING_FILES: { IdeProject *p = RootProject(); if (p) p->FixMissingFiles(); else LgiMsg(this, "No project loaded.", AppName); break; } case IDM_FIND_DUPE_SYM: { IdeProject *p = RootProject(); if (p) p->FindDuplicateSymbols(); else LgiMsg(this, "No project loaded.", AppName); break; } case IDM_RENAME_SYM: { if (!RenameDlg::Inst) new RenameDlg(this); break; } case IDM_START_DEBUG: { SaveAll([&](bool status) { if (!status) { LgiTrace("%s:%i - status=%i\n", _FL, status); return; } IdeProject *p = RootProject(); if (!p) { LgiMsg(this, "No project loaded.", "Error"); return; } LString ErrMsg; if (d->DbgContext) { d->DbgContext->OnCommand(IDM_CONTINUE); } else if ((d->DbgContext = p->Execute(ExeDebug, &ErrMsg))) { d->DbgContext->DebuggerLog = d->Output->DebuggerLog; d->DbgContext->Watch = d->Output->Watch; d->DbgContext->Locals = d->Output->Locals; d->DbgContext->CallStack = d->Output->CallStack; d->DbgContext->Threads = d->Output->Threads; d->DbgContext->ObjectDump = d->Output->ObjectDump; d->DbgContext->Registers = d->Output->Registers; d->DbgContext->MemoryDump = d->Output->MemoryDump; d->DbgContext->OnCommand(IDM_START_DEBUG); d->Output->Value(AppWnd::DebugTab); d->Output->DebugEdit->Focus(true); } else if (ErrMsg) { LgiMsg(this, "Error: %s", AppName, MB_OK, ErrMsg.Get()); } }); break; } case IDM_TOGGLE_BREAKPOINT: { IdeDoc *Cur = GetCurrentDoc(); if (Cur) ToggleBreakpoint(Cur->GetFileName(), Cur->GetLine()); break; } case IDM_ATTACH_TO_PROCESS: case IDM_PAUSE_DEBUG: case IDM_RESTART_DEBUGGING: case IDM_RUN_TO: case IDM_STEP_INTO: case IDM_STEP_OVER: case IDM_STEP_OUT: { if (d->DbgContext) d->DbgContext->OnCommand(Cmd); break; } case IDM_STOP_DEBUG: { if (d->DbgContext && d->DbgContext->OnCommand(Cmd)) { DeleteObj(d->DbgContext); } break; } case IDM_BUILD: { Build(); break; } case IDM_STOP_BUILD: { IdeProject *p = RootProject(); if (p) p->StopBuild(); break; } case IDM_CLEAN: { SaveAll([&](bool status) { if (!status) { LgiTrace("%s:%i - status=%i\n", _FL, status); return; } IdeProject *p = RootProject(); if (p) p->Clean(true, GetBuildMode()); }); break; } case IDM_NEXT_MSG: { d->SeekMsg(1); break; } case IDM_PREV_MSG: { d->SeekMsg(-1); break; } case IDM_DEBUG_MODE: { LMenuItem *Debug = GetMenu()->FindItem(IDM_DEBUG_MODE); LMenuItem *Release = GetMenu()->FindItem(IDM_RELEASE_MODE); if (Debug && Release) { Debug->Checked(true); Release->Checked(false); } break; } case IDM_RELEASE_MODE: { LMenuItem *Debug = GetMenu()->FindItem(IDM_DEBUG_MODE); LMenuItem *Release = GetMenu()->FindItem(IDM_RELEASE_MODE); if (Debug && Release) { Debug->Checked(false); Release->Checked(true); } break; } // // OS Platform // #define HANDLE_PLATFORM_ITEM(id, flag) \ case id: \ { \ auto mi = GetMenu()->FindItem(id); \ if (!mi) break; \ if (d->Platform == PLATFORM_ALL) d->Platform = 0; \ if (mi->Checked()) SetPlatform(d->Platform & ~flag); \ else SetPlatform(d->Platform | flag); \ break; \ } HANDLE_PLATFORM_ITEM(IDM_WIN, PLATFORM_WIN32) HANDLE_PLATFORM_ITEM(IDM_LINUX, PLATFORM_LINUX) HANDLE_PLATFORM_ITEM(IDM_MAC, PLATFORM_MAC) HANDLE_PLATFORM_ITEM(IDM_HAIKU, PLATFORM_HAIKU) case IDM_ALL_OS: { SetPlatform(PLATFORM_ALL); break; } // // Other // case IDM_LOOKUP_SYMBOLS: { IdeDoc *Cur = GetCurrentDoc(); if (Cur) { // LookupSymbols(Cur->Read()); } break; } case IDM_DEPENDS: { IdeProject *p = RootProject(); if (p) { LString Exe = p->GetExecutable(GetCurrentPlatform()); if (LFileExists(Exe)) { Depends Dlg(this, Exe); } else { LgiMsg(this, "Couldn't find '%s'\n", AppName, MB_OK, Exe ? Exe.Get() : ""); } } break; } case IDM_SP_TO_TAB: { IdeDoc *Doc = FocusDoc(); if (Doc) Doc->ConvertWhiteSpace(true); break; } case IDM_TAB_TO_SP: { IdeDoc *Doc = FocusDoc(); if (Doc) Doc->ConvertWhiteSpace(false); break; } case IDM_ESCAPE: { IdeDoc *Doc = FocusDoc(); if (Doc) Doc->EscapeSelection(true); break; } case IDM_DESCAPE: { IdeDoc *Doc = FocusDoc(); if (Doc) Doc->EscapeSelection(false); break; } case IDM_SPLIT: { IdeDoc *Doc = FocusDoc(); if (!Doc) break; LInput *i = new LInput(this, "", "Separator:", AppName); i->DoModal([&](auto dlg, auto ok) { if (ok) Doc->SplitSelection(i->GetStr()); delete i; }); break; } case IDM_JOIN: { IdeDoc *Doc = FocusDoc(); if (!Doc) break; LInput *i = new LInput(this, "", "Separator:", AppName); i->DoModal([&](auto dlg, auto ok) { if (ok) Doc->JoinSelection(i->GetStr()); delete i; }); break; } case IDM_EOL_LF: { IdeDoc *Doc = FocusDoc(); if (!Doc) break; Doc->SetCrLf(false); break; } case IDM_EOL_CRLF: { IdeDoc *Doc = FocusDoc(); if (!Doc) break; Doc->SetCrLf(true); break; } case IDM_LOAD_MEMDUMP: { NewMemDumpViewer(this); break; } case IDM_SYS_CHAR_SUPPORT: { new SysCharSupport(this); break; } default: { int index = Cmd - IDM_RECENT_FILE; auto r = d->RecentFiles.IdxCheck(index)? d->RecentFiles[index] : LString(); if (r) { auto idx = Cmd - IDM_RECENT_FILE; if (idx > 0) { d->RecentFiles.DeleteAt(idx, true); d->RecentFiles.AddAt(0, r); } IdeDoc *f = d->IsFileOpen(r); if (f) f->Raise(); else OpenFile(r); } index = Cmd - IDM_RECENT_PROJECT; auto p = d->RecentProjects.IdxCheck(index) ? d->RecentProjects[index] : LString(); if (p) { auto idx = Cmd - IDM_RECENT_PROJECT; if (idx > 0) { d->RecentProjects.DeleteAt(idx, true); d->RecentProjects.AddAt(0, p); } CloseAll(); OpenProject(p, NULL, false); if (d->Tree) { d->Tree->Focus(true); } } IdeDoc *Doc = d->Docs[Cmd - IDM_WINDOWS]; if (Doc) { Doc->Raise(); } IdePlatform PlatIdx = (IdePlatform) (Cmd - IDM_MAKEFILE_BASE); const char *Platform = PlatIdx >= 0 && PlatIdx < PlatformMax ? PlatformNames[Cmd - IDM_MAKEFILE_BASE] : NULL; if (Platform) { IdeProject *p = RootProject(); if (p) { p->CreateMakefile(PlatIdx, false); } } break; } } return 0; } LTree *AppWnd::GetTree() { return d->Tree; } IdeDoc *AppWnd::TopDoc() { return d->Mdi ? dynamic_cast(d->Mdi->GetTop()) : NULL; } LTextView3 *AppWnd::FocusEdit() { return dynamic_cast(GetWindow()->GetFocus()); } IdeDoc *AppWnd::FocusDoc() { IdeDoc *Doc = TopDoc(); if (Doc) { if (Doc->HasFocus()) { return Doc; } else { LViewI *f = GetFocus(); LgiTrace("%s:%i - Edit doesn't have focus, f=%p %s doc.edit=%s\n", _FL, f, f ? f->GetClass() : 0, Doc->Name()); } } return 0; } void AppWnd::OnProjectDestroy(IdeProject *Proj) { if (d) { auto locked = Lock(_FL); // printf("OnProjectDestroy(%s) %i\n", Proj->GetFileName(), locked); d->Projects.Delete(Proj); if (locked) Unlock(); } else LAssert(!"No priv"); } void AppWnd::OnProjectChange() { LArray Views; if (d->Mdi->GetChildren(Views)) { for (unsigned i=0; i(Views[i]); if (Doc) Doc->OnProjectChange(); } } } void AppWnd::OnDocDestroy(IdeDoc *Doc) { if (d) { auto locked = Lock(_FL); d->Docs.Delete(Doc); d->UpdateMenus(); if (locked) Unlock(); } else { LAssert(!"OnDocDestroy no priv...\n"); } } BuildConfig AppWnd::GetBuildMode() { LMenuItem *Release = GetMenu()->FindItem(IDM_RELEASE_MODE); if (Release && Release->Checked()) { return BuildRelease; } return BuildDebug; } LList *AppWnd::GetFtpLog() { return d->Output->FtpLog; } LStream *AppWnd::GetOutputLog() { return d->Output->Txt[AppWnd::OutputTab]; } LStream *AppWnd::GetBuildLog() { return d->Output->Txt[AppWnd::BuildTab]; } LStream *AppWnd::GetDebugLog() { return d->Output->Txt[AppWnd::DebugTab]; } void AppWnd::FindSymbol(int ResultsSinkHnd, const char *Sym) { d->FindSym->Search(ResultsSinkHnd, Sym, d->Platform); } bool AppWnd::GetSystemIncludePaths(::LArray &Paths) { if (d->SystemIncludePaths.Length() == 0) { #if !defined(WINNATIVE) // echo | gcc -v -x c++ -E - LSubProcess sp1("echo"); LSubProcess sp2("gcc", "-v -x c++ -E -"); sp1.Connect(&sp2); sp1.Start(true, false); char Buf[256]; ssize_t r; LStringPipe p; while ((r = sp1.Read(Buf, sizeof(Buf))) > 0) { p.Write(Buf, r); } bool InIncludeList = false; while (p.Pop(Buf, sizeof(Buf))) { if (stristr(Buf, "#include")) { InIncludeList = true; } else if (stristr(Buf, "End of search")) { InIncludeList = false; } else if (InIncludeList) { LAutoString a(TrimStr(Buf)); d->SystemIncludePaths.New() = a; } } #else char p[MAX_PATH_LEN]; LGetSystemPath(LSP_USER_DOCUMENTS, p, sizeof(p)); LMakePath(p, sizeof(p), p, "Visual Studio 2008\\Settings\\CurrentSettings.xml"); if (LFileExists(p)) { LFile f; if (f.Open(p, O_READ)) { LXmlTree t; LXmlTag r; if (t.Read(&r, &f)) { LXmlTag *Opts = r.GetChildTag("ToolsOptions"); if (Opts) { LXmlTag *Projects = NULL; char *Name; for (auto c: Opts->Children) { if (c->IsTag("ToolsOptionsCategory") && (Name = c->GetAttr("Name")) && !stricmp(Name, "Projects")) { Projects = c; break; } } LXmlTag *VCDirectories = NULL; if (Projects) for (auto c: Projects->Children) { if (c->IsTag("ToolsOptionsSubCategory") && (Name = c->GetAttr("Name")) && !stricmp(Name, "VCDirectories")) { VCDirectories = c; break; } } if (VCDirectories) for (auto prop: VCDirectories->Children) { if (prop->IsTag("PropertyValue") && (Name = prop->GetAttr("Name")) && !stricmp(Name, "IncludeDirectories")) { char *Bar = strchr(prop->GetContent(), '|'); LToken t(Bar ? Bar + 1 : prop->GetContent(), ";"); for (int i=0; iSystemIncludePaths.New().Reset(NewStr(s)); } } } } } } } #endif } for (int i=0; iSystemIncludePaths.Length(); i++) { Paths.Add(NewStr(d->SystemIncludePaths[i])); } return true; } /* class SocketTest : public LWindow, public LThread { LTextLog *Log; public: SocketTest() : LThread("SocketTest") { Log = new LTextLog(100); SetPos(LRect(200, 200, 900, 800)); Attach(0); Visible(true); Log->Attach(this); Run(); } int Main() { LSocket s; s.SetTimeout(15000); Log->Print("Starting...\n"); auto r = s.Open("192.168.1.30", 7000); Log->Print("Open =%i\n", r); return 0; } }; */ class TestView : public LView { public: TestView() { LRect r(10, 10, 110, 210); SetPos(r); Sunken(true); printf("_BorderSize=%i\n", _BorderSize); } void OnPaint(LSurface *pdc) { auto c = GetClient(); pdc->Colour(LColour::Red); pdc->Line(c.x1, c.y1, c.x2, c.y2); pdc->Ellipse(c.x1+(c.X()/2)+10, c.y1+(c.Y()/2), c.X()/2, c.Y()/2); } }; #include "lgi/common/Tree.h" #include "lgi/common/List.h" class Test : public LWindow { public: Test() { LRect r(100, 100, 800, 700); SetPos(r); Name("Test"); SetQuitOnClose(true); if (Attach(0)) { // AddView(new TestView); // auto t = new LTree(10, 10, 10, 100, 200); auto t = new LTextLabel(10, 10, 10, 100, 35, "Text"); AddView(t); AttachChildren(); Visible(true); } } }; int LgiMain(OsAppArguments &AppArgs) { printf("LgiIde v%s\n", APP_VER); LApp a(AppArgs, "LgiIde"); if (a.IsOk()) { - LPoint dpi = LScreenDpi(); a.AppWnd = new AppWnd; - // a.AppWnd->_Dump(); a.Run(); } return 0; } diff --git a/Ide/Code/SimpleCppParser.cpp b/Ide/Code/SimpleCppParser.cpp --- a/Ide/Code/SimpleCppParser.cpp +++ b/Ide/Code/SimpleCppParser.cpp @@ -1,982 +1,982 @@ /* Known bugs: 1) Duplicate brackets in #defines, e.g: #if SOME_DEF if (expr) { #else if (different_expr) { #endif Breaks the bracket counting. Causing the depth to get out of sync with capture... Workaround: Move the brackets outside of the #define'd area if possible. 2) This syntax is wrongly assumed to be a structure: struct netconn* netconn_alloc(enum netconn_type t, netconn_callback callback) { ... } Can't find these symbols: do_recv (in api_msg.c) process_apcp_msg (in ape-apcp.c) recv_udp (in api_msg.c) Wrong position: lwip_select (out by 4 lines?) nad_apcp_send tcpip_callback_with_block appears 3 times? */ #include "lgi/common/Lgi.h" #include "lgi/common/DocView.h" #include "ParserCommon.h" // #define DEBUG_FILE "Gdc2.h" // #define DEBUG_LINE 65 const char *TypeToStr(DefnType t) { switch (t) { default: case DefnNone: return "DefnNone"; case DefnDefine: return "DefnDefine"; case DefnFunc: return "DefnFunc"; case DefnClass: return "DefnClass"; case DefnEnum: return "DefnEnum"; case DefnEnumValue: return "DefnEnumValue"; case DefnTypedef: return "DefnTypedef"; case DefnVariable: return "DefnVariable"; } } bool IsFirst(LArray &a, int depth) { if (depth == 0) return true; for (int i=0; i 1) return false; return true; } bool IsFuncNameChar(char c) { return strchr("_:=~[]<>-+", c) || IsAlpha(c) || IsDigit(c); } bool ParseFunction(LRange &Return, LRange &Name, LRange &Args, const char *Defn) { if (!Defn) return false; int Depth = 0; const char *c, *Last = NULL; for (c = Defn; *c; c++) { if (*c == '(') { if (Depth == 0) Last = c; Depth++; } else if (*c == ')') { if (Depth > 0) Depth--; else { LgiTrace("%s:%i - Fn parse error '%s' (%i)\n", _FL, Defn, (int)(c - Defn)); return false; } } } if (!Last) return false; Args.Start = Last - Defn; Args.Len = c - Last; while (Last > Defn && IsWhiteSpace(Last[-1])) Last--; while (Last > Defn && IsFuncNameChar(Last[-1])) Last--; Return.Start = 0; Return.Len = Last - Defn; Name.Start = Last - Defn; Name.Len = Args.Start - Name.Start; if (Name.Len == 0 || Args.Len == 0) { /* LgiTrace("%s:%i - Fn parse empty section '%s' (%i,%i,%i)\n", _FL, Defn, (int)Return.Len, (int)Name.Len, (int)Args.Len); */ return false; } return true; } bool SeekPtr(char16 *&s, char16 *end, int &Line) { if (s > end) { LAssert(0); return false; } while (s < end) { if (*s == '\n') Line++; s++; } return true; } DefnType GuessDefnType(char16 *def, bool debug) { // In the context of a class member, this could be a variable defn: // // bool myVar = true; // bool myVar = someFn(); // // or a function defn: // // bool myFunction(int someArg = 10); // // Try to guess which it is: int roundDepth = 0; int equalsAtZero = 0; for (auto s = def; *s; s++) { if (*s == '(') roundDepth++; else if (*s == ')') roundDepth--; else if (*s == '=') { if (roundDepth == 0) equalsAtZero++; } } /* if (equalsAtZero && debug) printf("equalsAtZero: %S\n", def); */ return equalsAtZero ? DefnVariable : DefnFunc; } bool BuildCppDefnList(const char *FileName, char16 *Cpp, LArray &Defns, int LimitTo, bool Debug) { if (!Cpp) return false; static char16 StrClass[] = {'c', 'l', 'a', 's', 's', 0}; static char16 StrStruct[] = {'s', 't', 'r', 'u', 'c', 't', 0}; static char16 StrEnum[] = {'e', 'n', 'u', 'm', 0}; static char16 StrOpenBracket[] = {'{', 0}; static char16 StrColon[] = {':', 0}; static char16 StrSemiColon[] = {';', 0}; static char16 StrDefine[] = {'d', 'e', 'f', 'i', 'n', 'e', 0}; static char16 StrExtern[] = {'e', 'x', 't', 'e', 'r', 'n', 0}; static char16 StrTypedef[] = {'t', 'y', 'p', 'e', 'd', 'e', 'f', 0}; static char16 StrC[] = {'\"', 'C', '\"', 0}; static char16 StrHash[] = {'#', 0}; char WhiteSpace[] = " \t\r\n"; char16 *s = Cpp; char16 *LastDecl = s; int Depth = 0; int Line = 0; #ifdef DEBUG_LINE int PrevLine = 0; #endif int CaptureLevel = 0; int InClass = false; // true if we're in a class definition char16 *CurClassDecl = 0; bool IsEnum = 0, IsClass = false, IsStruct = false; bool FnEmit = false; // don't emit functions between a f(n) and the next '{' // they are only parent class initializers LArray ConditionalIndex; int ConditionalDepth = 0; bool ConditionalFirst = true; bool ConditionParsingErr = false; #ifdef DEBUG_FILE Debug |= FileName && stristr(FileName, DEBUG_FILE) != NULL; #endif while (s && *s) { // skip ws while (*s && strchr(" \t\r", *s)) s++; #ifdef DEBUG_LINE if (Debug) { if (Line >= DEBUG_LINE - 1) { - int asd=0; + // int asd=0; } else if (PrevLine != Line) { PrevLine = Line; // LgiTrace("%s:%i '%.10S'\n", FileName, Line + 1, s); } } #endif // tackle decl switch (*s) { case 0: break; case '\n': { Line ++; s++; break; } case '#': { const char16 *Hash = s; s++; skipws(s) const char16 *End = s; while (*End && IsAlpha(*End)) End++; if ( ( LimitTo == DefnNone || (LimitTo & DefnDefine) != 0 ) && (End - s) == 6 && StrncmpW(StrDefine, s, 6) == 0 ) { s += 6; defnskipws(s); LexCpp(s, LexNoReturn); char16 r = *s; *s = 0; Defns.New().Set(DefnDefine, FileName, Hash, Line + 1); *s = r; } char16 *Eol = Strchr(s, '\n'); if (!Eol) Eol = s + Strlen(s); // bool IsIf = false, IsElse = false, IsElseIf = false; if ( ((End - s) == 2 && !Strncmp(L"if", s, End - s)) || ((End - s) == 5 && !Strncmp(L"ifdef", s, End - s)) || ((End - s) == 6 && !Strncmp(L"ifndef", s, End - s)) ) { ConditionalIndex[ConditionalDepth] = 1; ConditionalDepth++; ConditionalFirst = IsFirst(ConditionalIndex, ConditionalDepth); if (Debug) LgiTrace("%s:%i - ConditionalDepth++=%i Line=%i: %.*S\n", _FL, ConditionalDepth, Line+1, Eol - s + 1, s - 1); } else if ( ((End - s) == 4 && !Strncmp(L"else", s, End - s)) || ((End - s) == 7 && !Strncmp(L"else if", s, 7)) ) { if (ConditionalDepth <= 0 && !ConditionParsingErr) { ConditionParsingErr = true; LgiTrace("%s:%i - Error parsing pre-processor conditions: %s:%i\n", _FL, FileName, Line+1); } if (ConditionalDepth > 0) { ConditionalIndex[ConditionalDepth-1]++; ConditionalFirst = IsFirst(ConditionalIndex, ConditionalDepth); if (Debug) LgiTrace("%s:%i - ConditionalDepth=%i Idx++ Line=%i: %.*S\n", _FL, ConditionalDepth, Line+1, Eol - s + 1, s - 1); } } else if ( ((End - s) == 5 && !Strncmp(L"endif", s, End - s)) ) { if (ConditionalDepth <= 0 && !ConditionParsingErr) { ConditionParsingErr = true; LgiTrace("%s:%i - Error parsing pre-processor conditions: %s:%i\n", _FL, FileName, Line+1); } if (ConditionalDepth > 0) ConditionalDepth--; ConditionalFirst = IsFirst(ConditionalIndex, ConditionalDepth); if (Debug) LgiTrace("%s:%i - ConditionalDepth--=%i Line=%i: %.*S\n", _FL, ConditionalDepth, Line+1, Eol - s + 1, s - 1); } while (*s) { if (*s == '\n') { // could be end of # command char Last = (s[-1] == '\r') ? s[-2] : s[-1]; if (Last != '\\') break; Line++; } s++; LastDecl = s; } break; } case '\"': case '\'': { char16 Delim = *s; s++; while (*s) { if (*s == Delim) { s++; break; } if (*s == '\\') s++; if (*s == '\n') Line++; s++; } break; } case '{': { s++; if (ConditionalFirst) Depth++; FnEmit = false; #ifdef DEBUG_FILE if (Debug) LgiTrace("%s:%i - FnEmit=%i Depth=%i @ line %i\n", _FL, FnEmit, Depth, Line+1); #endif break; } case '}': { s++; if (ConditionalFirst) { if (Depth > 0) { Depth--; #ifdef DEBUG_FILE if (Debug) LgiTrace("%s:%i - CaptureLevel=%i Depth=%i @ line %i\n", _FL, CaptureLevel, Depth, Line+1); #endif } else { #ifdef DEBUG_FILE if (Debug) LgiTrace("%s:%i - ERROR Depth already=%i @ line %i\n", _FL, Depth, Line+1); #endif } } defnskipws(s); LastDecl = s; if (IsEnum) { if (IsAlpha(*s)) // typedef'd enum? { LAutoWString t(LexCpp(s, LexStrdup)); if (t) Defns.New().Set(DefnEnum, FileName, t.Get(), Line + 1); } IsEnum = false; } break; } case ';': { s++; LastDecl = s; if (Depth == 0 && InClass) { // Check for typedef struct name char16 *Start = s - 1; while (Start > Cpp && Start[-1] != '}') Start--; LString TypeDef = LString(Start, s - Start - 1).Strip(); if (TypeDef.Length() > 0) { if (LimitTo == DefnNone || (LimitTo & DefnClass) != 0) { Defns.New().Set(DefnClass, FileName, TypeDef, Line + 1); } } // End the class def InClass = false; CaptureLevel = 0; #ifdef DEBUG_FILE if (Debug) LgiTrace("%s:%i - CaptureLevel=%i Depth=%i @ line %i\n", _FL, CaptureLevel, Depth, Line+1); #endif DeleteArray(CurClassDecl); } break; } case '/': { s++; if (*s == '/') { // one line comment while (*s && *s != '\n') s++; LastDecl = s; } else if (*s == '*') { // multi line comment s++; while (*s) { if (s[0] == '*' && s[1] == '/') { s += 2; break; } if (*s == '\n') Line++; s++; } LastDecl = s; } break; } case '(': { s++; if (Depth == CaptureLevel && !FnEmit && LastDecl && ConditionalFirst) { // function? // find start: char16 *Start = LastDecl; skipws(Start); if (Strnstr(Start, L"__attribute__", s - Start)) break; // find end (matching closing bracket) int RoundDepth = 1; char16 *End = s; while (*End) { if (*End == '/' && End[1] == '/') { End = Strchr(End, '\n'); if (!End) break; } if (*End == '(') { RoundDepth++; } else if (*End == ')') { if (--RoundDepth == 0) break; } End++; } if (End && *End) { End++; auto Buf = NewStrW(Start, End-Start); if (Buf) { // remove new-lines auto Out = Buf; for (auto In = Buf; *In; In++) { if (*In == '\r' || *In == '\n' || *In == '\t' || *In == ' ') { *Out++ = ' '; skipws(In); In--; } else if (In[0] == '/' && In[1] == '/') { In = StrchrW(In, '\n'); if (!In) break; } else { *Out++ = *In; } } *Out++ = 0; if (CurClassDecl) { char16 Str[1024]; ZeroObj(Str); StrncpyW(Str, Buf, CountOf(Str)); char16 *b = StrchrW(Str, '('); if (b) { // Skip whitespace between name and '(' while ( b > Str && strchr(WhiteSpace, b[-1]) ) { b--; } // Skip over to the start of the name.. while ( b > Str && b[-1] != '*' && b[-1] != '&' && !strchr(WhiteSpace, b[-1]) ) { b--; } auto ClsLen = StrlenW(CurClassDecl); auto BLen = StrlenW(b); if (ClsLen + BLen + 1 > CountOf(Str)) { LgiTrace("%s:%i - Defn too long: %i\n", _FL, ClsLen + BLen + 1); } else { memmove(b + ClsLen + 2, b, sizeof(*b) * (BLen+1)); memcpy(b, CurClassDecl, sizeof(*b) * ClsLen); b += ClsLen; *b++ = ':'; *b++ = ':'; DeleteArray(Buf); Buf = NewStrW(Str); } } } // cache f(n) def auto Type = GuessDefnType(Buf, Debug); if ( ( LimitTo == DefnNone || (LimitTo & Type) != 0 ) && *Buf != ')' ) Defns.New().Set(Type, FileName, Buf, Line + 1); DeleteArray(Buf); while (*End && !strchr(";:{#", *End)) End++; SeekPtr(s, End, Line); FnEmit = *End != ';'; #if 0 // def DEBUG_FILE if (Debug) LgiTrace("%s:%i - FnEmit=%i Depth=%i @ line %i\n", _FL, FnEmit, Depth, Line+1); #endif } } } else { #ifdef DEBUG_FILE if (Debug) LgiTrace("%s:%i - Not attempting fn parse: depth=%i, capture=%i, fnEmit=%i, CondFirst=%i, %s:%i:%.20S\n", _FL, Depth, CaptureLevel, FnEmit, ConditionalFirst, LGetLeaf(FileName), Line, s-1); #endif } break; } default: { bool InTypedef = false; if (IsAlpha(*s) || IsDigit(*s) || *s == '_') { char16 *Start = s; s++; defnskipsym(s); auto TokLen = s - Start; if (TokLen == 6 && StrncmpW(StrExtern, Start, 6) == 0) { // extern "C" block char16 *t = LexCpp(s, LexStrdup); // "C" if (t && StrcmpW(t, StrC) == 0) { defnskipws(s); if (*s == '{') { Depth--; } } DeleteArray(t); } else if (TokLen == 7 && StrncmpW(StrTypedef, Start, 7) == 0) { // Typedef skipws(s); IsStruct = !Strnicmp(StrStruct, s, StrlenW(StrStruct)); IsClass = !Strnicmp(StrClass, s, StrlenW(StrClass)); if (IsStruct || IsClass) { Start = s; InTypedef = true; goto DefineStructClass; } IsEnum = !Strnicmp(StrEnum, s, StrlenW(StrEnum)); if (IsEnum) { Start = s; s += 4; goto DefineEnum; } LStringPipe p; char16 *i; for (i = Start; i && *i;) { switch (*i) { case ' ': case '\t': { p.Push(Start, i - Start); defnskipws(i); char16 sp[] = {' ', 0}; p.Push(sp); Start = i; break; } case '\n': Line++; // fall thru case '\r': { p.Push(Start, i - Start); i++; Start = i; break; } case '{': { p.Push(Start, i - Start); int Depth = 1; i++; while (*i && Depth > 0) { switch (*i) { case '{': Depth++; break; case '}': Depth--; break; case '\n': Line++; break; } i++; } Start = i; break; } case ';': { p.Push(Start, i - Start); s = i; i = 0; break; } default: { i++; break; } } } char16 *Typedef = p.NewStrW(); if (Typedef) { if (LimitTo == DefnNone || (LimitTo & DefnTypedef) != 0) { Defns.New().Set(DefnTypedef, FileName, Typedef, Line + 1); } DeleteArray(Typedef); } } else if ( ( TokLen == 5 && (IsClass = !StrncmpW(StrClass, Start, 5)) ) || ( TokLen == 6 && (IsStruct = !StrncmpW(StrStruct, Start, 6)) ) ) { DefineStructClass: // Class / Struct if (Depth == 0) { // Check if this is really a class/struct definition or just a reference char16 *next = s; while (*next) { // If we seek to the next line, check it's not a preprocessor directive. if (*next == '\n') { next++; while (*next && strchr(WhiteSpace, *next)) next++; if (*next == '#') { // Skip the processor line... while (*next && *next != '\n') next++; } continue; } else if (strchr(";(){", *next)) break; next++; } if (*next == '{') { // Full definition InClass = true; CaptureLevel = 1; #ifdef DEBUG_FILE if (Debug) LgiTrace("%s:%i - CaptureLevel=%i Depth=%i @ line %i\n", _FL, CaptureLevel, Depth, Line+1); #endif char16 *n = Start + (IsClass ? StrlenW(StrClass) : StrlenW(StrStruct)), *t; List Tok; while (n && *n) { char16 *Last = n; if ((t = LexCpp(n, LexStrdup))) { if (StrcmpW(t, StrSemiColon) == 0) { DeleteArray(t); break; } else if (StrcmpW(t, StrHash) || StrcmpW(t, StrOpenBracket) == 0 || StrcmpW(t, StrColon) == 0) { DeleteArray(CurClassDecl); CurClassDecl = *Tok.rbegin(); Tok.Delete(CurClassDecl); if (LimitTo == DefnNone || (LimitTo & DefnClass) != 0) { char16 r = *Last; *Last = 0; Defns.New().Set(DefnClass, FileName, Start, Line + 1); *Last = r; SeekPtr(s, next, Line); } DeleteArray(t); break; } else { Tok.Insert(t); } } else { LgiTrace("%s:%i - LexCpp failed at %s:%i.\n", _FL, FileName, Line+1); break; } } Tok.DeleteArrays(); } else if (InTypedef) { // Typedef'ing some other structure... // char16 *Start = s; LexCpp(s, LexNoReturn); defnskipws(s); LArray a; char16 *t; ssize_t StartRd = -1, EndRd = -1; while ((t = LexCpp(s, LexStrdup))) { if (StartRd < 0 && !StrcmpW(t, L"(")) StartRd = a.Length(); else if (EndRd < 0 && !StrcmpW(t, L")")) EndRd = a.Length(); if (!StrcmpW(t, StrSemiColon)) break; a.Add(t); } if (a.Length()) { auto iName = StartRd > 0 && EndRd > StartRd ? StartRd - 1 : a.Length() - 1; auto sName = a[iName]; Defns.New().Set(DefnTypedef, FileName, sName, Line + 1); a.DeleteArrays(); } } } } else if ( TokLen == 4 && StrncmpW(StrEnum, Start, 4) == 0 ) { DefineEnum: IsEnum = true; defnskipws(s); LAutoWString t(LexCpp(s, LexStrdup)); if (t && isalpha(*t)) { Defns.New().Set(DefnEnum, FileName, t.Get(), Line + 1); } } else if (IsEnum) { char16 r = *s; *s = 0; Defns.New().Set(DefnEnumValue, FileName, Start, Line + 1); *s = r; defnskipws(s); if (*s == '=') { s++; while (true) { defnskipws(s); defnskipsym(s); defnskipws(s); if (*s == 0 || *s == ',' || *s == '}') break; LAssert(*s != '\n'); s++; } } } if (s[-1] == ':') { LastDecl = s; } } else { s++; } break; } } } DeleteArray(CurClassDecl); if (Debug) { for (unsigned i=0; iType), def->File.Get(), def->Line, def->Name.Get()); } } return Defns.Length() > 0; }