using System; using System.Collections; using System.Text; using System.Threading.Tasks; using System.Diagnostics; using Beefy; using Beefy.gfx; using Beefy.events; using Beefy.widgets; using Beefy.theme.dark; using Beefy.geom; using Beefy.utils; namespace IDE.ui { class DocumentationParser { public String mDocString = new String(256) ~ delete _; public String mBriefString ~ delete _; public Dictionary mParamInfo ~ DeleteDictionaryAndKeysAndItems!(_); public String ShowDocString { get { return mBriefString ?? mDocString; } } public this(StringView info) { bool atLineStart = true; bool lineHadStar = false; int blockDepth = 0; bool queuedSpace = false; bool docStringDone = false; bool lineHadContent = false; String curDocStr = null; for (int idx = 0; idx < info.Length; idx++) { char8 c = info[idx]; char8 nextC = 0; if (idx < info.Length - 1) nextC = info[idx + 1]; if ((c == '/') && (nextC == '*')) { idx++; blockDepth++; if ((idx < info.Length - 1) && (info[idx + 1] == '*')) { idx++; if ((idx < info.Length - 1) && (info[idx + 1] == '<')) idx++; } continue; } if ((c == '*') && (nextC == '/')) { idx++; blockDepth--; continue; } if (c == '\x03') // \n { if (!lineHadContent) { if (curDocStr != null) curDocStr = null; else if (!mDocString.IsEmpty) docStringDone = true; } queuedSpace = false; atLineStart = true; lineHadStar = false; lineHadContent = false; continue; } if (atLineStart) { if ((c == '*') && (blockDepth > 0) && (!lineHadStar)) { lineHadStar = false; continue; } if ((c == '/') && (!lineHadStar)) { if ((nextC == '<') && (!queuedSpace)) { idx++; } // Ignore any amount of '/' strings at a line start continue; } if ((c == '@') || (c == '\\')) { int pragmaEndPos = info.IndexOf('\x03', idx); if (pragmaEndPos == -1) pragmaEndPos = info.Length; StringView pragma = .(info, idx + 1, pragmaEndPos - idx - 1); var splitEnum = pragma.Split(' '); if (splitEnum.GetNext() case .Ok(var pragmaName)) { if (pragmaName == "param") { if (splitEnum.GetNext() case .Ok(var paramName)) { if (mParamInfo == null) mParamInfo = new .(); curDocStr = new String(pragma, Math.Min(splitEnum.MatchPos + 1, pragma.Length)); mParamInfo[new String(paramName)] = curDocStr; lineHadContent = true; } } else if (pragmaName == "brief") { if (mBriefString == null) mBriefString = new String(pragma.Length); else if (mBriefString != null) { if (!mBriefString[mBriefString.Length - 1].IsWhiteSpace) mBriefString.Append(" "); } var briefStr = StringView(pragma, splitEnum.MatchPos + 1); briefStr.Trim(); mBriefString.Append(briefStr); curDocStr = mBriefString; lineHadContent = true; } } idx = pragmaEndPos - 1; continue; } if (c.IsWhiteSpace) { continue; } else { queuedSpace = true; atLineStart = false; } } if (c.IsWhiteSpace) { queuedSpace = true; continue; } if ((curDocStr != null) && (docStringDone)) continue; String docStr = curDocStr ?? mDocString; if (queuedSpace) { if (!docStr.IsEmpty) { char8 endC = docStr[docStr.Length - 1]; if (!endC.IsWhiteSpace) docStr.Append(" "); } queuedSpace = false; } lineHadContent = true; docStr.Append(c); } } } public class AutoComplete { public class AutoCompleteContent : ScrollableWidget { public AutoComplete mAutoComplete; public bool mIsInitted; public int mIgnoreMove; public bool mOwnsWindow; public float mRightBoxAdjust; public float mWantHeight; public this(AutoComplete autoComplete) { mAutoComplete = autoComplete; } public ~this() { //Debug.WriteLine("~this {} {}", this, mIsInitted); if (mIsInitted) Cleanup(); } void LostFocusHandler(BFWindow window, BFWindow newFocus) { if (gApp.mRunningTestScript) return; if ((newFocus != mWidgetWindow) && (newFocus != mAutoComplete.mTargetEditWidget.mWidgetWindow)) mAutoComplete.Close(); } void HandleWindowMoved(BFWindow window) { if (gApp.mRunningTestScript) return; if ((mWidgetWindow == null) || (mWidgetWindow.mRootWidget != this)) return; // We're being replaced as root if (let widgetWindow = window as WidgetWindow) { if (widgetWindow.mRootWidget is DarkTooltipContainer) return; } if ((mIgnoreMove == 0) && (mWidgetWindow != null) && (!mWidgetWindow.mHasClosed)) mAutoComplete.Close(); } public void Init() { Debug.Assert(!mIsInitted); mIsInitted = true; //Console.WriteLine("AutoCompleteContent Init"); //Debug.WriteLine("Init {} {} {} {}", this, mIsInitted, mOwnsWindow, mAutoComplete); if (mOwnsWindow) { WidgetWindow.sOnWindowLostFocus.Add(new => LostFocusHandler); WidgetWindow.sOnMouseDown.Add(new => HandleMouseDown); WidgetWindow.sOnMouseWheel.Add(new => HandleMouseWheel); WidgetWindow.sOnWindowMoved.Add(new => HandleWindowMoved); WidgetWindow.sOnMenuItemSelected.Add(new => HandleSysMenuItemSelected); } } void HandleMouseWheel(MouseEvent evt) { if (gApp.mRunningTestScript) return; if (mWidgetWindow == null) return; WidgetWindow widgetWindow = (WidgetWindow)evt.mSender; if (!(widgetWindow.mRootWidget is AutoCompleteContent)) { float mouseScreenX = widgetWindow.mClientX + evt.mX; float mouseScreenY = widgetWindow.mClientY + evt.mY; let windowRect = Rect(mWidgetWindow.mX, mWidgetWindow.mY, mWidgetWindow.mWindowWidth, mWidgetWindow.mWindowHeight); if (windowRect.Contains(mouseScreenX, mouseScreenY)) { MouseWheel(evt.mX - mWidgetWindow.mX, evt.mY - mWidgetWindow.mY, evt.mWheelDelta); evt.mHandled = true; } else mAutoComplete.Close(); } } void HandleMouseDown(MouseEvent evt) { WidgetWindow widgetWindow = (WidgetWindow)evt.mSender; if (!(widgetWindow.mRootWidget is AutoCompleteContent)) mAutoComplete.Close(); } void HandleSysMenuItemSelected(IMenu sysMenu) { mAutoComplete.Close(); } public void Cleanup() { //Debug.WriteLine("Cleanup {} {}", this, mIsInitted); if (!mIsInitted) return; //Console.WriteLine("AutoCompleteContent Dispose"); if (mOwnsWindow) { WidgetWindow.sOnWindowLostFocus.Remove(scope => LostFocusHandler, true); WidgetWindow.sOnMouseDown.Remove(scope => HandleMouseDown, true); WidgetWindow.sOnMouseWheel.Remove(scope => HandleMouseWheel, true); WidgetWindow.sOnWindowMoved.Remove(scope => HandleWindowMoved, true); WidgetWindow.sOnMenuItemSelected.Remove(scope => HandleSysMenuItemSelected, true); mIsInitted = false; } } public override void Draw(Graphics g) { base.Draw(g); float drawHeight = (mWantHeight != 0) ? mWantHeight : mHeight; float boxWidth = mWidth - GS!(2) - mRightBoxAdjust; if (mOwnsWindow) { using (g.PushColor(0x80000000)) g.DrawBox(DarkTheme.sDarkTheme.GetImage(DarkTheme.ImageIdx.DropShadow), GS!(2), GS!(2), boxWidth, drawHeight - GS!(2)); base.Draw(g); using (g.PushColor(0xFFFFFFFF)) g.DrawBox(DarkTheme.sDarkTheme.GetImage(DarkTheme.ImageIdx.Menu), 0, 0, boxWidth - GS!(6), drawHeight - GS!(8)); } g.SetFont(IDEApp.sApp.mCodeFont); /*using (g.PushColor(0x80FF0000)) g.FillRect(0, 0, mWidth, mHeight);*/ } public override void Resize(float x, float y, float width, float height) { base.Resize(x, y, width, height); } public override void Update() { base.Update(); Debug.Assert((mIgnoreMove >= 0) && (mIgnoreMove <= 4)); } } public class AutoCompleteListWidget : AutoCompleteContent { BumpAllocator mAlloc = new BumpAllocator(.Ignore) ~ delete _; public List mFullEntryList = new List() ~ delete _; public List mEntryList = mFullEntryList; public float mItemSpacing = GS!(18); public int32 mSelectIdx = -1; public float mMaxWidth; public float mDocWidth; public float mDocHeight; public int mDocumentationDelay = -1; public ~this() { Debug.Assert(mParent == null); if (mEntryList != mFullEntryList) delete mEntryList; } public override void MouseEnter() { base.MouseEnter(); } public class EntryWidget { public int32 mShowIdx; public AutoCompleteListWidget mAutoCompleteListWidget; public String mEntryType; public String mEntryDisplay; public String mEntryInsert; public String mDocumentation; public Image mIcon; public float Y { get { return GS!(6) + mShowIdx * mAutoCompleteListWidget.mItemSpacing; } } public ~this() { } public void Draw(Graphics g) { if (mIcon != null) g.Draw(mIcon, 0, 0); g.SetFont(IDEApp.sApp.mCodeFont); g.DrawString(mEntryDisplay, GS!(20), 0); } } class Content : Widget { AutoCompleteListWidget mAutoCompleteListWidget; public this(AutoCompleteListWidget autoCompleteListWidget) { mAutoCompleteListWidget = autoCompleteListWidget; } public override void Draw(Graphics g) { base.Draw(g); float absX; float absY; mParent.SelfToRootTranslate(0, 0, out absX, out absY); float scrollPos = -g.mMatrix.ty + absY; int32 startIdx = (int32)(scrollPos / mAutoCompleteListWidget.mItemSpacing); int32 endIdx = Math.Min((int32)((scrollPos + mAutoCompleteListWidget.mHeight)/ mAutoCompleteListWidget.mItemSpacing) + 1, (int32)mAutoCompleteListWidget.mEntryList.Count); if (mAutoCompleteListWidget.mSelectIdx != -1) { var selectedEntry = mAutoCompleteListWidget.mEntryList[mAutoCompleteListWidget.mSelectIdx]; using (g.PushColor(DarkTheme.COLOR_MENU_FOCUSED)) { let dispWidth = g.mFont.GetWidth(selectedEntry.mEntryDisplay) + GS!(24); float width = mWidth - GS!(16) - mAutoCompleteListWidget.mRightBoxAdjust; if (mAutoCompleteListWidget.mVertScrollbar != null) width -= GS!(18); width = Math.Max(dispWidth, width); g.DrawButton(DarkTheme.sDarkTheme.GetImage(DarkTheme.ImageIdx.MenuSelect), GS!(4), selectedEntry.Y - GS!(2), width); } } for (int32 itemIdx = startIdx; itemIdx < endIdx; itemIdx++) { var entry = (EntryWidget)mAutoCompleteListWidget.mEntryList[itemIdx]; float curY = entry.Y; using (g.PushTranslate(4, curY)) entry.Draw(g); } } public override void MouseDown(float x, float y, int32 btn, int32 btnCount) { base.MouseDown(x, y, btn, btnCount); int32 idx = (int32)((y - GS!(4.0f)) / mAutoCompleteListWidget.mItemSpacing); if ((idx >= 0) && (idx < mAutoCompleteListWidget.mEntryList.Count)) { mAutoCompleteListWidget.Select(idx); if (!mAutoCompleteListWidget.mOwnsWindow) mAutoCompleteListWidget.SetFocus(); if (btnCount > 1) { mAutoCompleteListWidget.mAutoComplete.InsertSelection((char8)0); mAutoCompleteListWidget.mAutoComplete.Close(); } } } } public this(AutoComplete autoComplete) : base(autoComplete) { mScrollContent = new Content(this); mScrollContentContainer.AddWidget(mScrollContent); } public void UpdateWidth() { int firstEntry = (int)(-(int)mScrollContent.mY / mItemSpacing); int lastEntry = (int)((-(int)mScrollContent.mY + mScrollContentContainer.mHeight) / mItemSpacing); if (mScrollContentContainer.mHeight == 0) { firstEntry = Math.Max(mSelectIdx - 3, 0); lastEntry = mSelectIdx + 7; } lastEntry = Math.Min(lastEntry, mEntryList.Count); float prevMaxWidth = mMaxWidth; var font = IDEApp.sApp.mCodeFont; for (int i = firstEntry; i < lastEntry; i++) { var entry = mEntryList[i]; float entryWidth = font.GetWidth(entry.mEntryDisplay) + GS!(32); mMaxWidth = Math.Max(mMaxWidth, entryWidth); } float docWidth = 0.0f; float docHeight = 0; if ((mSelectIdx != -1) && (mSelectIdx < mEntryList.Count)) { let selectedEntry = mEntryList[mSelectIdx]; if (selectedEntry.mDocumentation != null) { DocumentationParser docParser = scope DocumentationParser(selectedEntry.mDocumentation); var showDocString = docParser.ShowDocString; docWidth = font.GetWidth(showDocString) + GS!(24); int drawScreenX = (.)(mWidgetWindow.mX + mWidth - mDocWidth); gApp.GetWorkspaceRectFrom(drawScreenX, mWidgetWindow.mY, 0, 0, var workspaceX, var workspaceY, var workspaceWidth, var workspaceHeight); float maxWidth = workspaceWidth - drawScreenX - GS!(8); float newDocWidth = Math.Min(docWidth, workspaceWidth - drawScreenX - GS!(8)); newDocWidth = Math.Max(newDocWidth, GS!(80)); if (docWidth > maxWidth) { docWidth = newDocWidth; docHeight = font.GetWrapHeight(showDocString, docWidth - GS!(20)) + GS!(17); } else docHeight = GS!(32); } } if ((mOwnsWindow) && ((prevMaxWidth != mMaxWidth) || (docWidth != mDocWidth) || (docHeight != mDocHeight)) && (mWidgetWindow != null)) { if (mWantHeight == 0) mWantHeight = mHeight; mDocWidth = docWidth; mDocHeight = docHeight; mRightBoxAdjust = docWidth + GS!(16); int32 windowWidth = (int32)mMaxWidth; windowWidth += (.)mDocWidth; windowWidth += GS!(32); if (mVertScrollbar != null) { windowWidth += GS!(12); } int windowHeight = (int)(mWantHeight + Math.Max(0, mDocHeight - GS!(32))); mIgnoreMove++; if (mAutoComplete.mInvokeWidget != null) mAutoComplete.mInvokeWidget.mIgnoreMove++; mWidgetWindow.Resize(mWidgetWindow.mX, mWidgetWindow.mY, windowWidth, windowHeight); mScrollContent.mWidth = mWidth; //Resize(0, 0, mWidgetWindow.mClientWidth, mWidgetWindow.mClientHeight); mIgnoreMove--; if (mAutoComplete.mInvokeWidget != null) mAutoComplete.mInvokeWidget.mIgnoreMove--; ResizeContent(-1, -1, mVertScrollbar != null); } } /*public override void Resize(float x, float y, float width, float height) { if (mWantHeight != 0) { mScrollContentInsets.mBottom = mHeight - mWantHeight; mScrollbarInsets.mBottom = mHeight - mWantHeight + 10; } else { mScrollContentInsets.mBottom = 0; mScrollbarInsets.mBottom = 10; } base.Resize(x, y, width, height); }*/ public void UpdateEntry(EntryWidget entry, int showIdx) { if (showIdx == -1) { //entry.mVisible = false; entry.mShowIdx = (int32)showIdx; return; } //entry.mVisible = true; //entry.Resize(GS!(4), GS!(6) + showIdx * mItemSpacing, int32.MaxValue, mItemSpacing); entry.mShowIdx = (int32)showIdx; /*if (showIdx < 10) { var font = IDEApp.sApp.mCodeFont; float entryWidth = font.GetWidth(entry.mEntryDisplay) + GS!(32); mMaxWidth = Math.Max(mMaxWidth, entryWidth); }*/ } public void AddEntry(StringView entryType, StringView entryDisplay, Image icon, StringView entryInsert = default, StringView documentation = default) { var entryWidget = new:mAlloc EntryWidget(); entryWidget.mAutoCompleteListWidget = this; entryWidget.mEntryType = new:mAlloc String(entryType); entryWidget.mEntryDisplay = new:mAlloc String(entryDisplay); if (!entryInsert.IsEmpty) entryWidget.mEntryInsert = new:mAlloc String(entryInsert); if (!documentation.IsEmpty) entryWidget.mDocumentation = new:mAlloc String(documentation); entryWidget.mIcon = icon; UpdateEntry(entryWidget, mEntryList.Count); mEntryList.Add(entryWidget); //mScrollContent.AddWidget(entryWidget); } public void EnsureSelectionVisible() { if (mVertScrollbar != null) { float extraSpacing = mOwnsWindow ? 0 : GS!(4); //int numItemsVisible = (int)((mVertScrollbar.mPageSize - GS!(6)) / mItemSpacing); //float usableHeight = numItemsVisible * mItemSpacing; float usableHeight = (float)mVertScrollbar.mPageSize; float height = mItemSpacing; var selectItem = mEntryList[mSelectIdx]; if (selectItem.Y - extraSpacing < mVertScrollbar.mContentPos) mVertScrollbar.ScrollTo(selectItem.Y - extraSpacing); if (selectItem.Y + height > mVertScrollbar.mContentPos + usableHeight) mVertScrollbar.ScrollTo(selectItem.Y + height - usableHeight); } UpdateWidth(); } public void CenterSelection() { if (mSelectIdx == -1) return; if (mVertScrollbar == null) return; var selectItem = mEntryList[mSelectIdx]; VertScrollTo(selectItem.Y + mItemSpacing - mVertScrollbar.mPageSize / 2, true); UpdateWidth(); } public void Select(int32 idx) { if (mSelectIdx == idx) return; MarkDirty(); mSelectIdx = idx; if ((gApp.mSettings.mEditorSettings.mAutoCompleteShowDocumentation) && (!mAutoComplete.mIsDocumentationPass)) { // Show faster when we have a panel to show within mDocumentationDelay = mOwnsWindow ? 40 : 20; } EnsureSelectionVisible(); } public void SelectDirection(int32 dir) { int32 newSelection = mSelectIdx + dir; if ((newSelection >= 0) && (newSelection < mEntryList.Count)) { if (mEntryList[newSelection].mShowIdx != -1) Select(newSelection); } } public override void ScrollPositionChanged() { if (mVertScrollbar != null) { //mVertScrollbar.mContentPos = (float)Math.Round(mVertScrollbar.mContentPos / mItemSpacing) * mItemSpacing; } base.ScrollPositionChanged(); } public override void Draw(Graphics g) { base.Draw(g); if (mSelectIdx != -1) { let selectedEntry = mEntryList[mSelectIdx]; if ((selectedEntry.mDocumentation != null) && (mDocumentationDelay <= 0)) { DocumentationParser docParser = scope .(selectedEntry.mDocumentation); if (mOwnsWindow) { if (mDocWidth > 0) { float drawX = mWidth - mDocWidth - GS!(22); //float drawX = mRightBoxAdjust + GS!(42); float drawY = GS!(4); //float drawHeight = GS!(32); float drawHeight = mDocHeight; using (g.PushColor(0x80000000)) g.DrawBox(DarkTheme.sDarkTheme.GetImage(.DropShadow), drawX + GS!(2), drawY + GS!(2), mRightBoxAdjust - GS!(2), drawHeight - GS!(2)); using (g.PushColor(0xFFFFFFFF)) g.DrawBox(DarkTheme.sDarkTheme.GetImage(.Menu), drawX, drawY, mRightBoxAdjust - GS!(8), drawHeight - GS!(8)); using (g.PushColor(gApp.mSettings.mUISettings.mColors.mAutoCompleteDocText)) g.DrawString(docParser.ShowDocString, drawX + GS!(8), drawY + GS!(4), .Left, mDocWidth - GS!(20), .Wrap); } } else { /*float drawX = GS!(8); float drawY = mHeight + GS!(2); using (g.PushColor(0xFFC0C0C0)) g.DrawString(docParser.ShowDocString, drawX, drawY, .Left, mWidth - drawX, .Wrap);*/ } } } } public override void DrawAll(Graphics g) { base.DrawAll(g); /*using (g.PushColor(0x20FF0000)) g.FillRect(0, 0, mWidth, mHeight);*/ } public override void Update() { base.Update(); if (mDocumentationDelay > 0) --mDocumentationDelay; } public void ResizeContent(int32 width, int32 height, bool wantScrollbar) { InitScrollbars(false, wantScrollbar); if ((wantScrollbar) && (mOwnsWindow)) { mVertScrollbar.mScrollIncrement = mItemSpacing; mVertScrollbar.mAlignItems = true; } if (mOwnsWindow) { mScrollbarInsets.mTop = GS!(2); mScrollbarInsets.mBottom = GS!(10); mScrollbarInsets.mRight = GS!(10) + mRightBoxAdjust; mScrollContentInsets.mBottom = 0; if (mWantHeight != 0) { mScrollbarInsets.mBottom += mHeight - mWantHeight; } } else { mScrollbarInsets.mTop = GS!(2); mScrollbarInsets.mBottom = GS!(2); mScrollbarInsets.mRight = GS!(2); } if (width != -1) { mScrollContent.mWidth = width /*- mRightBoxAdjust*/; mScrollContent.mHeight = height; } UpdateScrollbars(); } public override void RehupScale(float oldScale, float newScale) { base.RehupScale(oldScale, newScale); mAutoComplete.Close(); } public override void KeyDown(KeyCode keyCode, bool isRepeat) { base.KeyDown(keyCode, isRepeat); switch (keyCode) { case .Up: SelectDirection(-1); case .Down: SelectDirection(1); default: } } } public class InvokeWidget : AutoCompleteContent { public class Entry { public String mText ~ delete _; public String mDocumentation ~ delete _; public int GetParamCount() { int splitCount = 0; for (int32 i = 0; i < mText.Length; i++) { char8 c = mText[i]; if (c == '\x01') { splitCount++; i++; } } return splitCount - 1; } public bool HasParamsParam() { int lastSplit = mText.LastIndexOf('\x01'); if (lastSplit == -1) return false; lastSplit = mText.LastIndexOf('\x01', lastSplit - 1); if (lastSplit == -1) return false; StringView sv = .(mText, lastSplit); return sv.StartsWith("\x01params ") || sv.StartsWith("\x01 params "); } } public List mEntryList = new List() ~ DeleteContainerAndItems!(_); public int32 mSelectIdx; public float mMaxWidth; public int32 mLeftParenIdx; public bool mIsAboveText; public this(AutoComplete autoComplete) : base(autoComplete) { } public ~this() { } public void AddEntry(Entry entry) { mEntryList.Add(entry); var font = IDEApp.sApp.mCodeFont; String checkString = scope String(entry.mText); checkString.Replace("\x01", ""); float entryWidth = font.GetWidth(checkString) + GS!(32); mMaxWidth = Math.Max(mMaxWidth, entryWidth); } public void ResizeContent(bool resizeWindow) { if (mOwnsWindow) { int workspaceX; int workspaceY; int workspaceWidth; int workspaceHeight; BFApp.sApp.GetWorkspaceRect(out workspaceX, out workspaceY, out workspaceWidth, out workspaceHeight); mWidth = workspaceWidth; } else mWidth = gApp.mAutoCompletePanel.mWidth; float extWidth; float extHeight; DrawInfo(null, out extWidth, out extHeight); mWidth = extWidth; mHeight = extHeight; if (resizeWindow) { if (mOwnsWindow) { mIgnoreMove++; mAutoComplete.UpdateWindow(ref mWidgetWindow, this, mAutoComplete.mInvokeSrcPositions[0], (int32)extWidth, (int32)extHeight); mIgnoreMove--; } else { gApp.mAutoCompletePanel.ResizeComponents(); } } } public void Select(int32 idx) { mSelectIdx = idx; ResizeContent(mWidgetWindow != null); } public new void Init() { base.Init(); if (mSelectIdx >= mEntryList.Count) mSelectIdx = 0; } public void SelectDirection(int32 dir) { int32 newSelection = mSelectIdx + dir; if ((newSelection >= 0) && (newSelection < mEntryList.Count)) Select(newSelection); } public override void Update() { base.Update(); } void DrawInfo(Graphics g, out float extWidth, out float extHeight) { var font = IDEApp.sApp.mCodeFont; extWidth = 0; float curX = GS!(8); float curY = GS!(5); if (mEntryList.Count > 1) { String numStr = scope String(); numStr.AppendF("{0}/{1}", mSelectIdx + 1, mEntryList.Count); if (g != null) { using (g.PushColor(gApp.mSettings.mUISettings.mColors.mAutoCompleteSubText)) g.DrawString(numStr, curX, curY); } curX += font.GetWidth(numStr) + GS!(8); } var selectedEntry = mEntryList[mSelectIdx]; float maxWidth = mWidth; StringView paramName = .(); List textSections = scope List(selectedEntry.mText.Split('\x01')); int cursorPos = mAutoComplete.mTargetEditWidget.Content.CursorTextPos; int cursorSection = -1; for (int sectionIdx = 0; sectionIdx < mAutoComplete.mInvokeSrcPositions.Count - 1; sectionIdx++) { if (cursorPos > mAutoComplete.mInvokeSrcPositions[sectionIdx]) cursorSection = sectionIdx + 1; } // Just show last section hilighted even if we have too many params. // This accounts for variadic cases if (cursorSection >= textSections.Count - 1) cursorSection = textSections.Count - 2; float paramX = 0; for (int sectionIdx = 0; sectionIdx < textSections.Count; sectionIdx++) { bool isParam = (sectionIdx > 0) && (sectionIdx < textSections.Count - 1); if ((isParam) && (paramX == 0)) paramX = curX; StringView sectionStr = .(textSections[sectionIdx]); float sectionWidth = font.GetWidth(sectionStr); if (curX + sectionWidth > maxWidth) { curX = paramX; curY += font.GetLineSpacing(); while (sectionStr.StartsWith(" ")) sectionStr.RemoveFromStart(1); } if (sectionIdx == cursorSection) { int lastSpace = sectionStr.LastIndexOf(' '); if (lastSpace != -1) { paramName = .(sectionStr, lastSpace + 1); if (paramName.EndsWith(',')) paramName.RemoveFromEnd(1); } } if (g != null) { using (g.PushColor(((sectionIdx == cursorSection) && (isParam)) ? gApp.mSettings.mUISettings.mColors.mAutoCompleteActiveText : gApp.mSettings.mUISettings.mColors.mText)) g.DrawString(sectionStr, curX, curY); } curX += sectionWidth; extWidth = Math.Max(extWidth, curX); } extWidth += GS!(16); extHeight = curY + font.GetLineSpacing() + GS!(16); if ((selectedEntry.mDocumentation != null) && (gApp.mSettings.mEditorSettings.mAutoCompleteShowDocumentation)) { DocumentationParser docParser = scope .(selectedEntry.mDocumentation); var docString = docParser.mBriefString ?? docParser.mDocString; curX = GS!(32); float docHeight = 0; if (mWidgetWindow == null) { docHeight = font.GetHeight(); } else { int drawScreenX = (.)(mWidgetWindow.mX + curX); gApp.GetWorkspaceRectFrom(drawScreenX, mWidgetWindow.mY, 0, 0, var workspaceX, var workspaceY, var workspaceWidth, var workspaceHeight); float maxDocWidth = workspaceWidth - drawScreenX - GS!(8); maxDocWidth = Math.Min(maxDocWidth, workspaceWidth - drawScreenX - GS!(8)); maxDocWidth = Math.Max(maxDocWidth, GS!(80)); if (!docString.IsWhiteSpace) { curY += font.GetLineSpacing() + GS!(4); if (g != null) { using (g.PushColor(gApp.mSettings.mUISettings.mColors.mAutoCompleteDocText)) docHeight = g.DrawString(docString, curX, curY, .Left, maxDocWidth, .Wrap); } else docHeight = font.GetWrapHeight(docString, maxDocWidth); } extWidth = Math.Max(extWidth, Math.Min(font.GetWidth(docString), maxDocWidth) + GS!(48)); } extHeight += docHeight + GS!(4); /*if (docWidth > maxDocWidth) { docWidth = newDocWidth; docHeight = font.GetWrapHeight(showDocString, docWidth - GS!(20)) + GS!(17); } else docHeight = GS!(32);*/ /*curY += font.GetLineSpacing() + GS!(4); if (g != null) { using (g.PushColor(0xFFC0C0C0)) g.DrawString(docString, curX, curY, .Left, mWidth, .Ellipsis); } extWidth = Math.Max(extWidth, font.GetWidth(docString) + GS!(48)); extHeight += font.GetLineSpacing() + GS!(4);*/ if (docParser.mParamInfo != null) { if (docParser.mParamInfo.TryGetValue(scope String(paramName), var paramDoc)) { curY += font.GetLineSpacing() + GS!(4); if (g != null) { using (g.PushColor(gApp.mSettings.mUISettings.mColors.mText)) { g.DrawString(scope String(paramName.Length + 1)..AppendF("{0}:", paramName), curX, curY, .Left, mWidth, .Ellipsis); } using (g.PushColor(gApp.mSettings.mUISettings.mColors.mAutoCompleteDocText)) { g.DrawString(paramDoc, curX + font.GetWidth(paramName) + font.GetWidth(": "), curY, .Left, mWidth, .Ellipsis); } } } for (var paramDocKV in docParser.mParamInfo) { extWidth = Math.Max(extWidth, font.GetWidth(paramDocKV.key) + font.GetWidth(": ") + font.GetWidth(paramDocKV.value) + GS!(48)); } extHeight += font.GetLineSpacing() + GS!(4); } } } public override void Draw(Beefy.gfx.Graphics g) { base.Draw(g); float extWidth; float extHeight; DrawInfo(g, out extWidth, out extHeight); } public override void RehupScale(float oldScale, float newScale) { base.RehupScale(oldScale, newScale); mAutoComplete.Close(); } } public EditWidget mTargetEditWidget; public Event mOnAutoCompleteInserted ~ _.Dispose(); public Event mOnClosed ~ _.Dispose(); public WidgetWindow mListWindow; public AutoCompleteListWidget mAutoCompleteListWidget; public WidgetWindow mInvokeWindow; public InvokeWidget mInvokeWidget; public List mInvokeStack = new List() ~ delete _; // Previous invokes (from async) public int32 mInsertStartIdx = -1; public int32 mInsertEndIdx = -1; public String mInfoFilter ~ delete _; public List mInvokeSrcPositions ~ delete _; public static int32 sAutoCompleteIdx = 1; public static Dictionary sAutoCompleteMRU = new Dictionary() ~ delete _; public bool mIsAsync = true; public bool mIsMember; public bool mIsFixit; public bool mInvokeOnly; public bool mUncertain; public bool mIsDocumentationPass; public bool mIsUserRequested; bool mClosed; bool mPopulating; float mWantX; float mWantY; public this(EditWidget targetEditWidget) { mTargetEditWidget = targetEditWidget; } public ~this() { Close(false); } static ~this() { for (var key in sAutoCompleteMRU.Keys) delete key; } public void UpdateWindow(ref WidgetWindow widgetWindow, Widget rootWidget, int textIdx, int width, int height) { var textIdx; // This makes typing '..' NOT move the window after pressing the second '.' if (mTargetEditWidget.Content.SafeGetChar(textIdx - 2) == '.') { textIdx--; } Debug.Assert(textIdx >= 0); int line = 0; int column = 0; if (textIdx >= 0) mTargetEditWidget.Content.GetLineCharAtIdx(textIdx, out line, out column); float x; float y; mTargetEditWidget.Content.GetTextCoordAtLineChar(line, column, out x, out y); mTargetEditWidget.Content.GetTextCoordAtCursor(var cursorX, var cursorY); if (mInvokeWidget?.mIsAboveText != true) y = Math.Max(y, cursorY + gApp.mCodeFont.GetHeight() * 0.0f); /*if (cursorY > y + gApp.mCodeFont.GetHeight() * 2.5f) y = cursorY;*/ float screenX; float screenY; mTargetEditWidget.Content.SelfToRootTranslate(x, y, out screenX, out screenY); /// /*if ((mInvokeSrcPositions != null) && (mInvokeSrcPositions.Count > 0)) { textIdx = mInvokeSrcPositions[mInvokeSrcPositions.Count - 1]; mTargetEditWidget.Content.GetLineCharAtIdx(textIdx, out line, out column); mTargetEditWidget.Content.GetTextCoordAtLineChar(line, column, out x, out y); float endScreenX; float endScreenY; mTargetEditWidget.Content.SelfToRootTranslate(x, y, out endScreenX, out endScreenY); screenY = endScreenY; }*/ /// int screenWidth = width; int screenHeight = height; screenX += mTargetEditWidget.mWidgetWindow.mClientX; screenY += mTargetEditWidget.mWidgetWindow.mClientY; screenX -= GS!(24); screenY += GS!(20); float startScreenY = screenY; if (rootWidget == mInvokeWidget) { if (mInvokeWidget.mIsAboveText) screenY -= height + GS!(16); } //TODO: Do better positioning if ((mInvokeWindow != null) && (widgetWindow == mListWindow)) { if (!mInvokeWidget.mIsAboveText) screenY += mInvokeWindow.mWindowHeight - 6; } mWantX = screenX; mWantY = screenY; int workspaceX; int workspaceY; int workspaceWidth; int workspaceHeight; BFApp.sApp.GetWorkspaceRect(out workspaceX, out workspaceY, out workspaceWidth, out workspaceHeight); if (screenX + width > workspaceWidth) screenX = workspaceWidth - width; if (screenX < workspaceX) screenX = workspaceX; if (rootWidget == mAutoCompleteListWidget) { // May clip of bottom? if (screenY + GetMaxWindowHeight() >= workspaceHeight) { screenY = startScreenY - (height + GS!(16)); } } /*if (width > workspaceWidth) { screenWidth = workspaceWidth; var font = IDEApp.sApp.mCodeFont; //font.GetWrapHeight() }*/ if (widgetWindow == null) { BFWindow.Flags windowFlags = BFWindow.Flags.ClientSized | BFWindow.Flags.PopupPosition | BFWindow.Flags.NoActivate | BFWindow.Flags.NoMouseActivate | BFWindow.Flags.DestAlpha; widgetWindow = new WidgetWindow(mTargetEditWidget.mWidgetWindow, "Autocomplete", (int32)screenX, (int32)screenY, screenWidth, screenHeight, windowFlags, rootWidget); } else { if (widgetWindow.mRootWidget != rootWidget) { var prevRoot = widgetWindow.mRootWidget; //Debug.WriteLine("Setting window {0} to root {1} from root {2}", widgetWindow, rootWidget, prevRoot); widgetWindow.SetRootWidget(rootWidget); delete prevRoot; } widgetWindow.Resize((int)screenX, (int)screenY, width, height); } } public void UpdateAsyncInfo() { GetAsyncTextPos(); UpdateData(null, true); } public void Update() { if ((mInvokeWindow != null) && (!mInvokeWidget.mIsAboveText)) { int textIdx = mTargetEditWidget.Content.CursorTextPos; int line = 0; int column = 0; if (textIdx >= 0) mTargetEditWidget.Content.GetLineCharAtIdx(textIdx, out line, out column); float x; float y; mTargetEditWidget.Content.GetTextCoordAtLineChar(line, column, out x, out y); float screenX; float screenY; mTargetEditWidget.Content.SelfToRootTranslate(x, y, out screenX, out screenY); screenX += mTargetEditWidget.mWidgetWindow.mClientX; screenY += mTargetEditWidget.mWidgetWindow.mClientY; //if (screenY >= mInvokeWindow.mY - 8) int invokeLine = 0; int invokeColumn = 0; if (mInvokeSrcPositions != null) mTargetEditWidget.Content.GetLineCharAtIdx(mInvokeSrcPositions[0], out invokeLine, out invokeColumn); int insertLine = line; if ((insertLine != invokeLine) && ((insertLine - invokeLine) * gApp.mCodeFont.GetHeight() < GS!(40))) { mInvokeWidget.mIgnoreMove++; if (mListWindow != null) mAutoCompleteListWidget.mIgnoreMove++; mInvokeWidget.mIsAboveText = true; mInvokeWidget.ResizeContent(false); UpdateWindow(ref mInvokeWindow, mInvokeWidget, mInvokeSrcPositions[0], (int32)mInvokeWidget.mWidth, (int32)mInvokeWidget.mHeight); if (mListWindow != null) { UpdateWindow(ref mListWindow, mAutoCompleteListWidget, mInsertStartIdx, mListWindow.mWindowWidth, mListWindow.mWindowHeight); mAutoCompleteListWidget.mIgnoreMove--; } mInvokeWidget.mIgnoreMove--; } } if (mAutoCompleteListWidget != null) mAutoCompleteListWidget.UpdateWidth(); if ((IsShowing()) && (!IsInPanel())) { bool hasFocus = false; if ((mListWindow != null) && (mListWindow.mHasFocus)) hasFocus = true; if (mTargetEditWidget.mHasFocus) hasFocus = true; if (!hasFocus) { Close(); } } } public void GetFilter(String outFilter) { if ((mInsertEndIdx != -1) && (mInsertStartIdx != -1)) { mTargetEditWidget.Content.ExtractString(mInsertStartIdx, Math.Max(mInsertEndIdx - mInsertStartIdx, 0), outFilter); } } public void GetAsyncTextPos() { //Debug.WriteLine("GetAsyncTextPos start {0} {1}", mInsertStartIdx, mInsertEndIdx); mInsertEndIdx = (int32)mTargetEditWidget.Content.CursorTextPos; while ((mInsertStartIdx != -1) && (mInsertStartIdx < mInsertEndIdx)) { char8 c = (char8)mTargetEditWidget.Content.mData.mText[mInsertStartIdx].mChar; if ((c != ' ') && (c != ',')) break; mInsertStartIdx++; } /*mInsertStartIdx = mInsertEndIdx; while (mInsertStartIdx > 0) { char8 c = (char8)mTargetEditWidget.Content.mData.mText[mInsertStartIdx - 1].mChar; if ((!c.IsLetterOrDigit) && (c != '_')) { break; } mInsertStartIdx--; }*/ if ((mInvokeWidget != null) && (mInvokeWidget.mEntryList.Count > 0)) { var data = mTargetEditWidget.Content.mData; int32 startIdx = mInvokeSrcPositions[0]; if ((startIdx < data.mTextLength) && (data.mText[startIdx].mChar == '(')) { mInvokeSrcPositions.Clear(); int32 openDepth = 0; int32 checkIdx = startIdx; mInvokeSrcPositions.Add(startIdx); int32 argCount = 0; void HadContent() { if (argCount == 0) argCount++; } bool failed = false; while (checkIdx < mTargetEditWidget.Content.mData.mText.Count) { var char8Data = mTargetEditWidget.Content.mData.mText[checkIdx]; if (char8Data.mDisplayTypeId == 0) { if (char8Data.mChar == '{') { openDepth++; failed = true; break; } else if (char8Data.mChar == '}') openDepth--; else if (char8Data.mChar == '(') openDepth++; else if (char8Data.mChar == ')') { openDepth--; } else if ((char8Data.mChar == ',') && (openDepth == 1)) { mInvokeSrcPositions.Add(checkIdx); argCount++; } else if (!((char8)char8Data.mChar).IsWhiteSpace) HadContent(); if (openDepth == 0) { mInvokeSrcPositions.Add(checkIdx); break; } } else if (char8Data.mDisplayPassId != (.)SourceElementType.Comment) { HadContent(); } checkIdx++; } bool hasTooFewParams = false; if (!failed) { if (mInvokeWidget.mSelectIdx != -1) { let entry = mInvokeWidget.mEntryList[mInvokeWidget.mSelectIdx]; if (!entry.HasParamsParam()) hasTooFewParams = entry.GetParamCount() < argCount; } } if (hasTooFewParams) { // Make sure the current method has enough params to support the args coming in for (int checkOffset = 0; checkOffset < mInvokeWidget.mEntryList.Count; checkOffset++) { int checkEntryIdx = (mInvokeWidget.mSelectIdx + checkOffset) % mInvokeWidget.mEntryList.Count; let entry = mInvokeWidget.mEntryList[checkEntryIdx]; bool matches = false; int paramCount = entry.GetParamCount(); if (argCount <= paramCount) { matches = true; } if (entry.HasParamsParam()) { matches = true; } if ((matches) && (mInvokeWidget.mSelectIdx != -1)) { let prevEntry = mInvokeWidget.mEntryList[mInvokeWidget.mSelectIdx]; int prevMatchDiff = prevEntry.GetParamCount() - argCount; int newMatchDiff = entry.GetParamCount() - argCount; if ((prevMatchDiff >= 0) && (prevMatchDiff < newMatchDiff)) matches = false; } if (matches) { mInvokeWidget.mSelectIdx = (int32)checkEntryIdx; } } } } } //Debug.WriteLine("GetAsyncTextPos end {0} {1}", mInsertStartIdx, mInsertEndIdx); } bool SelectEntry(String curString) { if (mAutoCompleteListWidget == null) return false; int32 caseMatchMRUPriority = -1; int32 caseNotMatchMRUPriority = -1; int32 selectIdx = mAutoCompleteListWidget.mSelectIdx; bool hadMatch = false; for (int32 i = 0; i < mAutoCompleteListWidget.mEntryList.Count; i++) { var entry = mAutoCompleteListWidget.mEntryList[i]; if (entry.mEntryDisplay == curString) { hadMatch = true; selectIdx = i; break; } if (curString.Length > entry.mEntryDisplay.Length) continue; if (String.Compare(curString, 0, entry.mEntryDisplay, 0, curString.Length, false) == 0) { hadMatch = true; int32 priority = -1; sAutoCompleteMRU.TryGetValue(entry.mEntryDisplay, out priority); if (priority > caseMatchMRUPriority) { selectIdx = i; caseMatchMRUPriority = priority; } } else if ((caseMatchMRUPriority == -1) && (String.Compare(curString, 0, entry.mEntryDisplay, 0, curString.Length, true) == 0)) { hadMatch = true; int32 priority = -1; sAutoCompleteMRU.TryGetValue(entry.mEntryDisplay, out priority); if (priority > caseNotMatchMRUPriority) { selectIdx = i; caseNotMatchMRUPriority = priority; } } } if (selectIdx == -1) selectIdx = 0; if (!mAutoCompleteListWidget.mEntryList.IsEmpty) mAutoCompleteListWidget.Select(selectIdx); return hadMatch; } public void SetIgnoreMove(bool ignoreMove) { if (mAutoCompleteListWidget != null) mAutoCompleteListWidget.mIgnoreMove += ignoreMove ? 1 : -1; if (mInvokeWidget != null) mInvokeWidget.mIgnoreMove += ignoreMove ? 1 : -1; } bool DoesFilterMatch(String entry, String filter) { if (filter.Length == 0) return true; char8* entryPtr = entry.Ptr; char8* filterPtr = filter.Ptr; int filterLen = (int)filter.Length; int entryLen = (int)entry.Length; bool hasUnderscore = false; bool checkInitials = filterLen > 1; for (int i = 0; i < (int)filterLen; i++) { char8 c = filterPtr[i]; if (c == '_') hasUnderscore = true; else if (filterPtr[i].IsLower) checkInitials = false; } if (hasUnderscore) //return strnicmp(filter, entry, filterLen) == 0; return (entryLen >= filterLen) && (String.Compare(entryPtr, filterLen, filterPtr, filterLen, true) == 0); char8[256] initialStr; char8* initialStrP = &initialStr; //String initialStr; bool prevWasUnderscore = false; for (int entryIdx = 0; entryIdx < entryLen; entryIdx++) { char8 entryC = entryPtr[entryIdx]; if (entryC == '_') { prevWasUnderscore = true; continue; } if ((entryIdx == 0) || (prevWasUnderscore) || (entryC.IsUpper) || (entryC.IsDigit)) { /*if (strnicmp(filter, entry + entryIdx, filterLen) == 0) return true;*/ if ((entryLen - entryIdx >= filterLen) && (String.Compare(entryPtr + entryIdx, filterLen, filterPtr, filterLen, true) == 0)) return true; if (checkInitials) *(initialStrP++) = entryC; } prevWasUnderscore = false; if (filterLen == 1) break; // Don't check inners for single-character case } if (!checkInitials) return false; int initialLen = initialStrP - (char8*)&initialStr; return (initialLen >= filterLen) && (String.Compare(&initialStr, filterLen, filterPtr, filterLen, true) == 0); //*(initialStrP++) = 0; //return strnicmp(filter, initialStr, filterLen) == 0; } void UpdateData(String selectString, bool changedAfterInfo) { if ((mInsertEndIdx != -1) && (mInsertEndIdx < mInsertStartIdx)) { mPopulating = false; Close(); return; } int visibleCount = 0; if (mAutoCompleteListWidget != null) visibleCount = mAutoCompleteListWidget.mEntryList.Count; if ((mAutoCompleteListWidget != null) && ((mInsertEndIdx != -1) || (selectString != null))) { String curString; if (selectString != null) curString = selectString; else { curString = scope:: String(); mTargetEditWidget.Content.ExtractString(mInsertStartIdx, mInsertEndIdx - mInsertStartIdx, curString); } //if (selectString == null) if (changedAfterInfo) { mAutoCompleteListWidget.mSelectIdx = -1; if ((curString.Length == 0) && (!mIsMember) && (mInvokeSrcPositions == null)) { mPopulating = false; Close(); return; } // Only show applicable entries mAutoCompleteListWidget.mMaxWidth = 0; mAutoCompleteListWidget.mDocWidth = 0; mAutoCompleteListWidget.mRightBoxAdjust = 0; visibleCount = 0; if (mAutoCompleteListWidget.mEntryList == mAutoCompleteListWidget.mFullEntryList) mAutoCompleteListWidget.mEntryList = new List(); mAutoCompleteListWidget.mEntryList.Clear(); int spaceIdx = curString.LastIndexOf(' '); if (spaceIdx != -1) curString.Remove(0, spaceIdx + 1); curString.Trim(); if (curString == ".") curString.Clear(); for (int i < mAutoCompleteListWidget.mFullEntryList.Count) { var entry = mAutoCompleteListWidget.mFullEntryList[i]; //if (String.Compare(entry.mEntryDisplay, 0, curString, 0, curString.Length, true) == 0) if (DoesFilterMatch(entry.mEntryDisplay, curString)) { mAutoCompleteListWidget.mEntryList.Add(entry); mAutoCompleteListWidget.UpdateEntry(entry, visibleCount); visibleCount++; } else { mAutoCompleteListWidget.UpdateEntry(entry, -1); } } if ((visibleCount == 0) && (mInvokeSrcPositions == null)) { mPopulating = false; Close(); return; } } // Only take last part, useful for "overide " autocompletes for (int32 i = 0; i < curString.Length; i++) { char8 c = curString[i]; if ((c == '<') || (c == '(')) break; if (c.IsWhiteSpace) { curString.Remove(0, i + 1); i = 0; } } if ((!SelectEntry(curString)) && (curString.Length > 0)) { // If we can't find any matches, at least select a string that starts with the right char8acter curString.RemoveToEnd(1); SelectEntry(curString); } if (mAutoCompleteListWidget != null) { mAutoCompleteListWidget.UpdateWidth(); } } else if (selectString == null) { SelectEntry(""); } SetIgnoreMove(true); gApp.mAutoCompletePanel.StartBind(this); int32 prevInvokeSelect = 0; if (mInvokeWidget != null) { prevInvokeSelect = mInvokeWidget.mSelectIdx; if ((mInvokeWidget.mEntryList.Count > 0) && (!mInvokeSrcPositions.IsEmpty)) { if (IsInPanel()) { mInvokeWidget.mOwnsWindow = false; } else { mInvokeWidget.mOwnsWindow = true; mInvokeWidget.ResizeContent(false); UpdateWindow(ref mInvokeWindow, mInvokeWidget, mInvokeSrcPositions[0], (int32)mInvokeWidget.mWidth, (int32)mInvokeWidget.mHeight); mInvokeWidget.ResizeContent(true); } } else { if ((mInvokeWindow == null) || (mInvokeWindow.mRootWidget != mInvokeWidget)) delete mInvokeWidget; if (mInvokeWindow != null) { mInvokeWindow.Close(); mInvokeWindow = null; } mInvokeWidget = null; } } if (mAutoCompleteListWidget != null) { if (mAutoCompleteListWidget.mEntryList.Count > 0) { mAutoCompleteListWidget.mOwnsWindow = !IsInPanel(); mAutoCompleteListWidget.mAutoFocus = IsInPanel(); int32 windowWidth = (int32)mAutoCompleteListWidget.mMaxWidth; if (mAutoCompleteListWidget.mRightBoxAdjust != 0) windowWidth += (int32)mAutoCompleteListWidget.mRightBoxAdjust; // - GS!(16); //windowWidth += (int32)mAutoCompleteListWidget.mDocWidth; windowWidth += GS!(16); int32 contentHeight = (int32)(visibleCount * mAutoCompleteListWidget.mItemSpacing); int32 windowHeight = contentHeight + GS!(20); int32 maxWindowHeight = GetMaxWindowHeight(); bool wantScrollbar = false; if (windowHeight > maxWindowHeight) { windowHeight = maxWindowHeight; wantScrollbar = true; windowWidth += GS!(12); } contentHeight += GS!(8); mAutoCompleteListWidget.ResizeContent(windowWidth, contentHeight, wantScrollbar); if ((mInsertStartIdx != -1) && (!IsInPanel())) { UpdateWindow(ref mListWindow, mAutoCompleteListWidget, mInsertStartIdx, windowWidth, windowHeight); mAutoCompleteListWidget.mWantHeight = windowHeight; } mAutoCompleteListWidget.UpdateScrollbars(); mAutoCompleteListWidget.CenterSelection(); mAutoCompleteListWidget.UpdateWidth(); } else { if ((mListWindow == null) || (mListWindow.mRootWidget != mAutoCompleteListWidget)) { if (IsInPanel()) { gApp.mAutoCompletePanel.Unbind(this); if (mInvokeWidget != null) { if (mInvokeWidget.mParent != null) mInvokeWidget.RemoveSelf(); delete mInvokeWidget; mInvokeWidget = null; } } delete mAutoCompleteListWidget; } if (mListWindow != null) { mListWindow.Close(); mListWindow = null; } mAutoCompleteListWidget = null; } } gApp.mAutoCompletePanel.FinishBind(); SetIgnoreMove(false); if ((mAutoCompleteListWidget != null) && (!mAutoCompleteListWidget.mIsInitted)) mAutoCompleteListWidget.Init(); if ((mInvokeWidget != null) && (!mInvokeWidget.mIsInitted)) { mInvokeWidget.mSelectIdx = prevInvokeSelect; mInvokeWidget.Init(); } } public void UpdateInfo(String info) { for (var entryView in info.Split('\n')) { StringView entryType = StringView(entryView); int tabPos = entryType.IndexOf('\t'); StringView entryDisplay = default; if (tabPos != -1) { entryDisplay = StringView(entryView, tabPos + 1); entryType = StringView(entryType, 0, tabPos); } StringView documentation = default; int docPos = entryDisplay.IndexOf('\x03'); if (docPos != -1) { documentation = StringView(entryDisplay, docPos + 1); entryDisplay = StringView(entryDisplay, 0, docPos); } StringView entryInsert = default; tabPos = entryDisplay.IndexOf('\t'); if (tabPos != -1) { entryInsert = StringView(entryDisplay, tabPos + 1); entryDisplay = StringView(entryDisplay, 0, tabPos); } int entryIdx = 0; switch (entryType) { case "insertRange": case "invoke": case "invoke_cur": case "isMember": case "invokeInfo": case "invokeLeftParen": case "select": default: { if ((!documentation.IsEmpty) && (mAutoCompleteListWidget != null)) { while (entryIdx < mAutoCompleteListWidget.mEntryList.Count) { let entry = mAutoCompleteListWidget.mEntryList[entryIdx]; if ((entry.mEntryDisplay == entryDisplay) && (entry.mEntryType == entryType)) { if (entry.mDocumentation == null) entry.mDocumentation = new:(mAutoCompleteListWidget.[Friend]mAlloc) String(documentation); break; } entryIdx++; } } } } if (mAutoCompleteListWidget != null) mAutoCompleteListWidget.UpdateWidth(); } MarkDirty(); //Debug.WriteLine("UpdateInfo {0} {1}", mInsertStartIdx, mInsertEndIdx); } public void SetInfo(String info, bool clearList = true, int32 textOffset = 0, bool changedAfterInfo = false) { scope AutoBeefPerf("AutoComplete.SetInfo"); DeleteAndNullify!(mInfoFilter); mPopulating = true; //defer { mPopulating = false; }; Debug.Assert(!mClosed); mIsFixit = false; mInsertStartIdx = -1; mInsertEndIdx = -1; delete mInvokeSrcPositions; mInvokeSrcPositions = null; mUncertain = false; if (clearList) { if (mAutoCompleteListWidget != null) { mAutoCompleteListWidget.mIgnoreMove++; if (IsInPanel()) { mAutoCompleteListWidget.RemoveSelf(); delete mAutoCompleteListWidget; } else if (mListWindow != null) { // Will get deleted later... Debug.Assert(mListWindow.mRootWidget == mAutoCompleteListWidget); } else delete mAutoCompleteListWidget; mAutoCompleteListWidget = null; } } if (mAutoCompleteListWidget == null) { mAutoCompleteListWidget = new AutoCompleteListWidget(this); //Debug.WriteLine("Created mAutoCompleteListWidget {} in {}", mAutoCompleteListWidget, this); } bool queueClearInvoke = false; if (queueClearInvoke) { mInvokeSrcPositions = null; } else { if (IsInPanel()) { if (mInvokeWidget != null) { mInvokeWidget.RemoveSelf(); delete mInvokeWidget; } } mInvokeWidget = new InvokeWidget(this); } InvokeWidget oldInvokeWidget = null; String selectString = null; for (var entryView in info.Split('\n')) { Image entryIcon = null; StringView entryType = StringView(entryView); int tabPos = entryType.IndexOf('\t'); StringView entryDisplay = default; if (tabPos != -1) { entryDisplay = StringView(entryView, tabPos + 1); entryType = StringView(entryType, 0, tabPos); } StringView documentation = default; int docPos = entryDisplay.IndexOf('\x03'); if (docPos != -1) { documentation = StringView(entryDisplay, docPos + 1); entryDisplay = StringView(entryDisplay, 0, docPos); } StringView entryInsert = default; tabPos = entryDisplay.IndexOf('\t'); if (tabPos != -1) { entryInsert = StringView(entryDisplay, tabPos + 1); entryDisplay = StringView(entryDisplay, 0, tabPos); } if (entryDisplay.Ptr == null) { if (entryView == "uncertain") { mUncertain = true; } continue; } switch (entryType) { case "method": entryIcon = DarkTheme.sDarkTheme.GetImage(.Method); case "field": entryIcon = DarkTheme.sDarkTheme.GetImage(.Field); case "property": entryIcon = DarkTheme.sDarkTheme.GetImage(.Property); case "namespace": entryIcon = DarkTheme.sDarkTheme.GetImage(.Namespace); case "class": entryIcon = DarkTheme.sDarkTheme.GetImage(.Type_Class); case "interface": entryIcon = DarkTheme.sDarkTheme.GetImage(.Interface); case "valuetype": entryIcon = DarkTheme.sDarkTheme.GetImage(.Type_ValueType); case "object": entryIcon = DarkTheme.sDarkTheme.GetImage(.IconObject); case "pointer": entryIcon = DarkTheme.sDarkTheme.GetImage(.IconPointer); case "value": entryIcon = DarkTheme.sDarkTheme.GetImage(.IconValue); case "payloadEnum": entryIcon = DarkTheme.sDarkTheme.GetImage(.IconPayloadEnum); case "generic": //TODO: make icon entryIcon = DarkTheme.sDarkTheme.GetImage(.IconValue); case "folder": entryIcon = DarkTheme.sDarkTheme.GetImage(.ProjectFolder); case "file": entryIcon = DarkTheme.sDarkTheme.GetImage(.Document); } bool isInvoke = false; switch (entryType) { case "insertRange": { //var infoSections = scope List(entryDisplay.Split(' ')); int spacePos = entryDisplay.IndexOf(' '); if (spacePos != -1) { String str = scope String(); //infoSections[0].ToString(str); str.Append(StringView(entryDisplay, 0, spacePos)); mInsertStartIdx = int32.Parse(str).Get() + textOffset; str.Clear(); str.Append(StringView(entryDisplay, spacePos + 1)); //infoSections[1].ToString(str); mInsertEndIdx = int32.Parse(str).Get(); if (mInsertEndIdx != -1) mInsertEndIdx += textOffset; } } case "invoke": { isInvoke = true; } case "invoke_cur": // Only use the "invoke_cur" if we don't already have an invoke widget if ((mInvokeWidget == null) || (mInvokeWidget.mEntryList.Count <= 1)) { isInvoke = true; } else { for (int32 invokeIdx = 0; invokeIdx < mInvokeWidget.mEntryList.Count; invokeIdx++) { var invokeEntry = mInvokeWidget.mEntryList[invokeIdx]; if (invokeEntry.mText == entryDisplay) { mInvokeWidget.mSelectIdx = invokeIdx; } } } break; case "isMember": mIsMember = true; case "invokeInfo": { String invokeStr = scope String(entryDisplay); var infoSections = scope List(invokeStr.Split(' ')); var str = scope String(); infoSections[0].ToString(str); mInvokeWidget.mSelectIdx = int32.Parse(str); mInvokeSrcPositions = new List(); for (int32 i = 1; i < infoSections.Count; i++) { str.Clear(); infoSections[i].ToString(str); mInvokeSrcPositions.Add(int32.Parse(str)); } } case "invokeLeftParen": { mInvokeWidget.mLeftParenIdx = int32.Parse(entryDisplay); } case "select": { selectString = scope:: String(entryDisplay); } default: { if (!mInvokeOnly) { mIsFixit |= entryType == "fixit"; mAutoCompleteListWidget.AddEntry(entryType, entryDisplay, entryIcon, entryInsert, documentation); } } } if (isInvoke) { if (queueClearInvoke) { oldInvokeWidget = mInvokeWidget; mInvokeWidget = new InvokeWidget(this); queueClearInvoke = false; } var invokeEntry = new InvokeWidget.Entry(); invokeEntry.mText = new String(entryDisplay); if (!documentation.IsEmpty) invokeEntry.mDocumentation = new String(documentation); mInvokeWidget.AddEntry(invokeEntry); } } if (oldInvokeWidget != null) { /*if ((!mIsAsync) || (oldInvokeWidget.mLeftParenIdx == mInvokeWidget.mLeftParenIdx)) { // If it's not another embedded invoke then just get rid of it delete oldInvokeWidget; } else { mInvokeStack.Add(oldInvokeWidget); oldInvokeWidget.Cleanup(); }*/ } if (changedAfterInfo) { GetAsyncTextPos(); } if ((mInvokeWidget != null) && (mInvokeSrcPositions != null)) { int invokeLine = 0; int invokeColumn = 0; mTargetEditWidget.Content.GetLineCharAtIdx(mInvokeSrcPositions[0], out invokeLine, out invokeColumn); int insertLine = 0; int insertColumn = 0; mTargetEditWidget.Content.GetLineCharAtIdx(mTargetEditWidget.Content.CursorTextPos, out insertLine, out insertColumn); if ((insertLine != invokeLine) && ((insertLine - invokeLine) * gApp.mCodeFont.GetHeight() < GS!(40))) mInvokeWidget.mIsAboveText = true; } mInfoFilter = new String(); GetFilter(mInfoFilter); UpdateData(selectString, changedAfterInfo); mPopulating = false; //Debug.WriteLine("SetInfo {0} {1}", mInsertStartIdx, mInsertEndIdx); } public bool HasSelection() { return mAutoCompleteListWidget != null; } public bool IsShowing() { return (mInvokeWidget != null) || (mAutoCompleteListWidget != null); } public bool IsInPanel() { return (gApp.mAutoCompletePanel != null) && (this == gApp.mAutoCompletePanel.mAutoComplete); } public void Close(bool deleteSelf = true) { Debug.Assert(!mPopulating); if (!mClosed) { if ((DarkTooltipManager.sTooltip != null) && (DarkTooltipManager.sTooltip.mAllowMouseOutside)) DarkTooltipManager.CloseTooltip(); if (IsInPanel()) { gApp.mAutoCompletePanel.Unbind(this); if (mTargetEditWidget.mWidgetWindow != null) mTargetEditWidget.SetFocus(); } if (deleteSelf) { BFApp.sApp.DeferDelete(this); } mClosed = true; if (mInvokeStack != null) { for (var oldInvoke in mInvokeStack) delete oldInvoke; } if (mOnClosed.HasListeners) mOnClosed(); if ((mAutoCompleteListWidget != null) && (mAutoCompleteListWidget.mWidgetWindow == null)) { mAutoCompleteListWidget.Cleanup(); if (mListWindow?.mRootWidget == mAutoCompleteListWidget) mListWindow.mRootWidget = null; if (IsInPanel()) gApp.mAutoCompletePanel.Unbind(this); delete mAutoCompleteListWidget; } if (mListWindow != null) { mListWindow.Dispose(); mListWindow = null; } if ((mInvokeWidget != null) && (mInvokeWidget.mWidgetWindow == null)) { mInvokeWidget.Cleanup(); if (mInvokeWindow?.mRootWidget == mInvokeWidget) mInvokeWindow.mRootWidget = null; delete mInvokeWidget; mInvokeWidget = null; } if (mInvokeWindow != null) { mInvokeWindow.Dispose(); mInvokeWindow = null; } } } public void CloseListWindow() { if (mInvokeSrcPositions == null) { Close(); return; } if (IsInPanel()) { if (mAutoCompleteListWidget != null) { mAutoCompleteListWidget.RemoveSelf(); delete mAutoCompleteListWidget; mAutoCompleteListWidget = null; } } else if (mListWindow != null) { mListWindow.Dispose(); mListWindow = null; mAutoCompleteListWidget = null; } } public void ClearAsyncEdit() { CloseListWindow(); if (mInvokeSrcPositions != null) UpdateAsyncInfo(); } public void CloseInvoke() { if (mInvokeStack.Count > 0) { delete mInvokeWidget; mInvokeWidget = mInvokeStack[mInvokeStack.Count - 1]; mInvokeStack.RemoveAt(mInvokeStack.Count - 1); UpdateAsyncInfo(); return; } Close(); } public bool IsInsertEmpty() { return mInsertStartIdx == mInsertEndIdx; } void ApplyFixit(String data) { int splitIdx = data.IndexOf('\x01'); if (splitIdx != -1) { String lhs = scope String(data, 0, splitIdx); String rhs = scope String(data, splitIdx + 1); ApplyFixit(lhs); ApplyFixit(rhs); return; } var targetSourceEditWidgetContent = mTargetEditWidget.Content as SourceEditWidgetContent; var sourceEditWidgetContent = targetSourceEditWidgetContent; var prevCursorPosition = sourceEditWidgetContent.CursorTextPos; var prevScrollPos = mTargetEditWidget.mVertPos.mDest; UndoBatchStart undoBatchStart = null; var parts = String.StackSplit!(data, '|'); String fixitKind = parts[0]; String fixitFileName = parts[1]; SourceViewPanel sourceViewPanel = IDEApp.sApp.ShowSourceFile(fixitFileName); bool focusChange = !fixitKind.StartsWith("."); var historyEntry = targetSourceEditWidgetContent.RecordHistoryLocation(); historyEntry.mNoMerge = true; if (sourceEditWidgetContent.mSourceViewPanel != sourceViewPanel) { sourceEditWidgetContent = (SourceEditWidgetContent)sourceViewPanel.GetActivePanel().EditWidget.mEditWidgetContent; undoBatchStart = new UndoBatchStart("autocomplete"); sourceEditWidgetContent.mData.mUndoManager.Add(undoBatchStart); } if (!focusChange) { if (prevScrollPos != 0) sourceEditWidgetContent.CheckRecordScrollTop(true); } int32 fixitIdx = 0; int32 fixitLen = 0; StringView fixitLocStr = parts[2]; int dashPos = fixitLocStr.IndexOf('-'); if (dashPos != -1) { fixitLen = int32.Parse(fixitLocStr.Substring(dashPos + 1)); fixitLocStr.RemoveToEnd(dashPos); } if (fixitLocStr.Contains(':')) { var splitItr = fixitLocStr.Split(':'); int32 line = int32.Parse(splitItr.GetNext().Value).Value; int32 col = int32.Parse(splitItr.GetNext().Value).Value; fixitIdx = (.)sourceEditWidgetContent.GetTextIdx(line, col); } else fixitIdx = int32.Parse(fixitLocStr).GetValueOrDefault(); int prevTextLength = sourceEditWidgetContent.mData.mTextLength; int insertCount = 0; int dataIdx = 3; while (dataIdx < parts.Count) { int32 lenAdd = 0; String fixitInsert = scope String(parts[dataIdx++]); while (dataIdx < parts.Count) { var insertStr = parts[dataIdx++]; if (insertStr.StartsWith("`")) { lenAdd = int32.Parse(StringView(insertStr, 1)); break; } fixitInsert.Append('\n'); fixitInsert.Append(insertStr); } #unwarn bool hasMore = dataIdx < parts.Count; if (sourceViewPanel != null) { if (sourceViewPanel.IsReadOnly) { gApp.Fail(scope String()..AppendF("The selected fixit cannot be applied to locked file '{}'", sourceViewPanel.mFilePath)); return; } sourceEditWidgetContent.CursorTextPos = fixitIdx; if (focusChange) sourceEditWidgetContent.EnsureCursorVisible(true, true); sourceEditWidgetContent.mSelection = null; if (fixitLen > 0) { sourceEditWidgetContent.mSelection = EditSelection(fixitIdx, fixitIdx + fixitLen); sourceEditWidgetContent.DeleteSelection(); fixitLen = 0; } if (fixitInsert.StartsWith('\n')) sourceEditWidgetContent.PasteText(fixitInsert, fixitInsert.StartsWith("\n")); else InsertImplText(sourceEditWidgetContent, fixitInsert); fixitIdx = (.)sourceEditWidgetContent.CursorTextPos; insertCount++; } } if (!focusChange) { mTargetEditWidget.VertScrollTo(prevScrollPos, true); sourceEditWidgetContent.CursorTextPos = prevCursorPosition; int addedSize = sourceEditWidgetContent.mData.mTextLength - prevTextLength; sourceEditWidgetContent.[Friend]AdjustCursorsAfterExternalEdit(fixitIdx, addedSize); } if (historyEntry != null) { // Make sure when we go back that we'll go back to the insert position int idx = gApp.mHistoryManager.mHistoryList.LastIndexOf(historyEntry); if (idx != -1) gApp.mHistoryManager.mHistoryIdx = (.)idx; } if (undoBatchStart != null) sourceEditWidgetContent.mData.mUndoManager.Add(undoBatchStart.mBatchEnd); } void InsertImplText(SourceEditWidgetContent sourceEditWidgetContent, String implText) { String implSect = scope .(); int startIdx = 0; for (int i < implText.Length) { char8 c = implText[i]; if ((c == '\a') || (c == '\t') || (c == '\b') || (c == '\r') || (c == '\f')) { implSect.Clear(); implSect.Append(implText, startIdx, i - startIdx); if (!implSect.IsEmpty) { sourceEditWidgetContent.InsertAtCursor(implSect); } if (c == '\a') // Ensure we have spacing or an open brace on the previous line { int lineNum = sourceEditWidgetContent.CursorLineAndColumn.mLine; if (lineNum > 0) { sourceEditWidgetContent.GetLinePosition(lineNum - 1, var lineStart, var lineEnd); for (int idx = lineEnd; idx >= lineStart; idx--) { let charData = sourceEditWidgetContent.mData.mText[idx]; if (charData.mDisplayTypeId == (.)SourceElementType.Comment) continue; if (charData.mChar.IsWhiteSpace) continue; if (charData.mChar == '{') break; // Add new line sourceEditWidgetContent.InsertAtCursor("\n"); sourceEditWidgetContent.CursorToLineEnd(); break; } } } else if (c == '\f') // Make sure we're on an empty line { if (!sourceEditWidgetContent.IsLineWhiteSpace(sourceEditWidgetContent.CursorLineAndColumn.mLine)) { sourceEditWidgetContent.InsertAtCursor("\n"); } if (!sourceEditWidgetContent.IsLineWhiteSpace(sourceEditWidgetContent.CursorLineAndColumn.mLine)) { int prevPos = sourceEditWidgetContent.CursorTextPos; sourceEditWidgetContent.InsertAtCursor("\n"); sourceEditWidgetContent.CursorTextPos = prevPos; } sourceEditWidgetContent.CursorToLineEnd(); } else if (c == '\t') // Open block { sourceEditWidgetContent.InsertAtCursor("\n"); sourceEditWidgetContent.CursorToLineEnd(); sourceEditWidgetContent.OpenCodeBlock(); } else if (c == '\r') // Newline { sourceEditWidgetContent.InsertAtCursor("\n"); sourceEditWidgetContent.CursorToLineEnd(); } else if (c == '\b') // Close block { int cursorPos = sourceEditWidgetContent.CursorTextPos; while (cursorPos < sourceEditWidgetContent.mData.mTextLength) { char8 checkC = sourceEditWidgetContent.mData.mText[cursorPos].mChar; cursorPos++; if (checkC == '}') break; } sourceEditWidgetContent.CursorTextPos = cursorPos; } else { let lc = sourceEditWidgetContent.CursorLineAndColumn; sourceEditWidgetContent.CursorLineAndColumn = .(lc.mLine + 1, 0); sourceEditWidgetContent.CursorToLineEnd(); sourceEditWidgetContent.InsertAtCursor("\n"); sourceEditWidgetContent.CursorToLineEnd(); } startIdx = i + 1; } } implSect.Clear(); implSect.Append(implText, startIdx, implText.Length - startIdx); if (!implSect.IsEmpty) { sourceEditWidgetContent.InsertAtCursor(implSect); } } public void InsertSelection(char32 keyChar, String insertType = null, String insertStr = null) { //Debug.WriteLine("InsertSelection"); EditSelection editSelection = EditSelection(); editSelection.mStartPos = mInsertStartIdx; if (mInsertEndIdx != -1) editSelection.mEndPos = mInsertEndIdx; else editSelection.mEndPos = mInsertStartIdx; var entry = mAutoCompleteListWidget.mEntryList[mAutoCompleteListWidget.mSelectIdx]; if (keyChar == '!') { if (!entry.mEntryDisplay.EndsWith("!")) { // Try to find one that DOES end with a '!' for (var checkEntry in mAutoCompleteListWidget.mEntryList) { if (checkEntry.mEntryDisplay.EndsWith("!")) entry = checkEntry; } } } if (insertStr != null) insertStr.Append(entry.mEntryInsert ?? entry.mEntryDisplay); if (entry.mEntryType == "fixit") { if (insertType != null) insertType.Append(entry.mEntryType); ApplyFixit(entry.mEntryInsert); return; } String insertText = entry.mEntryInsert ?? entry.mEntryDisplay; if ((keyChar == '=') && (insertText.EndsWith("="))) insertText.RemoveToEnd(insertText.Length - 1); //insertText = insertText.Substring(0, insertText.Length - 1); String implText = null; int tabIdx = insertText.IndexOf('\t'); if (tabIdx != -1) { implText = scope:: String(); implText.Append(insertText, tabIdx); insertText.RemoveToEnd(tabIdx); } String prevText = scope String(); mTargetEditWidget.Content.ExtractString(editSelection.mStartPos, editSelection.mEndPos - editSelection.mStartPos, prevText); //sAutoCompleteMRU[insertText] = sAutoCompleteIdx++; String* keyPtr; int32* valuePtr; if (sAutoCompleteMRU.TryAdd(entry.mEntryDisplay, out keyPtr, out valuePtr)) { // Only create new string if this entry doesn't exist already *keyPtr = new String(entry.mEntryDisplay); } *valuePtr = sAutoCompleteIdx++; if (insertText == prevText) return; var sourceEditWidgetContent = mTargetEditWidget.Content as SourceEditWidgetContent; PersistentTextPosition[] persistentInvokeSrcPositons = null; if (mInvokeSrcPositions != null) { persistentInvokeSrcPositons = scope:: PersistentTextPosition[mInvokeSrcPositions.Count]; for (int32 i = 0; i < mInvokeSrcPositions.Count; i++) { persistentInvokeSrcPositons[i] = new PersistentTextPosition(mInvokeSrcPositions[i]); sourceEditWidgetContent.PersistentTextPositions.Add(persistentInvokeSrcPositons[i]); } } mTargetEditWidget.Content.mSelection = editSelection; //bool isMethod = (entry.mEntryType == "method"); if (insertText.EndsWith("<>")) { if (keyChar == '\t') mTargetEditWidget.Content.InsertCharPair(insertText); else if (keyChar == '<') { String str = scope String(); str.Append(insertText, 0, insertText.Length - 2); mTargetEditWidget.Content.InsertAtCursor(str, .NoRestoreSelectionOnUndo); } else mTargetEditWidget.Content.InsertAtCursor(insertText, .NoRestoreSelectionOnUndo); } else mTargetEditWidget.Content.InsertAtCursor(insertText, .NoRestoreSelectionOnUndo); /*if (mIsAsync) UpdateAsyncInfo();*/ if (implText != null) InsertImplText(sourceEditWidgetContent, implText); if (persistentInvokeSrcPositons != null) { for (int32 i = 0; i < mInvokeSrcPositions.Count; i++) { //TEST //var persistentTextPositon = persistentInvokeSrcPositons[i + 100]; var persistentTextPositon = persistentInvokeSrcPositons[i]; mInvokeSrcPositions[i] = persistentTextPositon.mIndex; sourceEditWidgetContent.PersistentTextPositions.Remove(persistentTextPositon); delete persistentTextPositon; } } mTargetEditWidget.Content.EnsureCursorVisible(); if ((insertType != null) && (insertText.Length > 0)) insertType.Append(entry.mEntryType); } public void MarkDirty() { if (mInvokeWidget != null) mInvokeWidget.MarkDirty(); if (mAutoCompleteListWidget != null) mAutoCompleteListWidget.MarkDirty(); } int32 GetMaxWindowHeight() { return (int32)(9 * mAutoCompleteListWidget.mItemSpacing) + GS!(20); } } }