#include "ide.h" VectorMap git_branch_cache; String GetGitBranchRaw(const String& dir) { String branch = GitCmd(dir, "branch --show"); branch.TrimEnd("\n"); return branch; } String GetGitBranch(const String& dir) { int q = git_branch_cache.Find(dir); String branch; if(q < 0) { String git_dir = dir; if(GetRepo(git_dir) == GIT_DIR) { q = git_branch_cache.Find(git_dir); if(q < 0) { branch = GetGitBranchRaw(git_dir); q = git_branch_cache.GetCount(); git_branch_cache.Add(git_dir, branch); } branch = git_branch_cache[q]; } q = git_branch_cache.Find(dir); if(q < 0) { q = git_branch_cache.GetCount(); git_branch_cache.Add(dir, branch); } } return git_branch_cache[q]; } void Ide::MakeTitle() { if(replace_in_files) return; String title; title << Nvl(main, "TheIDE"); Vector dirs = GetUppDirs(); if(dirs.GetCount()) { String branch = GetGitBranch(dirs[0]); if(branch.GetCount()) title << " [ " << branch << " ] "; } if(!mainconfigname.IsEmpty() && mainconfigname == mainconfigparam) title << " - " << mainconfigname; else if(!mainconfigname.IsEmpty()) { title << " - " << mainconfigname; title << " ( " << mainconfigparam << " )"; } if(designer) { title << " - " << designer->GetFileName(); int cs = designer->GetCharset(); if(cs >= 0) title << " " << CharsetName(cs); } else if(!editfile.IsEmpty()) { title << " - " << editfile; String branch = GetGitBranch(GetFileFolder(editfile)); if(branch.GetCount()) title << " [ " << branch << " ] "; int chrset = editor.GetCharset(); title << " " << IdeCharsetName(chrset) << " " << (findarg(Nvl(editfile_line_endings, line_endings), LF, DETECT_LF) >= 0 ? "LF" : "CRLF"); if(editor.IsTruncated()) title << " [Truncated]"; if(editor.IsView()) title << " [View]"; else if(editor.IsReadOnly()) title << " [Read Only]"; if(editor.IsDirty()) title << " *"; if(!bar_branch) branch = Null; if(findarg(branch, "", "master", "main") >= 0) editor.BarText(branch, GrayColor(IsDarkTheme() ? 100 : 220)); else { auto h = GetHashValue(branch); int r = h & 31; h >>= 5; int g = h & 15; h >>= 4; int b = h & 63; editor.BarText(branch, IsDarkTheme() ? Color(150 + r, 150 + g, 150 + b) : Color(200 - r, 200 - g, 200 - b)); } } if(!IsNull(editfile)) for(int i = 0; i < 10; i++) if(NormalizePath(editfile) == NormalizePath(bookmark[i].file)) title << Format(" <%d>", i); title << " { " << GetAssemblyId() << " }"; if(isscanning) title << " (scanning files)"; Title(title.ToWString()); DoDisplay(); } bool Ide::CanToggleReadOnly() { return NormalizePath(GetActiveFilePath()) == NormalizePath(editfile) && !editor.IsView(); } void Ide::ToggleReadOnly() { if(CanToggleReadOnly() && IsActiveFile()) { #ifdef PLATFORM_WIN32 FindFile ff(editfile); if(ff && ff.IsReadOnly()) { dword attrib = GetFileAttributes(editfile); attrib &= ~FILE_ATTRIBUTE_READONLY; SetFileAttributes(editfile, attrib); } #endif editor.SetEditable(editor.IsReadOnly()); ActiveFile().readonly = editor.IsReadOnly(); SavePackage(); MakeTitle(); SetBar(); } } void Ide::AdjustMainConfig() { const Workspace& wspc = IdeWorkspace(); if(!wspc.GetCount()) return; const Array& f = wspc.GetPackage(0).config; for(int i = 0; i < f.GetCount(); i++) if(f[i].param == mainconfigparam) return; if(f.GetCount()) { mainconfigparam = f[0].param; mainconfigname = f[0].name; SetMainConfigList(); } } String Ide::GetFirstFile() { const Workspace& wspc = IdeWorkspace(); for(int i = 0; i < wspc.GetCount(); i++) { const Package& p = wspc.GetPackage(i); for(int j = 0; j < p.GetCount(); j++) if(!p[j].separator) return SourcePath(wspc[i], p[j]); } return Null; } void Ide::SetMain(const String& package) { FlushFile(); SaveWorkspace(); transferfilecache.Clear(); main = package; export_dir = GetHomeDirFile(main); history.Clear(); mainconfigname.Clear(); mainconfigparam.Clear(); UppHubAuto(main); ScanWorkspace(); editfile.Clear(); LoadFromFile(THISBACK(SerializeWorkspace), WorkspaceFile()); tabs.FixIcons(); editorsplit.Zoom(0); String e = editfile; UpdateFormat(); editfile.Clear(); MakeTitle(); MakeIcon(); SyncMainConfigList(); AdjustMainConfig(); SyncBuildMode(); SetHdependDirs(); SetBar(); SyncUsc(); if(IsNull(e)) e = GetFirstFile(); EditFile(e); } void Ide::Exit() { if(debugger) debugger->Stop(); SaveFile(); SaveWorkspace(); FlushFile(); console.Kill(); Break(IDOK); IdeExit = true; } bool Ide::OpenMainPackage() { String version = SplashCtrl::GenerateVersionNumber(); String tt = "Select main package"; #ifdef bmYEAR tt << " (TheIDE " << version << Format(" %d-%02d-%02d %d:%02d)", bmYEAR , bmMONTH, bmDAY, bmHOUR, bmMINUTE); #else tt << " (TheIDE " << version << ')'; #endif String nest; String p = SelectPackage(nest, tt, main, true, true); if(p.IsEmpty()) return false; InvalidatePackageCache(); SetMainNest(nest); main.Clear(); if(!IsOpen()) Open(); if(IsExternalMode()) SyncEmptyPackage(p); SetMain(p); return true; } void Ide::NewMainPackage() { if(setmain_newide) { Host h; CreateHost(h, false, false); h.Launch(GetExeFilePath() + " --nosplash"); } else { OpenMainPackage(); StopBuild(); } } void Ide::PackageCursor() { WorkspaceWork::PackageCursor(); String p = GetActivePackage(); if(p.IsEmpty()) return; String ef = ToLower(NormalizePath(editfile)); for(int i = 0; i < filelist.GetCount(); i++) if(ToLower(NormalizePath(SourcePath(p, filelist[i]))) == ef) { filelist.SetCursor(i); break; } SetBar(); } void Ide::EditWorkspace() { EditPackages(main, GetActivePackage(), pocfg); ScanWorkspace(); SyncWorkspace(); InvalidateIncludes(); } String Ide::WorkspaceFile() { String nm; for(const char *s = main; *s; s++) nm.Cat(*s == '\\' || *s == '/' ? '$' : *s); String cfg = ConfigFile("cfg"); RealizeDirectory(cfg); return AppendFileName(cfg, ForceExt(nm + '@' + GetVarsName(), ".cfg")); } void Ide::SaveWorkspace() { if(console.console) return; if(main.IsEmpty()) return; StoreToFile(THISBACK(SerializeWorkspace), WorkspaceFile()); } void Ide::UscFile(const String& file) { try { ParseUscFile(file); } catch(CParser::Error& e) { ShowConsole(); console << e << "\n"; } } void Ide::UscProcessDir(const String& dir) { for(FindFile ff(AppendFileName(dir, "*.usc")); ff; ff.Next()) UscFile(AppendFileName(dir, ff.GetName())); } void Ide::UscProcessDirDeep(const String& dir) { UscProcessDir(dir); for(FindFile ff(AppendFileName(dir, "*")); ff; ff.Next()) if(ff.IsFolder()) UscProcessDirDeep(ff.GetPath()); } void Ide::SyncUsc() { CleanUsc(); UscProcessDir(GetLocalDir()); UscProcessDir(GetFileFolder(ConfigFile("x"))); if(IsNull(main)) return; ::Workspace wspc; wspc.Scan(main); int i; for(i = 0; i < wspc.GetCount(); i++) { const Package& p = wspc.GetPackage(i); for(int j = 0; j < p.file.GetCount(); j++) { String file = SourcePath(wspc[i], p.file[j]); if(ToLower(GetFileExt(file)) == ".usc") UscFile(file); } } } void Ide::SyncWorkspace() { SyncUsc(); } bool IsTextFile(const String& file, int maxline) { byte buffer[16384]; FileIn fi(file); if(!fi.IsOpen()) return false; int count = fi.Get(buffer, sizeof(buffer) - 1); buffer[count] = 0; const byte *end = buffer + count; const byte *q = buffer; const byte *x = q; while(q < end) { if(*q < 32) { int c = *q; if(c == '\n') { if(q - x > maxline) return false; x = q; } else if(c != '\r' && c != '\t' && c != '\v' && c != '\f' && c != 0x1a) return false; } q++; } return true; } /* Console& Ide::GetConsole() { int q = btabs.GetCursor(); return q == BFINDINFILES ? console2 : console; } */ void Ide::Renumber() { for(int i = 0; i < filedata.GetCount(); i++) ::Renumber(filedata[i].lineinfo); editor.Renumber(); } void Ide::CycleFiles() { if(++tabi >= tablru.GetCount()) tabi = 0; if(tabi < tablru.GetCount()) { blocktabs = true; EditFile(tablru[tabi]); } } void Ide::DeactivateBy(Ctrl *new_focus) { if(deactivate_save && issaving == 0 && !new_focus && editor.GetLength64() < 1000000) { DeactivationSave(true); SaveFile(); DeactivationSave(false); } TopWindow::DeactivateBy(new_focus); win_deactivated = !new_focus || dynamic_cast(new_focus); } void Ide::Activate() { if(win_deactivated) { TriggerIndexer(); editor.TriggerSyncFile(0); TriggerIdeBackgroundThread(5000); git_branch_cache.Clear(); win_deactivated = false; } TopWindow::Activate(); MakeTitle(); } bool Ide::Key(dword key, int count) { switch(key) { case K_ALT|K_CTRL_UP: case K_ALT|K_CTRL_DOWN: case K_ALT_DELETE: return filelist.Key(key, count); case K_ALT_UP: return filelist.Key(K_UP, 0); case K_ALT_DOWN: return filelist.Key(K_DOWN, 0); case K_ALT_PAGEUP: return package.Key(K_UP, 0); case K_ALT_PAGEDOWN: return package.Key(K_DOWN, 0); case K_CTRL|K_ALT_LEFT: TabsLR( TabBar::JumpDirLeft ); return true; case K_CTRL|K_ALT_RIGHT: TabsLR( TabBar::JumpDirRight ); return true; case K_CTRL|K_ALT_B: TabsStackLR( TabBar::JumpDirLeft ); return true; case K_CTRL|K_ALT_N: TabsStackLR( TabBar::JumpDirRight ); return true; case K_SHIFT|K_CTRL_O: AddFile(WorkspaceWork::ANY_FILE); return true; #ifdef PLATFORM_COCOA case K_ALT_KEY|K_KEYUP: case K_OPTION_KEY|K_KEYUP: #endif case K_CTRL_KEY|K_KEYUP: if(tabi) { tabi = 0; AddLru(); } return true; case K_CTRL_TAB: #ifdef PLATFORM_COCOA case K_ALT|K_TAB: case K_OPTION|K_TAB: #endif CycleFiles(); return true; case K_MOUSE_BACKWARD: History(-1); return true; case K_MOUSE_FORWARD: History(1); return true; #ifndef PLATFORM_COCOA default: if(key >= K_SHIFT_CTRL_0 && key <= K_SHIFT_CTRL_9) { Bookmark& b = bookmark[key - K_SHIFT_CTRL_0]; b.file = editfile; b.pos = editor.GetEditPos(); MakeTitle(); return true; } if(key >= K_CTRL_0 && key <= K_CTRL_9) { GotoBookmark(bookmark[key - K_CTRL_0]); return true; } #endif } return false; } void Ide::GotoBookmark(const Bookmark& b) { if(b.file.IsEmpty()) return; EditFile(b.file); if(bookmark_pos) editor.SetEditPos(b.pos); } bool Ide::IsHistDiff(int i) { if(i < 0 || i >= history.GetCount()) return false; Bookmark& b = history[i]; return b.file != editfile || abs(editor.GetCursor64() - b.pos.cursor) > 20; } void Ide::IdePaste(String& data) { data.Clear(); if(AcceptFiles(Clipboard())) { Vector s = GetFiles(Clipboard()); for(int i = 0; i < s.GetCount(); i++) if(FileExists(s[i]) && IsTextFile(s[i], 10000)) { int64 len = GetFileLength(s[i]); if(data.GetLength() + len > 104857600) { Exclamation("The paste size breaks the 100MB limit."); return; } data.Cat(LoadFile(s[i])); } } } void Ide::AddHistory() { if(history.GetCount()) { if(IsHistDiff(histi)) ++histi; } else histi = 0; history.At(histi); Bookmark& b = history.Top(); b.file = editfile; b.pos = editor.GetEditPos(); SetBar(); } void Ide::EditorEdit() { AddHistory(); TouchFile(editfile); } int Ide::GetHistory(int d) { if(history.GetCount()) for(int i = histi + (d > 0); i >= 0 && i < history.GetCount(); i += d) if(IsHistDiff(i)) return i; return -1; } void Ide::History(int d) { int i = GetHistory(d); if(i >= 0) { histi = i; GotoBookmark(history[histi]); SetBar(); } } void Ide::BookKey(int key) { Key(key, 1); } void Ide::SetIdeState(int newstate) { if(newstate != idestate) { if(newstate == BUILDING) build_start_time = GetSysTime(); else { if(idestate == BUILDING && !IsNull(build_start_time)) stat_build_time += int(GetSysTime() - build_start_time); build_start_time = Null; } } idestate = newstate; MakeTitle(); SetBar(); } void Ide::MakeIcon() { #ifdef PLATFORM_COCOA if(!macos_update_icon) { String badge = IsOpen() ? main : ""; SetBadgeLabel(badge); return; } #endif Image li = IdeImg::Icon256(); #ifndef PLATFORM_LINUX // Kubuntu is using this icon for window while ignoring it in taskbar... WString mp = main.ToWString(); if(!IsNull(mp)) { Size isz = li.GetSize(); ImageDraw idraw(isz); Draw& mdraw = idraw.Alpha(); idraw.DrawImage(0, 0, li); mdraw.DrawImage(0, 0, li, White); int fh = 112; Size sz(0, 0); Font font; while(fh > DPI2(64, 80)) { font = StdFont(fh); sz = GetTextSize(mp, font) + 8 * Size(4, 2); if(sz.cx <= isz.cx) break; fh--; } int x = max((isz.cx - sz.cx) / 2, 0); int y = isz.cy - sz.cy; idraw.DrawRect(x, y, sz.cx, sz.cy, White); mdraw.DrawRect(x, y, sz.cx, sz.cy, White); idraw.DrawText(x + 12, y + 1, mp, font, Black); DrawFrame(idraw, x, y, sz.cx, sz.cy, LtBlue); if(state_icon) idraw.DrawImage(0, 0, decode(state_icon, 1, IdeImg::IconDebuggingLarge256(), 2, IdeImg::IconRunningLarge256(), IdeImg::IconBuildingLarge256())); li = idraw; } #endif LargeIcon(li); } void Ide::SetIcon() { int new_state_icon = 0; if((bool)debugger && !IdeIsDebugLock()) new_state_icon = 1; else if((GetTimeClick() / 800) & 1) { if(debugger) new_state_icon = 2; else if(idestate == BUILDING) new_state_icon = 3; } if(state_icon == new_state_icon) return; state_icon = new_state_icon; MakeIcon(); #ifdef PLATFORM_WIN32 auto DoIcon = [&](const Image& shd, const Image& uhd) { if(GetDPIScale() == DPI_100) Icon(shd); else Icon(DPISmartRescaleCached(uhd, DPI(16, 16))); }; switch(state_icon) { case 1: DoIcon(IdeImg::IconDebugging(), IdeImg::IconDebuggingLarge()); break; case 2: DoIcon(IdeImg::IconRunning(), IdeImg::IconRunningLarge()); break; case 3: DoIcon(IdeImg::IconBuilding(), IdeImg::IconBuildingLarge()); break; default: DoIcon(IdeImg::Icon(), IdeImg::PackageLarge()); } #else switch(state_icon) { case 1: Icon(IdeImg::IconDebugging()); break; case 2: Icon(IdeImg::IconRunning()); break; case 3: Icon(IdeImg::IconBuilding()); break; default: Icon(IdeImg::Icon()); } #endif } Rect Ide::GetFileInfoRect() { Rect r = display.GetScreenRect(); r.top = r.bottom - GetStdFontCy() / 2; r.bottom = r.top + GetStdFontCy() + DPI(2); r.left = r.right - Zx(400); return r - GetScreenRect().TopLeft(); } void Ide::PaintTitlebarInfo(Draw& w) { if(fileinfo_visible) { Time tm(edittime); String qtf = "[g "; qtf << AsString(Format(" [@b* %d]-[@b* %02d]-[@b* %02d] [* %02d]:[* %02d]:%02d", (int)tm.year, (int)tm.month, (int)tm.day, (int)tm.hour, (int)tm.minute, (int)tm.second)); double d = double(GetSysTime() - tm) / 60; String unit = "minute"; auto DoUnit = [&](double m, const char *s) { if(d > m) { d /= m; unit = s; return true; } return false; }; DoUnit(60, "hour") && DoUnit(24, "day") && DoUnit(30.5, "month") && DoUnit(12, "year"); int n = (int)round(d); if(n != 1) unit << "s"; qtf << " ([* " << n << ' ' << unit << "] ago), size [* " << FormatFileSize(editfile_length); if(editfile_isreadonly) qtf << "][@B read only "; RichText txt = ParseQTF(qtf); txt.ApplyZoom(GetRichTextStdScreenZoom()); Size tsz(txt.GetWidth(), txt.GetHeight(INT_MAX)); Rect r = GetFileInfoRect(); r.left = r.right - tsz.cx - DPI(4); DrawFrame(w, r, SBlack()); r.Deflate(1, 1); w.DrawRect(r, SColorPaper()); Size sz = r.GetSize(); txt.Paint(w, DPI(2) + r.left, (sz.cy - tsz.cy) / 2 + r.top, INT_MAX / 2); } if(assemblyinfo_visible) { Vector nests = GetUppDirs(); Size tsz; Rect r = GetAssemblyInfoRect(&tsz); RichText txt = ParseQTF(GetAssemblyInfoQtf()); txt.ApplyZoom(GetRichTextStdScreenZoom()); DrawFrame(w, r, SBlack()); r.Deflate(1, 1); w.DrawRect(r, SColorPaper()); Size sz = r.GetSize(); txt.Paint(w, DPI(2) + r.left, (sz.cy - tsz.cy) / 2 + r.top, tsz.cx); } } String Ide::GetAssemblyInfoQtf() { String qtf = "[g "; const Workspace& wspc = GetIdeWorkspace(); Vector nests = GetUppDirs(); struct PkgInfo : Moveable { String name; bool bold, italic; Color ink; }; VectorMap> map; for(String s : nests) { String nest = UnixPath(s); for(int i = 0; i < wspc.GetCount(); i++) if(UnixPath(NormalizePath(PackageDirectory(wspc[i]))).StartsWith(nest)) { PkgInfo& m = map.GetAdd(nest).Add(); m.name = wspc[i]; const Package& p = wspc.GetPackage(i); m.bold = p.bold || i == 0; m.italic = p.italic; m.ink = p.ink; } } for(int i = 0; i < map.GetCount(); i++) { String nest = map.GetKey(i); Vector& pkgs = map[i]; Sort(pkgs, [=](const PkgInfo& a, const PkgInfo& b) { return a.name < b.name; }); qtf << "\1" << nest << "\1&"; String g = GetGitBranch(nest); if(g.GetCount()) qtf << "[$L@bl100 \1" << g << "\1&]"; else qtf << "[@K/l100 not a git repo&]"; qtf << "[l100 "; for(int i = 0; i < pkgs.GetCount(); i++) { const PkgInfo& p = pkgs[i]; if(i) qtf << ", "; qtf << "["; if(p.bold) qtf << "*"; if(p.italic) qtf << "/"; if(!IsNull(p.ink)) qtf << "@(" << p.ink.GetR() << "." << p.ink.GetG() << "." << p.ink.GetB() << ")"; qtf << " \1" << p.name << "\1]"; } qtf << "&]"; } qtf.TrimEnd("&]"); return qtf; } Rect Ide::GetAssemblyInfoRect(Size *tp) { Rect r = display_main.GetScreenRect(); r.top = r.bottom - GetStdFontCy() / 2; RichText txt = ParseQTF(GetAssemblyInfoQtf()); txt.ApplyZoom(GetRichTextStdScreenZoom()); Size tsz; tsz.cx = min(txt.GetWidth(), DPI(450)); tsz.cy = txt.GetHeight(tsz.cx); if(tp) *tp = tsz; r.left = r.right - tsz.cx - DPI(4) - 2; r.bottom = r.top + tsz.cy + DPI(4) + 2; return r - GetScreenRect().TopLeft(); } void Ide::Periodic() { CheckFileUpdate(); SetIcon(); if(debugger && debugger->IsFinished() && !IdeIsDebugLock()) IdeEndDebug(); SyncClang(); Rect r = display.GetScreenRect(); r.left = max(r.left, r.right - Zx(150)); bool b = r.Contains(GetMousePos()); if(fileinfo_visible != b) { RefreshFrame(GetFileInfoRect()); fileinfo_visible = b; } b = display_main.IsVisible() && display_main.GetScreenRect().Contains(GetMousePos()); if(assemblyinfo_visible != b) { RefreshFrame(GetAssemblyInfoRect()); assemblyinfo_visible = b; } } struct IndexerProgress : ImageMaker { double pos; String Key() const override { String h; RawCat(h, pos); return h; } Image Make() const override { Size sz = IdeImg::Indexer().GetSize(); ImagePainter iw(sz); iw.Clear(RGBAZero()); iw.Move(sz.cx / 2, sz.cy / 2).Arc(sz.cx / 2, sz.cy / 2, sz.cx / 2, -M_PI/2, pos * M_2PI).Line(sz.cx / 2, sz.cy / 2).Fill(SGray()); return iw; } }; void Ide::SyncClang() { Vector a; int phase = msecs() / 30; auto AnimColor = [](int animator) { return Blend(IsDarkTheme() ? GrayColor(70) : SColorLtFace(), Color(198, 170, 0), animator); }; auto Animate = [=](int& animator, int& dir, bool animate) -> Color { if(animator <= 0 && !animate) return Null; if(animate) animator = 20; else animator -= 3; return AnimColor(animator); }; Color bg = Animate(animate_current_file, animate_current_file_dir, (editor.annotating || IsCurrentFileParsing()) && HasLibClang()); int cx = editor.GetBarSize().cx; if(!IsNull(bg)) { for(int i = 0; i < cx; i++) a.Add(i > cx - DPI(6) ? bg : Null); } if(IsAutocompleteParsing() && HasLibClang()) a.At((phase % DPI(6)) + cx - DPI(6)) = SGray(); editor.AnimateBar(pick(a)); editor.search.SetBackground(Animate(animate_indexer, animate_indexer_dir, Indexer::IsRunning())); if(Indexer::IsRunning()) { IndexerProgress mi; mi.pos = Indexer::Progress(); indeximage.SetImage(MakeImage(mi)); static Image waitani[64]; ONCELOCK { int sz = DPI(16); for(int ani = 0; ani < 64; ani++) { ImagePainter sw(sz, sz); sw.Clear(RGBAZero()); Pointf prev = Null; for(int i = 0; i <= 128; i++) { Pointf next = (sz / 2 - 1) * Polar((i + ani * 4) * M_2PI / 256) + Pointf(sz / 2, sz / 2); if(!IsNull(prev)) sw.Move(prev).Line(next).Stroke(DPI(i) / 128.0, Blend(SLtGray, SLtRed, 2 * i)); prev = next; } waitani[ani] = sw.GetResult(); } } indeximage2.SetImage(waitani[(msecs() / 20) & 63]); } else { indeximage.SetImage(Null); indeximage2.SetImage(Null); } animate_phase = phase; } const Workspace& Ide::IdeWorkspace() const { static Workspace wspc; static String _main; if(main != _main || wspc.GetCount() == 0) { wspc.Scan(main); _main = main; } else { for(int i = 0; i < wspc.GetCount(); i++) if(wspc.GetPackage(i).time != FileGetTime(PackageFile(wspc[i]))) { wspc.Scan(main); break; } } return wspc; } const Workspace& Ide::AssistWorkspace() const { static Workspace wspc; static String _main; bool update = false; if(main != _main || wspc.GetCount() == 0) { update = true; _main = main; } else { for(int i = 0; i < wspc.GetCount(); i++) if(wspc.GetPackage(i).time != FileGetTime(PackageFile(wspc[i]))) { update = true; break; } } if(update) wspc.Scan(main, SplitFlags(mainconfigparam, false)); return wspc; } void Ide::AddPackage(const String& p) { const Workspace& wspc = IdeWorkspace(); for(int i = 0; i < wspc.GetCount(); i++){ if(wspc[i] == p) return; } if(!PromptOKCancel("Package [* " + p + "] is not yet in the workspace.&Do you want to add it?")) return; OptItem& m = actual.uses.Add(); m.text = p; SaveLoadPackage(); } int Ide::GetPackageIndex() { const Workspace& wspc = IdeWorkspace(); for(int i = 0; i < wspc.GetCount(); i++) if(wspc[i] == package.GetCurrentName()) return i; return -1; } void Ide::DoDiff(FileDiff *df) { auto Do = [=](const String& file, int line) { EditFile(file); editor.SetCursor(editor.GetPos64(line)); editor.SetFocus(); }; df->diff.WhenLeftLine = [=](int line) { Do(df->editfile, line); }; df->diff.WhenRightLine = [=](int line) { Do(df->GetExtPath(), line); }; df->OpenMain(); } void Ide::Diff() { if(IsNull(editfile)) return; FileDiff& diffdlg = CreateNewWindow(AnySourceFs()); diffdlg.Set(editfile); String s = diffdlg.GetExtPath(); if(FileExists(s)) LruAdd(difflru, s); DoDiff(&diffdlg); } void Ide::DiffWith(const String& path, const Image& icon) { #ifdef CPU_64 int64 maxsize = 2000*1024*1024; #else int64 maxsize = 100*1024*1024; #endif if(IsNull(editfile) || IsNull(path)) return; if(max(GetFileLength(editfile), GetFileLength(path)) > maxsize) { Exclamation("Too big to compare"); return; } FileDiff& diffdlg = CreateNewWindow(AnySourceFs()); diffdlg.Icon(icon); diffdlg.Set(editfile, path); DoDiff(&diffdlg); } struct ConflictDiff : TopWindow { Label left, right; TextDiffCtrl diff; virtual void Layout() { Size sz = GetSize(); int fy = GetStdFont().GetCy() + DPI(5); left.LeftPos(0, sz.cx / 2).TopPos(0, fy); right.RightPos(0, sz.cx / 2).TopPos(0, fy); diff.HSizePos().VSizePos(fy, 0); } void Set(const char *lname, const String& l, const char *rname, const String& r) { left = "\1[=* \1" + String(lname); right = "\1[=* \1" + String(rname); diff.Set(l, r); } ConflictDiff() { SetRect(GetWorkArea().Deflated(DPI(32))); Sizeable().Zoomable(); Add(left); Add(right); Add(diff); } }; String Ide::LoadConflictFile(const String& n) { return n.GetCount() == 1 ? GitCmd(GetFileFolder(editfile), "show :" + n + ":./" + GetFileName(editfile)) : LoadFile(n); } void Ide::DiffFiles(const char *lname, const String& l, const char *rname, const String& r) { ConflictDiff diff; diff.Set(lname, LoadConflictFile(l), rname, LoadConflictFile(r)); diff.Execute(); } void Ide::TriggerIndexer0() { Indexer::Start(main, GetCurrentIncludePath(), GetCurrentDefines()); } void Ide::TriggerIndexer() { if(AutoIndexer) TriggerIndexer0(); }