diff --git a/Ide/Code/LgiIde.cpp b/Ide/Code/LgiIde.cpp --- a/Ide/Code/LgiIde.cpp +++ b/Ide/Code/LgiIde.cpp @@ -1,4108 +1,4108 @@ #include #include #include #include "Lgi.h" #include "LgiIde.h" #include "GMdi.h" #include "GToken.h" #include "GXmlTree.h" #include "GPanel.h" #include "GProcess.h" #include "GButton.h" #include "GTabView.h" #include "FtpThread.h" #include "GClipBoard.h" #include "FindSymbol.h" #include "GBox.h" #include "GTextLog.h" #include "GEdit.h" #include "GTableLayout.h" #include "GTextLabel.h" #include "GCombo.h" #include "GCheckBox.h" #include "GDebugger.h" #include "LgiRes.h" #include "ProjectNode.h" #include "GBox.h" #include "GSubProcess.h" #include "GAbout.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 1 #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 IsSymbolChar(c) ( IsDigit(c) || IsAlpha(c) || strchr("-_", c) ) ////////////////////////////////////////////////////////////////////////////////////////// class FindInProject : public GDialog { AppWnd *App; LList *Lst; public: FindInProject(AppWnd *app) { Lst = NULL; App = app; if (LoadFromResource(IDC_FIND_PROJECT_FILE)) { MoveSameScreen(App); GViewI *v; if (GetViewById(IDC_TEXT, v)) v->Focus(true); if (!GetViewById(IDC_FILES, Lst)) return; RegisterHook(this, GKeyEvents, 0); } } bool OnViewKey(GView *v, GKey &k) { switch (k.vkey) { case VK_UP: case VK_DOWN: case VK_PAGEDOWN: case VK_PAGEUP: { return Lst->OnKey(k); break; } case VK_RETURN: { LListItem *i = Lst->GetSelected(); if (i) { char *Ref = i->GetText(0); App->GotoReference(Ref, 1, false); } EndModal(1); break; } case VK_ESCAPE: { EndModal(0); break; } } return false; } void Search(const char *s) { IdeProject *p = App->RootProject(); if (!p || !s) return; GArray Matches, Nodes; List All; p->GetChildProjects(All); All.Insert(p); for (p=All.First(); p; p=All.Next()) { p->GetAllNodes(Nodes); } FilterFiles(Matches, Nodes, s); Lst->Empty(); for (unsigned i=0; iSetText(Matches[i]->GetFileName()); Lst->Insert(li); } Lst->ResizeColumnsToContent(); } int OnNotify(GViewI *c, int f) { switch (c->GetId()) { case IDC_FILES: if (f == GNotifyItem_DoubleClick) { LListItem *i = Lst->GetSelected(); if (i) { App->GotoReference(i->GetText(0), 1, false); EndModal(1); } } break; case IDC_TEXT: if (f != GNotify_ReturnKey) Search(c->Name()); break; case IDCANCEL: EndModal(0); break; } return 0; } }; ////////////////////////////////////////////////////////////////////////////////////////// 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 Dependency : public GTreeItem { char *File; bool Loaded; GTreeItem *Fake; public: Dependency(const char *file) { File = NewStr(file); char *d = strrchr(File, DIR_CHAR); Loaded = false; Insert(Fake = new GTreeItem); if (FileExists(File)) { SetText(d?d+1:File); } else { char s[256]; sprintf(s, "%s (missing)", d?d+1:File); SetText(s); } } ~Dependency() { DeleteArray(File); } char *GetFile() { return File; } void Copy(GStringPipe &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 (GTreeItem *i=GetChild(); i; i=i->GetNext()) { ((Dependency*)i)->Copy(p, Depth+1); } } } char *Find(const char *Paths, char *e) { GToken Path(Paths, LGI_PATH_SEPARATOR); for (int p=0; pSunken(true); Root = new Dependency(File); if (Root) { t->Insert(Root); Root->Expanded(true); } Children.Insert(t); Children.Insert(new GButton(IDC_COPY, 10, t->GView::GetPos().y2 + 10, 60, 20, "Copy")); Children.Insert(new GButton(IDOK, 80, t->GView::GetPos().y2 + 10, 60, 20, "Ok")); } DoModal(); } int OnNotify(GViewI *c, int f) { switch (c->GetId()) { case IDC_COPY: { if (Root) { GStringPipe p; Root->Copy(p); char *s = p.NewStr(); if (s) { GClipBoard c(this); c.Text(s); DeleteArray(s); } break; } break; } case IDOK: { EndModal(0); break; } } return 0; } }; ////////////////////////////////////////////////////////////////////////////////////////// class DebugTextLog : public GTextLog { public: DebugTextLog(int id) : GTextLog(id) { } void PourText(size_t Start, ssize_t Len) override { GTextView3::PourText(Start, Len); for (GTextLine *l=Line.First(); l; l=Line.Next()) { 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 GTreeItem); } WatchItem::~WatchItem() { } bool WatchItem::SetValue(GVariant &v) { char *Str = v.CastString(); if (ValidStr(Str)) SetText(Str, 2); else GTreeItem::SetText(NULL, 2); return true; } bool WatchItem::SetText(const char *s, int i) { if (ValidStr(s)) { GTreeItem::SetText(s, i); if (i == 0 && Tree && Tree->GetWindow()) { GViewI *Tabs = Tree->GetWindow()->FindControl(IDC_DEBUG_TAB); if (Tabs) Tabs->SendNotify(GNotifyValueChanged); } return true; } if (i == 0) delete this; return false; } void WatchItem::OnExpand(bool b) { if (b && PlaceHolder) { // Do something } } class BuildLog : public GTextLog { public: BuildLog(int id) : GTextLog(id) { } void PourStyle(size_t Start, ssize_t Length) { List::I it = GTextView3::Line.begin(); for (GTextLine *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.Set(LC_TEXT, 24); } } } }; class IdeOutput : public GTabView { public: AppWnd *App; GTabPage *Build; GTabPage *Output; GTabPage *Debug; GTabPage *Find; GTabPage *Ftp; LList *FtpLog; GTextLog *Txt[3]; GArray Buf[3]; GFont Small; GFont Fixed; GTabView *DebugTab; GBox *DebugBox; GBox *DebugLog; LList *Locals, *CallStack, *Threads; GTree *Watch; GTextLog *ObjectDump, *MemoryDump, *Registers; GTableLayout *MemTable; GEdit *DebugEdit; GTextLog *DebuggerLog; IdeOutput(AppWnd *app) { 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 = *SysFont; Small.PointSize(Small.PointSize()-1); Small.Create(); LgiAssert(Small.Handle()); GFontType Type; if (Type.GetSystemFont("Fixed")) { Type.SetPointSize(SysFont->PointSize()-1); Fixed.Create(&Type); } else { Fixed.PointSize(SysFont->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 GTextLog(IDC_OUTPUT_LOG)); if (Find) Find->Append(Txt[AppWnd::FindTab] = new GTextLog(IDC_FIND_LOG)); if (Ftp) Ftp->Append(FtpLog = new LList(104, 0, 0, 100, 100)); if (Debug) { Debug->Append(DebugBox = new GBox); if (DebugBox) { DebugBox->SetVertical(false); if ((DebugTab = new GTabView(IDC_DEBUG_TAB))) { DebugTab->GetCss(true)->Padding("0px"); DebugTab->SetFont(&Small); DebugBox->AddView(DebugTab); GTabPage *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 GTextLog(IDC_OBJECT_DUMP))) { ObjectDump->SetFont(&Fixed); ObjectDump->SetPourLargest(true); Page->Append(ObjectDump); } } if ((Page = DebugTab->Append("Watch"))) { Page->SetFont(&Small); if ((Watch = new GTree(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); GXmlTag *w = App->GetOptions()->LockTag("watches", _FL); if (!w) { App->GetOptions()->CreateTag("watches"); w = App->GetOptions()->LockTag("watches", _FL); } if (w) { for (GXmlTag *c = w->Children.First(); c; c = w->Children.Next()) { 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 GTableLayout(IDC_MEMORY_TABLE))) { GCombo *cbo; GCheckBox *chk; GTextLabel *txt; GEdit *ed; MemTable->SetFont(&Small); int x = 0, y = 0; GLayoutCell *c = MemTable->GetCell(x++, y); if (c) { c->VerticalAlign(GCss::VerticalMiddle); c->Add(txt = new GTextLabel(IDC_STATIC, 0, 0, -1, -1, "Address:")); txt->SetFont(&Small); } c = MemTable->GetCell(x++, y); if (c) { c->PaddingRight(GCss::Len("1em")); c->Add(ed = new GEdit(IDC_MEM_ADDR, 0, 0, 60, 20)); ed->SetFont(&Small); } c = MemTable->GetCell(x++, y); if (c) { c->PaddingRight(GCss::Len("1em")); c->Add(cbo = new GCombo(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(GCss::VerticalMiddle); c->Add(txt = new GTextLabel(IDC_STATIC, 0, 0, -1, -1, "Page width:")); txt->SetFont(&Small); } c = MemTable->GetCell(x++, y); if (c) { c->PaddingRight(GCss::Len("1em")); c->Add(ed = new GEdit(IDC_MEM_ROW_LEN, 0, 0, 60, 20)); ed->SetFont(&Small); } c = MemTable->GetCell(x++, y); if (c) { c->VerticalAlign(GCss::VerticalMiddle); c->Add(chk = new GCheckBox(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 GTextLog(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 GTextLog(IDC_REGISTERS))) { Registers->SetFont(&Small); Registers->SetPourLargest(true); Page->Append(Registers); } } } if ((DebugLog = new GBox)) { DebugLog->SetVertical(true); DebugBox->AddView(DebugLog); DebugLog->AddView(DebuggerLog = new DebugTextLog(IDC_DEBUGGER_LOG)); DebuggerLog->SetFont(&Small); DebugLog->AddView(DebugEdit = new GEdit(IDC_DEBUG_EDIT, 0, 0, 60, 20)); DebugEdit->GetCss(true)->Height(GCss::Len(GCss::LenPx, (float)(SysFont->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) { GXmlTag *w = App->GetOptions()->LockTag("watches", _FL); if (!w) { App->GetOptions()->CreateTag("watches"); w = App->GetOptions()->LockTag("watches", _FL); } if (w) { w->EmptyChildren(); for (GTreeItem *ti = Watch->GetChild(); ti; ti = ti->GetNext()) { GXmlTag *t = new GXmlTag("watch"); if (t) { t->SetContent(ti->GetText(0)); w->InsertTag(t); } } App->GetOptions()->Unlock(); } } } void OnCreate() { #if !USE_HAIKU_PULSE_HACK SetPulse(1000); #endif AttachChildren(); } void RemoveAnsi(GArray &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(Utf8ToWide(Utf, (ssize_t)Size)); char16 *OldText = Txt[Channel]->NameW(); size_t OldLen = 0; if (OldText) OldLen = StrlenW(OldText); auto Cur = Txt[Channel]->GetCaret(); Txt[Channel]->Insert(OldLen, w, StrlenW(w)); if (Cur > OldLen - 1) { Txt[Channel]->SetCaret(OldLen + StrlenW(w), false); } Changed = Channel; Buf[Channel].Length(0); Txt[Channel]->Invalidate(); } } if (Changed >= 0) Value(Changed); } }; int DocSorter(IdeDoc *a, IdeDoc *b, NativeInt d) { char *A = a->GetFileName(); char *B = b->GetFileName(); if (A && B) { char *Af = strrchr(A, DIR_CHAR); char *Bf = strrchr(B, DIR_CHAR); return stricmp(Af?Af+1:A, Bf?Bf+1:B); } return 0; } struct FileLoc { GAutoString File; int Line; void Set(const char *f, int l) { File.Reset(NewStr(f)); Line = l; } }; class AppWndPrivate { public: AppWnd *App; GMdiParent *Mdi; GOptionsFile Options; GBox *HBox, *VBox; List Docs; List Projects; GImageList *Icons; GTree *Tree; IdeOutput *Output; bool Debugging; bool Running; bool Building; GSubMenu *WindowsMenu; GSubMenu *CreateMakefileMenu; GAutoPtr FindSym; GArray SystemIncludePaths; GArray BreakPoints; // Debugging GDebugContext *DbgContext; // Cursor history tracking int HistoryLoc; GArray 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 GAutoPtr FindParameters; GAutoPtr Finder; int AppHnd; // Mru List RecentFiles; GSubMenu *RecentFilesMenu; List RecentProjects; GSubMenu *RecentProjectsMenu; // Object AppWndPrivate(AppWnd *a) : AppHnd(GEventSinkMap::Dispatch.AddSink(a)), Options(GOptionsFile::DesktopMode, AppName) { FindSym.Reset(new FindSymbolSystem(AppHnd)); HistoryLoc = 0; InHistorySeek = false; WindowsMenu = 0; App = a; HBox = VBox = NULL; Tree = 0; DbgContext = NULL; Output = 0; Debugging = false; Running = false; Building = false; RecentFilesMenu = 0; RecentProjectsMenu = 0; Icons = LgiLoadImageList("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(); Output->Save(); App->SerializeState(&Options, "WndPos", false); SerializeStringList("RecentFiles", &RecentFiles, true); SerializeStringList("RecentProjects", &RecentProjects, true); Options.SerializeFile(true); RecentFiles.DeleteArrays(); RecentProjects.DeleteArrays(); Docs.DeleteObjects(); Projects.DeleteObjects(); DeleteObj(Icons); } bool FindSource(GAutoString &Full, char *File, char *Context) { if (!LgiIsRelativePath(File)) { Full.Reset(NewStr(File)); } char *ContextPath = 0; if (Context && !Full) { char *Dir = strrchr(Context, DIR_CHAR); for (IdeProject *p=Projects.First(); p && !ContextPath; p=Projects.Next()) { ContextPath = p->FindFullPath(Dir?Dir+1:Context); } if (ContextPath) { LgiTrimDir(ContextPath); char p[300]; LgiMakePath(p, sizeof(p), ContextPath, File); if (FileExists(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) { GAutoString Base = p->GetBasePath(); if (Base) { char Path[MAX_PATH]; LgiMakePath(Path, sizeof(Path), Base, File); if (FileExists(Path)) { Full.Reset(NewStr(Path)); break; } } } } if (!Full) { char *Dir = dirchar(File, true); for (IdeProject *p=Projects.First(); p && !Full; p=Projects.Next()) { Full.Reset(p->FindFullPath(Dir?Dir+1:File)); } if (!Full) { if (FileExists(File)) { Full.Reset(NewStr(File)); } } } return ValidStr(Full); } void ViewMsg(char *File, int Line, char *Context) { GAutoString Full; if (FindSource(Full, File, Context)) { App->GotoReference(Full, Line, false); } } void GetContext(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; char16 *Start = Txt + i; // Skip to end of doc or line 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); } } } #define PossibleLineSep(ch) \ ( (ch) == ':' || (ch) == '(' ) void SeekMsg(int Direction) { GString Comp; IdeProject *p = App->RootProject(); if (p) p ->GetSettings()->GetStr(ProjCompiler); // bool IsIAR = Comp.Equals("IAR"); if (!Output) return; int64 Current = Output->Value(); GTextView3 *o = Current < CountOf(Output->Txt) ? Output->Txt[Current] : 0; if (!o) return; char16 *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]) ) { 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 GAutoString File(WideToUtf8(Txt+Line, i-Line)); if (!File) return; // Scan over the line number.. auto NumIndex = ++i; while (isdigit(Txt[NumIndex])) NumIndex++; // Store the line number GAutoString NumStr(WideToUtf8(Txt + i, NumIndex - i)); if (!NumStr) return; // Convert it to an integer int LineNumber = atoi(NumStr); o->SetCaret(Line, false); o->SetCaret(NumIndex + 1, true); GString 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(); int n=0; char *f=RecentFiles.First(); if (f) { for (; f; f=RecentFiles.Next()) { RecentFilesMenu->AppendItem(f, IDM_RECENT_FILE+n++, true); } } else { RecentFilesMenu->AppendItem(None, 0, false); } } if (RecentProjectsMenu) { RecentProjectsMenu->Empty(); int n=0; char *f=RecentProjects.First(); if (f) { for (; f; f=RecentProjects.Next()) { RecentProjectsMenu->AppendItem(f, IDM_RECENT_PROJECT+n++, true); } } else { RecentProjectsMenu->AppendItem(None, 0, false); } } if (WindowsMenu) { WindowsMenu->Empty(); Docs.Sort(DocSorter); int n=0; for (IdeDoc *d=Docs.First(); d; d=Docs.Next()) { 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.First()) { WindowsMenu->AppendItem(None, 0, false); } } } void OnFile(const char *File, bool IsProject = false) { if (File) { List *Recent = IsProject ? &RecentProjects : &RecentFiles; for (char *f=Recent->First(); f; f=Recent->Next()) { if (stricmp(f, File) == 0) { Recent->Delete(f); Recent->Insert(f, 0); UpdateMenus(); return; } } Recent->Insert(NewStr(File), 0); while (Recent->Length() > 10) { char *f = Recent->Last(); Recent->Delete(f); DeleteArray(f); } UpdateMenus(); } } void RemoveRecent(char *File) { if (File) { List *Recent[3] = { &RecentProjects, &RecentFiles, 0 }; for (List **r = Recent; *r; r++) { for (char *f=(*r)->First(); f; f=(*r)->Next()) { if (stricmp(f, File) == 0) { // LgiTrace("Remove '%s'\n", f); (*r)->Delete(f); DeleteArray(f); break; } } } UpdateMenus(); } } IdeDoc *IsFileOpen(const char *File) { if (!File) { LgiTrace("%s:%i - No input File?\n", _FL); return NULL; } for (IdeDoc *Doc = Docs.First(); Doc; Doc = Docs.Next()) { if (Doc->IsFile(File)) { return Doc; } } // LgiTrace("%s:%i - '%s' not found in %i docs.\n", _FL, File, Docs.Length()); return 0; } IdeProject *IsProjectOpen(char *File) { if (File) { for (IdeProject *p = Projects.First(); p; p = Projects.Next()) { if (p->GetFileName() && stricmp(p->GetFileName(), File) == 0) { return p; } } } return 0; } void SerializeStringList(const char *Opt, List *Lst, bool Write) { GVariant v; if (Write) { GMemQueue p; for (char *s = Lst->First(); s; s = Lst->Next()) { p.Write((uchar*)s, strlen(s)+1); } ssize_t Size = (ssize_t)p.GetSize(); v.SetBinary(Size, p.New(), true); Options.SetValue(Opt, v); } else { Lst->DeleteArrays(); if (Options.GetValue(Opt, v) && v.Type == GV_BINARY) { char *Data = (char*)v.Value.Binary.Data; for (char *s=Data; (NativeInt)s<(NativeInt)Data+v.Value.Binary.Length; s += strlen(s) + 1) { auto ns = NewStr(s); Lst->Insert(ns); } } } } }; #ifdef COCOA #define Chk printf("%s:%i - Cnt=%i\n", LgiGetLeaf(__FILE__), __LINE__, (int)WindowHandle().p.retainCount) #else #define Chk #endif AppWnd::AppWnd() { #ifdef __GTK_H__ LgiGetResObj(true, AppName); #endif Chk; GRect r(0, 0, 1300, 900); #ifdef BEOS r.Offset(GdcD->X() - r.X() - 10, GdcD->Y() - r.Y() - 10); SetPos(r); #else SetPos(r); Chk; MoveToCenter(); #endif Chk; d = new AppWndPrivate(this); Name(AppName); SetQuitOnClose(true); Chk; #if WINNATIVE SetIcon((char*)MAKEINTRESOURCE(IDI_APP)); #else SetIcon("Icon64.png"); #endif Chk; if (Attach(0)) { Chk; Menu = new GMenu; if (Menu) { Menu->Attach(this); bool Loaded = Menu->Load(this, "IDM_MENU"); LgiAssert(Loaded); if (Loaded) { 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); GMenuItem *Debug = GetMenu()->FindItem(IDM_DEBUG_MODE); if (Debug) { Debug->Checked(true); } else LgiTrace("%s:%i - FindSubMenu failed.\n", _FL); d->UpdateMenus(); } } Chk; GToolBar *Tools; if (GdcD->Y() > 1200) Tools = LgiLoadToolbar(this, "cmds-32px.png", 32, 32); else Tools = LgiLoadToolbar(this, "cmds-16px.png", 16, 16); if (Tools) { Chk; 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(); Chk; 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); Chk; GVariant v = 270, OutPx = 250; d->Options.GetValue(OPT_SPLIT_PX, v); d->Options.GetValue(OPT_OUTPUT_PX, OutPx); AddView(d->VBox = new GBox); d->VBox->SetVertical(true); d->HBox = new GBox; 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 GMdiParent); if (d->Mdi) { d->Mdi->HasButton(true); } Chk; d->HBox->Value(MAX(v.CastInt32(), 20)); GRect c = GetClient(); if (c.Y() > OutPx.CastInt32()) { GCss::Len y(GCss::LenPx, OutPx.CastDouble()); d->Output->GetCss(true)->Height(y); } AttachChildren(); OnPosChange(); Chk; #ifdef LINUX GString f = LgiFindFile("lgiide.png"); if (f) { // Handle()->setIcon(f); } #endif UpdateState(); Chk; Visible(true); DropTarget(true); Chk; SetPulse(1000); } #ifdef LINUX LgiFinishXWindowsStartup(this); #endif #if USE_HAIKU_PULSE_HACK d->Output->SetPulse(1000); #endif Chk; } AppWnd::~AppWnd() { if (d->HBox) { GVariant v = d->HBox->Value(); d->Options.SetValue(OPT_SPLIT_PX, v); } if (d->Output) { GVariant v = d->Output->Y(); d->Options.SetValue(OPT_OUTPUT_PX, v); } ShutdownFtpThread(); CloseAll(); LgiApp->AppWnd = 0; DeleteObj(d); } void AppWnd::OnPulse() { IdeDoc *Top = TopDoc(); if (Top) Top->OnPulse(); } GDebugContext *AppWnd::GetDebugContext() { return d->DbgContext; } struct DumpBinThread : public LThread { GStream *Out; GString InFile; bool IsLib; public: DumpBinThread(GStream *out, GString file) : LThread("DumpBin.Thread") { Out = out; InFile = file; DeleteOnExit = true; auto Ext = LgiGetExtension(InFile); IsLib = Ext && !stricmp(Ext, "lib"); Run(); } bool DumpBin(GString Args, GStream *Str) { char Buf[256]; ssize_t Rd; GSubProcess s("c:\\Program Files (x86)\\Microsoft Visual Studio 12.0\\VC\\bin\\amd64\\dumpbin.exe", Args); if (!s.Start(true, false)) return false; while ((Rd = s.Read(Buf, sizeof(Buf))) > 0) Str->Write(Buf, Rd); return true; } GString::Array Dependencies() { GString Args; GStringPipe p; Args.Printf("/dependents \"%s\"", InFile.Get()); DumpBin(Args, &p); auto Parts = p.NewGStr().Replace("\r", "").Split("\n\n"); auto Files = Parts[4].Strip().Split("\n"); auto Path = LGetPath(); for (auto &f : Files) { f = f.Strip(); bool Found = false; for (auto s : Path) { GFile::Path c(s); c += f.Get(); if (c.IsFile()) { f = c.GetFull(); Found = true; break; } } if (!Found) f += " (not found in path)"; } return Files; } GString GetArch() { GString Args; GStringPipe p; Args.Printf("/headers \"%s\"", InFile.Get()); DumpBin(Args, &p); const char *Key = " machine "; GString Arch; auto Lines = p.NewGStr().SplitDelimit("\r\n"); int64 Machine = 0; for (auto &Ln : Lines) { if (Ln.Find(Key) >= 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; } GString GetExports() { GString Args; GStringPipe p; if (IsLib) Args.Printf("/symbols \"%s\"", InFile.Get()); else Args.Printf("/exports \"%s\"", InFile.Get()); DumpBin(Args, &p); GString Exp; auto Sect = p.NewGStr().Replace("\r", "").Split("\n\n"); if (IsLib) { GString::Array Lines, Funcs; for (auto &s : Sect) { if (s.Find("COFF", 0, 100) == 0) { Lines = s.Split("\n"); break; } } Funcs.SetFixedLength(false); for (auto &l : Lines) { if (l.Length() < 34) continue; const char *Type = l.Get() + 33; if (!Strnicmp(Type, "External", 8)) { auto Nm = l.RSplit("|",1).Last().Strip(); if (!strchr("@$.?", Nm(0))) Funcs.New() = Nm; } } Exp = GString("\n").Join(Funcs); } 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; } } return Exp; } int Main() { if (!IsLib) { auto Deps = Dependencies(); Out->Print("Dependencies:\n\t%s\n\n", GString("\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(GArray &Files) { for (int i=0; iSetFileName(Docs, false); new DumpBinThread(Doc, Files[i]); } } else { OpenFile(f); } } Raise(); } 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(GString &s, int pos) { if (pos < 0) return false; if (pos >= s.Length()) return false; char i = s[pos]; return IsAlpha(i) || IsDigit(i) || i == '_'; } bool ReplaceWholeWord(GString &Ln, GString Word, GString NewWord) { int Pos = 0; bool Status = false; while (Pos >= 0) { Pos = Ln.Find(Word, Pos); if (Pos < 0) return Status; int End = Pos + Word.Length(); if (!IsVarChar(Ln, Pos-1) && !IsVarChar(Ln, End)) { GString NewLn = Ln(0,Pos) + NewWord + Ln(End,-1); Ln = NewLn; Status = true; } Pos++; } return Status; } struct LFileInfo { GString Path; GString::Array Lines; bool Dirty; LFileInfo() { Dirty = false; } bool Save() { GFile f; if (!f.Open(Path, O_WRITE)) return false; GString NewFile = GString("\n").Join(Lines); f.SetSize(0); f.Write(NewFile); f.Close(); return true; } }; void AppWnd::OnFixBuildErrors() { LHashTbl, GString> Map; GVariant v; if (GetOptions()->GetValue(OPT_RENAMED_SYM, v)) { auto Lines = GString(v.Str()).Split("\n"); for (auto Ln: Lines) { auto p = Ln.SplitDelimit(); if (p.Length() == 2) Map.Add(p[0], p[1]); } if (Map.Length() == 0) { LgiMsg(this, "No renamed symbols defined.", AppName); return; } } else return; GString Raw = d->Output->Txt[AppWnd::BuildTab]->Name(); GString::Array Lines = Raw.Split("\n"); auto *Log = d->Output->Txt[AppWnd::OutputTab]; Log->Name(NULL); Log->Print("Parsing errors...\n"); int i = 0; int Replacements = 0; GArray Files; for (auto Ln : Lines) { auto ErrPos = Ln.Find("error"); if (ErrPos >= 0) { Log->Print("Error pos = %i\n", (int)ErrPos); #ifdef WINDOWS GString::Array p = Ln.SplitDelimit(">()"); #else GString::Array p = Ln(0, ErrPos).Strip().SplitDelimit(":"); #endif Log->Print("p.Len = %i\n", (int)p.Length()); if (p.Length() > 2) { #ifdef WINDOWS int Base = p[0].IsNumeric() ? 1 : 0; GString Fn = p[Base]; if (Fn.Find("Program Files") >= 0) continue; auto LineNo = p[Base+1].Int(); #else GString Fn = p[0]; auto LineNo = p[1].Int(); #endif GAutoString Full; if (d->FindSource(Full, Fn, NULL)) { LFileInfo *Fi = NULL; for (auto &i: Files) { if (i.Path.Equals(Full)) { Fi = &i; break; } } if (!Fi) { GFile 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); } } if (Fi) { if (LineNo <= Fi->Lines.Length()) { GString &s = Fi->Lines[LineNo-1]; for (auto i: Map) { if (ReplaceWholeWord(s, i.key, i.value)) { Fi->Dirty = true; Replacements++; } } } } else { int asd=0; } } } } } for (auto &Fi : Files) { if (Fi.Dirty) Fi.Save(); } Log->Print("%i replacements made.\n", Replacements); d->Output->Value(AppWnd::OutputTab); } void AppWnd::OnBuildStateChanged(bool NewState) { GVariant v; if (!NewState && GetOptions()->GetValue(OPT_FIX_RENAMED, v) && v.CastInt32()) { OnFixBuildErrors(); } } void AppWnd::UpdateState(int Debugging, int 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 { d->Output->Txt[Channel]->Name(""); } } void AppWnd::SaveAll() { List::I Docs = d->Docs.begin(); for (IdeDoc *Doc = *Docs; Doc; Doc = *++Docs) { Doc->SetClean(); d->OnFile(Doc->GetFileName()); } List::I Projs = d->Projects.begin(); for (IdeProject *Proj = *Projs; Proj; Proj = *++Projs) { Proj->SetClean(); d->OnFile(Proj->GetFileName(), true); } } void AppWnd::CloseAll() { SaveAll(); while (d->Docs.First()) delete d->Docs.First(); IdeProject *p = RootProject(); if (p) DeleteObj(p); while (d->Projects.First()) delete d->Projects.First(); DeleteObj(d->DbgContext); } bool AppWnd::OnRequestClose(bool IsClose) { SaveAll(); return GWindow::OnRequestClose(IsClose); } bool AppWnd::OnBreakPoint(GDebugger::BreakPoint &b, bool Add) { List::I it = d->Docs.begin(); for (IdeDoc *doc = *it; doc; doc = *++it) { char *fn = doc->GetFileName(); bool Match = !_stricmp(fn, b.File); 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; char *fn = doc->GetFileName(); for (int i=0; iBreakPoints.Length(); i++) { GDebugger::BreakPoint &b = d->BreakPoints[i]; if (!_stricmp(fn, b.File)) { doc->AddBreakPoint(b.Line, true); } } return true; } bool AppWnd::LoadBreakPoints(GDebugger *db) { if (!db) return false; for (int i=0; iBreakPoints.Length(); i++) { GDebugger::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++) { GDebugger::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) { GDebugger::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(GArray &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) LgiAssert(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); GRect p = d->Mdi->NewPos(); Doc->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); /* else LgiTrace("%s:%i - No file '%s' found.\n", _FL, File); */ 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) { char *f = i->GetFileName(); if (f) { IdeProject *p = i->GetProject(); if (p) { GAutoString Base = p->GetBasePath(); if (Base) { char Path[MAX_PATH]; if (*f == '.') LgiMakePath(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; } GString FullPath; if (LgiIsRelativePath(File)) { IdeProject *Proj = Src && Src->GetProject() ? Src->GetProject() : RootProject(); if (Proj) { List Projs; Projs.Insert(Proj); Proj->CollectAllSubProjects(Projs); for (auto Project : Projs) { GAutoString ProjPath = Project->GetBasePath(); char p[MAX_PATH]; LgiMakePath(p, sizeof(p), ProjPath, File); if (FileExists(p)) { FullPath = p; File = FullPath; break; } } } } Doc = d->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(LgiIsRelativePath(File), File, true, &Doc); } DoingProjectFind = false; d->OnFile(File); } } if (!Doc && FileExists(File)) { Doc = new IdeDoc(this, 0, File); if (Doc) { GRect p = d->Mdi->NewPos(); Doc->SetPos(p); d->Docs.Insert(Doc); d->OnFile(File); } } if (Doc) { #ifdef BEOS BView *h = Doc->Handle(); BWindow *w = h ? h->Window() : 0; bool att = Doc->IsAttached(); printf("%s:%i - att=%i h=%p w=%p\n", _FL, att, h, w); #endif if (!Doc->IsAttached()) { Doc->Attach(d->Mdi); } Doc->Focus(true); Doc->Raise(); } return Doc; } IdeProject *AppWnd::RootProject() { IdeProject *Root = 0; for (IdeProject *p=d->Projects.First(); p; p=d->Projects.Next()) { if (!p->GetParentProject()) { LgiAssert(Root == 0); Root = p; } } return Root; } IdeProject *AppWnd::OpenProject(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; } GString::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) { char *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; } GMessage::Result AppWnd::OnEvent(GMessage *m) { switch (MsgCode(m)) { case M_START_BUILD: { IdeProject *p = RootProject(); if (p) p->Build(true, IsReleaseMode()); 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*)MsgB(m); if (Msg) { d->Output->Txt[AppWnd::BuildTab]->Print("Build Error: %s\n", Msg); DeleteArray(Msg); } break; } case M_APPEND_TEXT: { char *Text = (char*) MsgA(m); Channels Ch = (Channels) MsgB(m); AppendOutput(Text, Ch); DeleteArray(Text); 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(GNotifyValueChanged); } } 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 GWindow::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; d->FindSym->OnFile(Path, Action, Node->GetPlatforms()); return true; } GOptionsFile *AppWnd::GetOptions() { return &d->Options; } class Options : public GDialog { AppWnd *App; GFontType 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); } GVariant v; if (App->GetOptions()->GetValue(OPT_Jobs, v)) SetCtrlValue(IDC_JOBS, v.CastInt32()); else SetCtrlValue(IDC_JOBS, 2); DoModal(); } } int OnNotify(GViewI *c, int f) { switch (c->GetId()) { case IDOK: { GVariant v; Font.Serialize(App->GetOptions(), OPT_EditorFont, true); App->GetOptions()->SetValue(OPT_Jobs, v = GetCtrlValue(IDC_JOBS)); } case IDCANCEL: { EndModal(c->GetId()); break; } case IDC_SET_FONT: { if (Font.DoUI(this)) { char s[256]; if (Font.GetDescription(s, sizeof(s))) { SetCtrlName(IDC_FONT, s); } } break; } } return 0; } }; void AppWnd::UpdateMemoryDump() { if (d->DbgContext) { 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(GViewI *Ctrl, int Flags) { switch (Ctrl->GetId()) { case IDC_PROJECT_TREE: { if (Flags == GNotify_DeleteKey) { ProjectNode *n = dynamic_cast(d->Tree->Selection()); if (n) n->Delete(); } break; } case IDC_DEBUG_EDIT: { if (Flags == VK_RETURN && d->DbgContext) { char *Cmd = Ctrl->Name(); if (Cmd) { d->DbgContext->OnUserCommand(Cmd); Ctrl->Name(NULL); } } break; } case IDC_MEM_ADDR: { if (Flags == VK_RETURN) { if (d->DbgContext) { char *s = Ctrl->Name(); if (s) { char *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 { LgiAssert(!"No MemoryDump."); } } else LgiAssert(!"No debug context."); } break; } case IDC_MEM_ROW_LEN: { if (Flags == VK_RETURN) UpdateMemoryDump(); break; } case IDC_MEM_HEX: case IDC_MEM_SIZE: { UpdateMemoryDump(); break; } case IDC_DEBUG_TAB: { if (d->DbgContext && Flags == GNotifyValueChanged) { 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 && Flags == GNotifyItem_DoubleClick && d->DbgContext) { LListItem *it = d->Output->Locals->GetSelected(); if (it) { char *Var = it->GetText(2); 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 (Flags == M_CHANGE) { if (d->Output->DebugTab) d->Output->DebugTab->Value(AppWnd::CallStackTab); } else if (Flags == GNotifyItem_Select) { // This takes the user to a given call stack reference if (d->Output->CallStack && d->DbgContext) { LListItem *item = d->Output->CallStack->GetSelected(); if (item) { GAutoString File; int Line; if (d->DbgContext->ParseFrameReference(item->GetText(1), File, Line)) { GAutoString Full; if (d->FindSource(Full, File, NULL)) { GotoReference(Full, Line, false); char *sFrame = item->GetText(0); if (sFrame && IsDigit(*sFrame)) d->DbgContext->SetFrame(atoi(sFrame)); } } } } } break; } case IDC_WATCH_LIST: { WatchItem *Edit = NULL; switch (Flags) { case GNotify_DeleteKey: { GArray Sel; for (GTreeItem *c = d->Output->Watch->GetChild(); c; c = c->GetNext()) { if (c->Select()) Sel.Add(c); } Sel.DeleteObjects(); break; } case GNotifyItem_Click: { Edit = dynamic_cast(d->Output->Watch->Selection()); break; } case GNotifyContainer_Click: { // Create new watch. Edit = new WatchItem(d->Output); if (Edit) d->Output->Watch->Insert(Edit); break; } } if (Edit) Edit->EditLabel(0); break; } case IDC_THREADS: { if (Flags == GNotifyItem_Select) { // This takes the user to a given thread if (d->Output->Threads && d->DbgContext) { LListItem *item = d->Output->Threads->GetSelected(); if (item) { GString sId = item->GetText(0); int ThreadId = (int)sId.Int(); if (ThreadId > 0) { d->DbgContext->SelectThread(ThreadId); } } } } break; } } return 0; } bool AppWnd::IsReleaseMode() { GMenuItem *Release = GetMenu()->FindItem(IDM_RELEASE_MODE); bool IsRelease = Release ? Release->Checked() : false; return IsRelease; } bool AppWnd::Build() { SaveAll(); IdeDoc *Top; IdeProject *p = RootProject(); if (p) { UpdateState(-1, true); GMenuItem *Release = GetMenu()->FindItem(IDM_RELEASE_MODE); bool IsRelease = Release ? Release->Checked() : false; p->Build(false, IsRelease); return true; } else if ((Top = TopDoc())) { return Top->Build(); } return false; } class RenameDlg : public GDialog { AppWnd *App; public: RenameDlg(AppWnd *a) { SetParent(App = a); MoveSameScreen(a); if (LoadFromResource(IDC_RENAME)) { GVariant 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()); } } int OnNotify(GViewI *c, int f) { switch (c->GetId()) { case IDOK: { GVariant v; App->GetOptions()->SetValue(OPT_RENAMED_SYM, v = GetCtrlName(IDC_SYM)); App->GetOptions()->SetValue(OPT_FIX_RENAMED, v = GetCtrlValue(IDC_FIX_RENAMED)); } case IDCANCEL: { EndModal(c->GetId() == IDOK); break; } } return 0; } }; bool AppWnd::ShowInProject(const char *Fn) { if (!Fn) return false; for (IdeProject *p=d->Projects.First(); p; p=d->Projects.Next()) { ProjectNode *Node = NULL; if (p->FindFullPath(Fn, &Node)) { for (GTreeItem *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: { LgiCloseApp(); break; } case IDM_OPTIONS: { Options Dlg(this); break; } case IDM_HELP: { LgiExecute(APP_URL); break; } case IDM_ABOUT: { GAbout a(this, AppName, APP_VER, "\nLGI Integrated Development Environment", "icon128.png", APP_URL, "fret@memecode.com"); break; } case IDM_NEW: { IdeDoc *Doc; d->Docs.Insert(Doc = new IdeDoc(this, 0, 0)); if (Doc) { GRect p = d->Mdi->NewPos(); Doc->SetPos(p); Doc->Attach(d->Mdi); Doc->Focus(true); } break; } case IDM_OPEN: { GFileSelect s; s.Parent(this); if (s.Open()) { OpenFile(s.Name()); } break; } case IDM_SAVE_ALL: { SaveAll(); break; } case IDM_SAVE: { IdeDoc *Top = TopDoc(); if (Top) Top->SetClean(); break; } case IDM_SAVEAS: { IdeDoc *Top = TopDoc(); if (Top) { GFileSelect s; s.Parent(this); if (s.Save()) { Top->SetFileName(s.Name(), true); d->OnFile(s.Name()); } } 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: { GTextView3 *Doc = FocusEdit(); if (Doc) { Doc->Undo(); } else LgiTrace("%s:%i - No focus doc.\n", _FL); break; } case IDM_REDO: { GTextView3 *Doc = FocusEdit(); if (Doc) { Doc->Redo(); } else LgiTrace("%s:%i - No focus doc.\n", _FL); break; } case IDM_FIND: { GTextView3 *Doc = FocusEdit(); if (Doc) { Doc->DoFind(); } else LgiTrace("%s:%i - No focus doc.\n", _FL); break; } case IDM_FIND_NEXT: { GTextView3 *Doc = FocusEdit(); if (Doc) { Doc->DoFindNext(); } else LgiTrace("%s:%i - No focus doc.\n", _FL); break; } case IDM_REPLACE: { GTextView3 *Doc = FocusEdit(); if (Doc) { Doc->DoReplace(); } else LgiTrace("%s:%i - No focus doc.\n", _FL); break; } case IDM_GOTO: { GTextView3 *Doc = FocusEdit(); if (Doc) Doc->DoGoto(); else { GInput Inp(this, NULL, LgiLoadString(L_TEXTCTRL_GOTO_LINE, "Goto [file:]line:"), "Goto"); if (Inp.DoModal()) { GString s = Inp.GetStr(); GString::Array p = s.SplitDelimit(":,"); if (p.Length() == 2) { GString 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); } } break; } case IDM_CUT: { GTextView3 *Doc = FocusEdit(); if (Doc) Doc->PostEvent(M_CUT); break; } case IDM_COPY: { GTextView3 *Doc = FocusEdit(); if (Doc) Doc->PostEvent(M_COPY); break; } case IDM_PASTE: { GTextView3 *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)) { GVariant var; if (GetOptions()->GetValue(OPT_ENTIRE_SOLUTION, var)) d->FindParameters->Type = var.CastInt32() ? FifSearchSolution : FifSearchDirectory; } FindInFiles Dlg(this, d->FindParameters); GViewI *Focus = GetFocus(); if (Focus) { GTextView3 *Edit = dynamic_cast(Focus); if (Edit && Edit->HasSelection()) { GAutoString a(Edit->GetSelection()); Dlg.Params->Text = a; } } IdeProject *p = RootProject(); if (p) { GAutoString Base = p->GetBasePath(); if (Base) Dlg.Params->Dir = Base; } if (Dlg.DoModal()) { if (p && Dlg.Params->Type == FifSearchSolution) { Dlg.Params->ProjectFiles.Length(0); List Projects; Projects.Insert(p); p->GetChildProjects(Projects); GArray Nodes; for (IdeProject *p = Projects.First(); p; p = Projects.Next()) p->GetAllNodes(Nodes); for (unsigned i=0; iGetFullPath(); if (s) Dlg.Params->ProjectFiles.Add(s); } } GVariant var = d->FindParameters->Type == FifSearchSolution; GetOptions()->SetValue(OPT_ENTIRE_SOLUTION, var); d->Finder->Stop(); d->Finder->PostEvent(FindInFilesThread::M_START_SEARCH, (GMessage::Param) new FindParams(d->FindParameters)); } } break; } case IDM_FIND_SYMBOL: { IdeDoc *Doc = FocusDoc(); if (Doc) { Doc->GotoSearch(IDC_SYMBOL_SEARCH); } else { FindSymResult r = d->FindSym->OpenSearchDlg(this); 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 { FindInProject Dlg(this); Dlg.DoModal(); } break; } case IDM_FIND_REFERENCES: { GViewI *f = LgiApp->GetFocus(); GDocView *doc = dynamic_cast(f); if (!doc) break; ssize_t c = doc->GetCaret(); if (c < 0) break; GString 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; GString 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); GArray Nodes; for (p = Projects.First(); p; p = Projects.Next()) p->GetAllNodes(Nodes); GAutoPtr 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, (GMessage::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_OPEN_PROJECT: { GFileSelect s; s.Parent(this); s.Type("Projects", "*.xml"); if (s.Open()) { CloseAll(); OpenProject(s.Name(), NULL, Cmd == IDM_NEW_PROJECT); if (d->Tree) { d->Tree->Focus(true); } } break; } case IDM_IMPORT_DSP: { IdeProject *p = RootProject(); if (p) { GFileSelect s; s.Parent(this); s.Type("Developer Studio Project", "*.dsp"); if (s.Open()) { p->ImportDsp(s.Name()); } } break; } case IDM_RUN: { SaveAll(); IdeProject *p = RootProject(); if (p) { p->Execute(); } break; } case IDM_VALGRIND: { SaveAll(); 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: { RenameDlg Dlg(this); Dlg.DoModal(); break; } case IDM_START_DEBUG: { SaveAll(); IdeProject *p = RootProject(); if (!p) { LgiMsg(this, "No project loaded.", "Error"); break; } if (d->DbgContext) { d->DbgContext->OnCommand(IDM_CONTINUE); } else if ((d->DbgContext = p->Execute(ExeDebug))) { 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); } 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(); IdeProject *p = RootProject(); if (p) p->Clean(true, IsReleaseMode()); break; } case IDM_NEXT_MSG: { d->SeekMsg(1); break; } case IDM_PREV_MSG: { d->SeekMsg(-1); break; } case IDM_DEBUG_MODE: { GMenuItem *Debug = GetMenu()->FindItem(IDM_DEBUG_MODE); GMenuItem *Release = GetMenu()->FindItem(IDM_RELEASE_MODE); if (Debug && Release) { Debug->Checked(true); Release->Checked(false); } break; } case IDM_RELEASE_MODE: { GMenuItem *Debug = GetMenu()->FindItem(IDM_DEBUG_MODE); GMenuItem *Release = GetMenu()->FindItem(IDM_RELEASE_MODE); if (Debug && Release) { Debug->Checked(false); Release->Checked(true); } break; } // // Other // case IDM_LOOKUP_SYMBOLS: { IdeDoc *Cur = GetCurrentDoc(); if (Cur) { // LookupSymbols(Cur->Read()); } break; } case IDM_DEPENDS: { IdeProject *p = RootProject(); if (p) { GString Exe = p->GetExecutable(GetCurrentPlatform()); if (FileExists(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_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: { char *r = d->RecentFiles[Cmd - IDM_RECENT_FILE]; if (r) { IdeDoc *f = d->IsFileOpen(r); if (f) { f->Raise(); } else { OpenFile(r); } } char *p = d->RecentProjects[Cmd - IDM_RECENT_PROJECT]; if (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; } GTree *AppWnd::GetTree() { return d->Tree; } IdeDoc *AppWnd::TopDoc() { return dynamic_cast(d->Mdi->GetTop()); } GTextView3 *AppWnd::FocusEdit() { return dynamic_cast(GetWindow()->GetFocus()); } IdeDoc *AppWnd::FocusDoc() { IdeDoc *Doc = TopDoc(); if (Doc) { if (Doc->HasFocus()) { return Doc; } else { GViewI *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) { d->Projects.Delete(Proj); } void AppWnd::OnProjectChange() { GArray Views; if (d->Mdi->GetChildren(Views)) { for (unsigned i=0; i(Views[i]); if (Doc) Doc->OnProjectChange(); } } } void AppWnd::OnDocDestroy(IdeDoc *Doc) { if (d) { d->Docs.Delete(Doc); d->UpdateMenus(); } } int AppWnd::GetBuildMode() { GMenuItem *Release = GetMenu()->FindItem(IDM_RELEASE_MODE); if (Release && Release->Checked()) { return BUILD_TYPE_RELEASE; } return BUILD_TYPE_DEBUG; } LList *AppWnd::GetFtpLog() { return d->Output->FtpLog; } GStream *AppWnd::GetBuildLog() { return d->Output->Txt[AppWnd::BuildTab]; } void AppWnd::FindSymbol(int ResultsSinkHnd, const char *Sym, bool AllPlatforms) { d->FindSym->Search(ResultsSinkHnd, Sym, AllPlatforms); } #include "GSubProcess.h" bool AppWnd::GetSystemIncludePaths(::GArray &Paths) { if (d->SystemIncludePaths.Length() == 0) { #if !defined(WINNATIVE) // echo | gcc -v -x c++ -E - GSubProcess sp1("echo"); GSubProcess sp2("gcc", "-v -x c++ -E -"); sp1.Connect(&sp2); sp1.Start(true, false); char Buf[256]; ssize_t r; GStringPipe 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) { GAutoString a(TrimStr(Buf)); d->SystemIncludePaths.New() = a; } } #else char p[MAX_PATH]; LGetSystemPath(LSP_USER_DOCUMENTS, p, sizeof(p)); LgiMakePath(p, sizeof(p), p, "Visual Studio 2008\\Settings\\CurrentSettings.xml"); if (FileExists(p)) { GFile f; if (f.Open(p, O_READ)) { GXmlTree t; GXmlTag r; if (t.Read(&r, &f)) { GXmlTag *Opts = r.GetChildTag("ToolsOptions"); if (Opts) { GXmlTag *Projects = NULL; char *Name; for (GXmlTag *c = Opts->Children.First(); c; c = Opts->Children.Next()) { if (c->IsTag("ToolsOptionsCategory") && (Name = c->GetAttr("Name")) && !stricmp(Name, "Projects")) { Projects = c; break; } } GXmlTag *VCDirectories = NULL; for (GXmlTag *c = Projects ? Projects->Children.First() : NULL; c; c = Projects->Children.Next()) { if (c->IsTag("ToolsOptionsSubCategory") && (Name = c->GetAttr("Name")) && !stricmp(Name, "VCDirectories")) { VCDirectories = c; break; } } for (GXmlTag *prop = VCDirectories ? VCDirectories->Children.First() : NULL; prop; prop = VCDirectories->Children.Next()) { if (prop->IsTag("PropertyValue") && (Name = prop->GetAttr("Name")) && !stricmp(Name, "IncludeDirectories")) { char *Bar = strchr(prop->GetContent(), '|'); GToken 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; } /* #include "GSubProcess.h" void Test() { GDirectory d; for (int b = d.First("C:\\Users\\matthew\\AppData\\Local\\Google\\Chrome\\User Data\\Default\\Cache"); b; b = d.Next()) { if (!d.IsDir()) { char p[MAX_PATH]; d.Path(p, sizeof(p)); GFile f; if (f.Open(p, O_READ)) { char Buf[256]; ssize_t Rd = f.Read(Buf, sizeof(Buf)); if (Rd > 3 && !strnicmp(Buf, "Ogg", 3)) { char out[MAX_PATH]; f.Close(); LgiMakePath(out, sizeof(out), "C:\\Users\\matthew\\Desktop\\new day", d.GetName()); strcat(out, ".ogg"); if (!FileDev->Copy(p, out)) { LgiTrace("%s:%i - Failed to copy '%s'\n", _FL, d.GetName()); } } else { LgiTrace("%s:%i - Not an ogg '%s'\n", _FL, d.GetName()); } } else { LgiTrace("%s:%i - Can't open '%s'\n", _FL, d.GetName()); } } } } */ int LgiMain(OsAppArguments &AppArgs) { printf("LgiIde v%s\n", APP_VER); GApp a(AppArgs, "LgiIde"); if (a.IsOk()) { /* GString mt = LGetAppForProtocol("mailto"); GString https = LGetAppForProtocol("https"); printf("%s\n%s\n", mt.Get(), https.Get()); GArray Out; if (GSocket::EnumInterfaces(Out)) { for (auto &i : Out) { printf("%s %s %s\n", i.Name.Get(), i.ToString().Get(), i.ToString(i.Netmask4).Get()); } } */ a.AppWnd = new AppWnd; a.Run(); } return 0; } diff --git a/Lvc/Src/VcFolder.cpp b/Lvc/Src/VcFolder.cpp --- a/Lvc/Src/VcFolder.cpp +++ b/Lvc/Src/VcFolder.cpp @@ -1,2656 +1,2658 @@ #include "Lvc.h" #include "../Resources/resdefs.h" #ifndef CALL_MEMBER_FN #define CALL_MEMBER_FN(object,ptrToMember) ((object).*(ptrToMember)) #endif int Ver2Int(GString v) { auto p = v.Split("."); int i = 0; for (auto s : p) { auto Int = s.Int(); if (Int < 256) { i <<= 8; i |= (uint8_t)Int; } else { LgiAssert(0); return 0; } } return i; } int ToolVersion[VcMax] = {0}; ReaderThread::ReaderThread(GSubProcess *p, GStream *out) : LThread("ReaderThread") { Process = p; Out = out; Run(); } ReaderThread::~ReaderThread() { Out = NULL; while (!IsExited()) LgiSleep(1); } int ReaderThread::Main() { bool b = Process->Start(true, false); if (!b) { GString s("Process->Start failed.\n"); Out->Write(s.Get(), s.Length(), ErrSubProcessFailed); return ErrSubProcessFailed; } while (Process->IsRunning()) { if (Out) { char Buf[1024]; ssize_t r = Process->Read(Buf, sizeof(Buf)); if (r > 0) Out->Write(Buf, r); } else { Process->Kill(); return -1; break; } } if (Out) { char Buf[1024]; ssize_t r = Process->Read(Buf, sizeof(Buf)); if (r > 0) Out->Write(Buf, r); } return (int) Process->GetExitValue(); } ///////////////////////////////////////////////////////////////////////////////////////////// void VcFolder::Init(AppPriv *priv) { d = priv; IsCommit = false; IsLogging = false; IsGetCur = false; IsUpdate = false; IsFilesCmd = false; IsWorkingFld = false; CommitListDirty = false; IsUpdatingCounts = false; Unpushed = Unpulled = -1; Type = VcNone; CmdErrors = 0; Expanded(false); Insert(Tmp = new GTreeItem); Tmp->SetText("Loading..."); LgiAssert(d != NULL); } VcFolder::VcFolder(AppPriv *priv, const char *p) { Init(priv); Path = p; } VcFolder::VcFolder(AppPriv *priv, GXmlTag *t) { Init(priv); Serialize(t, false); } VersionCtrl VcFolder::GetType() { if (Type == VcNone) Type = DetectVcs(Path); return Type; } char *VcFolder::GetText(int Col) { switch (Col) { case 0: { if (Cmds.Length()) { Cache = Path; Cache += " (...)"; return Cache; } return Path; } case 1: { CountCache.Printf("%i/%i", Unpulled, Unpushed); CountCache = CountCache.Replace("-1", "--"); return CountCache; } } return NULL; } bool VcFolder::Serialize(GXmlTag *t, bool Write) { if (Write) t->SetContent(Path); else Path = t->GetContent(); return true; } GXmlTag *VcFolder::Save() { GXmlTag *t = new GXmlTag(OPT_Folder); if (t) Serialize(t, true); return t; } const char *VcFolder::GetVcName() { const char *Def = NULL; switch (GetType()) { case VcGit: Def = "git"; break; case VcSvn: Def = "svn"; break; case VcHg: Def = "hg"; break; case VcCvs: Def = "cvs"; break; default: break; } if (!VcCmd) { GString Opt; Opt.Printf("%s-path", Def); GVariant v; if (d->Opts.GetValue(Opt, v)) VcCmd = v.Str(); } if (!VcCmd) VcCmd = Def; return VcCmd; } bool VcFolder::StartCmd(const char *Args, ParseFn Parser, ParseParams *Params, LoggingType Logging) { const char *Exe = GetVcName(); if (!Exe) return false; if (CmdErrors > 2) return false; if (d->Log && Logging != LogSilo) d->Log->Print("%s %s\n", Exe, Args); GAutoPtr Process(new GSubProcess(Exe, Args)); if (!Process) return false; Process->SetInitFolder(Params && Params->AltInitPath ? Params->AltInitPath : Path); GString::Array Ctx; Ctx.SetFixedLength(false); Ctx.Add(Path); Ctx.Add(Exe); Ctx.Add(Args); GAutoPtr c(new Cmd(Ctx, Logging, d->Log)); if (!c) return false; c->PostOp = Parser; c->Params.Reset(Params); c->Rd.Reset(new ReaderThread(Process.Release(), c)); Cmds.Add(c.Release()); Update(); LgiTrace("Cmd: %s %s\n", Exe, Args); return true; } int LogDateCmp(LListItem *a, LListItem *b, NativeInt Data) { VcCommit *A = dynamic_cast(a); VcCommit *B = dynamic_cast(b); if ((A != NULL) ^ (B != NULL)) { // This handles keeping the "working folder" list item at the top return (A != NULL) - (B != NULL); } // Sort the by date from most recent to least return -A->GetTs().Compare(&B->GetTs()); } bool VcFolder::ParseBranches(int Result, GString s, ParseParams *Params) { switch (GetType()) { case VcGit: { GString::Array a = s.SplitDelimit("\r\n"); for (GString *l = NULL; a.Iterate(l); ) { GString n = l->Strip(); if (n(0) == '*') { GString::Array c = n.SplitDelimit(" \t", 1); if (c.Length() > 1) Branches.New() = CurrentBranch = c[1]; else Branches.New() = n; } else Branches.New() = n; } break; } case VcHg: { Branches = s.SplitDelimit("\r\n"); break; } default: { break; } } OnBranchesChange(); return false; } void VcFolder::OnBranchesChange() { GWindow *w = d->Tree->GetWindow(); if (!w) return; DropDownBtn *dd; if (w->GetViewById(IDC_BRANCH_DROPDOWN, dd)) { dd->SetList(IDC_BRANCH, Branches); } if (Branches.Length() > 0) { GViewI *b; if (w->GetViewById(IDC_BRANCH, b)) { if (!ValidStr(b->Name())) b->Name(Branches.First()); } } } void VcFolder::Select(bool b) { if (!b) { GWindow *w = d->Tree->GetWindow(); w->SetCtrlName(IDC_BRANCH, NULL); } GTreeItem::Select(b); if (b) { if (!DirExists(Path)) return; if ((Log.Length() == 0 || CommitListDirty) && !IsLogging) { switch (GetType()) { case VcGit: { IsLogging = StartCmd("rev-list --all --header --timestamp --author-date-order", &VcFolder::ParseRevList); break; } case VcSvn: { if (CommitListDirty) { IsLogging = StartCmd("up", &VcFolder::ParsePull, new ParseParams("log")); break; } // else fall through } default: { IsLogging = StartCmd("log", &VcFolder::ParseLog); } } CommitListDirty = false; } if (Branches.Length() == 0) { switch (GetType()) { case VcGit: StartCmd("branch -a", &VcFolder::ParseBranches); break; case VcSvn: Branches.New() = "trunk"; break; case VcHg: StartCmd("branch", &VcFolder::ParseBranches); break; case VcCvs: break; default: LgiAssert(!"Impl me."); break; } } OnBranchesChange(); /* if (!IsUpdatingCounts && Unpushed < 0) { switch (GetType()) { case VcGit: IsUpdatingCounts = StartCmd("cherry -v", &VcFolder::ParseCounts); break; case VcSvn: IsUpdatingCounts = StartCmd("status -u", &VcFolder::ParseCounts); break; default: LgiAssert(!"Impl me."); break; } } */ char *Ctrl = d->Lst->GetWindow()->GetCtrlName(IDC_FILTER); GString Filter = ValidStr(Ctrl) ? Ctrl : NULL; if (d->CurFolder != this) { d->CurFolder = this; d->Lst->RemoveAll(); } if (!Uncommit) Uncommit.Reset(new UncommitedItem(d)); d->Lst->Insert(Uncommit, 0); int64 CurRev = Atoi(CurrentCommit.Get()); List Ls; for (unsigned i=0; iGetRev()) { switch (GetType()) { case VcSvn: { int64 LogRev = Atoi(Log[i]->GetRev()); if (CurRev >= 0 && CurRev >= LogRev) { CurRev = -1; Log[i]->SetCurrent(true); } else { Log[i]->SetCurrent(false); } break; } default: Log[i]->SetCurrent(!_stricmp(CurrentCommit, Log[i]->GetRev())); break; } } bool Add = !Filter; if (Filter) { const char *s = Log[i]->GetRev(); if (s && strstr(s, Filter) != NULL) Add = true; s = Log[i]->GetAuthor(); if (s && stristr(s, Filter) != NULL) Add = true; s = Log[i]->GetMsg(); if (s && stristr(s, Filter) != NULL) Add = true; } LList *CurOwner = Log[i]->GetList(); if (Add ^ (CurOwner != NULL)) { if (Add) Ls.Insert(Log[i]); else d->Lst->Remove(Log[i]); } } d->Lst->Insert(Ls); // d->Lst->Sort(LogDateCmp); if (GetType() == VcGit) { d->Lst->ColumnAt(0)->Width(40); d->Lst->ColumnAt(1)->Width(270); d->Lst->ColumnAt(2)->Width(240); d->Lst->ColumnAt(3)->Width(130); d->Lst->ColumnAt(4)->Width(400); } else d->Lst->ResizeColumnsToContent(); d->Lst->UpdateAllItems(); if (!CurrentCommit && !IsGetCur) { switch (GetType()) { case VcGit: IsGetCur = StartCmd("rev-parse HEAD", &VcFolder::ParseInfo); break; case VcSvn: IsGetCur = StartCmd("info", &VcFolder::ParseInfo); break; case VcHg: IsGetCur = StartCmd("id -i", &VcFolder::ParseInfo); break; case VcCvs: break; default: LgiAssert(!"Impl me."); break; } } } } int CommitRevCmp(VcCommit **a, VcCommit **b) { int64 arev = Atoi((*a)->GetRev()); int64 brev = Atoi((*b)->GetRev()); int64 diff = (int64)brev - arev; if (diff < 0) return -1; return (diff > 0) ? 1 : 0; } int CommitDateCmp(VcCommit **a, VcCommit **b) { uint64 ats, bts; (*a)->GetTs().Get(ats); (*b)->GetTs().Get(bts); int64 diff = (int64)bts - ats; if (diff < 0) return -1; return (diff > 0) ? 1 : 0; } bool VcFolder::ParseRevList(int Result, GString s, ParseParams *Params) { + /* GFile f("C:\\Users\\matthew\\Code\\Lgi\\trunk\\rev-list.txt", O_WRITE); if (f.IsOpen()) { f.SetSize(0); f.Write(s); f.Close(); } + */ LHashTbl, VcCommit*> Map; for (VcCommit **pc = NULL; Log.Iterate(pc); ) Map.Add((*pc)->GetRev(), *pc); int Skipped = 0, Errors = 0; switch (GetType()) { case VcGit: { GString::Array Commits; Commits.SetFixedLength(false); // Split on the NULL chars... char *c = s.Get(); char *e = c + s.Length(); while (c < e) { char *nul = c; while (nul < e && *nul) nul++; if (nul <= c) break; Commits.New().Set(c, nul-c); if (nul >= e) break; c = nul + 1; } for (auto Commit: Commits) { GAutoPtr Rev(new VcCommit(d, this)); if (Rev->GitParse(Commit, true)) { if (!Map.Find(Rev->GetRev())) Log.Add(Rev.Release()); else Skipped++; } else { LgiTrace("%s:%i - Failed:\n%s\n\n", _FL, Commit.Get()); Errors++; } } // Log.Sort(CommitDateCmp); LinkParents(); break; } default: LgiAssert(!"Impl me."); break; } return true; } bool VcFolder::ParseLog(int Result, GString s, ParseParams *Params) { LHashTbl, VcCommit*> Map; for (VcCommit **pc = NULL; Log.Iterate(pc); ) Map.Add((*pc)->GetRev(), *pc); int Skipped = 0, Errors = 0; switch (GetType()) { case VcGit: { GString::Array c; c.SetFixedLength(false); char *prev = s.Get(); for (char *i = s.Get(); *i; ) { if (!strnicmp(i, "commit ", 7)) { if (i > prev) { c.New().Set(prev, i - prev); // LgiTrace("commit=%i\n", (int)(i - prev)); } prev = i; } while (*i) { if (*i++ == '\n') break; } } for (unsigned i=0; i Rev(new VcCommit(d, this)); if (Rev->GitParse(c[i], false)) { if (!Map.Find(Rev->GetRev())) Log.Add(Rev.Release()); else Skipped++; } else { LgiTrace("%s:%i - Failed:\n%s\n\n", _FL, c[i].Get()); Errors++; } } Log.Sort(CommitDateCmp); break; } case VcSvn: { GString::Array c = s.Split("------------------------------------------------------------------------"); for (unsigned i=0; i Rev(new VcCommit(d, this)); GString Raw = c[i].Strip(); if (Rev->SvnParse(Raw)) { if (!Map.Find(Rev->GetRev())) Log.Add(Rev.Release()); else Skipped++; } else if (Raw) { LgiTrace("%s:%i - Failed:\n%s\n\n", _FL, Raw.Get()); Errors++; } } Log.Sort(CommitRevCmp); break; } case VcHg: { GString::Array c = s.Split("\n\n"); for (GString *Commit = NULL; c.Iterate(Commit); ) { GAutoPtr Rev(new VcCommit(d, this)); if (Rev->HgParse(*Commit)) { if (!Map.Find(Rev->GetRev())) Log.Add(Rev.Release()); } } break; } case VcCvs: { LHashTbl, VcCommit*> Map; GString::Array c = s.Split("============================================================================="); for (GString *Commit = NULL; c.Iterate(Commit);) { if (Commit->Strip().Length()) { GString Head, File; GString::Array Versions = Commit->Split("----------------------------"); GString::Array Lines = Versions[0].SplitDelimit("\r\n"); for (GString *Line = NULL; Lines.Iterate(Line);) { GString::Array p = Line->Split(":", 1); if (p.Length() == 2) { // LgiTrace("Line: %s\n", Line->Get()); GString Var = p[0].Strip().Lower(); GString Val = p[1].Strip(); if (Var.Equals("branch")) { if (Val.Length()) Branches.Add(Val); } else if (Var.Equals("head")) { Head = Val; } else if (Var.Equals("rcs file")) { GString::Array f = Val.SplitDelimit(","); File = f.First(); } } } // LgiTrace("%s\n", Commit->Get()); for (unsigned i=1; i= 3) { GString Ver = Lines[0].Split(" ").Last(); GString::Array a = Lines[1].SplitDelimit(";"); GString Date = a[0].Split(":", 1).Last().Strip(); GString Author = a[1].Split(":", 1).Last().Strip(); GString Id = a[2].Split(":", 1).Last().Strip(); GString Msg = Lines[2]; LDateTime Dt; if (Dt.Parse(Date)) { uint64 Ts; if (Dt.Get(Ts)) { VcCommit *Cc = Map.Find(Ts); if (!Cc) { Map.Add(Ts, Cc = new VcCommit(d, this)); Log.Add(Cc); Cc->CvsParse(Dt, Author, Msg); } Cc->Files.Add(File.Get()); } else LgiAssert(!"NO ts for date."); } else LgiAssert(!"Date parsing failed."); } } } } break; } default: LgiAssert(!"Impl me."); break; } // LgiTrace("%s:%i - ParseLog: Skip=%i, Error=%i\n", _FL, Skipped, Errors); IsLogging = false; return true; } void VcFolder::LinkParents() { LHashTbl,VcCommit*> Map; // Index all the commits int i = 0; for (auto c:Log) { c->Idx = i++; c->NodeIdx = -1; Map.Add(c->GetRev(), c); } // Create all the edges... for (auto c:Log) { auto *Par = c->GetParents(); for (auto &pRev : *Par) { auto *p = Map.Find(pRev); if (p) new VcEdge(p, c); else LgiAssert(0); } } // Map the edges to positions typedef GArray EdgeArr; GArray Active; for (auto c:Log) { #if 1 if (c->IsRev("49d6765e37dfffb0ad4e924da5169c076c87c871")) { int asd=0; } #endif for (unsigned i=0; c->NodeIdx<0 && iParent == c) { c->NodeIdx = i; break; } } } // Add starting edges to active set for (auto e:c->Edges) { if (e->Child == c) { if (c->NodeIdx < 0) c->NodeIdx = (int)Active.Length(); e->Idx = c->NodeIdx; c->Pos.Add(e, e->Idx); Active[e->Idx].Add(e); } } // Now for all active edges... assign positions for (unsigned i=0; iChild || c == e->Parent) { LgiAssert(c->NodeIdx >= 0); c->Pos.Add(e, c->NodeIdx); } else { // May need to untangle edges with different parents here bool Diff = false; for (auto edge: Edges) { if (edge != e && edge->Child != c && edge->Parent != e->Parent) { Diff = true; break; } } if (Diff) { int NewIndex = -1; // Look through existing indexes for a parent match for (unsigned ii=0; iiParent? bool Match = true; for (auto ee:Active[ii]) { if (ee->Parent != e->Parent) { Match = false; break; } } if (Match) NewIndex = ii; } if (NewIndex < 0) // Create new index for this parent NewIndex = (int)Active.Length(); Edges.Delete(e); Active[NewIndex].Add(e); e->Idx = NewIndex; c->Pos.Add(e, NewIndex); n--; } else { LgiAssert(e->Idx == i); c->Pos.Add(e, i); } } } } // Process terminating edges for (auto e:c->Edges) { if (e->Parent == c) { if (e->Idx < 0) { // This happens with out of order commits..? continue; } int i = e->Idx; if (c->NodeIdx < 0) c->NodeIdx = i; LgiAssert(Active[i].HasItem(e)); Active[i].Delete(e); } } // Collapse any empty active columns for (unsigned i=0; iIdx > 0); edge->Idx--; c->Pos.Add(edge, edge->Idx); } } i--; } } } } VcFile *VcFolder::FindFile(const char *Path) { if (!Path) return NULL; GArray Files; if (d->Files->GetAll(Files)) { GString p = Path; p = p.Replace(DIR_STR, "/"); for (auto f : Files) { auto Fn = f->GetFileName(); if (p.Equals(Fn)) { return f; } } } return NULL; } void VcFolder::OnCmdError(GString Output, const char *Msg) { if (!CmdErrors) { d->Log->Write(Output, Output.Length()); GString::Array a = GetProgramsInPath(GetVcName()); d->Log->Print("'%s' executables in the path:\n", GetVcName()); for (auto Bin : a) d->Log->Print(" %s\n", Bin.Get()); } CmdErrors++; d->Tabs->Value(1); Color(GColour::Red); } void VcFolder::ClearError() { Color(GCss::ColorInherit); } bool VcFolder::ParseInfo(int Result, GString s, ParseParams *Params) { switch (GetType()) { case VcGit: case VcHg: { CurrentCommit = s.Strip(); break; } case VcSvn: { if (s.Find("client is too old") >= 0) { OnCmdError(s, "Client too old"); break; } GString::Array c = s.Split("\n"); for (unsigned i=0; iIsWorking = true; ParseStatus(Result, s, Params); } else ParseDiffs(s, NULL, true); IsWorkingFld = false; d->Files->ResizeColumnsToContent(); if (GetType() == VcSvn) { Unpushed = d->Files->Length() > 0 ? 1 : 0; Update(); } return false; } bool VcFolder::ParseDiff(int Result, GString s, ParseParams *Params) { ParseDiffs(s, NULL, true); return false; } void VcFolder::Diff(VcFile *file) { switch (GetType()) { case VcSvn: case VcGit: case VcHg: { GString a; a.Printf("diff \"%s\"", file->GetFileName()); StartCmd(a, &VcFolder::ParseDiff); break; } default: LgiAssert(!"Impl me."); break; } } bool VcFolder::ParseDiffs(GString s, GString Rev, bool IsWorking) { LgiAssert(IsWorking || Rev.Get() != NULL); switch (GetType()) { case VcGit: { GString::Array a = s.Split("\n"); GString Diff; VcFile *f = NULL; for (unsigned i=0; iSetDiff(Diff); Diff.Empty(); auto Bits = a[i].SplitDelimit(); GString Fn, State = "M"; if (Bits[1].Equals("--cc")) { Fn = Bits.Last(); State = "C"; } else Fn = Bits.Last()(2,-1); LgiTrace("%s\n", a[i].Get()); f = FindFile(Fn); if (!f) f = new VcFile(d, this, Rev, IsWorking); f->SetText(State, COL_STATE); f->SetText(Fn.Replace("\\","/"), COL_FILENAME); f->GetStatus(); d->Files->Insert(f); } else if (!_strnicmp(Ln, "new file", 8)) { if (f) f->SetText("A", COL_STATE); } else if (!_strnicmp(Ln, "index", 5) || !_strnicmp(Ln, "commit", 6) || !_strnicmp(Ln, "Author:", 7) || !_strnicmp(Ln, "Date:", 5) || !_strnicmp(Ln, "+++", 3) || !_strnicmp(Ln, "---", 3)) { // Ignore } else { if (Diff) Diff += "\n"; Diff += a[i]; } } if (f && Diff) { f->SetDiff(Diff); Diff.Empty(); } break; } case VcHg: { GString::Array a = s.Split("\n"); GString Diff; VcFile *f = NULL; for (unsigned i=0; iSetDiff(Diff); Diff.Empty(); GString Fn = a[i].Split(" ").Last(); f = new VcFile(d, this, Rev, IsWorking); f->SetText(Fn.Replace("\\","/"), COL_FILENAME); d->Files->Insert(f); } else if (!_strnicmp(Ln, "index", 5) || !_strnicmp(Ln, "commit", 6) || !_strnicmp(Ln, "Author:", 7) || !_strnicmp(Ln, "Date:", 5) || !_strnicmp(Ln, "+++", 3) || !_strnicmp(Ln, "---", 3)) { // Ignore } else { if (Diff) Diff += "\n"; Diff += a[i]; } } if (f && Diff) { f->SetDiff(Diff); Diff.Empty(); } break; } case VcSvn: { GString::Array a = s.Replace("\r").Split("\n"); GString Diff; VcFile *f = NULL; bool InPreamble = false; bool InDiff = false; for (unsigned i=0; iSetDiff(Diff); f->Select(false); } Diff.Empty(); InDiff = false; InPreamble = false; GString Fn = a[i].Split(":", 1).Last().Strip(); f = FindFile(Fn); if (!f) f = new VcFile(d, this, Rev, IsWorking); f->SetText(Fn.Replace("\\","/"), COL_FILENAME); f->SetText("M", COL_STATE); f->GetStatus(); d->Files->Insert(f); } else if (!_strnicmp(Ln, "------", 6)) { InPreamble = !InPreamble; } else if (!_strnicmp(Ln, "======", 6)) { InPreamble = false; InDiff = true; } else if (InDiff) { if (!strncmp(Ln, "--- ", 4) || !strncmp(Ln, "+++ ", 4)) { } else { if (Diff) Diff += "\n"; Diff += a[i]; } } } if (f && Diff) { f->SetDiff(Diff); Diff.Empty(); } break; } case VcCvs: { break; } default: { LgiAssert(!"Impl me."); break; } } return true; } bool VcFolder::ParseFiles(int Result, GString s, ParseParams *Params) { d->ClearFiles(); ParseDiffs(s, Params->Str, false); IsFilesCmd = false; d->Files->ResizeColumnsToContent(); return false; } void VcFolder::OnPulse() { bool Reselect = false, CmdsChanged = false; static bool Processing = false; if (!Processing) { Processing = true; // Lock out processing, if it puts up a dialog or something... // bad things happen if we try and re-process something. for (unsigned i=0; iRd->IsExited()) { GString s = c->GetBuf(); int Result = c->Rd->ExitCode(); if (Result == ErrSubProcessFailed) { if (!CmdErrors) d->Log->Print("Error: Can't run '%s'\n", GetVcName()); CmdErrors++; } if (c->PostOp) Reselect |= CALL_MEMBER_FN(*this, c->PostOp)(Result, s, c->Params); Cmds.DeleteAt(i--, true); delete c; CmdsChanged = true; } } Processing = false; } if (Reselect) { if (GTreeItem::Select()) Select(true); } if (CmdsChanged) Update(); } void VcFolder::OnRemove() { GXmlTag *t = d->Opts.LockTag(NULL, _FL); if (t) { for (GXmlTag *c = t->Children.First(); c; c = t->Children.Next()) { if (c->IsTag(OPT_Folder) && c->GetContent() && !_stricmp(c->GetContent(), Path)) { c->RemoveTag(); delete c; break; } } d->Opts.Unlock(); } } void VcFolder::OnMouseClick(GMouse &m) { if (m.IsContextMenu()) { GSubMenu s; s.AppendItem("Browse To", IDM_BROWSE_FOLDER); s.AppendItem( #ifdef WINDOWS "Command Prompt At", #else "Terminal At", #endif IDM_TERMINAL); s.AppendItem("Clean", IDM_CLEAN); s.AppendSeparator(); s.AppendItem("Remove", IDM_REMOVE); int Cmd = s.Float(GetTree(), m); switch (Cmd) { case IDM_BROWSE_FOLDER: { LgiBrowseToFile(Path); break; } case IDM_TERMINAL: { #if defined(MAC) LgiExecute("/Applications/Utilities/Terminal.app/Contents/MacOS/Terminal", Path); #elif defined(WINDOWS) TCHAR w[MAX_PATH]; auto r = GetWindowsDirectory(w, CountOf(w)); if (r > 0) { GFile::Path p = GString(w); p += "system32\\cmd.exe"; FileDev->SetCurrentFolder(Path); LgiExecute(p); } #elif defined(LINUX) // #error "Impl me." #endif break; } case IDM_CLEAN: { Clean(); break; } case IDM_REMOVE: { OnRemove(); delete this; break; } default: break; } } } void VcFolder::OnUpdate(const char *Rev) { if (!Rev) return; if (!IsUpdate) { GString Args; NewRev = Rev; switch (GetType()) { case VcGit: Args.Printf("checkout %s", Rev); IsUpdate = StartCmd(Args, &VcFolder::ParseUpdate, NULL, LogNormal); break; case VcSvn: Args.Printf("up -r %s", Rev); IsUpdate = StartCmd(Args, &VcFolder::ParseUpdate, NULL, LogNormal); break; default: { LgiAssert(!"Impl me."); break; } } } } /////////////////////////////////////////////////////////////////////////////////////// int FolderCompare(GTreeItem *a, GTreeItem *b, NativeInt UserData) { VcLeaf *A = dynamic_cast(a); VcLeaf *B = dynamic_cast(b); if (!A || !B) return 0; return A->Compare(B); } void VcFolder::ReadDir(GTreeItem *Parent, const char *Path) { // Read child items GDirectory Dir; for (int b = Dir.First(Path); b; b = Dir.Next()) { if (Dir.IsDir()) { if (Dir.GetName()[0] != '.') { new VcLeaf(this, Parent, Path, Dir.GetName(), true); } } else if (!Dir.IsHidden()) { char *Ext = LgiGetExtension(Dir.GetName()); if (!Ext) continue; if (!stricmp(Ext, "c") || !stricmp(Ext, "cpp") || !stricmp(Ext, "h")) { new VcLeaf(this, Parent, Path, Dir.GetName(), false); } } } Parent->SortChildren(FolderCompare); } void VcFolder::OnExpand(bool b) { if (Tmp && b) { Tmp->Remove(); DeleteObj(Tmp); ReadDir(this, Path); } } void VcFolder::OnPaint(ItemPaintCtx &Ctx) { auto c = Color(); if (c.IsValid()) Ctx.Fore = c; c = BackgroundColor(); if (c.IsValid() && !GTreeItem::Select()) Ctx.Back = c; GTreeItem::OnPaint(Ctx); } void VcFolder::ListCommit(VcCommit *c) { if (!IsFilesCmd) { GString Args; switch (GetType()) { case VcGit: // Args.Printf("show --oneline --name-only %s", Rev); Args.Printf("show %s", c->GetRev()); IsFilesCmd = StartCmd(Args, &VcFolder::ParseFiles, new ParseParams(c->GetRev())); break; case VcSvn: Args.Printf("log --verbose --diff -r %s", c->GetRev()); IsFilesCmd = StartCmd(Args, &VcFolder::ParseFiles, new ParseParams(c->GetRev())); break; case VcCvs: { d->ClearFiles(); for (unsigned i=0; iFiles.Length(); i++) { VcFile *f = new VcFile(d, this, c->GetRev(), false); if (f) { f->SetText(c->Files[i], COL_FILENAME); d->Files->Insert(f); } } d->Files->ResizeColumnsToContent(); break; } default: LgiAssert(!"Impl me."); break; } if (IsFilesCmd) d->ClearFiles(); } } GString ConvertUPlus(GString s) { GArray c; GUtf8Ptr p(s); int32 ch; while ((ch = p)) { if (ch == '{') { auto n = p.GetPtr(); if (n[1] == 'U' && n[2] == '+') { // Convert unicode code point p += 3; ch = (int32)htoi(p.GetPtr()); c.Add(ch); while ((ch = p) != '}') p++; } else c.Add(ch); } else c.Add(ch); p++; } c.Add(0); #ifdef LINUX return GString((char16*)c.AddressOf()); #else return GString(c.AddressOf()); #endif } bool VcFolder::ParseStatus(int Result, GString s, ParseParams *Params) { bool ShowUntracked = d->Files->GetWindow()->GetCtrlValue(IDC_UNTRACKED) != 0; bool IsWorking = Params ? Params->IsWorking : false; List Ins; switch (GetType()) { case VcCvs: { GString::Array a = s.Split("==================================================================="); for (auto i : a) { GString::Array Lines = i.SplitDelimit("\r\n"); GString f = Lines[0].Strip(); if (f.Find("File:") == 0) { GString::Array Parts = f.SplitDelimit("\t"); GString File = Parts[0].Split(": ").Last(); GString Status = Parts[1].Split(": ").Last(); GString WorkingRev; for (auto l : Lines) { GString::Array p = l.Strip().Split(":", 1); if (p.Length() > 1 && p[0].Strip().Equals("Working revision")) { WorkingRev = p[1].Strip(); } } VcFile *f = new VcFile(d, this, WorkingRev, IsWorking); f->SetText(Status, COL_STATE); f->SetText(File, COL_FILENAME); Ins.Insert(f); } else if (f(0) == '?' && ShowUntracked) { GString File = f(2, -1); VcFile *f = new VcFile(d, this, NULL, IsWorking); f->SetText("?", COL_STATE); f->SetText(File, COL_FILENAME); Ins.Insert(f); } } break; } case VcGit: { GString::Array Lines = s.SplitDelimit("\r\n"); int Fmt = ToolVersion[VcGit] >= Ver2Int("2.8.0") ? 2 : 1; for (auto Ln : Lines) { char Type = Ln(0); if (Ln.Lower().Find("error:") >= 0) { } else if (Ln.Find("usage: git") >= 0) { // It's probably complaining about the --porcelain=2 parameter OnCmdError(s, "Args error"); } else if (Type != '?') { VcFile *f = NULL; if (Fmt == 2) { GString::Array p = Ln.SplitDelimit(" ", 8); if (p.Length() < 7) d->Log->Print("%s:%i - Error: not enough tokens: '%s'\n", _FL, Ln.Get()); else { f = new VcFile(d, this, p[6], IsWorking); f->SetText(p[1].Strip("."), COL_STATE); f->SetText(p.Last(), COL_FILENAME); } } else if (Fmt == 1) { GString::Array p = Ln.SplitDelimit(" "); f = new VcFile(d, this, NULL, IsWorking); f->SetText(p[0], COL_STATE); f->SetText(p.Last(), COL_FILENAME); } if (f) Ins.Insert(f); } else if (ShowUntracked) { VcFile *f = new VcFile(d, this, NULL, IsWorking); f->SetText("?", COL_STATE); f->SetText(Ln(2,-1), COL_FILENAME); Ins.Insert(f); } } break; } case VcHg: case VcSvn: { GString::Array Lines = s.SplitDelimit("\r\n"); for (auto Ln : Lines) { char Type = Ln(0); if (Ln.Lower().Find("error:") >= 0) { } else if (Ln.Find("client is too old") >= 0) { OnCmdError(s, "Client too old."); return false; } else if (Strchr(" \t", Type) || Ln.Find("Summary of conflicts") >= 0) { // Ignore } else if (Type != '?') { GString::Array p = Ln.SplitDelimit(" ", 1); if (p.Length() == 2) { GString File; if (GetType() == VcSvn) File = ConvertUPlus(p.Last()); else File = p.Last(); VcFile *f = new VcFile(d, this, NULL, IsWorking); f->SetText(p[0], COL_STATE); f->SetText(File.Replace("\\","/"), COL_FILENAME); f->GetStatus(); Ins.Insert(f); } else LgiAssert(!"What happen?"); } else if (ShowUntracked) { VcFile *f = new VcFile(d, this, NULL, IsWorking); f->SetText("?", COL_STATE); f->SetText(Ln(2,-1), COL_FILENAME); Ins.Insert(f); } } break; } default: { LgiAssert(!"Impl me."); break; } } Unpushed = Ins.Length() > 0; Update(); if (GTreeItem::Select()) { d->Files->Insert(Ins); d->Files->ResizeColumnsToContent(); } else { Ins.DeleteObjects(); } return false; // Don't refresh list } void VcFolder::FolderStatus(const char *Path, VcLeaf *Notify) { if (GTreeItem::Select()) d->ClearFiles(); GString Arg; switch (GetType()) { case VcSvn: case VcHg: Arg = "status"; break; case VcCvs: Arg = "status -l"; break; case VcGit: if (!ToolVersion[VcGit]) LgiAssert(!"Where is the version?"); // What version did =2 become available? It's definately not in v2.5.4 // Not in v2.7.4 either... if (ToolVersion[VcGit] >= Ver2Int("2.8.0")) Arg = "status --porcelain=2"; else Arg = "status --porcelain"; break; default: return; } ParseParams *p = new ParseParams; if (Path && Notify) { p->AltInitPath = Path; p->Leaf = Notify; } else { p->IsWorking = true; } StartCmd(Arg, &VcFolder::ParseStatus, p); } void VcFolder::ListWorkingFolder() { if (!IsWorkingFld) { d->ClearFiles(); GString Arg; switch (GetType()) { case VcCvs: Arg = "-q diff --brief"; break; case VcSvn: Arg = "status"; break; case VcGit: StartCmd("diff --staged", &VcFolder::ParseWorking); Arg = "diff --diff-filter=ACDMRTU"; // return FolderStatus(); break; default: Arg ="diff"; break; } IsWorkingFld = StartCmd(Arg, &VcFolder::ParseWorking); } } void VcFolder::GitAdd() { if (!PostAdd) return; GString Args; if (PostAdd->Files.Length() == 0) { GString m(PostAdd->Msg); m = m.Replace("\"", "\\\""); Args.Printf("commit -m \"%s\"", m.Get()); IsCommit = StartCmd(Args, &VcFolder::ParseCommit, PostAdd->Param, LogNormal); PostAdd.Reset(); } else { GString Last = PostAdd->Files.Last(); Args.Printf("add \"%s\"", Last.Replace("\"", "\\\"").Replace("/", DIR_STR).Get()); PostAdd->Files.PopLast(); StartCmd(Args, &VcFolder::ParseGitAdd, NULL, LogNormal); } } bool VcFolder::ParseGitAdd(int Result, GString s, ParseParams *Params) { GitAdd(); return false; } bool VcFolder::ParseCommit(int Result, GString s, ParseParams *Params) { if (GTreeItem::Select()) Select(true); CommitListDirty = Result == 0; CurrentCommit.Empty(); IsCommit = false; if (Result) { switch (GetType()) { case VcGit: { if (s.Find("Please tell me who you are") >= 0) { { GInput i(GetTree(), "", "Git user name:", AppName); if (i.DoModal()) { GString Args; Args.Printf("config --global user.name \"%s\"", i.GetStr().Get()); StartCmd(Args); } } { GInput i(GetTree(), "", "Git user email:", AppName); if (i.DoModal()) { GString Args; Args.Printf("config --global user.email \"%s\"", i.GetStr().Get()); StartCmd(Args); } } } break; } default: break; } return false; } if (Result == 0 && GTreeItem::Select()) { d->ClearFiles(); GWindow *w = d->Diff ? d->Diff->GetWindow() : NULL; if (w) w->SetCtrlName(IDC_MSG, NULL); } switch (GetType()) { case VcGit: { Unpushed++; Update(); if (Params && Params->Str.Find("Push") >= 0) Push(); break; } case VcSvn: { CurrentCommit.Empty(); CommitListDirty = true; GetTree()->SendNotify(LvcCommandEnd); if (!Result) { Unpushed = 0; Update(); } break; } default: { LgiAssert(!"Impl me."); break; } } return true; } void VcFolder::Commit(const char *Msg, const char *Branch, bool AndPush) { VcFile *f = NULL; GArray Add; bool Partial = false; while (d->Files->Iterate(f)) { int c = f->Checked(); if (c > 0) Add.Add(f); else Partial = true; } if (CurrentBranch && Branch && !CurrentBranch.Equals(Branch)) { int Response = LgiMsg(GetTree(), "Do you want to start a new branch?", AppName, MB_YESNO); if (Response != IDYES) return; } if (!IsCommit) { GString Args; ParseParams *Param = AndPush ? new ParseParams("Push") : NULL; switch (GetType()) { case VcGit: { if (Add.Length() == 0) { break; } else if (Partial) { if (PostAdd.Reset(new GitCommit)) { PostAdd->Files.SetFixedLength(false); for (auto f : Add) PostAdd->Files.Add(f->GetFileName()); PostAdd->Msg = Msg; PostAdd->Branch = Branch; PostAdd->Param = Param; GitAdd(); } } else { GString m(Msg); m = m.Replace("\"", "\\\""); Args.Printf("commit -am \"%s\"", m.Get()); IsCommit = StartCmd(Args, &VcFolder::ParseCommit, Param, LogNormal); } break; } case VcSvn: { GString::Array a; a.New().Printf("commit -m \"%s\"", Msg); for (VcFile **pf = NULL; Add.Iterate(pf); ) { GString s = (*pf)->GetFileName(); if (s.Find(" ") >= 0) a.New().Printf("\"%s\"", s.Get()); else a.New() = s; } Args = GString(" ").Join(a); IsCommit = StartCmd(Args, &VcFolder::ParseCommit, NULL, LogNormal); if (d->Tabs && IsCommit) { d->Tabs->Value(1); GetTree()->SendNotify(LvcCommandStart); } break; } default: { LgiAssert(!"Impl me."); break; } } } } void VcFolder::Push() { GString Args; bool Working = false; switch (GetType()) { case VcGit: Working = StartCmd("push", &VcFolder::ParsePush, NULL, LogNormal); break; case VcSvn: // Nothing to do here.. the commit pushed the data already break; default: LgiAssert(!"Impl me."); break; } if (d->Tabs && Working) { d->Tabs->Value(1); GetTree()->SendNotify(LvcCommandStart); } } bool VcFolder::ParsePush(int Result, GString s, ParseParams *Params) { bool Status = false; if (Result) { OnCmdError(s, "Push failed."); } else { ClearError(); switch (GetType()) { case VcGit: break; case VcSvn: break; default: break; } Unpushed = 0; Update(); Status = true; } GetTree()->SendNotify(LvcCommandEnd); return Status; // no reselect } void VcFolder::Pull(LoggingType Logging) { GString Args; bool Status = false; switch (GetType()) { case VcHg: case VcGit: Status = StartCmd("pull", &VcFolder::ParsePull, NULL, Logging); break; case VcSvn: Status = StartCmd("up", &VcFolder::ParsePull, NULL, Logging); break; default: LgiAssert(!"Impl me."); break; } if (d->Tabs && Status) { d->Tabs->Value(1); GetTree()->SendNotify(LvcCommandStart); } } bool VcFolder::ParsePull(int Result, GString s, ParseParams *Params) { if (Result) { OnCmdError(s, "Pull failed."); return false; } else ClearError(); switch (GetType()) { case VcGit: case VcHg: { // Git does a merge by default, so the current commit changes... CurrentCommit.Empty(); break; } case VcSvn: { // Svn also does a merge by default and can update our current position... CurrentCommit.Empty(); GString::Array a = s.SplitDelimit("\r\n"); for (GString *Ln = NULL; a.Iterate(Ln); ) { if (Ln->Find("At revision") >= 0) { GString::Array p = Ln->SplitDelimit(" ."); CurrentCommit = p.Last(); break; } else if (Ln->Find("svn cleanup") >= 0) { OnCmdError(s, "Needs cleanup"); break; } } if (Params && Params->Str.Equals("log")) { IsLogging = StartCmd("log", &VcFolder::ParseLog); return false; } break; } default: break; } GetTree()->SendNotify(LvcCommandEnd); CommitListDirty = true; return true; // Yes - reselect and update } void VcFolder::Clean() { switch (GetType()) { case VcSvn: StartCmd("cleanup", &VcFolder::ParseClean, NULL, LogNormal); break; default: LgiMsg(GetTree(), "Not implemented.", AppName); break; } } bool VcFolder::ParseClean(int Result, GString s, ParseParams *Params) { switch (GetType()) { case VcSvn: if (Result == 0) Color(ColorInherit); break; default: LgiAssert(!"Impl me."); break; } return false; } void VcFolder::GetVersion() { auto t = GetType(); switch (t) { case VcGit: case VcSvn: case VcHg: case VcCvs: StartCmd("--version", &VcFolder::ParseVersion, NULL, LogNormal); break; default: OnCmdError(NULL, "No version control found."); break; } } bool VcFolder::ParseVersion(int Result, GString s, ParseParams *Params) { auto p = s.SplitDelimit(); switch (GetType()) { case VcGit: { if (p.Length() > 2) { ToolVersion[GetType()] = Ver2Int(p[2]); printf("Git version: %s\n", p[2].Get()); } else LgiAssert(0); break; } case VcSvn: { if (p.Length() > 2) { ToolVersion[GetType()] = Ver2Int(p[2]); printf("Svn version: %s\n", p[2].Get()); } else LgiAssert(0); break; } case VcHg: { if (p.Length() >= 5) { auto Ver = p[4].Strip("()"); ToolVersion[GetType()] = Ver2Int(Ver); printf("Hg version: %s\n", Ver.Get()); } break; } case VcCvs: { #ifdef _DEBUG for (auto i : p) printf("i='%s'\n", i.Get()); #endif LgiAssert(!"Impl me."); break; } default: break; } return false; } bool VcFolder::ParseAddFile(int Result, GString s, ParseParams *Params) { switch (GetType()) { case VcCvs: { break; } default: break; } return false; } bool VcFolder::AddFile(const char *Path, bool AsBinary) { if (!Path) return false; switch (GetType()) { case VcCvs: { GString a; a.Printf("add%s \"%s\"", AsBinary ? " -kb" : "", Path); return StartCmd(a, &VcFolder::ParseAddFile); break; } default: { LgiAssert(!"Impl me."); break; } } return false; } bool VcFolder::ParseRevert(int Result, GString s, ParseParams *Params) { ListWorkingFolder(); return false; } bool VcFolder::Revert(const char *Path, const char *Revision) { if (!Path) return false; switch (GetType()) { case VcGit: { GString a; a.Printf("checkout \"%s\"", Path); return StartCmd(a, &VcFolder::ParseRevert); break; } case VcSvn: { GString a; a.Printf("revert \"%s\"", Path); return StartCmd(a, &VcFolder::ParseRevert); break; } default: { LgiAssert(!"Impl me."); break; } } return false; } bool VcFolder::ParseResolve(int Result, GString s, ParseParams *Params) { switch (GetType()) { case VcGit: { break; } case VcSvn: case VcHg: case VcCvs: default: { LgiAssert(!"Impl me."); break; } } return true; } bool VcFolder::Resolve(const char *Path) { if (!Path) return false; switch (GetType()) { case VcGit: { GString a; a.Printf("add \"%s\"", Path); return StartCmd(a, &VcFolder::ParseResolve, new ParseParams(Path)); } case VcSvn: case VcHg: case VcCvs: default: { LgiAssert(!"Impl me."); break; } } return false; } bool VcFolder::ParseBlame(int Result, GString s, ParseParams *Params) { new BlameUi(d, GetType(), s); return false; } bool VcFolder::Blame(const char *Path) { if (!Path) return false; switch (GetType()) { case VcGit: { GString a; a.Printf("blame \"%s\"", Path); return StartCmd(a, &VcFolder::ParseBlame); break; } case VcHg: { GString a; a.Printf("annotate -un \"%s\"", Path); return StartCmd(a, &VcFolder::ParseBlame); break; } case VcSvn: { GString a; a.Printf("blame \"%s\"", Path); return StartCmd(a, &VcFolder::ParseBlame); break; } default: { LgiAssert(!"Impl me."); break; } } return true; } bool VcFolder::SaveFileAs(const char *Path, const char *Revision) { if (!Path || !Revision) return false; return true; } bool VcFolder::ParseSaveAs(int Result, GString s, ParseParams *Params) { return false; } bool VcFolder::ParseCounts(int Result, GString s, ParseParams *Params) { switch (GetType()) { case VcGit: { Unpushed = (int) s.Strip().Split("\n").Length(); break; } case VcSvn: { int64 ServerRev = 0; bool HasUpdate = false; GString::Array c = s.Split("\n"); for (unsigned i=0; i 1 && a[0].Equals("Status")) ServerRev = a.Last().Int(); else if (a[0].Equals("*")) HasUpdate = true; } if (ServerRev > 0 && HasUpdate) { int64 CurRev = CurrentCommit.Int(); Unpulled = (int) (ServerRev - CurRev); } else Unpulled = 0; Update(); break; } default: { LgiAssert(!"Impl me."); break; } } IsUpdatingCounts = false; Update(); return false; // No re-select } void VcFolder::SetEol(const char *Path, int Type) { if (!Path) return; switch (Type) { case IDM_EOL_LF: { ConvertEol(Path, false); break; } case IDM_EOL_CRLF: { ConvertEol(Path, true); break; } case IDM_EOL_AUTO: { #ifdef WINDOWS ConvertEol(Path, true); #else ConvertEol(Path, false); #endif break; } } } void VcFolder::UncommitedItem::Select(bool b) { LListItem::Select(b); if (b) { GTreeItem *i = d->Tree->Selection(); VcFolder *f = dynamic_cast(i); if (f) f->ListWorkingFolder(); if (d->Msg) { d->Msg->Name(NULL); GWindow *w = d->Msg->GetWindow(); if (w) { w->SetCtrlEnabled(IDC_COMMIT, true); w->SetCtrlEnabled(IDC_COMMIT_AND_PUSH, true); } } } } void VcFolder::UncommitedItem::OnPaint(GItem::ItemPaintCtx &Ctx) { GFont *f = GetList()->GetFont(); f->Transparent(false); f->Colour(Ctx.Fore, Ctx.Back); GDisplayString ds(f, "(working folder)"); ds.Draw(Ctx.pDC, Ctx.x1 + ((Ctx.X() - ds.X()) / 2), Ctx.y1 + ((Ctx.Y() - ds.Y()) / 2), &Ctx); } ////////////////////////////////////////////////////////////////////////////////////////// VcLeaf::VcLeaf(VcFolder *parent, GTreeItem *Item, GString path, GString leaf, bool folder) { Parent = parent; d = Parent->GetPriv(); Path = path; Leaf = leaf; Folder = folder; Tmp = NULL; Item->Insert(this); if (Folder) { Insert(Tmp = new GTreeItem); Tmp->SetText("Loading..."); } } GString VcLeaf::Full() { GFile::Path p(Path); p += Leaf; return p.GetFull(); } void VcLeaf::OnBrowse() { Parent->FolderStatus(Full(), this); } void VcLeaf::AfterBrowse() { LList *Files = d->Files; Files->Empty(); GDirectory Dir; for (int b = Dir.First(Full()); b; b = Dir.Next()) { if (Dir.IsDir()) continue; VcFile *f = new VcFile(d, Parent, NULL); if (f) { // f->SetText(COL_STATE, f->SetText(Dir.GetName(), COL_FILENAME); Files->Insert(f); } } Files->ResizeColumnsToContent(); } void VcLeaf::OnExpand(bool b) { if (Tmp && b) { Tmp->Remove(); DeleteObj(Tmp); GFile::Path p(Path); p += Leaf; Parent->ReadDir(this, p); } } char *VcLeaf::GetText(int Col) { if (Col == 0) return Leaf; return NULL; } int VcLeaf::GetImage(int Flags) { return Folder ? IcoFolder : IcoFile; } int VcLeaf::Compare(VcLeaf *b) { // Sort folders to the top... if (Folder ^ b->Folder) return (int)b->Folder - (int)Folder; // Then alphabetical return Stricmp(Leaf.Get(), b->Leaf.Get()); } bool VcLeaf::Select() { return GTreeItem::Select(); } void VcLeaf::Select(bool b) { GTreeItem::Select(b); if (b) OnBrowse(); } void VcLeaf::OnMouseClick(GMouse &m) { if (m.IsContextMenu()) { GSubMenu s; s.AppendItem("Log", IDM_LOG); s.AppendItem("Blame", IDM_BLAME, !Folder); int Cmd = s.Float(GetTree(), m); switch (Cmd) { case IDM_LOG: { break; } case IDM_BLAME: { Parent->Blame(Full()); break; } } } }