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 27ef0572..07f5a7dc 100644 Binary files a/IDE/dist/images/DarkUI.png and b/IDE/dist/images/DarkUI.png differ diff --git a/IDE/dist/images/DarkUI.psd b/IDE/dist/images/DarkUI.psd index 39869f84..1caa884f 100644 Binary files a/IDE/dist/images/DarkUI.psd and b/IDE/dist/images/DarkUI.psd differ diff --git a/IDE/dist/images/DarkUI_2.png b/IDE/dist/images/DarkUI_2.png index 295c6d6e..e80b9d0c 100644 Binary files a/IDE/dist/images/DarkUI_2.png and b/IDE/dist/images/DarkUI_2.png differ diff --git a/IDE/dist/images/DarkUI_4.png b/IDE/dist/images/DarkUI_4.png index 4bfcf50f..f114f6ef 100644 Binary files a/IDE/dist/images/DarkUI_4.png and b/IDE/dist/images/DarkUI_4.png differ 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;