Browse Source

Serialize IDE collapse state

Brian Fiete 3 năm trước cách đây
mục cha
commit
4c2530e227

+ 7 - 2
BeefLibs/corlib/src/IO/MemoryStream.bf

@@ -4,7 +4,8 @@ namespace System.IO
 {
 	class MemoryStream : Stream
 	{
-		List<uint8> mMemory ~ delete _;
+		bool mOwns;
+		List<uint8> 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<uint8>();
 		}
 
-		public this(List<uint8> memory)
+		public this(List<uint8> memory, bool owns = true)
 		{
+			mOwns = owns;
 			mMemory = memory;
 		}
 
+		public List<uint8> Memory => mMemory;
+
 		public override Result<int> TryRead(Span<uint8> data)
 		{
 			let count = data.Length;

+ 127 - 1
IDE/src/FileRecovery.bf

@@ -57,6 +57,14 @@ namespace IDE
 		String mWorkspaceDir = new String() ~ delete _;
 		bool mWantWorkspaceCleanup;
 		public bool mDisabled;
+		Dictionary<String, List<uint8>> 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<uint32>() == 0xBEEF0701)
+					{
+						String filePath = scope .();
+						while (true)
+						{
+							filePath.Clear();
+							if (fs.ReadStrSized32(filePath) case .Err)
+								break;
+							if (filePath.IsEmpty)
+								break;
+
+							int32 dataSize = fs.Read<int32>();
+							List<uint8> list = new List<uint8>();
+							mDB[new String(filePath)] = list;
+
+							list.Resize(dataSize);
+							if (fs.TryRead(.(list.Ptr, dataSize)) case .Err)
+								break;
+						}
+					}
+				}
+			}
+		}
+
+		public void SetDB(StringView key, Span<uint8> 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<uint8> 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;
 			}
 

+ 60 - 3
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<PersistentTextPosition> 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<int32> toggledIndices = scope .();
+
+					List<uint8> dbData = scope .();
+					if (gApp.mFileRecovery.GetDB(filePath, dbData))
+					{
+						MemoryStream memStream = scope .(dbData, false);
+						var dbHash = memStream.Read<MD5Hash>().GetValueOrDefault();
+
+						String text = scope .();
+						mEditWidget.GetText(text);
+						var curHash = MD5.Hash(.((uint8*)text.Ptr, text.Length));
+						if (curHash == dbHash)
+						{
+							while (true)
+							{
+								if (memStream.Read<int32>() 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;
 

+ 30 - 0
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)