From 90735e3bf88d91145d6c1cb500d42211b55fce48 Mon Sep 17 00:00:00 2001 From: Brian Fiete Date: Mon, 28 Feb 2022 11:27:12 -0800 Subject: [PATCH] Collapsible regions (aka outlining aka code folding) --- BeefLibs/Beefy2D/src/Utils.bf | 3 + .../Beefy2D/src/theme/dark/DarkEditWidget.bf | 496 ++++++-- BeefLibs/Beefy2D/src/theme/dark/DarkTheme.bf | 2 + BeefLibs/Beefy2D/src/utils/IdSpan.bf | 179 ++- BeefLibs/Beefy2D/src/widgets/EditWidget.bf | 278 +++-- BeefLibs/corlib/src/Array.bf | 2 +- BeefLibs/corlib/src/Collections/Dictionary.bf | 52 +- BeefLibs/corlib/src/Collections/List.bf | 16 + IDE/dist/images/DarkUI.png | Bin 30633 -> 30733 bytes IDE/dist/images/DarkUI.psd | Bin 382441 -> 384821 bytes IDE/dist/images/DarkUI_2.png | Bin 50958 -> 51061 bytes IDE/dist/images/DarkUI_4.png | Bin 91293 -> 91427 bytes IDE/src/Commands.bf | 6 + IDE/src/Compiler/BfCompiler.bf | 8 + IDE/src/IDEApp.bf | 103 ++ IDE/src/ScriptManager.bf | 24 + IDE/src/Settings.bf | 6 + IDE/src/ui/QuickFind.bf | 2 +- IDE/src/ui/SourceEditWidgetContent.bf | 1006 ++++++++++++++++- IDE/src/ui/SourceViewPanel.bf | 346 +++++- IDEHelper/Compiler/BfCompiler.cpp | 266 +++++ 21 files changed, 2518 insertions(+), 277 deletions(-) diff --git a/BeefLibs/Beefy2D/src/Utils.bf b/BeefLibs/Beefy2D/src/Utils.bf index a17074d1..9ff8fb2e 100644 --- a/BeefLibs/Beefy2D/src/Utils.bf +++ b/BeefLibs/Beefy2D/src/Utils.bf @@ -346,6 +346,7 @@ namespace Beefy return list; }*/ + [DisableChecks] public static int64 DecodeInt64(ref uint8* ptr) { int64 value = 0; @@ -364,6 +365,7 @@ namespace Beefy return value; } + [DisableChecks] public static int32 DecodeInt(uint8[] buf, ref int idx) { int32 value = 0; @@ -383,6 +385,7 @@ namespace Beefy return value; } + [DisableChecks] public static void EncodeInt(uint8[] buf, ref int idx, int value) { int curValue = value; diff --git a/BeefLibs/Beefy2D/src/theme/dark/DarkEditWidget.bf b/BeefLibs/Beefy2D/src/theme/dark/DarkEditWidget.bf index 7c4e7833..dea391d6 100644 --- a/BeefLibs/Beefy2D/src/theme/dark/DarkEditWidget.bf +++ b/BeefLibs/Beefy2D/src/theme/dark/DarkEditWidget.bf @@ -5,11 +5,49 @@ using System.Text; using Beefy.widgets; using Beefy.gfx; using Beefy.utils; +using Beefy.geom; namespace Beefy.theme.dark { public class DarkEditWidgetContent : EditWidgetContent { + public class Embed + { + public enum Kind + { + LineStart, + HideLine, + LineEnd, + } + + public Kind mKind; + + public ~this() + { + + } + + public virtual void Draw(Graphics g, Rect rect, bool hideLine) + { + + } + + public virtual void MouseDown(float x, float y, int btn, int btnCount) + { + + } + + public virtual float GetWidth(bool hideLine) + { + return GS!(24); + } + } + + public class Data : EditWidgetContent.Data + { + + } + public Font mFont; public uint32[] mTextColors = sDefaultColors; public uint32 mHiliteColor = 0xFF2f5c88; @@ -23,6 +61,7 @@ namespace Beefy.theme.dark public uint32 mViewWhiteSpaceColor; public bool mScrollToStartOnLostFocus; public bool mHiliteCurrentLine; + public Dictionary mEmbeds = new .() ~ DeleteDictionaryAndValues!(_); protected static uint32[] sDefaultColors = new uint32[] ( Color.White ) ~ delete _; @@ -39,6 +78,48 @@ namespace Beefy.theme.dark mFont = DarkTheme.sDarkTheme?.mSmallFont; } + protected override EditWidgetContent.Data CreateEditData() + { + return new Data(); + } + + public virtual void CheckLineCoords() + { + if (mLineCoordTextVersionId == mData.mCurTextVersionId) + return; + + mLineCoordTextVersionId = mData.mCurTextVersionId; + + if (mLineCoords == null) + mLineCoords = new .(); + if (mLineCoordJumpTable == null) + mLineCoordJumpTable = new .(); + + mLineCoords.Clear(); + mLineCoords.GrowUnitialized(mData.mLineStarts.Count); + mLineCoordJumpTable.Clear(); + + float fontHeight = mFont.GetLineSpacing(); + int prevJumpIdx = -1; + float jumpCoordSpacing = GetJumpCoordSpacing(); + + double curY = 0; + for (int line < mData.mLineStarts.Count) + { + float lineHeight = fontHeight; + mLineCoords[line] = (float)curY; + + int jumpIdx = (.)(curY / jumpCoordSpacing); + while (prevJumpIdx < jumpIdx) + { + mLineCoordJumpTable.Add(((int32)line, (int32)line + 1)); + prevJumpIdx++; + } + mLineCoordJumpTable[jumpIdx].max = (.)line + 1; + curY += lineHeight; + } + } + public override void GetTextData() { // Generate text flags if we need to... @@ -97,8 +178,56 @@ namespace Beefy.theme.dark } base.GetTextData(); + + CheckLineCoords(); } + public int32 mLineCoordTextVersionId = -1; + public List mLineCoords ~ delete _; + public List<(int32 min, int32 max)> mLineCoordJumpTable ~ delete _; + + public bool IsLineCollapsed(int line) + { + if (mLineCoords == null) + return false; + if ((line >= 0) && (line < mLineCoords.Count - 1)) + return (mLineCoords[line + 1] - mLineCoords[line]) < 0.1f; + return false; + } + + public float GetLineHeight(int line, float defaultVal) + { + if ((line >= 0) && (line < mLineCoords.Count - 1)) + return mLineCoords[line + 1] - mLineCoords[line]; + return defaultVal; + } + + public float GetLineY(int line, float defaultVal) + { + if ((line >= 0) && (line < mLineCoords.Count)) + return mLineCoords[line]; + return defaultVal; + } + + public int FindUncollapsedLine(int line) + { + var line; + while ((line > 0) && (IsLineCollapsed(line))) + line--; + return line; + } + + public bool IsInCollapseGroup(int anchorLine, int checkLine) + { + if (checkLine < anchorLine) + return false; + if (checkLine == anchorLine) + return true; + if (checkLine == anchorLine + 1) + return IsLineCollapsed(checkLine); + return mLineCoords[anchorLine + 1] == mLineCoords[checkLine + 1]; + } + protected override void AdjustCursorsAfterExternalEdit(int index, int ofs) { base.AdjustCursorsAfterExternalEdit(index, ofs); @@ -322,6 +451,8 @@ namespace Beefy.theme.dark Utils.RoundScale(ref mTabSize, newScale / oldScale); SetFont(mFont, mCharWidth != -1, mAllowVirtualCursor); mContentChanged = true; // Defer calling of RecalcSize + GetTextData(); + LineStartsChanged(); } public virtual float DrawText(Graphics g, String str, float x, float y, uint16 typeIdAndFlags) @@ -335,15 +466,52 @@ namespace Beefy.theme.dark return mEditWidget.mHasFocus ? mHiliteColor : mUnfocusedHiliteColor; } + protected Rect GetEmbedRect(int lineIdx, DarkEditWidgetContent.Embed embed) + { + GetLinePosition(lineIdx, var lineStart, var lineEnd); + + bool hideLine = false; + if ((embed.mKind == .HideLine) && + ((!mEditWidget.mHasFocus) || (!IsInCollapseGroup(lineIdx, CursorLine)))) + hideLine = true; + + int wsEnd = lineEnd; + if ((hideLine) || (embed.mKind == .LineStart)) + { + for (int i in lineStart..= lineEnd) break; - } + } + + if (embed != null) + { + var embedRect = GetEmbedRect(lineIdx, embed); + embed.Draw(g, embedRect, false); + g.SetFont(mFont); + + if ((!drewCursor) && (IsLineCollapsed(CursorLineAndColumn.mLine)) && + (GetLineY(CursorLineAndColumn.mLine, -1) == GetLineY(lineIdx + 1, -1))) + DrawCursor(embedRect.Right + GS!(2), curY); + } } g.PopMatrix(); @@ -561,6 +763,8 @@ namespace Beefy.theme.dark public override void GetTextCoordAtLineChar(int line, int lineChar, out float x, out float y) { + GetTextData(); + String lineText = scope String(256); GetLineText(line, lineText); if (lineChar > lineText.Length) @@ -571,74 +775,95 @@ namespace Beefy.theme.dark subText.Append(lineText, 0, lineChar); x = GetTabbedWidth(subText, 0, true) + mTextInsets.mLeft; } - y = mTextInsets.mTop + line * mFont.GetLineSpacing(); + y = mTextInsets.mTop + mLineCoords[line]; } public override void GetTextCoordAtLineAndColumn(int line, int column, out float x, out float y) { + GetTextData(); Debug.Assert((mCharWidth != -1) || (column == 0)); String lineText = scope String(256); GetLineText(line, lineText); x = mTextInsets.mLeft + column * mCharWidth; - y = mTextInsets.mTop + line * mFont.GetLineSpacing(); + y = mTextInsets.mTop + mLineCoords[line]; } - public override bool GetLineCharAtCoord(float x, float y, out int line, out int char8Idx, out float overflowX) + protected int GetLineAt(float y) + { + if (y < 0) + return 0; + + GetTextData(); + + int lineCount = GetLineCount(); + var checkY = y - mTextInsets.mTop + 0.001f; + var jumpEntry = mLineCoordJumpTable[Math.Clamp((int)(y / GetJumpCoordSpacing()), 0, mLineCoordJumpTable.Count - 1)]; + + int line = jumpEntry.min - 1; + for (int checkLine in jumpEntry.min ..< jumpEntry.max) + { + if (checkY >= mLineCoords[checkLine]) + line = checkLine; + } + + if (line < 0) + line = 0; + if (line >= lineCount) + line = lineCount - 1; + return line; + } + + public override bool GetLineCharAtCoord(float x, float y, out int line, out int lineChar, out float overflowX) { - line = (int) ((y - mTextInsets.mTop) / mFont.GetLineSpacing() + 0.001f); - int lineCount = GetLineCount(); + line = GetLineAt(y); + return GetLineCharAtCoord(line, x, out lineChar, out overflowX); + } - if (line < 0) - line = 0; - if (line >= lineCount) - line = lineCount - 1; + public override bool GetLineAndColumnAtCoord(float x, float y, out int line, out int column) + { + line = GetLineAt(y); + column = Math.Max(0, (int32)((x - mTextInsets.mLeft + 1) / mCharWidth + 0.6f)); + return mCharWidth != -1; + } - String lineText = scope String(256); - GetLineText(line, lineText); - int32 char8Count = GetTabbedCharCountToLength(lineText, x - mTextInsets.mLeft); - char8Idx = char8Count; + public override bool GetLineCharAtCoord(int line, float x, out int lineChar, out float overflowX) + { + String lineText = scope String(256); + GetLineText(line, lineText); + int32 char8Count = GetTabbedCharCountToLength(lineText, x - mTextInsets.mLeft); + lineChar = char8Count; - if (char8Count < lineText.Length) - { + if (char8Count < lineText.Length) + { String subString = new:ScopedAlloc! String(char8Count); subString.Append(lineText, 0, char8Count); - float subWidth = GetTabbedWidth(subString, 0); + float subWidth = GetTabbedWidth(subString, 0); var utf8enumerator = lineText.DecodedChars(char8Count); if (utf8enumerator.MoveNext()) { char32 c = utf8enumerator.Current; - float checkCharWidth = 0; - if (c == '\t') - checkCharWidth = mTabSize * 0.5f; - else + float checkCharWidth = 0; + if (c == '\t') + checkCharWidth = mTabSize * 0.5f; + else { checkCharWidth = mFont.GetWidth(c) * 0.5f; } - if (x >= subWidth + mTextInsets.mLeft + checkCharWidth) - char8Idx = (int32)utf8enumerator.NextIndex; + if (x >= subWidth + mTextInsets.mLeft + checkCharWidth) + lineChar = (int32)utf8enumerator.NextIndex; } - } - else - { - overflowX = (x - mTextInsets.mLeft) - (GetTabbedWidth(lineText, 0) + 0.001f); - return overflowX <= 0; - } + } + else + { + overflowX = (x - mTextInsets.mLeft) - (GetTabbedWidth(lineText, 0) + 0.001f); + return overflowX <= 0; + } - overflowX = 0; - return true; - } - - public override bool GetLineAndColumnAtCoord(float x, float y, out int line, out int column) - { - line = (int32)((y - mTextInsets.mTop) / mFont.GetLineSpacing() + 0.001f); - if (line >= GetLineCount()) - line = GetLineCount() - 1; - line = Math.Max(0, line); - column = Math.Max(0, (int32)((x - mTextInsets.mLeft + 1) / mCharWidth + 0.6f)); - return mCharWidth != -1; - } + overflowX = 0; + return true; + } void RecalcSize(int32 startLineNum, int32 endLineNum, bool forceAccurate = false) { @@ -670,6 +895,53 @@ namespace Beefy.theme.dark base.CursorToLineEnd(); } + public override void GetTextCoordAtCursor(out float x, out float y) + { + int32 line = CursorLine; + if (IsLineCollapsed(line)) + { + line = (.)FindUncollapsedLine(line); + GetTextCoordAtLineAndColumn(line, CursorLineAndColumn.mColumn, out x, out y); + return; + } + + base.GetTextCoordAtCursor(out x, out y); + } + + public override void MoveCursorToCoord(float x, float y) + { + base.MoveCursorToCoord(x, y); + + if ((!mEmbeds.IsEmpty) && (mEmbeds.GetValue(CursorLineAndColumn.mLine) case .Ok(let embed))) + { + var embedRect = GetEmbedRect(CursorLineAndColumn.mLine, embed); + if (x > embedRect.Right) + { + int endLine = CursorLineAndColumn.mLine; + while (true) + { + if (!IsLineCollapsed(endLine + 1)) + break; + endLine++; + } + + if (endLine != CursorLineAndColumn.mLine) + { + GetLinePosition(endLine, var endLineStart, var endLineEnd); + GetLineAndColumnAtLineChar(endLine, endLineEnd - endLineStart, var column); + CursorLineAndColumn = .(endLine, column); + } + } + } + } + + public override void CursorToLineStart(bool allowToScreenStart) + { + while (IsLineCollapsed(CursorLineAndColumn.mLine)) + CursorLineAndColumn = .(CursorLineAndColumn.mLine - 1, 1); + base.CursorToLineStart(allowToScreenStart); + } + public void RecalcSize(bool forceAccurate = false) { mMaximalScrollAddedHeight = 0; @@ -707,7 +979,9 @@ namespace Beefy.theme.dark Debug.Assert(!mWidth.IsNaN); } - mHeight = GetLineCount() * mFont.GetLineSpacing() + mTextInsets.mTop + mTextInsets.mBottom; + GetTextData(); + mHeight = mLineCoords.Back + mTextInsets.mTop + mTextInsets.mBottom; + Debug.Assert(mHeight > 0); UpdateMaximalScroll(); base.RecalcSize(); } @@ -721,6 +995,21 @@ namespace Beefy.theme.dark { base.ContentChanged(); mRecalcSizeLineNum = -1; + + mLineCoordTextVersionId = -1; + DeleteAndNullify!(mLineCoords); + DeleteAndNullify!(mLineCoordJumpTable); + } + + public float GetJumpCoordSpacing() + { + return mFont.GetLineSpacing() * 4; + } + + public override void LineStartsChanged() + { + base.LineStartsChanged(); + mLineCoordTextVersionId = -1; } public override void TextAppended(String str) @@ -734,7 +1023,7 @@ namespace Beefy.theme.dark base.TextAppended(str); } - void UpdateMaximalScroll() + protected void UpdateMaximalScroll() { if (mAllowMaximalScroll) { @@ -744,6 +1033,8 @@ namespace Beefy.theme.dark mMaximalScrollAddedHeight = mEditWidget.mScrollContentContainer.mHeight - mFont.GetLineSpacing(); mHeight += mMaximalScrollAddedHeight; + Debug.Assert(mHeight >= 0); + if (mHeight != prevHeight) mEditWidget.UpdateScrollbars(); } @@ -757,7 +1048,9 @@ namespace Beefy.theme.dark public override float GetLineHeight(int line) { - return mFont.GetLineSpacing(); + if (mLineCoords == null) + GetTextData(); + return mLineCoords[line + 1] - mLineCoords[line]; } public override float GetPageScrollTextHeight() @@ -843,6 +1136,27 @@ namespace Beefy.theme.dark CheckRecordScrollTop(); } + + public override void MouseMove(float x, float y) + { + base.MouseMove(x, y); + + int line = GetLineAt(y); + + bool isOverEmbed = false; + + if (mEmbeds.GetValue(line) case .Ok(let embed)) + { + Rect embedRect = GetEmbedRect(line, embed); + if (embedRect.Contains(x, y)) + isOverEmbed = true; + } + + if (isOverEmbed) + BFApp.sApp.SetCursor(Cursor.Pointer); + else + BFApp.sApp.SetCursor(Cursor.Text); + } } public class DarkEditWidget : EditWidget diff --git a/BeefLibs/Beefy2D/src/theme/dark/DarkTheme.bf b/BeefLibs/Beefy2D/src/theme/dark/DarkTheme.bf index 66998f48..2ff33e08 100644 --- a/BeefLibs/Beefy2D/src/theme/dark/DarkTheme.bf +++ b/BeefLibs/Beefy2D/src/theme/dark/DarkTheme.bf @@ -181,6 +181,8 @@ namespace Beefy.theme.dark PanelHeader, ExtMethod, + CollapseClosed, + CollapseOpened, COUNT }; diff --git a/BeefLibs/Beefy2D/src/utils/IdSpan.bf b/BeefLibs/Beefy2D/src/utils/IdSpan.bf index 4bd97f8e..f41e704f 100644 --- a/BeefLibs/Beefy2D/src/utils/IdSpan.bf +++ b/BeefLibs/Beefy2D/src/utils/IdSpan.bf @@ -6,8 +6,129 @@ using System.Threading.Tasks; namespace Beefy.utils { - public struct IdSpan + public struct IdSpan : IFormattable { + public class LookupContext + { + public enum SortKind + { + None, + Id, + Index + } + + public struct Entry + { + public int32 mIdStart; + public int32 mIndexStart; + public int32 mLength; + } + + public List mEntries = new .() ~ delete _; + public SortKind mSortKind; + + public this(IdSpan idSpan) + { + int encodeIdx = 0; + int charId = 1; + int charIdx = 0; + while (true) + { + int cmd = Utils.DecodeInt(idSpan.mData, ref encodeIdx); + if (cmd > 0) + charId = cmd; + else + { + int spanSize = -cmd; + + Entry entry; + entry.mIdStart = (.)charId; + entry.mIndexStart = (.)charIdx; + entry.mLength = (.)spanSize; + mEntries.Add(entry); + + charId += spanSize; + charIdx += spanSize; + + if (cmd == 0) + break; + } + } + } + + public int GetIndexFromId(int32 findCharId) + { + if (mSortKind != .Id) + { + mEntries.Sort(scope (lhs, rhs) => lhs.mIdStart <=> rhs.mIdStart); + mSortKind = .Id; + } + + int lo = 0; + int hi = mEntries.Count - 1; + + while (lo <= hi) + { + int i = (lo + hi) / 2; + var midVal = ref mEntries[i]; + if ((findCharId >= midVal.mIdStart) && (findCharId < midVal.mIdStart + midVal.mLength)) + return midVal.mIndexStart + (findCharId - midVal.mIdStart); + + if (findCharId > midVal.mIdStart) + lo = i + 1; + else + hi = i - 1; + } + + return -1; + } + + public int32 GetIdAtIndex(int32 findCharId) + { + if (mSortKind != .Index) + { + mEntries.Sort(scope (lhs, rhs) => lhs.mIndexStart <=> rhs.mIndexStart); + mSortKind = .Index; + } + + int lo = 0; + int hi = mEntries.Count - 1; + + while (lo <= hi) + { + int i = (lo + hi) / 2; + var midVal = ref mEntries[i]; + if ((findCharId >= midVal.mIndexStart) && (findCharId < midVal.mIndexStart + midVal.mLength)) + return midVal.mIdStart + (findCharId - midVal.mIndexStart); + + if (findCharId > midVal.mIndexStart) + lo = i + 1; + else + hi = i - 1; + } + + return -1; + } + + public override void ToString(String strBuffer) + { + if (mSortKind != .Index) + { + mEntries.Sort(scope (lhs, rhs) => lhs.mIndexStart <=> rhs.mIndexStart); + mSortKind = .Index; + } + + strBuffer.AppendF("IdSpan.LookupCtx("); + for (var entry in mEntries) + { + if (@entry.Index > 0) + strBuffer.Append(' '); + strBuffer.AppendF($"{entry.mIndexStart}:{entry.mLength}={entry.mIdStart}"); + } + strBuffer.AppendF(")"); + } + } + enum Change { case Insert(int32 index, int32 id, int16 length); @@ -671,7 +792,6 @@ namespace Beefy.utils public IdSpan Duplicate() { - Debug.Assert(!HasChangeList); IdSpan idSpan = IdSpan(); if (mData != null) { @@ -679,6 +799,12 @@ namespace Beefy.utils mData.CopyTo(idSpan.mData, 0, 0, mLength); idSpan.mLength = mLength; } + if (mChangeList != null) + { + idSpan.mChangeList = new .(); + for (var change in mChangeList) + idSpan.mChangeList.Add(change); + } return idSpan; } @@ -866,6 +992,11 @@ namespace Beefy.utils return idSpan; } + public override void ToString(String strBuffer) + { + ToString(strBuffer, "", null); + } + public void Dump() mut { Prepare(); @@ -895,5 +1026,49 @@ namespace Beefy.utils } } } + + public void ToString(String outString, String format, IFormatProvider formatProvider) + { + if (HasChangeList) + { + IdSpan span = Duplicate(); + span.ToString(outString, format, formatProvider); + return; + } + + outString.AppendF($"Span(Length:{mLength} ChangeList:{(mChangeList?.Count).GetValueOrDefault()})"); + + if (format == "D") + { + outString.Append("{"); + int encodeIdx = 0; + int charId = 1; + int charIdx = 0; + while (true) + { + int32 cmd = Utils.DecodeInt(mData, ref encodeIdx); + if (cmd > 0) + { + charId = cmd; + outString.AppendF($" #{charId}"); + } + else + { + int32 spanSize = -cmd; + + charId += spanSize; + charIdx += spanSize; + + if (cmd == 0) + { + outString.Append("}"); + return; + } + + outString.AppendF($":{spanSize}"); + } + } + } + } } } diff --git a/BeefLibs/Beefy2D/src/widgets/EditWidget.bf b/BeefLibs/Beefy2D/src/widgets/EditWidget.bf index 51076556..35f8f8ea 100644 --- a/BeefLibs/Beefy2D/src/widgets/EditWidget.bf +++ b/BeefLibs/Beefy2D/src/widgets/EditWidget.bf @@ -571,16 +571,16 @@ namespace Beefy.widgets get { if (mCursorTextPos == -1) - { + { float x; float y; GetTextCoordAtLineAndColumn(mVirtualCursorPos.Value.mLine, mVirtualCursorPos.Value.mColumn, out x, out y); - int line; int lineChar; float overflowX; - GetLineCharAtCoord(x, y, out line, out lineChar, out overflowX); - mCursorTextPos = (int32)GetTextIdx(line, lineChar); + GetLineCharAtCoord(mVirtualCursorPos.Value.mLine, x, out lineChar, out overflowX); + + mCursorTextPos = (int32)GetTextIdx(mVirtualCursorPos.Value.mLine, lineChar); } return mCursorTextPos; @@ -606,14 +606,9 @@ namespace Beefy.widgets int lineChar; GetLineCharAtIdx(mCursorTextPos, out line, out lineChar); - float x; - float y; - GetTextCoordAtLineChar(line, lineChar, out x, out y); - - int coordLine; int coordLineColumn; - GetLineAndColumnAtCoord(x, y, out coordLine, out coordLineColumn); - lineAndColumn.mLine = (int32)coordLine; + GetLineAndColumnAtLineChar(line, lineChar, out coordLineColumn); + lineAndColumn.mLine = (int32)line; lineAndColumn.mColumn = (int32)coordLineColumn; return lineAndColumn; } @@ -627,6 +622,20 @@ namespace Beefy.widgets } } + public int32 CursorLine + { + get + { + if (mVirtualCursorPos.HasValue) + return mVirtualCursorPos.Value.mLine; + + int line; + int lineChar; + GetLineCharAtIdx(mCursorTextPos, out line, out lineChar); + return (.)line; + } + } + public bool WantsUndo { get @@ -644,7 +653,8 @@ namespace Beefy.widgets mData.Ref(this); mContentChanged = true; } - + + protected virtual Data CreateEditData() { return new Data(); @@ -661,13 +671,13 @@ namespace Beefy.widgets { if (mVirtualCursorPos.HasValue) { + int32 line = mVirtualCursorPos.Value.mLine; float x; float y; - GetTextCoordAtLineAndColumn(mVirtualCursorPos.Value.mLine, mVirtualCursorPos.Value.mColumn, out x, out y); + GetTextCoordAtLineAndColumn(line, mVirtualCursorPos.Value.mColumn, out x, out y); - int line; int lineChar; - bool success = GetLineCharAtCoord(x, y, out line, out lineChar, out overflowX); + bool success = GetLineCharAtCoord(line, x, out lineChar, out overflowX); textPos = GetTextIdx(line, lineChar); return success; @@ -851,7 +861,7 @@ namespace Beefy.widgets public override void MouseLeave() { base.MouseLeave(); - BFApp.sApp.SetCursor(Cursor.Pointer); + BFApp.sApp.SetCursor(Cursor.Pointer); } public virtual void ClearText() @@ -1344,6 +1354,11 @@ namespace Beefy.widgets TextChanged(); } + public virtual void LineStartsChanged() + { + + } + public virtual void TextAppended(String str) { if ((mWordWrap) || (mData.mLineStarts == null)) @@ -1366,6 +1381,7 @@ namespace Beefy.widgets } } mData.mLineStarts.Add(mData.mTextLength); + LineStartsChanged(); mContentChanged = true; @@ -1385,69 +1401,70 @@ namespace Beefy.widgets { // Generate line starts and text flags if we need to - if (mData.mLineStarts == null) - { - scope AutoBeefPerf("EWC.GetTextData"); + if (mData.mLineStarts != null) + return; + + scope AutoBeefPerf("EWC.GetTextData"); - CharData* char8DataPtr = mData.mText.CArray(); - uint8* textFlagsPtr = null; - if (mData.mTextFlags != null) - textFlagsPtr = mData.mTextFlags.CArray(); + CharData* char8DataPtr = mData.mText.CArray(); + uint8* textFlagsPtr = null; + if (mData.mTextFlags != null) + textFlagsPtr = mData.mTextFlags.CArray(); - int32 lineIdx = 0; - if (textFlagsPtr != null) + int32 lineIdx = 0; + if (textFlagsPtr != null) + { + for (int32 i < mData.mTextLength) { - for (int32 i < mData.mTextLength) - { - if ((char8DataPtr[i].mChar == '\n') || ((textFlagsPtr != null) && ((textFlagsPtr[i] & ((int32)TextFlags.Wrap)) != 0))) - lineIdx++; - } + if ((char8DataPtr[i].mChar == '\n') || ((textFlagsPtr != null) && ((textFlagsPtr[i] & ((int32)TextFlags.Wrap)) != 0))) + lineIdx++; } - else + } + else + { + for (int32 i < mData.mTextLength) { - for (int32 i < mData.mTextLength) - { - if (char8DataPtr[i].mChar == '\n') - lineIdx++; - } + if (char8DataPtr[i].mChar == '\n') + lineIdx++; } + } - mData.mLineStarts = new List(); - mData.mLineStarts.GrowUnitialized(lineIdx + 2); - int32* lineStartsPtr = mData.mLineStarts.Ptr; - lineStartsPtr[0] = 0; - - lineIdx = 0; - if (textFlagsPtr != null) + mData.mLineStarts = new List(); + mData.mLineStarts.GrowUnitialized(lineIdx + 2); + int32* lineStartsPtr = mData.mLineStarts.Ptr; + lineStartsPtr[0] = 0; + + lineIdx = 0; + if (textFlagsPtr != null) + { + for (int32 i < mData.mTextLength) + { + if ((textFlagsPtr != null) && ((textFlagsPtr[i] & ((int32)TextFlags.Wrap)) != 0)) + { + lineIdx++; + lineStartsPtr[lineIdx] = i; + } + else if ((char8)char8DataPtr[i].mChar == '\n') + { + lineIdx++; + lineStartsPtr[lineIdx] = i + 1; + } + } + } + else + { + for (int32 i < mData.mTextLength) { - for (int32 i < mData.mTextLength) - { - if ((textFlagsPtr != null) && ((textFlagsPtr[i] & ((int32)TextFlags.Wrap)) != 0)) - { - lineIdx++; - lineStartsPtr[lineIdx] = i; - } - else if ((char8)char8DataPtr[i].mChar == '\n') - { - lineIdx++; - lineStartsPtr[lineIdx] = i + 1; - } - } + if ((char8)char8DataPtr[i].mChar == '\n') + { + lineIdx++; + lineStartsPtr[lineIdx] = i + 1; + } } - else - { - for (int32 i < mData.mTextLength) - { - if ((char8)char8DataPtr[i].mChar == '\n') - { - lineIdx++; - lineStartsPtr[lineIdx] = i + 1; - } - } - } - - mData.mLineStarts[lineIdx + 1] = mData.mTextLength; - } + } + + mData.mLineStarts[lineIdx + 1] = mData.mTextLength; + LineStartsChanged(); } public virtual void Backspace() @@ -1519,7 +1536,7 @@ namespace Beefy.widgets ContentChanged(); if (offset != 0) { - MoveCursorToIdx(textPos, false, .FromTyping); + MoveCursorToIdx(textPos, false, .FromTyping_Deleting); EnsureCursorVisible(); } } @@ -1648,7 +1665,7 @@ namespace Beefy.widgets } else { - int32 char8Count = 1; + int32 charCount = 1; int checkIdx = textPos + 1; while (true) { @@ -1660,12 +1677,12 @@ namespace Beefy.widgets if (!checkChar.IsCombiningMark) break; } - char8Count++; + charCount++; checkIdx++; } - mData.mUndoManager.Add(new DeleteCharAction(this, 0, char8Count)); - PhysDeleteChars(0, char8Count); + mData.mUndoManager.Add(new DeleteCharAction(this, 0, charCount)); + PhysDeleteChars(0, charCount); } } @@ -1934,7 +1951,7 @@ namespace Beefy.widgets return .Other; } - public void GetTextCoordAtCursor(out float x, out float y) + public virtual void GetTextCoordAtCursor(out float x, out float y) { if (mVirtualCursorPos.HasValue) { @@ -1963,8 +1980,9 @@ namespace Beefy.widgets float y; GetTextCoordAtLineAndColumn(mVirtualCursorPos.Value.mLine, mVirtualCursorPos.Value.mColumn, out x, out y); - float overflowX; - return GetLineCharAtCoord(x, y, out line, out lineChar, out overflowX); + + float overflowX; + return GetLineCharAtCoord(line, x, out lineChar, out overflowX); } public virtual bool PrepareForCursorMove(int dir = 0) @@ -2064,6 +2082,7 @@ namespace Beefy.widgets if (var insertTextAction = mData.mUndoManager.GetLastUndoAction() as InsertTextAction) insertTextAction.mVirtualCursorPos = origPosition; CursorLineAndColumn = lineStartPosition; + CursorToLineStart(false); // Adjust to requested column @@ -2110,15 +2129,15 @@ namespace Beefy.widgets while (true) { if (lineChar > 0) - MoveCursorTo(lineIdx, lineChar - 1); + MoveCursorTo(lineIdx, lineChar - 1, false, 0, .SelectLeft); else if (lineIdx > 0) { int cursorIdx = mCursorTextPos; String lineText = scope String(); GetLineText(lineIdx - 1, lineText); - MoveCursorTo(lineIdx - 1, (int32)lineText.Length); + MoveCursorTo(lineIdx - 1, (int32)lineText.Length, false, 0, .SelectLeft); if ((!mAllowVirtualCursor) && (cursorIdx == mCursorTextPos)) - MoveCursorTo(lineIdx - 1, (int32)lineText.Length - 1); + MoveCursorTo(lineIdx - 1, (int32)lineText.Length - 1, false, 0, .SelectLeft); break; } @@ -2180,9 +2199,9 @@ namespace Beefy.widgets } if (isWithinLine) - MoveCursorTo(lineIdx, lineChar + 1, false, 1); + MoveCursorTo(lineIdx, lineChar + 1, false, 1, .SelectRight); else if (lineIdx < GetLineCount() - 1) - MoveCursorTo(lineIdx + 1, 0); + MoveCursorTo(lineIdx + 1, 0, false, 0, .SelectRight); if (!mWidgetWindow.IsKeyDown(KeyCode.Control)) break; @@ -2274,7 +2293,7 @@ namespace Beefy.widgets mEditWidget.Submit(); case KeyCode.Left: { - if (!PrepareForCursorMove(-1)) + if (!PrepareForCursorMove(-1)) { PrepareForCursorMove(-1); @@ -2349,7 +2368,7 @@ namespace Beefy.widgets var lineAndColumn = CursorLineAndColumn; CursorLineAndColumn = LineAndColumn(lineAndColumn.mLine, lineAndColumn.mColumn + 1); EnsureCursorVisible(true, false, false); - CursorMoved(); + PhysCursorMoved(.SelectRight); ClampCursor(); if (lineAndColumn != CursorLineAndColumn) @@ -2393,22 +2412,40 @@ namespace Beefy.widgets wasMoveKey = true; if ((lineIdx + aDir >= 0) && (lineIdx + aDir < GetLineCount())) { + float wantedX = mCursorWantX; + float wantY = 0; + if (mAllowVirtualCursor) { float cursorX; float cursorY; GetTextCoordAtCursor(out cursorX, out cursorY); + + GetLineAndColumnAtCoord(cursorX, cursorY, var virtLine, ?); + + /*GetTextCoordAtLineAndColumn(lineIdx, 0, ?, var lineY); + Debug.WriteLine($"Line:{lineIdx} LineY:{lineY} Cursor:{cursorX},{cursorY}");*/ + + if (aDir < 0) + { + wantY = cursorY - 0.1f; + } + else + { + wantY = cursorY + GetLineHeight(virtLine) + 0.1f; + } //mCursorWantX = cursorX; } + else + { + lineIdx += aDir; - lineIdx += aDir; - - float wantedX = mCursorWantX; - - float aX; - float aY; - GetTextCoordAtLineChar(lineIdx, 0, out aX, out aY); - MoveCursorToCoord(mCursorWantX, aY); + float aX; + float aY; + GetTextCoordAtLineChar(lineIdx, 0, out aX, out aY); + wantY = aY; + } + MoveCursorToCoord(mCursorWantX, wantY); ClampCursor(); @@ -2645,6 +2682,12 @@ namespace Beefy.widgets return false; } + public virtual bool GetLineCharAtCoord(int line, float x, out int theChar, out float overflowX) + { + GetTextCoordAtLineAndColumn(line, 0, ?, var y); + return GetLineCharAtCoord(x, y, ?, out theChar, out overflowX); + } + public virtual bool GetLineAndColumnAtCoord(float x, float y, out int line, out int column) { line = -1; @@ -2725,7 +2768,7 @@ namespace Beefy.widgets return; } if (c < 0) - lo = i + 1; + lo = i + 1; else hi = i - 1; } @@ -2818,11 +2861,11 @@ namespace Beefy.widgets ExtractString(lineStart, lineEnd - lineStart, outStr); // Full line } - public int GetTextIdx(int line, int char8Idx) + public int GetTextIdx(int line, int charIdx) { GetTextData(); int useLine = Math.Min(line, mData.mLineStarts.Count - 1); - return mData.mLineStarts[useLine] + char8Idx; + return mData.mLineStarts[useLine] + charIdx; } public int GetCharIdIdx(int32 findCharId) @@ -2839,7 +2882,7 @@ namespace Beefy.widgets int curLine = 0; int curColumn = 0; - int char8Idx = 0; + int charIdx = 0; mData.mTextIdData.Prepare(); while (true) { @@ -2863,7 +2906,7 @@ namespace Beefy.widgets if ((curLine == line) && (curColumn == column)) return char8Id; - char8 c = (char8)mData.mText[char8Idx++].mChar; + char8 c = (char8)mData.mText[charIdx++].mChar; if (c == '\n') { if (curLine == line) @@ -2878,7 +2921,7 @@ namespace Beefy.widgets } } - public virtual void GetTextCoordAtLineChar(int line, int char8Idx, out float x, out float y) + public virtual void GetTextCoordAtLineChar(int line, int charIdx, out float x, out float y) { x = 0; y = 0; @@ -2892,8 +2935,13 @@ namespace Beefy.widgets public enum CursorMoveKind { - FromTyping, - Unknown + case FromTyping; + case FromTyping_Deleting; + case Unknown; + case SelectRight; + case SelectLeft; + + public bool IsFromTyping => (this == FromTyping) || (this == FromTyping_Deleting); } // We used to have a split between PhysCursorMoved and CursorMoved. CursorMoved has a "ResetWantX" and was non-virtual... uh- @@ -2936,7 +2984,7 @@ namespace Beefy.widgets { int lineIdx; int lineChar; - GetCursorLineChar(out lineIdx, out lineChar); + GetCursorLineChar(out lineIdx, out lineChar); String lineText = scope String(); GetLineText(lineIdx, lineText); @@ -3379,14 +3427,22 @@ namespace Beefy.widgets Debug.Assert(mSelection.Value.mEndPos <= mData.mTextLength); } - //public void MoveCursorTo + public virtual void GetLineAndColumnAtLineChar(int line, int lineChar, out int lineColumn) + { + float x; + float y; + GetTextCoordAtLineChar(line, lineChar, out x, out y); - public void MoveCursorTo(int line, int char8Idx, bool centerCursor = false, int movingDir = 0, CursorMoveKind cursorMoveKind = .Unknown) + int coordLine; + GetLineAndColumnAtCoord(x, y, out coordLine, out lineColumn); + } + + public virtual void MoveCursorTo(int line, int charIdx, bool centerCursor = false, int movingDir = 0, CursorMoveKind cursorMoveKind = .Unknown) { - int useCharIdx = char8Idx; + int useCharIdx = charIdx; mShowCursorAtLineEnd = false; - CursorTextPos = GetTextIdx(line, char8Idx); + CursorTextPos = GetTextIdx(line, charIdx); // Skip over UTF8 parts AND unicode combining marks (ie: when we have a letter with an accent mark following it) while (true) @@ -3433,13 +3489,13 @@ namespace Beefy.widgets public void MoveCursorToIdx(int index, bool centerCursor = false, CursorMoveKind cursorMoveKind = .Unknown) { - int aLine; - int aCharIdx; - GetLineCharAtIdx(index, out aLine, out aCharIdx); - MoveCursorTo(aLine, aCharIdx, centerCursor, 0, cursorMoveKind); + int line; + int charIdx; + GetLineCharAtIdx(index, out line, out charIdx); + MoveCursorTo(line, charIdx, centerCursor, 0, cursorMoveKind); } - public void MoveCursorToCoord(float x, float y) + public virtual void MoveCursorToCoord(float x, float y) { bool failed = false; diff --git a/BeefLibs/corlib/src/Array.bf b/BeefLibs/corlib/src/Array.bf index 7a250fed..385655bb 100644 --- a/BeefLibs/corlib/src/Array.bf +++ b/BeefLibs/corlib/src/Array.bf @@ -55,7 +55,7 @@ namespace System int c = midVal <=> value; if (c == 0) return i; if (c < 0) - lo = i + 1; + lo = i + 1; else hi = i - 1; } diff --git a/BeefLibs/corlib/src/Collections/Dictionary.bf b/BeefLibs/corlib/src/Collections/Dictionary.bf index 35ce02bf..45fd8f75 100644 --- a/BeefLibs/corlib/src/Collections/Dictionary.bf +++ b/BeefLibs/corlib/src/Collections/Dictionary.bf @@ -822,6 +822,20 @@ namespace System.Collections return false; } + public bool TryGetRef(TKey key, out TKey* matchKey, out TValue* value) + { + int_cosize i = (int_cosize)FindEntry(key); + if (i >= 0) + { + matchKey = &mEntries[i].mKey; + value = &mEntries[i].mValue; + return true; + } + matchKey = null; + value = null; + return false; + } + public bool TryGetAlt(TAltKey key, out TKey matchKey, out TValue value) where TAltKey : IHashable where bool : operator TKey == TAltKey { int_cosize i = (int_cosize)FindEntryAlt(key); @@ -836,6 +850,20 @@ namespace System.Collections return false; } + public bool TryGetRefAlt(TAltKey key, out TKey* matchKey, out TValue* value) where TAltKey : IHashable where bool : operator TKey == TAltKey + { + int_cosize i = (int_cosize)FindEntryAlt(key); + if (i >= 0) + { + matchKey = &mEntries[i].mKey; + value = &mEntries[i].mValue; + return true; + } + matchKey = null; + value = null; + return false; + } + public TValue GetValueOrDefault(TKey key) { int_cosize i = (int_cosize)FindEntry(key); @@ -848,7 +876,7 @@ namespace System.Collections public struct Enumerator : IEnumerator, IRefEnumerator { - private Dictionary mDictionary; + private Dictionary mDictionary; #if VERSION_DICTIONARY private int_cosize mVersion; #endif @@ -1024,7 +1052,7 @@ namespace System.Collections private int_cosize mVersion; #endif private int_cosize mIndex; - private TValue mCurrent; + private TValue* mCurrent; const int_cosize cDictEntry = 1; const int_cosize cKeyValuePair = 2; @@ -1051,7 +1079,7 @@ namespace System.Collections { if (mDictionary.mEntries[mIndex].mHashCode >= 0) { - mCurrent = mDictionary.mEntries[mIndex].mValue; + mCurrent = &mDictionary.mEntries[mIndex].mValue; mIndex++; return true; } @@ -1065,12 +1093,12 @@ namespace System.Collections public TValue Current { - get { return mCurrent; } + get { return *mCurrent; } } public ref TValue CurrentRef { - get mut { return ref mCurrent; } + get mut { return ref *mCurrent; } } public ref TKey Key @@ -1128,7 +1156,7 @@ namespace System.Collections } } - public struct KeyEnumerator : IEnumerator, IResettable + public struct KeyEnumerator : IEnumerator, IRefEnumerator, IResettable { private Dictionary mDictionary; #if VERSION_DICTIONARY @@ -1187,6 +1215,11 @@ namespace System.Collections get { return *mCurrent; } } + public ref TKey CurrentRef + { + get { return ref *mCurrent; } + } + public ref TValue Value { get @@ -1224,6 +1257,13 @@ namespace System.Collections return .Err; return Current; } + + public Result GetNextRef() mut + { + if (!MoveNext()) + return .Err; + return &CurrentRef; + } } } } diff --git a/BeefLibs/corlib/src/Collections/List.bf b/BeefLibs/corlib/src/Collections/List.bf index 47901aa9..16496e2f 100644 --- a/BeefLibs/corlib/src/Collections/List.bf +++ b/BeefLibs/corlib/src/Collections/List.bf @@ -536,6 +536,15 @@ namespace System.Collections EnsureCapacity(size, true); } + public void Resize(int size) + { + EnsureCapacity(size, true); + int addSize = size - mSize; + if (addSize > 0) + Internal.MemSet(Ptr + mSize, 0, addSize * alignof(T)); + mSize = (.)size; + } + public Enumerator GetEnumerator() { return Enumerator(this); @@ -716,6 +725,13 @@ namespace System.Collections sorter.[Friend]Sort(0, mSize); } + public void Sort(Comparison comp, int index, int count) + { + Debug.Assert((uint)index + (uint)count <= (uint)mSize); + var sorter = Sorter(mItems, null, mSize, comp); + sorter.[Friend]Sort(index, count); + } + public int RemoveAll(Predicate match) { int_cosize freeIndex = 0; // the first free slot in items array diff --git a/IDE/dist/images/DarkUI.png b/IDE/dist/images/DarkUI.png index 27ef05725a3cc0ddc67b34f897e606c63816a3c7..07f5a7dca5ff780fdee627dea387a486863b92fa 100644 GIT binary patch delta 12350 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 diff --git a/IDE/dist/images/DarkUI.psd b/IDE/dist/images/DarkUI.psd index 39869f8435b460d6984f54f61ebe85877d016958..1caa884fc9d855c083cedc51408ad60604e502a8 100644 GIT binary patch delta 3542 zcmcInYfw~G96#soUQm4SSeD1s2QbJ{+JIdkz%7)gkhIDICZL3}IL5-RA{f3_avV~p zl)cz0s94R8k<;kRl{vwOFdTInu(HHQXhs?fl^LP~y2|a`*RnXfW#e@3oO}Q8^ZWhJ z*>hp&SYX?ifqO$|d=Q_UnJtw9_Bz+_a(hG4lm?s3=U}*3g{*3m37qm_GP*Y2q`jb> zkSP^WN`*2eH(Hgbj8BY>QAEX4K~j|~JH0}Bz$>gQx-6zFDj`;(i;IanB@txsk*EM)ZVP=GQb5YxIhm80f2|-i&;b7;YT^gbk^Y; zfFIZej^HsLi$4SK4ip z`IJgChRkljQ`g~noA7%VkTMLGwRixK#KB7fEa9XNm%;gqW~LbQ`VwQ6PGi(n>Pk79 z`s&IxI3NU%nIHuifF9o^zzC{<4ruUO2P*L>Wtr=1*OcNoyh-3;%6Q<20IkassUZTQ zQ@3Fscd?i%u*71jnS@EpVbXG#w39KV=nYj*gDC~$GjQmh#B|6BQ=esRKM@GGS$ylD zVS@OxPtxo($$frJbVjYPCU@n$<;L3AMN0%7UA03E~KXo{2t7El1=nuM!;rHZsq8r76|Qug8vNq zC>TnFRLxX$>{YmmpUw2ZHL^-U;r)vPffRRLTo z2JArimmm-_YnsqV(M06ud~(GKC@g?k(}g;Vq0m6HrWYlYK!aFn5FKAR0q8O6-w3x0 zzQsPMd^0qOS{NGM0_#OB0hvsWnv35MiOfW^>tLD~w35d{y$eqZ?Wmr`n&fERQTXEv ze80YEM%r#DEP}axDLT^wGlUMcpG2}>p|E+(?UzyAfP;7YeRR|2i0qKJMH6CRhl-!L z6B1$*I<&}FLWt|oVL+RtgxHdf-F&#*gBTG&*@Agf}U}IZ_Y9ky|*y@q(C1Rd{ z)7FaSq!GflG~2q-o^(Q7AKPs-oZ-kD^h3p&6Pgm8&vM`x%%NJ=%TduHB43zuu!^!D z3_(}33E|8$51J7^YQ!ZPJb~I56JqZMyHP|Ak-}4;NMBT4O{fGd$cm2dCtl(4QbA|Y z?Gr?js9ocZFO>N;5h|MQA$)p73R>_Cbo9Z57RvYLv9ClxMFqzptxR(KMQ%l@umL_` zPNE|q{CQ-WClPiX6<&evt0h~NCgJnMexrng)1|#6ANPU^iCa7^B2S5H#eLFj;q?GOUx2`0)W+V z2{x-03%v|uVnm4*df97|g$v>>Y^I}(^&e_ZD)28Qi~on3&Lu4u>LekSI~r+_C33sO zv%b|JL9GVqreHK{r*uP>Zys0;Hh^{b*5jcA+32$$q>+%b*Kl?tXJ3MTyC~ZEcq9I^ zU^Q5R=3Zj$3qc+z1O=c7-}xX1q@uSkOHa<5Ho%NoM;U;hc1x&?%<~(-yO7<9qYPv? z!EULQktG4FeVhTkcsTp6%g7>kKD9sR!@K8EFD^F95DXTrShQQ3{mFcHdH47{m-A`m zq}CP2RympMOa}caCr_)LEtZoxuIWwJl5e?#V$iy$_YLoJNl%eW7PzaqE7yI^pz7^ucs1YiIb*_vTJoX=&=iXL*lyP7Ak&lSdz9x}n142nEYc1-yj@Xl@g delta 1967 zcma)5dr(wW9KPq?dw@t`)m2d0L}7C#UERIA2rXy`^4K+CkRV>0u7FG_0^^8p=~5$; zP6K#lHc?E5X|jCKc+y`|YH5u5hh^bo6s63^4B|{VH8m+lch8=){^K9*nce-q@AtUh z`JHo*or)Sd7}Yv`tUD>ql_Sf7|8u%+&WX0)CK&R*AAKqMt>X40d;4ZR>h^S#@#wKQ z!D_KtE!L#hY>HEHI30?`nyAIf_Gjs2g}gi5;;`BsNmU7n7G*`%a*N`1%(E;{vaYaL zE9WK7t8yfHE351&bJy3@<>WYBTHSafNsT-q$N!%)&16h?)`PX4@viP8MhE#e+li0@DAdkrDS$q`8Auj`0T!kMtjZXutkV z)!{FKuwI9TK@wgzV8A=`N=F3}vMLf8tWKNNX-$gd6Vl8z?}t^%f;{!m=yB3pvjl}+ z{rTG9+dma+^$oGaUYo~-ChfhtSQ53ifD0dx`i7O9945EdRdaHN)3XKn=oBZ>E;QLxeMsl(5UVPvIBd5s8`@@qNxdA;g;uc4qfGq?~ zWlo+Lq2L811a71>*)D%CJ9_i3l~(3lr=p={Uk zH%x?xe38#V*{2=u(yzo4k-c=PaujYAi2R(CK3G{8+@W#}ZWf8@YzOlTVdh5B&RAv_ zcss>J<~j2#`0$wc3TGF6de2Gl_<}f{OLw&`Yofq;PmJcu-%#?eIG3@kF?3)(lO%2| z%LF)D>Ul9RzT6fXA6O8K=Y9^hREb}{-1G49LTRhwBhAu8UtO~#`#jqvqpxPW6xw{U zL4uPF@|MYv(kicYkINB?g-T(mzDo3Ok&ppruFG=~)hnp(p}Gsi8(a_MdjyYO>w>hK zx^7ApGKFj*OK|BcU0*4}d_fUr3laJX_q7hnGvU~vtR_qSqYr%xr{F>g4otyb3Z_lP zPX=rCT z3&|eV@l6lTq#b)b_)ehXCmpP#VGr}zyoZ(j!@(N5M;+J{dkNd3Y? w--UjhPQi>zID>*Mm+&GAu3qwI>pAwz476OvsZ_bkLE;tsvR{EsS8#9Ge^uT}xBvhE diff --git a/IDE/dist/images/DarkUI_2.png b/IDE/dist/images/DarkUI_2.png index 295c6d6effdd2a9fce886518f1a004f7265ee1e0..e80b9d0c3ce405755547a610cc2a0f27dc6584aa 100644 GIT binary patch delta 14336 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%0Ag3F6EZ!wgK6nz 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 diff --git a/IDE/dist/images/DarkUI_4.png b/IDE/dist/images/DarkUI_4.png index 4bfcf50fbf470aa5402e641d8aa3f902f15ff852..f114f6ef1a8d4fd023cc9582e82a75ac58e285a3 100644 GIT binary patch delta 18049 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 eq8bh~`%isEmFSa=d#Wzp$Pzjf1_^z delta 17914 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 diff --git a/IDE/src/Commands.bf b/IDE/src/Commands.bf index 4ffdff7f..0d4ccea1 100644 --- a/IDE/src/Commands.bf +++ b/IDE/src/Commands.bf @@ -214,6 +214,12 @@ namespace IDE Add("Close Document", new () => { gApp.[Friend]TryCloseCurrentDocument(); }); Add("Close Panel", new () => { gApp.[Friend]TryCloseCurrentPanel(); }); Add("Close Workspace", new => gApp.[Friend]Cmd_CloseWorkspaceAndSetupNew); + Add("Collapse All", new => gApp.[Friend]CollapseAll); + Add("Collapse To Definition", new => gApp.[Friend]CollapseToDefinition); + Add("Collapse Redo", new => gApp.[Friend]CollapseRedo); + Add("Collapse Toggle", new => gApp.[Friend]CollapseToggle); + Add("Collapse Toggle All", new => gApp.[Friend]CollapseToggleAll); + Add("Collapse Undo", new => gApp.[Friend]CollapseUndo); Add("Comment Block", new => gApp.[Friend]CommentBlock, .Editor); Add("Comment Lines", new => gApp.[Friend]CommentLines, .Editor); Add("Comment Toggle", new => gApp.[Friend]CommentToggle, .Editor); diff --git a/IDE/src/Compiler/BfCompiler.bf b/IDE/src/Compiler/BfCompiler.bf index 97103c61..8114eca9 100644 --- a/IDE/src/Compiler/BfCompiler.bf +++ b/IDE/src/Compiler/BfCompiler.bf @@ -60,6 +60,9 @@ namespace IDE.Compiler [CallingConvention(.Stdcall), CLink] static extern bool BfCompiler_ClassifySource(void* bfCompiler, void* bfPassInstance, void* bfParser, void* bfResolvePassData, void* char8Data); + [CallingConvention(.Stdcall), CLink] + static extern char8* BfCompiler_GetCollapseRegions(void* bfCompiler, void* bfParser); + [CallingConvention(.Stdcall), CLink] static extern char8* BfCompiler_GetAutocompleteInfo(void* bfCompiler); @@ -266,6 +269,11 @@ namespace IDE.Compiler return BfCompiler_ClassifySource(mNativeBfCompiler, bfPassInstance.mNativeBfPassInstance, (parser != null) ? parser.mNativeBfParser : null, nativeResolvePassData, char8DataPtr); } + public void GetCollapseRegions(BfParser parser, String outData) + { + outData.Append(BfCompiler_GetCollapseRegions(mNativeBfCompiler, (parser != null) ? parser.mNativeBfParser : null)); + } + public bool VerifyTypeName(String typeName, int cursorPos) { return BfCompiler_VerifyTypeName(mNativeBfCompiler, typeName, (.)cursorPos); diff --git a/IDE/src/IDEApp.bf b/IDE/src/IDEApp.bf index 140bd9c4..7ed18857 100644 --- a/IDE/src/IDEApp.bf +++ b/IDE/src/IDEApp.bf @@ -2438,6 +2438,42 @@ namespace IDE //CloseWorkspace(); //FinishShowingNewWorkspace(); } + + [IDECommand] + void CollapseAll() + { + GetActiveSourceEditWidgetContent()?.CollapseAll(); + } + + [IDECommand] + void CollapseToDefinition() + { + GetActiveSourceEditWidgetContent()?.CollapseToDefinition(); + } + + [IDECommand] + void CollapseRedo() + { + + } + + [IDECommand] + void CollapseToggle() + { + GetActiveSourceEditWidgetContent()?.CollapseToggle(); + } + + [IDECommand] + void CollapseToggleAll() + { + GetActiveSourceEditWidgetContent()?.CollapseToggleAll(); + } + + [IDECommand] + void CollapseUndo() + { + + } [IDECommand] void DeleteAllRight() @@ -2485,6 +2521,73 @@ namespace IDE sewc.ToggleComment(false); } + [IDECommand] + void ComplexIdSpan() + { + if (var sourceViewPanel = GetLastActiveDocumentPanel() as SourceViewPanel) + { + var sewc = sourceViewPanel.mEditWidget.mEditWidgetContent as SourceEditWidgetContent; + uint8[] newData = new uint8[sewc.mData.mTextLength*4]; + + var idData = ref sewc.mData.mTextIdData; + /*idData.Prepare(); + + int encodeIdx = 0; + int decodeIdx = 0; + int charId = 1; + int charIdx = 0; + while (true) + { + int32 cmd = Utils.DecodeInt(idData.mData, ref decodeIdx); + if (cmd > 0) + { + charId = cmd; + Utils.EncodeInt(newData, ref encodeIdx, charId); + } + else + { + int32 spanSize = -cmd; + + charId += spanSize; + charIdx += spanSize; + + if (cmd == 0) + { + Utils.EncodeInt(newData, ref encodeIdx, 0); + break; + } + + while (spanSize > 65) + { + Utils.EncodeInt(newData, ref encodeIdx, -64); + spanSize -= 64; + } + Utils.EncodeInt(newData, ref encodeIdx, -spanSize); + } + }*/ + + int encodeIdx = 0; + int sizeLeft = sewc.mData.mTextLength; + while (sizeLeft > 0) + { + int writeLength = Math.Min(sizeLeft, 64); + Utils.EncodeInt(newData, ref encodeIdx, sewc.mData.mNextCharId); + Utils.EncodeInt(newData, ref encodeIdx, -writeLength); + sewc.mData.mNextCharId += (.)writeLength; + sewc.mData.mNextCharId++; + sizeLeft -= writeLength; + } + Utils.EncodeInt(newData, ref encodeIdx, 0); + + IdSpan newSpan = .(newData, (.)encodeIdx); + + //Runtime.Assert(newSpan.Equals(idData)); + + idData.Dispose(); + idData = newSpan; + } + } + public Result StructuredLoad(StructuredData data, StringView filePath) { if (mWorkspace.IsSingleFileWorkspace) diff --git a/IDE/src/ScriptManager.bf b/IDE/src/ScriptManager.bf index 44dc6f22..ea5bf61e 100644 --- a/IDE/src/ScriptManager.bf +++ b/IDE/src/ScriptManager.bf @@ -2583,6 +2583,30 @@ namespace IDE #endif } + [IDECommand] + public void UndoFill() + { + var documentPanel = gApp.GetLastActiveDocumentPanel(); + if (var sourceViewPanel = documentPanel as SourceViewPanel) + { + int count = 0; + for (int i < 400) + { + for (char8 c = 'A'; c <= 'Z'; c++) + { + String str = scope .(32); + if (count++ % 131 == 0) + str.Append("\n//"); + str.Append(c); + + sourceViewPanel.mEditWidget.mEditWidgetContent.mData.mUndoManager.[Friend]mSkipNextMerge = true; + sourceViewPanel.mEditWidget.mEditWidgetContent.InsertAtCursor(str); + } + } + + } + } + [IDECommand] public void SetVal(String valName, String value) { diff --git a/IDE/src/Settings.bf b/IDE/src/Settings.bf index 6c02ea8b..a3e8fa77 100644 --- a/IDE/src/Settings.bf +++ b/IDE/src/Settings.bf @@ -774,6 +774,12 @@ namespace IDE Add("Build Workspace", "F7"); Add("Cancel Build", "Ctrl+Break"); Add("Close Document", "Ctrl+W"); + Add("Collapse All", "Ctrl+M, Ctrl+A"); + Add("Collapse To Definition", "Ctrl+M, Ctrl+O"); + Add("Collapse Redo", "Ctrl+M, Ctrl+Y"); + Add("Collapse Toggle", "Ctrl+M, Ctrl+M"); + Add("Collapse Toggle All", "Ctrl+M, Ctrl+L"); + Add("Collapse Undo", "Ctrl+M, Ctrl+Z"); Add("Compile File", "Ctrl+F7"); Add("Comment Block", "Ctrl+K, Ctrl+C"); Add("Comment Lines", "Ctrl+K, Ctrl+/"); diff --git a/IDE/src/ui/QuickFind.bf b/IDE/src/ui/QuickFind.bf index da02b666..80f4f7f3 100644 --- a/IDE/src/ui/QuickFind.bf +++ b/IDE/src/ui/QuickFind.bf @@ -85,7 +85,7 @@ namespace IDE.ui public bool mIsReplace; public int32 mLastTextVersion; bool mFoundMatches; - bool mIsShowingMatches = false; + public bool mIsShowingMatches = false; static String sLastSearchString = new String() ~ delete _; public bool mOwnsSelection; diff --git a/IDE/src/ui/SourceEditWidgetContent.bf b/IDE/src/ui/SourceEditWidgetContent.bf index a3956f0d..78ee73fa 100644 --- a/IDE/src/ui/SourceEditWidgetContent.bf +++ b/IDE/src/ui/SourceEditWidgetContent.bf @@ -12,6 +12,7 @@ using Beefy.theme.dark; using Beefy.utils; using IDE.Debugger; using IDE.Compiler; +using Beefy.geom; namespace IDE.ui { @@ -132,10 +133,98 @@ namespace IDE.ui public class SourceEditWidgetContent : DarkEditWidgetContent { - public class Data : EditWidgetContent.Data + public class CollapseSummary : DarkEditWidgetContent.Embed + { + public SourceEditWidgetContent mEditWidgetContent; + public int32 mCollapseIndex; + public String mHideString ~ delete _; + + public override float GetWidth(bool hideLine) + { + if (hideLine) + return DarkTheme.sDarkTheme.mSmallBoldFont.GetWidth(mHideString) + GS!(24); + + return GS!(24); + } + + public override void Draw(Graphics g, Rect rect, bool hideLine) + { + if (rect.mHeight >= DarkTheme.sDarkTheme.mSmallBoldFont.GetLineSpacing()) + g.SetFont(DarkTheme.sDarkTheme.mSmallBoldFont); + + if (mEditWidgetContent.mSelection != null) + { + var collapseEntry = mEditWidgetContent.mOrderedCollapseEntries[mCollapseIndex]; + if ((mEditWidgetContent.mSelection.Value.MinPos <= collapseEntry.mEndIdx) && (mEditWidgetContent.mSelection.Value.MaxPos >= collapseEntry.mStartIdx)) + { + using (g.PushColor(mEditWidgetContent.GetSelectionColor(0))) + g.FillRect(rect.mX, rect.mY, rect.mWidth, rect.mHeight); + } + } + + using (g.PushColor(0x80FFFFFF)) + { + g.OutlineRect(rect.mX, rect.mY, rect.mWidth, rect.mHeight); + } + + using (g.PushColor(0x20FFFFFF)) + { + g.FillRect(rect.mX, rect.mY, rect.mWidth, rect.mHeight); + } + + var summaryString = "..."; + if ((mHideString != null) && (hideLine)) + summaryString = scope:: $"{mHideString} ..."; + g.DrawString(summaryString, rect.mX, rect.mY + (int)((rect.mHeight - g.mFont.GetLineSpacing()) * 0.4f), .Centered, rect.mWidth); + } + } + + public struct CollapseData + { + public enum Kind : char8 + { + Zero = 0, + Comment = 'C', + Method = 'M', + Namespace = 'N', + Property = 'P', + Region = 'R', + Type = 'T', + Unknown = '?' + } + + public Kind mKind; + public int32 mAnchorIdx; + public int32 mStartIdx; + public int32 mEndIdx; + + public int32 mAnchorId; + public int32 mStartId; + public int32 mEndId; + + public int32 mAnchorLine = -1; + public int32 mStartLine = -1; + public int32 mEndLine = -1; + } + + public struct CollapseEntry : CollapseData + { + public float mOpenPct = 1.0f; + public bool mIsOpen = true; + public int32 mPrevAnchorLine = -1; + + public int32 mParseRevision; + public int32 mTextRevision; + public bool mDeleted; + } + + public class Data : DarkEditWidgetContent.Data { public List mPersistentTextPositions = new List() ~ DeleteContainerAndItems!(_); public QuickFind mCurQuickFind; // Only allow one QuickFind on this buffer at a time + public int32 mCollapseParseRevision; + public int32 mCollapseTextVersionId; + public List mCollapseData = new .() ~ delete _; } struct QueuedTextEntry @@ -236,7 +325,12 @@ namespace IDE.ui FastCursorState mFastCursorState ~ delete _; public HashSet mCurParenPairIdSet = new .() ~ delete _; HilitePairedCharState mHilitePairedCharState = .NeedToRecalculate; - + public Dictionary mCollapseMap = new .() ~ delete _; + public List mOrderedCollapseEntries = new .() ~ delete _; + public int32 mCollapseParseRevision; + public int32 mCollapseTextVersionId; + public bool mCollapseNeedsUpdate; + public List PersistentTextPositions { get @@ -253,6 +347,17 @@ namespace IDE.ui } } + public Data PreparedData + { + get + { + GetTextData(); + var data = (Data)mData; + data.mTextIdData.Prepare(); + return data; + } + } + public this(EditWidgetContent refContent = null) : base(refContent) { mAllowVirtualCursor = true; @@ -1502,6 +1607,9 @@ namespace IDE.ui public override void InsertAtCursor(String theString, InsertFlags insertFlags = .None) { + if (!theString.IsWhiteSpace) + CheckCollapseOpen(CursorLineAndColumn.mLine); + var insertFlags; if ((HasSelection()) && (gApp.mSymbolReferenceHelper != null) && (gApp.mSymbolReferenceHelper.mKind == .Rename)) @@ -1545,6 +1653,9 @@ namespace IDE.ui //CursorLineAndColumn = LineAndColumn(line, GetLineEndColumn(line, false, false)); //CursorMoved(); + if (IsLineCollapsed(line)) + return; + float x; float y; float wantWidth = 0; @@ -1554,6 +1665,21 @@ namespace IDE.ui x = wantWidth + mTextInsets.mLeft; MoveCursorToCoord(x, y); + int endLine = CursorLineAndColumn.mLine; + while (true) + { + if (!IsLineCollapsed(endLine + 1)) + break; + endLine++; + } + + if (endLine != CursorLineAndColumn.mLine) + { + GetLinePosition(endLine, var endLineStart, var endLineEnd); + GetLineAndColumnAtLineChar(endLine, endLineEnd - endLineStart, var endColumn); + CursorLineAndColumn = .(endLine, endColumn); + } + return; } @@ -3001,9 +3127,25 @@ namespace IDE.ui public void MoveLine(VertDir dir) { int lineNum = CursorLineAndColumn.mLine; - GetLinePosition(lineNum, var lineStart, var lineEnd); + int endLineNum = lineNum; + + GetLinePosition(lineNum, var lineStart, ?); + + // Copy collapsed lines below as well + while (endLineNum < mLineCoords.Count - 1) + { + if (GetLineHeight(endLineNum + 1) > 0.1f) + break; + endLineNum++; + } + GetLinePosition(endLineNum, ?, var lineEnd); + mSelection = .(lineStart, Math.Min(lineEnd + 1, mData.mTextLength)); - MoveSelection(lineNum + (int)dir, false); + + if (dir == .Down) + MoveSelection(endLineNum + (int)dir, false); + else + MoveSelection(lineNum + (int)dir, false); } public void MoveStatement(VertDir dir) @@ -4393,6 +4535,26 @@ namespace IDE.ui } } + public override void MouseDown(float x, float y, int32 btn, int32 btnCount) + { + int line = GetLineAt(y); + if (mEmbeds.GetValue(line) case .Ok(let embed)) + { + Rect embedRect = GetEmbedRect(line, embed); + if (embedRect.Contains(x, y)) + { + if ((btn == 0) && (btnCount == 2)) + { + if (var collapseSummary = embed as SourceEditWidgetContent.CollapseSummary) + SetCollapseOpen(collapseSummary.mCollapseIndex, true); + } + return; + } + } + + base.MouseDown(x, y, btn, btnCount); + } + public override void Undo() { var symbolReferenceHelper = IDEApp.sApp.mSymbolReferenceHelper; @@ -4485,11 +4647,61 @@ namespace IDE.ui IDEApp.sApp.mSymbolReferenceHelper.SourceUpdateText(this, index); } + public override void Backspace() + { + CheckCollapseOpen(CursorLineAndColumn.mLine); + base.Backspace(); + } + public override void RemoveText(int index, int length) { if (IDEApp.sApp.mSymbolReferenceHelper != null) IDEApp.sApp.mSymbolReferenceHelper.SourcePreRemoveText(this, index, length); + if (CursorTextPos == index) + { + CheckCollapseOpen(CursorLine); + } + + //TODO: Needed? + /*var data = Data; + + int linesRemoved = 0; + for (int i in index.. 0) // Optimization to only handle block moves, otherwise we wait for RefreshCollapseRegions + { + for (var collapseEntry in mOrderedCollapseEntries) + { + bool failed = false; + + void Update(ref int32 collapseIdx, ref int32 line) + { + if (index > collapseIdx) + return; + if (index + length > collapseIdx) + { + failed = true; + return; + } + collapseIdx -= (.)length; + line -= (.)linesRemoved; + } + + int32 prevAnchorLine = collapseEntry.mAnchorLine; + + Update(ref collapseEntry.mAnchorIdx, ref collapseEntry.mAnchorLine); + Update(ref collapseEntry.mStartIdx, ref collapseEntry.mStartLine); + Update(ref collapseEntry.mEndIdx, ref collapseEntry.mEndLine); + + RefreshCollapseRegion(collapseEntry, prevAnchorLine, failed); + } + }*/ + for (var persistentTextPosition in PersistentTextPositions) { if (persistentTextPosition.mIndex >= index) @@ -4548,11 +4760,99 @@ namespace IDE.ui mVirtualCursorPos.ValueRef.mColumn = (.)Math.Min(mVirtualCursorPos.Value.mColumn, Math.Max(virtualEnd, lineEnd)); } - public override void PhysCursorMoved(CursorMoveKind moveKind) - { - //Debug.WriteLine("Cursor moved {0} {1} {2}", CursorLineAndColumn.mLine, CursorLineAndColumn.mColumn, moveKind); + bool CheckCollapseOpen(int checkLine, CursorMoveKind cursorMoveKind = .Unknown) + { + if (cursorMoveKind == .FromTyping_Deleting) + return true; - if (moveKind != .FromTyping) + if (IsLineCollapsed(checkLine)) + { + if ((cursorMoveKind == .SelectLeft) || (cursorMoveKind == .SelectRight)) + { + if (!IsLineCollapsed(CursorLineAndColumn.mLine)) + { + int anchorLine = FindUncollapsedLine(checkLine); + CursorLineAndColumn = .(anchorLine, 0); + CursorToLineEnd(); + return false; + } + } + else + { + for (var collapseRegion in mOrderedCollapseEntries) + { + if ((checkLine >= collapseRegion.mStartLine) && (checkLine <= collapseRegion.mEndLine) && (!collapseRegion.mIsOpen)) + { + SetCollapseOpen(@collapseRegion.Index, true, true); + } + } + RehupLineCoords(); + } + } + return true; + } + + public override bool PrepareForCursorMove(int dir = 0) + { + bool hadSelection = HasSelection(); + + if ((dir > 0) && (HasSelection())) + { + GetLineCharAtIdx(mSelection.Value.MaxPos - 1, var maxLine, ?); + if (IsLineCollapsed(maxLine)) + { + if (hadSelection) + { + mSelection = null; + CursorToLineEnd(); + return true; + } + } + } + + if (dir < 0) + { + int line = CursorLineAndColumn.mLine; + if (IsLineCollapsed(line)) + { + int anchorLine = FindUncollapsedLine(line); + CursorLineAndColumn = .(anchorLine, 0); + base.CursorToLineEnd(); + return true; + } + } + + return base.PrepareForCursorMove(dir); + } + + public override void MoveCursorTo(int line, int charIdx, bool centerCursor, int movingDir, CursorMoveKind cursorMoveKind) + { + if (!CheckCollapseOpen(line, cursorMoveKind)) + return; + base.MoveCursorTo(line, charIdx, centerCursor, movingDir, cursorMoveKind); + + /*if (mSourceViewPanel?.mQuickFind.mIsShowingMatches == true) + { + mSourceViewPanel.mQuickFind.ClearFlags(false, true); + mSourceViewPanel.mQuickFind.mCurFindIdx = (.)CursorTextPos; + }*/ + } + + public override void PhysCursorMoved(CursorMoveKind moveKind) + { + if (mVirtualCursorPos != null) + { + CheckCollapseOpen(mVirtualCursorPos.Value.mLine, moveKind); + } + else + { + GetLineCharAtIdx(mCursorTextPos, var checkLine, ?); + CheckCollapseOpen(checkLine, moveKind); + } + + //Debug.WriteLine("Cursor moved Idx:{0} Line:{1} Col:{2} MoveKind:{3}", CursorTextPos, CursorLineAndColumn.mLine, CursorLineAndColumn.mColumn, moveKind); + + if (!moveKind.IsFromTyping) { int cursorIdx = CursorTextPos; mData.mTextIdData.Prepare(); @@ -4582,6 +4882,7 @@ namespace IDE.ui if ((mSourceViewPanel != null) && (mSourceViewPanel.mHoverWatch != null)) mSourceViewPanel.mHoverWatch.Close(); + } public override void Update() @@ -4721,12 +5022,213 @@ namespace IDE.ui } } + public void RehupLineCoords(int animIdx = -1, Span animLines = default) + { + Debug.Assert(Thread.CurrentThread == gApp.mMainThread); + + var data = mData as Data; + + if (mLineCoords == null) + mLineCoords = new .(); + if (mLineCoordJumpTable == null) + mLineCoordJumpTable = new .(); + + mLineCoords.Clear(); + mLineCoords.GrowUnitialized(data.mLineStarts.Count); + mLineCoordJumpTable.Clear(); + + float fontHeight = mFont.GetLineSpacing(); + int prevJumpIdx = -1; + float jumpCoordSpacing = GetJumpCoordSpacing(); + + SourceEditWidgetContent.CollapseEntry* animEntry = null; + if (animIdx != -1) + animEntry = mOrderedCollapseEntries[animIdx]; + + double curY = 0; + + List collapseStack = scope .(16); + int32 curIdx = 0; + int32 closeDepth = 0; + int32 curAnimLineIdx = 0; + bool inAnim = false; + for (int line < data.mLineStarts.Count) + { + while (curIdx < mOrderedCollapseEntries.Count) + { + var entry = mOrderedCollapseEntries[curIdx]; + if (entry.mDeleted) + { + curIdx++; + continue; + } + + if (entry.mAnchorLine > line) + break; + collapseStack.Add(curIdx); + curIdx++; + } + + bool deferClose = false; + + if (!collapseStack.IsEmpty) + { + int32 activeIdx = collapseStack.Back; + var entry = mOrderedCollapseEntries[activeIdx]; + if (line == entry.mStartLine) + { + if (activeIdx == animIdx) + inAnim = true; + if ((!entry.mIsOpen) && (activeIdx != animIdx)) + { + if ((closeDepth == 0) && (entry.mAnchorLine == entry.mStartLine)) + deferClose = true; + else + closeDepth++; + } + } + } + + float lineHeight = fontHeight; + if (closeDepth > 0) + { + lineHeight = 0; + } + else if ((animEntry != null) && (inAnim) && (line != animEntry.mAnchorLine)) + { + if (inAnim) + lineHeight *= 0.50f + animEntry.mOpenPct*0.50f; + + if ((curAnimLineIdx < animLines.Length) && (animLines[curAnimLineIdx] == line)) + curAnimLineIdx++; + + float pow = Math.Min(animLines.Length / 200.0f, 5.0f); + float linePct = Math.Pow(animEntry.mOpenPct, pow); + + int maxAnimLineIdx = (.)Math.Round(Math.Min(animLines.Length, 1000) * linePct); + if (curAnimLineIdx > maxAnimLineIdx) + lineHeight = 0; + } + + mLineCoords[line] = (float)curY; + + int jumpIdx = (.)(curY / jumpCoordSpacing); + while (prevJumpIdx < jumpIdx) + { + mLineCoordJumpTable.Add(((int32)line, (int32)line + 1)); + prevJumpIdx++; + } + mLineCoordJumpTable[jumpIdx].max = (.)line + 1; + curY += lineHeight; + + if (deferClose) + closeDepth++; + + while (!collapseStack.IsEmpty) + { + int32 activeIdx = collapseStack.Back; + var entry = mOrderedCollapseEntries[activeIdx]; + if (line < entry.mEndLine) + break; + if (activeIdx == animIdx) + inAnim = false; + if ((!entry.mIsOpen) && (closeDepth > 0)) + closeDepth--; + collapseStack.PopBack(); + } + } + + float height = mLineCoords.Back + mTextInsets.mTop + mTextInsets.mBottom; + if ((height + mMaximalScrollAddedHeight != mHeight) && (mHeight > 0) && (mMaximalScrollAddedHeight > 0)) + { + mMaximalScrollAddedHeight = 0; + mHeight = height; + UpdateMaximalScroll(); + mEditWidget.UpdateScrollbars(); + } + + mHilitePairedCharState = .NeedToRecalculate; + } + + public override void GetTextData() + { + var data = Data; + + if (data.mCollapseParseRevision != mCollapseParseRevision) + { + mCollapseParseRevision = data.mCollapseParseRevision; + mCollapseTextVersionId = data.mCollapseTextVersionId; + + for (var collapseData in ref data.mCollapseData) + { + if (mCollapseMap.TryAdd(collapseData.mAnchorId, ?, var entry)) + *entry = .(); + Internal.MemCpy(entry, &collapseData, sizeof(CollapseData)); + entry.mPrevAnchorLine = entry.mAnchorLine; + entry.mParseRevision = mCollapseParseRevision; + entry.mDeleted = false; + } + + for (var entry in ref mCollapseMap.Values) + { + if (entry.mParseRevision != data.mCollapseParseRevision) + { + if (mEmbeds.GetAndRemove(entry.mAnchorLine) case .Ok(let val)) + delete val.value; + @entry.Remove(); + } + } + + mOrderedCollapseEntries.Clear(); + for (var entry in ref mCollapseMap.Values) + { + mOrderedCollapseEntries.Add(&entry); + } + + mOrderedCollapseEntries.Sort(scope (lhs, rhs) => lhs.mAnchorIdx <=> rhs.mAnchorIdx); + + for (var entry in mOrderedCollapseEntries) + { + if (entry.mKind == .Region) + entry.mStartLine++; + + if ((entry.mPrevAnchorLine != -1) && (entry.mPrevAnchorLine != entry.mAnchorLine)) + { + if (mEmbeds.GetAndRemove(entry.mPrevAnchorLine) case .Ok(let val)) + { + if (!mEmbeds.TryAdd(entry.mAnchorLine, var keyPtr, var valuePtr)) + { + if (var collapseSummary = val.value as SourceEditWidgetContent.CollapseSummary) + collapseSummary.mCollapseIndex = (.)@entry.Index; + *valuePtr = val.value; + } + else + delete val.value; + } + } + } + + //Debug.WriteLine($"ParseCollapseRegions Count:{mOrderedCollapseEntries.Count} Time:{sw.ElapsedMilliseconds}ms"); + } + + base.GetTextData(); + } + + public override void CheckLineCoords() + { + if (mLineCoordTextVersionId == mData.mCurTextVersionId) + return; + + RehupLineCoords(); + mLineCoordTextVersionId = mData.mCurTextVersionId; + } + public override void Draw(Graphics g) { base.Draw(g); - + // Highlight matching paired characters under cursor - if (mEditWidget.mHasFocus && !HasSelection()) + if ((mEditWidget.mHasFocus) && (!HasSelection()) && (!IsLineCollapsed(CursorLineAndColumn.mLine))) { int64 stateHash = (.)(CursorTextPos ^ ((int64)mData.mCurTextVersionId << 31)); if (mHilitePairedCharState case .Valid(var cachedStateHash, ?, ?, ?, ?, ?)) @@ -4826,7 +5328,8 @@ namespace IDE.ui GetLineColumnAtIdx(matchingParenIndex, var line2, var column2); GetTextCoordAtLineAndColumn(line1, column1, var x1, var y1); GetTextCoordAtLineAndColumn(line2, column2, var x2, var y2); - mHilitePairedCharState = .Valid(stateHash, x1, y1, x2, y2, charWidth); + if (GetLineHeight(line2) > 0.1f) + mHilitePairedCharState = .Valid(stateHash, x1, y1, x2, y2, charWidth); } } } @@ -4880,5 +5383,486 @@ namespace IDE.ui } } } + + public void CollapseToggle() + { + CollapseEntry* foundEntry = null; + int foundIdx = -1; + + GetTextData(); + int line = CursorLineAndColumn.mLine; + for (var entry in mOrderedCollapseEntries) + { + if ((line >= entry.mAnchorLine) && (line <= entry.mEndLine)) + { + foundIdx = @entry.Index; + foundEntry = entry; + } + } + + if (foundEntry != null) + SetCollapseOpen(foundIdx, !foundEntry.mIsOpen); + } + + public void CollapseAll() + { + GetTextData(); + for (var entry in mOrderedCollapseEntries) + { + if (entry.mDeleted) + continue; + SetCollapseOpen(@entry.Index, false); + } + } + + public void CollapseToggleAll() + { + bool hasClosed = false; + + GetTextData(); + for (var entry in mOrderedCollapseEntries) + { + if (entry.mDeleted) + continue; + if (!entry.mIsOpen) + hasClosed = true; + } + + for (var entry in mOrderedCollapseEntries) + { + if (entry.mDeleted) + continue; + SetCollapseOpen(@entry.Index, hasClosed); + } + } + + public void CollapseToDefinition() + { + GetTextData(); + List hadDefs = scope .(); + hadDefs.Resize(mOrderedCollapseEntries.Count); + List collapseStack = scope .(); + int stackTypeCount = 0; + + for (var entry in mOrderedCollapseEntries) + { + if (entry.mDeleted) + continue; + + SourceEditWidgetContent.CollapseEntry* activeEntry = null; + + while (!collapseStack.IsEmpty) + { + activeEntry = mOrderedCollapseEntries[collapseStack.Back]; + if (entry.mAnchorLine < activeEntry.mEndLine) + break; + if (activeEntry.mKind == .Type) + stackTypeCount--; + collapseStack.PopBack(); + activeEntry = null; + } + + if (activeEntry != null) + { + if (activeEntry.mKind == .Type) + { + if ((entry.mKind != .Type) && (entry.mKind != .Region)) + { + SetCollapseOpen(@entry.Index, false); + } + } + } + + collapseStack.Add((.)@entry.Index); + + if (entry.mKind == .Type) + { + stackTypeCount++; + + for (var entryId in collapseStack) + { + var checkEntry = mOrderedCollapseEntries[entryId]; + if (!checkEntry.mIsOpen) + SetCollapseOpen(entryId, true); + } + } + + if (stackTypeCount == 0) + SetCollapseOpen(@entry.Index, false); + } + } + + + public void SetCollapseOpen(int collapseIdx, bool wantOpen, bool immediate = false) + { + var entry = mOrderedCollapseEntries[collapseIdx]; + + entry.mIsOpen = wantOpen; + if (immediate) + entry.mOpenPct = entry.mIsOpen ? 1.0f : 0.0f; + else + mCollapseNeedsUpdate = true; + + var cursorLineAndColumn = CursorLineAndColumn; + + if (wantOpen) + { + if (mEmbeds.GetValue(entry.mAnchorLine) case .Ok(let embed)) + { + if ((embed.mKind == .HideLine) || (embed.mKind == .LineEnd)) + { + delete embed; + mEmbeds.Remove(entry.mAnchorLine); + } + } + } + else if (immediate) + { + FinishCollapseClose(collapseIdx, entry); + } + + if ((!wantOpen) && (mSelection != null) && (mSelection.Value.MinPos >= entry.mStartIdx) && (mSelection.Value.MinPos <= entry.mEndIdx)) + { + if (mSelection.Value.MaxPos > entry.mEndIdx + 1) + mSelection = .(entry.mEndIdx + 1, mSelection.Value.MaxPos); + else + mSelection = null; + } + + if ((!wantOpen) && (cursorLineAndColumn.mLine >= entry.mStartLine) && (cursorLineAndColumn.mLine <= entry.mEndLine)) + { + if (CursorTextPos < entry.mEndIdx) + { + CursorLineAndColumn = .(entry.mAnchorLine, 0); + CursorToLineStart(false); + } + } + } + + public void RefreshCollapseRegion(SourceEditWidgetContent.CollapseEntry* entry, int32 prevAnchorLine, bool failed) + { + if (failed) + { + if (mEmbeds.GetAndRemove(prevAnchorLine) case .Ok(let val)) + delete val.value; + entry.mDeleted = true; + } + + if (prevAnchorLine != entry.mAnchorLine) + { + if (mEmbeds.GetAndRemove(prevAnchorLine) case .Ok(let val)) + { + if (mEmbeds.TryAdd(entry.mAnchorLine, var keyPtr, var valuePtr)) + *valuePtr = val.value; + else + delete val.value; + } + } + } + + public void RefreshCollapseRegions() + { + GetTextData(); + + var data = Data; + if (mCollapseTextVersionId == data.mCurTextVersionId) + return; + mCollapseTextVersionId = data.mCurTextVersionId; + data.mTextIdData.Prepare(); + + Stopwatch sw = scope .(); + sw.Start(); + + bool failed = false; + bool needsRefresh = true; + + IdSpan.LookupContext lookupCtx = scope .(data.mTextIdData); + + void Update(int32 id, ref int32 idx, ref int32 line) + { + idx = (.)lookupCtx.GetIndexFromId(id); + + if (idx == -1) + { + //Debug.WriteLine($"Failed! {lookupCtx}"); + + failed = true; + return; + } + GetLineCharAtIdx(idx, var tempLine, var lineChar); + line = (.)tempLine; + } + + for (var entry in mOrderedCollapseEntries) + { + if (entry.mDeleted) + { + if (mEmbeds.GetAndRemove(entry.mAnchorIdx) case .Ok(let val)) + delete val.value; + continue; + } + + int32 prevAnchorLine = entry.mAnchorLine; + + failed = false; + Update(entry.mAnchorId, ref entry.mAnchorIdx, ref entry.mAnchorLine); + Update(entry.mStartId, ref entry.mStartIdx, ref entry.mStartLine); + Update(entry.mEndId, ref entry.mEndIdx, ref entry.mEndLine); + + if (entry.mKind == .Region) + entry.mStartLine++; + + if (failed) + needsRefresh = true; + + RefreshCollapseRegion(entry, prevAnchorLine, failed); + } + + //Debug.WriteLine($"RefreshCollapseRegions Count:{mOrderedCollapseEntries.Count} Time:{sw.ElapsedMilliseconds}ms"); + sw.Stop(); + + if (needsRefresh) + RehupLineCoords(); + } + + public void ParseCollapseRegions(String collapseText, int32 textVersion, ref IdSpan idSpan) + { + IdSpan.LookupContext lookupCtx = scope .(idSpan); + + var data = PreparedData; + + data.mCollapseData.Clear(); + + for (var line in collapseText.Split('\n', .RemoveEmptyEntries)) + { + SourceEditWidgetContent.CollapseEntry.Kind kind = (.)line[0]; + line.RemoveFromStart(1); + + var itr = line.Split(','); + + CollapseData collapseData; + + collapseData.mAnchorIdx = int32.Parse(itr.GetNext().Value); + collapseData.mAnchorId = lookupCtx.GetIdAtIndex(collapseData.mAnchorIdx); + collapseData.mKind = kind; + collapseData.mStartIdx = int32.Parse(itr.GetNext().Value); + collapseData.mEndIdx = int32.Parse(itr.GetNext().Value); + + GetLineCharAtIdx(collapseData.mAnchorIdx, var line, var lineChar); + collapseData.mAnchorLine = (.)line; + + collapseData.mStartId = lookupCtx.GetIdAtIndex(collapseData.mStartIdx); + GetLineCharAtIdx(collapseData.mStartIdx, out line, out lineChar); + collapseData.mStartLine = (.)line; + + collapseData.mEndId = lookupCtx.GetIdAtIndex(collapseData.mEndIdx); + GetLineCharAtIdx(collapseData.mEndIdx, out line, out lineChar); + collapseData.mEndLine = (.)line; + + if ((collapseData.mAnchorId == -1) || (collapseData.mStartId == -1) || (collapseData.mEndId == -1)) + { + Debug.FatalError(); + continue; + } + + data.mCollapseData.Add(collapseData); + } + + data.mCollapseParseRevision++; + data.mCollapseTextVersionId = textVersion; + } + + public void FinishCollapseClose(int collapseIdx, SourceEditWidgetContent.CollapseEntry* entry) + { + if (entry.mAnchorLine == entry.mStartLine) + { + if (mEmbeds.TryAdd(entry.mAnchorLine, var keyPtr, var valuePtr)) + { + SourceEditWidgetContent.CollapseSummary collapseSummary = new .(); + collapseSummary.mEditWidgetContent = this; + collapseSummary.mCollapseIndex = (.)collapseIdx; + collapseSummary.mKind = .HideLine; + *valuePtr = collapseSummary; + + var content = ExtractString(entry.mStartIdx, Math.Min(entry.mEndId - entry.mStartIdx + 1, 8192), .. scope .()); + content.Trim(); + + collapseSummary.mHideString = new .(); + + if (content.StartsWith("/*")) + collapseSummary.mHideString.Append("/* "); + else if (content.StartsWith("//")) + collapseSummary.mHideString.Append("// "); + + bool hadChar = false; + for (int i < content.Length) + { + char8 c = content[i]; + if (c.IsWhiteSpace) + { + if (hadChar) + break; + } + else + { + if ((c != '/') && (c != '*')) + hadChar = true; + } + + if (hadChar) + collapseSummary.mHideString.Append(c); + + if (collapseSummary.mHideString.Length > 32) + break; + } + } + } + else + { + if (mEmbeds.TryAdd(entry.mAnchorLine, var keyPtr, var valuePtr)) + { + SourceEditWidgetContent.CollapseSummary collapseSummary = new .(); + collapseSummary.mEditWidgetContent = this; + collapseSummary.mCollapseIndex = (.)collapseIdx; + collapseSummary.mKind = .LineEnd; + *valuePtr = collapseSummary; + } + } + } + + public void UpdateCollapse() + { + MarkDirty(); + + var data = Data; + + bool hasMultiAnim = false; + int animIdx = -1; + List animLines = scope .(1024); + + for (var entry in mOrderedCollapseEntries) + { + if ((entry.mIsOpen) && (entry.mOpenPct < 1.0f)) + { + if (animIdx != -1) + hasMultiAnim = true; + animIdx = @entry.Index; + } + + if ((!entry.mIsOpen) && (entry.mOpenPct > 0)) + { + if (animIdx != -1) + hasMultiAnim = true; + animIdx = @entry.Index; + } + } + + if (hasMultiAnim) + { + for (var entry in mOrderedCollapseEntries) + { + if (entry.mIsOpen) + entry.mOpenPct = 1.0f; + else + { + if (entry.mOpenPct > 0) + { + entry.mOpenPct = 0.0f; + FinishCollapseClose(@entry.Index, entry); + } + } + } + + animIdx = -1; + } + + if (animIdx == -1) + { + RehupLineCoords(); + mCollapseNeedsUpdate = false; + return; + } + + List collapseStack = scope .(16); + int32 curIdx = 0; + bool inAnim = false; + int32 insideCloseDepth = 0; + for (int line < data.mLineStarts.Count) + { + bool deferClose = false; + + while (curIdx < mOrderedCollapseEntries.Count) + { + var entry = mOrderedCollapseEntries[curIdx]; + if (entry.mAnchorLine > line) + break; + if (!entry.mDeleted) + { + if ((inAnim) && (!entry.mIsOpen)) + { + if ((insideCloseDepth == 0) && (entry.mAnchorLine == entry.mStartLine)) + deferClose = true; + else + insideCloseDepth++; + } + collapseStack.Add(curIdx); + } + curIdx++; + } + + if ((inAnim) && (insideCloseDepth == 0)) + animLines.Add((.)line); + + if (deferClose) + insideCloseDepth++; + + while (!collapseStack.IsEmpty) + { + int32 activeIdx = collapseStack.Back; + var entry = mOrderedCollapseEntries[activeIdx]; + if (line == entry.mStartLine) + { + if ((inAnim) && (!entry.mIsOpen)) + insideCloseDepth++; + if (activeIdx == animIdx) + { + if (entry.mStartLine != entry.mAnchorLine) + animLines.Add((.)line); + inAnim = true; + } + } + if (line < entry.mEndLine) + break; + if ((!entry.mIsOpen) && (insideCloseDepth > 0)) + insideCloseDepth--; + if (activeIdx == animIdx) + inAnim = false; + collapseStack.PopBack(); + } + } + + float animSpeed = Math.Clamp(0.2f - Math.Pow(animLines.Count / 4000.0f, 0.6f), 0.065f, 0.15f); + + //animSpeed *= 0.01f; + + //Debug.WriteLine($"Lines: {animLines.Count} AnimSpeed: {animSpeed}"); + + var entry = mOrderedCollapseEntries[animIdx]; + if ((entry.mIsOpen) && (entry.mOpenPct < 1.0f)) + { + entry.mOpenPct = Math.Min(entry.mOpenPct + animSpeed, 1.0f); + } + + if ((!entry.mIsOpen) && (entry.mOpenPct > 0)) + { + entry.mOpenPct = Math.Max(entry.mOpenPct - animSpeed, 0.0f); + + if (entry.mOpenPct == 0.0f) + FinishCollapseClose(animIdx, entry); + } + + RehupLineCoords(animIdx, animLines); + } + } } diff --git a/IDE/src/ui/SourceViewPanel.bf b/IDE/src/ui/SourceViewPanel.bf index 04a11c19..e2c1782c 100644 --- a/IDE/src/ui/SourceViewPanel.bf +++ b/IDE/src/ui/SourceViewPanel.bf @@ -165,7 +165,7 @@ namespace IDE.ui public ~this() { - NOP!(); + } } @@ -300,6 +300,35 @@ namespace IDE.ui public int32 mDebuggerContinueIdx; } + class CollapseRegionView + { + public const uint32 cStartFlag = 0x8000'0000; + public const uint32 cMidFlag = 0x4000'0000; + public const uint32 cEndFlag = 0x2000'0000; + public const uint32 cIdMask = 0x0FFF'FFFF; + + public List mCollapseIndices = new .() ~ delete _; + public int32 mLineStart; + public int32 mCollapseRevision; + public int32 mTextVersionId; + + public uint32 GetCollapseValue(int param) + { + if (param < mLineStart) + return 0; + if (param - mLineStart < mCollapseIndices.Count) + return mCollapseIndices[param - mLineStart]; + return 0; + } + } + + class QueuedCollapseData + { + public String mData = new .() ~ delete _; + public int32 mTextVersion; + public IdSpan mCharIdSpan ~ _.Dispose(); + } + public class SourceViewPanel : TextPanel { enum SourceDisplayId @@ -349,10 +378,13 @@ namespace IDE.ui int32 mTicksSinceTextChanged; int32 mErrorLookupTextIdx = -1; LinePointerDrawData mLinePointerDrawData; + Point? mMousePos; #if IDE_C_SUPPORT public String mClangHoverErrorData ~ delete mClangHoverErrorData; #endif - + CollapseRegionView mCollapseRegionView = new .() ~ delete _; + QueuedCollapseData mQueuedCollapseData ~ delete _; + public EditWidgetContent.CharData[] mProcessSpellCheckCharData ~ delete _; public IdSpan mProcessSpellCheckCharIdSpan ~ _.Dispose(); //public int mBackgroundCursorIdx; @@ -1785,18 +1817,18 @@ namespace IDE.ui parser = bfSystem.CreateEmptyParser((BfProject)null); } - EditWidgetContent.CharData[] char8Data = null; - int char8Len = 0; + EditWidgetContent.CharData[] charData = null; + int charLen = 0; if ((resolveParams != null) && (resolveParams.mCharData != null)) { - char8Data = resolveParams.mCharData; - char8Len = resolveParams.mCharData.Count; + charData = resolveParams.mCharData; + charLen = resolveParams.mCharData.Count; } - if (char8Data == null) + if (charData == null) { Debug.Assert(!isBackground); - char8Data = mEditWidget.Content.mData.mText; - char8Len = mEditWidget.Content.mData.mTextLength; + charData = mEditWidget.Content.mData.mText; + charLen = mEditWidget.Content.mData.mTextLength; } /*var char8Data = (!isBackground) ? mEditWidget.Content.mData.mText : mProcessResolveCharData; @@ -1818,12 +1850,12 @@ namespace IDE.ui if (!isBackground) bfSystem.PerfZoneStart("DoClassify.CreateChars"); - char8[] chars = new char8[char8Len]; + char8[] chars = new char8[charLen]; defer delete chars; - for (int32 i = 0; i < char8Len; i++) + for (int32 i = 0; i < charLen; i++) { - char8Data[i].mDisplayPassId = (int32)SourceDisplayId.Cleared; - chars[i] = (char8)char8Data[i].mChar; + charData[i].mDisplayPassId = (int32)SourceDisplayId.Cleared; + chars[i] = (char8)charData[i].mChar; } String text = scope String(); @@ -1933,7 +1965,7 @@ namespace IDE.ui if ((!isFastClassify) && (bfCompiler != null)) { - if (!bfCompiler.ClassifySource(passInstance, parser, resolvePassData, char8Data)) + if (!bfCompiler.ClassifySource(passInstance, parser, resolvePassData, charData)) { //DeleteAndNullify!(mProcessResolveCharData); //mProcessResolveCharIdSpan.Dispose(); @@ -1945,10 +1977,23 @@ namespace IDE.ui } bfCompiler.QueueDeferredResolveAll(); } + + if ((resolveType == ResolveType.Classify) || (resolveType == ResolveType.ClassifyFullRefresh)) + { + var collapseData = bfCompiler.GetCollapseRegions(parser, .. scope .()); + using (mMonitor.Enter()) + { + DeleteAndNullify!(mQueuedCollapseData); + mQueuedCollapseData = new .(); + mQueuedCollapseData.mData.Set(collapseData); + mQueuedCollapseData.mTextVersion = resolveParams.mTextVersion; + mQueuedCollapseData.mCharIdSpan = resolveParams.mCharIdSpan.Duplicate(); + } + } } else { - parser.ClassifySource(char8Data, !mIsBeefSource); + parser.ClassifySource(charData, !mIsBeefSource); } if (!isBackground) @@ -3637,6 +3682,18 @@ namespace IDE.ui AddWidget(mSplitTopPanel); + var ewc = (SourceEditWidgetContent)mEditWidget.mEditWidgetContent; + var topEWC = (SourceEditWidgetContent)mSplitTopPanel.mEditWidget.mEditWidgetContent; + + for (var entry in ewc.mOrderedCollapseEntries) + { + if (!entry.mIsOpen) + { + topEWC.SetCollapseOpen(@entry.Index, false, true); + } + } + topEWC.RehupLineCoords(); + ResizeComponents(); // Match scroll positions @@ -4106,8 +4163,9 @@ namespace IDE.ui if (mLoadFailed) return; - DarkEditWidgetContent darkEditWidgetContent = (DarkEditWidgetContent)mEditWidget.Content; - + var ewc = (SourceEditWidgetContent)mEditWidget.Content; + ewc.GetTextData(); + g.SetFont(IDEApp.sApp.mTinyCodeFont); using (g.PushClip(0, mEditWidget.mY, mWidth, mEditWidget.mHeight - GS!(20))) @@ -4115,13 +4173,93 @@ namespace IDE.ui using (g.PushTranslate(0, mEditWidget.mY + mEditWidget.Content.Y + GS!(2))) { float editX = GetEditX(); - float lineSpacing = darkEditWidgetContent.mFont.GetLineSpacing(); - int cursorLineNumber = mEditWidget.mEditWidgetContent.CursorLineAndColumn.mLine; - using (g.PushColor(gApp.mSettings.mUISettings.mColors.mCurrentLineNumberHilite)) - g.FillRect(0, GS!(2) + cursorLineNumber * lineSpacing, editX - GS!(2), lineSpacing); + float leftAdjust = GS!(12); + + float lineSpacing = ewc.mFont.GetLineSpacing(); + int cursorLineNumber = mEditWidget.mEditWidgetContent.CursorLineAndColumn.mLine; + bool hiliteCurrentLine = true; + + var jumpEntry = ewc.mLineCoordJumpTable[Math.Clamp((int)(-mEditWidget.Content.Y / ewc.GetJumpCoordSpacing()), 0, ewc.mLineCoordJumpTable.Count - 1)]; + int lineStart = jumpEntry.min; + jumpEntry = ewc.mLineCoordJumpTable[Math.Clamp((int)((-mEditWidget.Content.Y + mHeight) / ewc.GetJumpCoordSpacing()), 0, ewc.mLineCoordJumpTable.Count - 1)]; + int lineEnd = jumpEntry.max - 1; + + int drawLineCount = lineEnd - lineStart + 1; + + ewc.RefreshCollapseRegions(); + if ((mCollapseRegionView.mLineStart != lineStart) || (mCollapseRegionView.mCollapseIndices.Count != drawLineCount) || + (mCollapseRegionView.mCollapseRevision != ewc.mCollapseParseRevision) || (mCollapseRegionView.mTextVersionId != ewc.mCollapseTextVersionId)) + { + mCollapseRegionView.mLineStart = (.)lineStart; + mCollapseRegionView.mCollapseIndices.Clear(); + Internal.MemSet(mCollapseRegionView.mCollapseIndices.GrowUnitialized(drawLineCount), 0, drawLineCount * sizeof(int32)); + mCollapseRegionView.mCollapseRevision = ewc.mCollapseParseRevision; + mCollapseRegionView.mTextVersionId = ewc.mCollapseTextVersionId; + + List collapseStack = scope .(16); + int32 curIdx = 0; + for (int line in lineStart...lineEnd) + { + uint32 indexVal = 0; + + while (curIdx < ewc.mOrderedCollapseEntries.Count) + { + var entry = ewc.mOrderedCollapseEntries[curIdx]; + if (entry.mAnchorLine > line) + break; + if (!entry.mDeleted) + { + indexVal = (uint32)curIdx | CollapseRegionView.cStartFlag; + collapseStack.Add(curIdx); + } + curIdx++; + } + + while (!collapseStack.IsEmpty) + { + var entry = ewc.mOrderedCollapseEntries[collapseStack.Back]; + if (line < entry.mEndLine) + break; + if (indexVal == 0) + indexVal = (uint32)collapseStack.Back | CollapseRegionView.cEndFlag; + collapseStack.PopBack(); + } + + if ((indexVal == 0) && (!collapseStack.IsEmpty)) + indexVal = (uint32)collapseStack.Back | CollapseRegionView.cMidFlag; + mCollapseRegionView.mCollapseIndices[line - lineStart] = indexVal; + + } + } + + if ((mMousePos != null) && (mMousePos.Value.x >= mEditWidget.mX - GS!(13)) && (mMousePos.Value.x < mEditWidget.mX - GS!(0))) + { + int lineClick = GetLineAt(0, mMousePos.Value.y); + uint32 collapseVal = mCollapseRegionView.GetCollapseValue(lineClick); + if (collapseVal != 0) + { + var entry = ewc.mOrderedCollapseEntries[collapseVal & CollapseRegionView.cIdMask]; + float startY = ewc.mLineCoords[entry.mAnchorLine]; + float endY = ewc.mLineCoords[entry.mEndLine + 1]; + + using (g.PushColor(gApp.mSettings.mUISettings.mColors.mCurrentLineNumberHilite)) + g.FillRect(0, GS!(2) + startY, editX - GS!(10), endY - startY); + + hiliteCurrentLine = false; + } + } + + if (hiliteCurrentLine) + { + using (g.PushColor(gApp.mSettings.mUISettings.mColors.mCurrentLineNumberHilite)) + { + int hiliteLineNum = cursorLineNumber; + while (ewc.IsLineCollapsed(hiliteLineNum)) + hiliteLineNum--; + g.FillRect(0, GS!(2) + ewc.mLineCoords[hiliteLineNum], editX - GS!(10), lineSpacing); + } + } - int lineStart = (int)((-mEditWidget.Content.Y) / lineSpacing) - 1; - int lineEnd = Math.Min(darkEditWidgetContent.GetLineCount(), lineStart + (int)(mHeight / lineSpacing) + 3); if (lineEnd <= lineStart) { return; @@ -4144,8 +4282,8 @@ namespace IDE.ui int breakpointCount = (.)(curLineFlags & .BreakpointCountMask); curLineFlags++; - float iconX = Math.Max(GS!(-2), mEditWidget.mX - GS!(24)) + breakpointCount*-GS!(2); - float iconY = 0 + drawLineNum * lineSpacing + (lineSpacing - DarkTheme.sUnitSize + GS!(5)) / 2; + float iconX = Math.Max(GS!(-2), mEditWidget.mX - GS!(24) - leftAdjust) + breakpointCount*-GS!(2); + float iconY = 0 + ewc.mLineCoords[drawLineNum] + (lineSpacing - DarkTheme.sUnitSize + GS!(5)) / 2; // Just leave last digit visible /*using (g.PushColor(0xFF595959)) @@ -4166,8 +4304,8 @@ namespace IDE.ui if ((drawLineNum < lineStart) || (drawLineNum >= lineEnd)) continue; //hadLineIcon[drawLineNum - lineStart] = true; - g.Draw(DarkTheme.sDarkTheme.GetImage(.IconBookmark), Math.Max(GS!(-5), mEditWidget.mX - GS!(30)), - 0 + bookmark.mLineNum * lineSpacing); + g.Draw(DarkTheme.sDarkTheme.GetImage(.IconBookmark), Math.Max(GS!(-5), mEditWidget.mX - GS!(30) - leftAdjust), + 0 + bookmark.mLineNum * lineSpacing); var curLineFlags = ref lineFlags[drawLineNum - lineStart]; curLineFlags |= .Boomkmark; @@ -4199,6 +4337,10 @@ namespace IDE.ui { for (int lineIdx = lineStart; lineIdx < lineEnd; lineIdx++) { + float drawHeight = ewc.mLineCoords[lineIdx + 1] - ewc.mLineCoords[lineIdx]; + if (drawHeight < lineSpacing * 0.25f) + continue; + lineStr.Clear(); int maxLineChars = Int32.MaxValue; @@ -4216,10 +4358,46 @@ namespace IDE.ui case 2: lineStr.AppendF("{0}", (lineIdx + 1) % 100); default: lineStr.AppendF("{0}", lineIdx + 1); } - g.DrawString(lineStr, 0, GS!(2) + lineIdx * lineSpacing, FontAlign.Right, editX - GS!(2)); + g.DrawString(lineStr, 0, GS!(2) + ewc.mLineCoords[lineIdx], FontAlign.Right, editX - GS!(14)); } } } + + for (int lineIdx = lineStart; lineIdx < lineEnd; lineIdx++) + { + int collapseLookup = lineIdx - mCollapseRegionView.mLineStart; + if ((collapseLookup >= 0) && (collapseLookup < mCollapseRegionView.mCollapseIndices.Count)) + { + float drawHeight = ewc.mLineCoords[lineIdx + 1] - ewc.mLineCoords[lineIdx]; + if (drawHeight < lineSpacing * 0.25f) + continue; + + float boxAdjustTop = Math.Floor((lineSpacing - DarkTheme.sUnitSize)/2); + float boxAdjustBot = Math.Ceiling((lineSpacing - DarkTheme.sUnitSize)/2); + + uint32 collapseIdx = mCollapseRegionView.mCollapseIndices[lineIdx - mCollapseRegionView.mLineStart]; + if ((collapseIdx & CollapseRegionView.cStartFlag) != 0) + { + var entry = ewc.mOrderedCollapseEntries[collapseIdx & CollapseRegionView.cIdMask]; + g.Draw(DarkTheme.sDarkTheme.GetImage(entry.mIsOpen ? .CollapseOpened : .CollapseClosed), editX - GS!(16), ewc.mLineCoords[lineIdx] + boxAdjustTop + GS!(2)); + } + else if ((collapseIdx & CollapseRegionView.cEndFlag) != 0) + { + using (g.PushColor(0xFFA5A5A5)) + { + g.FillRect(editX - (int)GS!(7.5f), ewc.mLineCoords[lineIdx] - (int)GS!(0.5f), (int)GS!(1.5f), lineSpacing); + g.FillRect(editX - (int)GS!(7.5f), ewc.mLineCoords[lineIdx] + lineSpacing - (int)GS!(1.5f), GS!(5), (int)GS!(1.5f)); + } + } + else if (collapseIdx != 0) + { + using (g.PushColor(0xFFA5A5A5)) + { + g.FillRect(editX - (int)GS!(7.5f), ewc.mLineCoords[lineIdx] - boxAdjustBot - GS!(5), (int)GS!(1.5f), lineSpacing + boxAdjustBot + boxAdjustTop + (int)GS!(12f)); + } + } + } + } if (IDEApp.sApp.mExecutionPaused) { @@ -5255,6 +5433,11 @@ namespace IDE.ui Point mousePos; bool mouseoverFired = DarkTooltipManager.CheckMouseover(editWidgetContent, 10, out mousePos); + if (mouseoverFired) + { + + } + #unwarn CompilerBase compiler = ResolveCompiler; @@ -5571,26 +5754,29 @@ namespace IDE.ui if (checkIdx >= mDeferredResolveResults.Count) break; resolveResult = mDeferredResolveResults[checkIdx]; - if ((autocompleteOnly) && (resolveResult.mResolveType != .Autocomplete)) - { - checkIdx++; - continue; - } - - if (!resolveResult.mWaitEvent.WaitFor(0)) - { - if (waitTime != 0) - ResolveCompiler.RequestFastFinish(); - } - - if (!resolveResult.mWaitEvent.WaitFor(waitTime)) - { - checkIdx++; - continue; - } - mDeferredResolveResults.RemoveAt(checkIdx); } + if ((autocompleteOnly) && (resolveResult.mResolveType != .Autocomplete)) + { + checkIdx++; + continue; + } + + if (!resolveResult.mWaitEvent.WaitFor(0)) + { + if (waitTime != 0) + ResolveCompiler.RequestFastFinish(); + } + + if (!resolveResult.mWaitEvent.WaitFor(waitTime)) + { + checkIdx++; + continue; + } + + using (mMonitor.Enter()) + mDeferredResolveResults.RemoveAt(checkIdx); + //Debug.WriteLine($"HandleResolveResult {resolveResult}"); HandleResolveResult(resolveResult.mResolveType, resolveResult.mAutocompleteInfo, resolveResult); @@ -6127,8 +6313,6 @@ namespace IDE.ui DeleteAndNullify!(mQueuedAutoComplete); } - ProcessDeferredResolveResults(0); - if (mLockFlashPct != 0) { mLockFlashPct += 0.02f; @@ -6139,6 +6323,20 @@ namespace IDE.ui if ((mEmitRevision >= 0) && ((mUpdateCnt % 30) == 0)) CheckEmitRevision(); + + var ewc = (SourceEditWidgetContent)mEditWidget.Content; + using (mMonitor.Enter()) + { + if (mQueuedCollapseData != null) + ewc.ParseCollapseRegions(mQueuedCollapseData.mData, mQueuedCollapseData.mTextVersion, ref mQueuedCollapseData.mCharIdSpan); + DeleteAndNullify!(mQueuedCollapseData); + } + + if (ewc.mCollapseNeedsUpdate) + ewc.UpdateCollapse(); + + // Process after mQueuedCollapseData so mCharIdSpan is still valid + ProcessDeferredResolveResults(0); } void InjectErrors(BfPassInstance processingPassInstance, EditWidgetContent.CharData[] processResolveCharData, IdSpan processCharIdSpan, bool keepPersistentErrors) @@ -6267,7 +6465,7 @@ namespace IDE.ui var font = IDEApp.sApp.mTinyCodeFont; - float lineWidth = Math.Max(font.GetWidth(ToStackString!(mEditWidget.Content.GetLineCount())) + GS!(8), GS!(32)); + float lineWidth = Math.Max(font.GetWidth(ToStackString!(mEditWidget.Content.GetLineCount())) + GS!(24), GS!(32)); return Math.Max(GS!(24), lineWidth); } @@ -6285,7 +6483,7 @@ namespace IDE.ui } // Always leave enough to read the first 3 lines - if (mHeight < GS!(88)) + if ((mHeight < GS!(88)) && (mSplitBottomPanel == null)) mHeight = GS!(88); float splitterHeight = GS!(3); @@ -6367,6 +6565,30 @@ namespace IDE.ui { base.MouseDown(x, y, btn, btnCount); + var ewc = (SourceEditWidgetContent)mEditWidget.Content; + + if ((btn == 0) && (x >= mEditWidget.mX - GS!(13)) && (x < mEditWidget.mX - GS!(0))) + { + int lineClick = GetLineAt(0, y); + if (lineClick >= mCollapseRegionView.mLineStart) + { + int relLine = lineClick - mCollapseRegionView.mLineStart; + if (relLine < mCollapseRegionView.mCollapseIndices.Count) + { + uint32 collapseVal = mCollapseRegionView.mCollapseIndices[relLine]; + if ((((collapseVal & CollapseRegionView.cStartFlag) != 0) && (btnCount == 1)) || + (btnCount > 1)) + { + int collapseIndex = collapseVal & CollapseRegionView.cIdMask; + + var entry = ewc.mOrderedCollapseEntries[collapseIndex]; + ewc.SetCollapseOpen(collapseIndex, !entry.mIsOpen); + } + return; + } + } + } + if (mSplitBottomPanel != null) { return; @@ -6438,12 +6660,15 @@ namespace IDE.ui if (x > mEditWidget.mX - GS!(4)) return -1; - DarkEditWidgetContent darkEditWidgetContent = (DarkEditWidgetContent)mEditWidget.Content; - float lineSpacing = darkEditWidgetContent.mFont.GetLineSpacing(); + var ewc = (SourceEditWidgetContent)mEditWidget.Content; + float relY = y - mEditWidget.mY - mEditWidget.Content.Y - GS!(3); if (relY < 0) return -1; - return (int)(relY / lineSpacing); + int resultIdx = ewc.mLineCoords.BinarySearch(relY); + if (resultIdx < 0) + return ~resultIdx - 1; + return resultIdx; } public bool SelectBreakpointsAtLine(int selectLine) @@ -6481,7 +6706,7 @@ namespace IDE.ui if (btn == 0) { - if ((x >= GS!(3)) && (x < mEditWidget.mX - GS!(8))) + if ((x >= GS!(3)) && (x < mEditWidget.mX - GS!(14))) { int lineClick = GetLineAt(x, y); if (lineClick >= 0) @@ -6503,6 +6728,18 @@ namespace IDE.ui } } + public override void MouseLeave() + { + base.MouseLeave(); + mMousePos = null; + } + + public override void MouseMove(float x, float y) + { + base.MouseMove(x, y); + mMousePos = .(x, y); + } + public override void DrawAll(Graphics g) { DarkEditWidgetContent darkEditWidgetContent = (DarkEditWidgetContent)mEditWidget.Content; @@ -6623,5 +6860,6 @@ namespace IDE.ui } } } + } } diff --git a/IDEHelper/Compiler/BfCompiler.cpp b/IDEHelper/Compiler/BfCompiler.cpp index 90b5f9fd..cc14c865 100644 --- a/IDEHelper/Compiler/BfCompiler.cpp +++ b/IDEHelper/Compiler/BfCompiler.cpp @@ -9151,6 +9151,272 @@ BF_EXPORT bool BF_CALLTYPE BfCompiler_ClassifySource(BfCompiler* bfCompiler, BfP return !canceled; } +BF_EXPORT const char* BF_CALLTYPE BfCompiler_GetCollapseRegions(BfCompiler* bfCompiler, BfParser* bfParser) +{ + String& outString = *gTLStrReturn.Get(); + outString.Clear(); + + class CollapseVisitor : public BfElementVisitor + { + public: + BfParser* mParser; + String& mOutString; + HashSet mStartsFound; + char mSeriesKind; + int mStartSeriesIdx; + int mEndSeriesIdx; + + public: + CollapseVisitor(BfParser* parser, String& string) : mOutString(string) + { + mParser = parser; + + mSeriesKind = 0; + mStartSeriesIdx = -1; + mEndSeriesIdx = -1; + } + + void UpdateSeries(BfAstNode* node, char kind) + { + if (mStartSeriesIdx != -1) + { + if ((node->mTriviaStart != mEndSeriesIdx + 1) || (kind != mSeriesKind)) + { + // Flush + ConditionalAdd(mStartSeriesIdx, mStartSeriesIdx, mEndSeriesIdx, mSeriesKind); + mStartSeriesIdx = node->mSrcStart; + } + } + else + mStartSeriesIdx = node->mSrcStart; + + mSeriesKind = kind; + mEndSeriesIdx = node->mSrcEnd - 1; + } + + void FlushSeries() + { + if (mStartSeriesIdx != -1) + ConditionalAdd(mStartSeriesIdx, mStartSeriesIdx, mEndSeriesIdx, mSeriesKind); + mStartSeriesIdx = -1; + } + + void ConditionalAdd(int anchor, int start, int end, char kind = '?') + { + bool isMultiline = false; + for (int i = start; i < end; i++) + { + if (mParser->mSrc[i] == '\n') + { + isMultiline = true; + break; + } + } + if (!isMultiline) + return; + char str[1024]; + sprintf(str, "%c%d,%d,%d\n", kind, anchor, start, end); + mOutString.Append(str); + } + + void Add(BfAstNode* anchor, BfAstNode* start, BfAstNode* end, char kind = '?') + { + if ((anchor == NULL) || (start == NULL) || (end == NULL)) + return; + if (!mStartsFound.Add(start->mSrcStart)) + return; + char str[1024]; + sprintf(str, "%c%d,%d,%d\n", kind, anchor->mSrcStart, start->mSrcStart, end->mSrcStart); + mOutString.Append(str); + } + + void Add(BfAstNode* anchor, BfAstNode* body, char kind = '?') + { + if (auto block = BfNodeDynCast(body)) + { + Add(anchor, block->mOpenBrace, block->mCloseBrace, kind); + } + } + + virtual void Visit(BfMethodDeclaration* methodDeclaration) override + { + BfAstNode* anchorNode = methodDeclaration->mNameNode; + if (auto ctorDeclaration = BfNodeDynCast(methodDeclaration)) + anchorNode = ctorDeclaration->mThisToken; + if (auto dtorDeclaration = BfNodeDynCast(methodDeclaration)) + anchorNode = dtorDeclaration->mThisToken; + if (auto propertyDeclaration = BfNodeDynCast(methodDeclaration)) + anchorNode = propertyDeclaration->mOperatorToken; + Add(anchorNode, methodDeclaration->mBody, 'M'); + + BfElementVisitor::Visit(methodDeclaration); + } + + virtual void Visit(BfNamespaceDeclaration* namespaceDeclaration) override + { + Add(namespaceDeclaration->mNamespaceNode, namespaceDeclaration->mBlock, 'N'); + + BfElementVisitor::Visit(namespaceDeclaration); + } + + virtual void Visit(BfUsingDirective* usingDirective) override + { + UpdateSeries(usingDirective, 'U'); + + BfElementVisitor::Visit(usingDirective); + } + + virtual void Visit(BfTypeDeclaration* typeDeclaration) override + { + BfAstNode* anchor = typeDeclaration->mNameNode; + if (anchor == NULL) + anchor = typeDeclaration->mStaticSpecifier; + Add(anchor, typeDeclaration->mDefineNode, 'T'); + + BfElementVisitor::Visit(typeDeclaration); + } + + virtual void Visit(BfPropertyDeclaration* properyDeclaration) override + { + Add(properyDeclaration->mNameNode, properyDeclaration->mDefinitionBlock, 'P'); + + BfElementVisitor::Visit(properyDeclaration); + } + + virtual void Visit(BfIndexerDeclaration* indexerDeclaration) override + { + Add(indexerDeclaration->mThisToken, indexerDeclaration->mDefinitionBlock, 'P'); + + BfElementVisitor::Visit(indexerDeclaration); + } + + virtual void Visit(BfPropertyMethodDeclaration* methodDeclaration) override + { + Add(methodDeclaration->mNameNode, methodDeclaration->mBody); + + BfElementVisitor::Visit(methodDeclaration); + } + + virtual void Visit(BfIfStatement* ifStatement) override + { + Add(ifStatement->mIfToken, ifStatement->mTrueStatement); + Add(ifStatement->mElseToken, ifStatement->mFalseStatement); + + BfElementVisitor::Visit(ifStatement); + } + + virtual void Visit(BfRepeatStatement* repeatStatement) override + { + Add(repeatStatement->mRepeatToken, repeatStatement->mEmbeddedStatement); + + BfElementVisitor::Visit(repeatStatement); + } + + virtual void Visit(BfDoStatement* doStatement) override + { + Add(doStatement->mDoToken, doStatement->mEmbeddedStatement); + + BfElementVisitor::Visit(doStatement); + } + + virtual void Visit(BfForStatement* forStatement) override + { + Add(forStatement->mForToken, forStatement->mEmbeddedStatement); + + BfElementVisitor::Visit(forStatement); + } + + virtual void Visit(BfForEachStatement* forStatement) override + { + Add(forStatement->mForToken, forStatement->mEmbeddedStatement); + + BfElementVisitor::Visit(forStatement); + } + + virtual void Visit(BfUsingStatement* usingStatement) override + { + Add(usingStatement->mUsingToken, usingStatement->mEmbeddedStatement); + + BfElementVisitor::Visit(usingStatement); + } + + virtual void Visit(BfSwitchStatement* switchStatement) override + { + Add(switchStatement->mSwitchToken, switchStatement->mOpenBrace, switchStatement->mCloseBrace); + + BfElementVisitor::Visit(switchStatement); + } + + virtual void Visit(BfLambdaBindExpression* lambdaExpression) override + { + Add(lambdaExpression->mFatArrowToken, lambdaExpression->mBody); + + BfElementVisitor::Visit(lambdaExpression); + } + + virtual void Visit(BfBlock* block) override + { + Add(block->mOpenBrace, block->mOpenBrace, block->mCloseBrace); + + BfElementVisitor::Visit(block); + } + }; + + CollapseVisitor collapseVisitor(bfParser, outString); + collapseVisitor.VisitChild(bfParser->mRootNode); + + BfAstNode* regionStart = NULL; + BfPreprocessorNode* prevPreprocessorNode = NULL; + int ignoredSectionStart = -1; + + for (auto element : bfParser->mSidechannelRootNode->mChildArr) + { + if (auto preprocessorNode = BfNodeDynCast(element)) + { + if ((ignoredSectionStart != -1) && (prevPreprocessorNode != NULL) && (prevPreprocessorNode->mCommand != NULL)) + { + collapseVisitor.ConditionalAdd(prevPreprocessorNode->mCommand->mSrcStart, ignoredSectionStart, preprocessorNode->mSrcEnd - 1); + ignoredSectionStart = -1; + } + + StringView sv = preprocessorNode->mCommand->ToStringView(); + if (sv == "region") + regionStart = preprocessorNode->mCommand; + else if (sv == "endregion") + { + collapseVisitor.Add(regionStart, regionStart, preprocessorNode->mCommand, 'R'); + regionStart = NULL; + } + + prevPreprocessorNode = preprocessorNode; + } + + if (auto preprocessorNode = BfNodeDynCast(element)) + { + if (ignoredSectionStart == -1) + { + for (int i = preprocessorNode->mSrcStart; i < preprocessorNode->mSrcEnd - 1; i++) + { + if (bfParser->mSrc[i] == '\n') + { + ignoredSectionStart = i + 1; + break; + } + } + } + } + + char kind = 0; + if (auto commentNode = BfNodeDynCast(element)) + collapseVisitor.UpdateSeries(commentNode, 'C'); + } + + collapseVisitor.FlushSeries(); + + + return outString.c_str(); +} + BF_EXPORT bool BF_CALLTYPE BfCompiler_VerifyTypeName(BfCompiler* bfCompiler, char* name, int cursorPos) { String typeName = name;