diff --git a/BeefLibs/corlib/src/IO/MemoryStream.bf b/BeefLibs/corlib/src/IO/MemoryStream.bf index 53dbe501..1522dad0 100644 --- a/BeefLibs/corlib/src/IO/MemoryStream.bf +++ b/BeefLibs/corlib/src/IO/MemoryStream.bf @@ -4,7 +4,8 @@ namespace System.IO { class MemoryStream : Stream { - List mMemory ~ delete _; + bool mOwns; + List mMemory ~ { if (mOwns) delete _; } int mPosition = 0; public override int64 Position @@ -46,14 +47,18 @@ namespace System.IO public this() { + mOwns = true; mMemory = new List(); } - public this(List memory) + public this(List memory, bool owns = true) { + mOwns = owns; mMemory = memory; } + public List Memory => mMemory; + public override Result TryRead(Span data) { let count = data.Length; diff --git a/IDE/src/FileRecovery.bf b/IDE/src/FileRecovery.bf index 1cbc6ce4..97cefb35 100644 --- a/IDE/src/FileRecovery.bf +++ b/IDE/src/FileRecovery.bf @@ -57,6 +57,14 @@ namespace IDE String mWorkspaceDir = new String() ~ delete _; bool mWantWorkspaceCleanup; public bool mDisabled; + Dictionary> mDB = new .() ~ DeleteDictionaryAndKeysAndValues!(_); + public bool mDBDirty; + public String mDBWorkspaceDir = new String() ~ delete _; + + public this() + { + + } public ~this() { @@ -84,6 +92,26 @@ namespace IDE bool wantWorkspaceCleanup = false; using (mMonitor.Enter()) { + if (mDBDirty) + { + String recoverPath = scope String(); + recoverPath.Append(mWorkspaceDir); + recoverPath.Append("/recovery/db.bin"); + + FileStream fs = scope .(); + if (fs.Create(recoverPath) case .Ok) + { + fs.Write((uint32)0xBEEF0701); + for (var kv in mDB) + { + fs.WriteStrSized32(kv.key).IgnoreError(); + fs.Write((int32)kv.value.Count); + fs.TryWrite(kv.value).IgnoreError(); + } + } + mDBDirty = false; + } + for (var entry in mFileSet) { if (entry.mRecoveryFileName == null) @@ -250,6 +278,104 @@ namespace IDE } } + public void CheckDB() + { + if (mDBWorkspaceDir == gApp.mWorkspace.mDir) + return; + + using (mMonitor.Enter()) + { + for (var kv in mDB) + { + delete kv.key; + delete kv.value; + } + mDB.Clear(); + + mDBWorkspaceDir.Set(gApp.mWorkspace.mDir); + if (mDBWorkspaceDir.IsEmpty) + return; + + String recoverPath = scope String(); + recoverPath.Append(mDBWorkspaceDir); + recoverPath.Append("/recovery/db.bin"); + + FileStream fs = scope .(); + if (fs.Open(recoverPath) case .Ok) + { + if (fs.Read() == 0xBEEF0701) + { + String filePath = scope .(); + while (true) + { + filePath.Clear(); + if (fs.ReadStrSized32(filePath) case .Err) + break; + if (filePath.IsEmpty) + break; + + int32 dataSize = fs.Read(); + List list = new List(); + mDB[new String(filePath)] = list; + + list.Resize(dataSize); + if (fs.TryRead(.(list.Ptr, dataSize)) case .Err) + break; + } + } + } + } + } + + public void SetDB(StringView key, Span data) + { + using (mMonitor.Enter()) + { + CheckDB(); + + if (mDB.TryAddAlt(key, var keyPtr, var valuePtr)) + { + *keyPtr = new .(key); + *valuePtr = new .(); + } + (*valuePtr).Clear(); + (*valuePtr).AddRange(data); + mDBDirty = true; + } + } + + public bool GetDB(StringView key, List data) + { + using (mMonitor.Enter()) + { + CheckDB(); + + if (mDB.TryGetAlt(key, var matchKey, var value)) + { + data.AddRange(value); + return true; + } + return false; + } + } + + public bool DeleteDB(StringView key) + { + using (mMonitor.Enter()) + { + CheckDB(); + + if (mDB.GetAndRemoveAlt(key) case .Ok((var mapKey, var value))) + { + mDBDirty = true; + delete mapKey; + delete value; + return true; + } + return false; + } + } + public void Update() { if (mProcessingEvent != null) @@ -261,7 +387,7 @@ namespace IDE using (mMonitor.Enter()) { - if ((!mDirty) && (!mWantWorkspaceCleanup)) + if ((!mDirty) && (!mDBDirty) && (!mWantWorkspaceCleanup)) return; } diff --git a/IDE/src/ui/SourceEditWidgetContent.bf b/IDE/src/ui/SourceEditWidgetContent.bf index da3d770c..1c6c4ec4 100644 --- a/IDE/src/ui/SourceEditWidgetContent.bf +++ b/IDE/src/ui/SourceEditWidgetContent.bf @@ -14,6 +14,8 @@ using IDE.Debugger; using IDE.Compiler; using Beefy.geom; using Beefy.events; +using System.Security.Cryptography; +using System.IO; namespace IDE.ui { @@ -656,6 +658,8 @@ namespace IDE.ui public int32 mParseRevision; public int32 mTextRevision; public bool mDeleted; + + public bool DefaultOpen => mKind != .Region; } public struct EmitData @@ -812,6 +816,8 @@ namespace IDE.ui public int32 mCollapseTextVersionId; public bool mCollapseNeedsUpdate; public bool mCollapseNoCheckOpen; + public bool mCollapseDBDirty; + public bool mCollapseAwaitingDB = true; public List PersistentTextPositions { @@ -5950,7 +5956,8 @@ namespace IDE.ui for (var collapseData in ref data.mCollapseData) { - if (mCollapseMap.TryAdd(collapseData.mAnchorId, ?, var entry)) + bool isNew = mCollapseMap.TryAdd(collapseData.mAnchorId, ?, var entry); + if (isNew) { *entry = .(); } @@ -5959,6 +5966,12 @@ namespace IDE.ui entry.mPrevAnchorLine = prevAnchorLine; entry.mParseRevision = mCollapseParseRevision; entry.mDeleted = false; + + if ((isNew) && (!entry.DefaultOpen) && (!mCollapseAwaitingDB)) + { + // Likely a '#region' that we need to serialize as being open + mCollapseDBDirty = true; + } } for (var entry in ref mCollapseMap.Values) @@ -6026,6 +6039,50 @@ namespace IDE.ui } } + if ((mCollapseAwaitingDB) && (mSourceViewPanel != null)) + { + String filePath = scope .(mSourceViewPanel.mFilePath); + IDEUtils.MakeComparableFilePath(filePath); + + HashSet toggledIndices = scope .(); + + List dbData = scope .(); + if (gApp.mFileRecovery.GetDB(filePath, dbData)) + { + MemoryStream memStream = scope .(dbData, false); + var dbHash = memStream.Read().GetValueOrDefault(); + + String text = scope .(); + mEditWidget.GetText(text); + var curHash = MD5.Hash(.((uint8*)text.Ptr, text.Length)); + if (curHash == dbHash) + { + while (true) + { + if (memStream.Read() case .Ok(let idx)) + { + // We recorded indices, which (upon load) will generate an id of idx+1 + toggledIndices.Add(idx + 1); + } + else + break; + } + } + } + + for (var collapseEntry in mOrderedCollapseEntries) + { + bool wantOpen = collapseEntry.DefaultOpen; + if (toggledIndices.Contains(collapseEntry.mAnchorId)) + wantOpen = !wantOpen; + + if (collapseEntry.mIsOpen != wantOpen) + SetCollapseOpen(@collapseEntry.Index, wantOpen, true); + } + + mCollapseAwaitingDB = false; + } + //Debug.WriteLine($"ParseCollapseRegions Count:{mOrderedCollapseEntries.Count} Time:{sw.ElapsedMilliseconds}ms"); } @@ -6335,8 +6392,8 @@ namespace IDE.ui entry.mIsOpen = wantOpen; if (immediate) entry.mOpenPct = entry.mIsOpen ? 1.0f : 0.0f; - else - mCollapseNeedsUpdate = true; + mCollapseNeedsUpdate = true; + mCollapseDBDirty = true; var cursorLineAndColumn = CursorLineAndColumn; diff --git a/IDE/src/ui/SourceViewPanel.bf b/IDE/src/ui/SourceViewPanel.bf index 813f9592..5be49b0d 100644 --- a/IDE/src/ui/SourceViewPanel.bf +++ b/IDE/src/ui/SourceViewPanel.bf @@ -6951,6 +6951,36 @@ namespace IDE.ui // Process after mQueuedCollapseData so mCharIdSpan is still valid ProcessDeferredResolveResults(0); + + if (ewc.mCollapseDBDirty) + { + MemoryStream memStream = scope .(); + + String text = scope .(); + mEditWidget.GetText(text); + var hash = MD5.Hash(.((uint8*)text.Ptr, text.Length)); + memStream.Write(hash); + + bool hadData = false; + + for (var kv in ewc.mOrderedCollapseEntries) + { + if (kv.mIsOpen != kv.DefaultOpen) + { + hadData = true; + memStream.Write(kv.mAnchorIdx); + } + } + + String filePath = scope .(mFilePath); + IDEUtils.MakeComparableFilePath(filePath); + + if (!hadData) + gApp.mFileRecovery.DeleteDB(filePath); + else + gApp.mFileRecovery.SetDB(filePath, memStream.Memory); + ewc.mCollapseDBDirty = false; + } } public override void UpdateF(float updatePct)