using System; using System.Collections.Generic; using System.Text; using System.Threading.Tasks; using Beefy; using Beefy.widgets; using Beefy.theme; using Beefy.theme.dark; using Beefy.events; using Beefy.utils; using IDE.ui; using System.Diagnostics; namespace IDE.ui { public class QuickFind : Widget { enum FindType { CurrentFile, Selection, } public class FindEdit : DarkEditWidget { QuickFind mQuickFind; int32 mFoundVersion; public this(QuickFind quickFind) { mQuickFind = quickFind; } public override void KeyDown(KeyCode keyCode, bool isRepeat) { if (keyCode == KeyCode.Escape) { mQuickFind.Close(); return; } base.KeyDown(keyCode, isRepeat); } public override void KeyChar(char32 theChar) { /*if ((theChar == '\r') || (theChar == '\n')) { mQuickFind.FindNext(); return; } */ if (theChar == '\t') return; base.KeyChar(theChar); } public override void Update() { base.Update(); if (Content.mData.mCurTextVersionId != mFoundVersion) { mFoundVersion = Content.mData.mCurTextVersionId; mQuickFind.mCurFindIdx = -1; mQuickFind.FindAll(); mQuickFind.ShowCurrentSelection(); } } } public TextPanel mPanel; public EditWidget mEditWidget; public EditWidget mFindEditWidget; public EditWidget mReplaceEditWidget; public int32 mLastActiveCursorPos; bool mHasNewActiveCursorPos; public int32 mCurFindIdx = -1; public int32 mCurFindStart = 0; public int32 mCurFindCount; public int32 mCurFindDir; //public bool mSearchDidWrap; public bool mIsReplace; public int32 mLastTextVersion; bool mFoundMatches; bool mIsShowingMatches = false; static String sLastSearchString = new String() ~ delete _; public bool mOwnsSelection; PersistentTextPosition mSelectionStart ~ { Debug.Assert(_ == null); }; PersistentTextPosition mSelectionEnd ~ { Debug.Assert(_ == null); }; public this(Widget parent, EditWidget editWidget, bool isReplace) { mPanel = parent as TextPanel; mEditWidget = editWidget; parent.AddWidget(this); mIsReplace = isReplace; UpdateCursorPos(); mFindEditWidget = new FindEdit(this); mFindEditWidget.Content.SelectAll(); mFindEditWidget.mOnKeyDown.Add(new => KeyDownHandler); AddWidget(mFindEditWidget); if (isReplace) { mReplaceEditWidget = new FindEdit(this); mReplaceEditWidget.mOnKeyDown.Add(new => KeyDownHandler); AddWidget(mReplaceEditWidget); } mLastTextVersion = mEditWidget.Content.mData.mCurTextVersionId; var content = editWidget.Content; var sourceContent = editWidget.Content as SourceEditWidgetContent; if (content.HasSelection()) { int selStart = content.mSelection.Value.MinPos; int selEnd = content.mSelection.Value.MaxPos; bool isMultiline = false; for (int i = selStart; i < selEnd; i++) { if (content.mData.mText[i].mChar == '\n') { isMultiline = true; break; } } if (isMultiline) { Debug.Assert(mSelectionStart == null); mSelectionStart = new PersistentTextPosition((int32)selStart); mSelectionEnd = new PersistentTextPosition((int32)selEnd); if (sourceContent != null) { sourceContent.PersistentTextPositions.Add(mSelectionStart); sourceContent.PersistentTextPositions.Add(mSelectionEnd); } else mOwnsSelection = true; } else { String text = scope String(); content.ExtractString(selStart, selEnd - selStart, text); text.Trim(); mFindEditWidget.SetText(text); mFindEditWidget.Content.SelectAll(); } } } public ~this() { } void UpdateCursorPos() { if (mEditWidget.mHasFocus) { mHasNewActiveCursorPos = true; mLastActiveCursorPos = (int32)mEditWidget.Content.CursorTextPos; if (mEditWidget.Content.HasSelection()) mLastActiveCursorPos = (int32)mEditWidget.Content.mSelection.Value.MinPos; } } void DoReplace(bool all) { int32 replaceCount = -1; if (all) { mEditWidget.SetFocus(); replaceCount = Replace(); if (replaceCount > 0) IDEApp.sApp.MessageDialog("Replace Results", StackStringFormat!("{0} instance(s) replaced.", replaceCount)); } else { //replaceCount = Replace((byte)SourceElementFlags.Find_CurrentSelection); var content = mEditWidget.Content; String replaceText = scope String(); mReplaceEditWidget.GetText(replaceText); bool hasMatch = false; if (content.HasSelection()) { String selText = scope String(); content.GetSelectionText(selText); String findText = scope String(); mFindEditWidget.GetText(findText); if (selText == findText) hasMatch = true; } if (hasMatch) { content.InsertAtCursor(replaceText); mCurFindIdx = (int32)content.CursorTextPos - 1; } FindNext(1, false); } if (replaceCount != -1) { if (replaceCount == 0) IDEApp.sApp.Fail("The search text wasn't found."); mWidgetWindow.mIsKeyDownHandled = true; } } void KeyDownHandler(KeyDownEvent evt) { if (evt.mKeyCode == KeyCode.Escape) Close(); if (evt.mKeyCode == KeyCode.Return) { if (evt.mSender == mFindEditWidget) { FindNext(1, true); } else if (evt.mSender == mReplaceEditWidget) { DoReplace(false); } } if (evt.mKeyCode == KeyCode.Tab) { IDEUtils.SelectNextChildWidget(this, mWidgetWindow.IsKeyDown(KeyCode.Shift)); } //int replaceCount = -1; if ((evt.mKeyCode == (KeyCode)'R') && (mWidgetWindow.IsKeyDown(KeyCode.Menu))) DoReplace(false); if ((evt.mKeyCode == (KeyCode)'A') && (mWidgetWindow.IsKeyDown(KeyCode.Menu))) DoReplace(true); } void EditWidgetSubmit(EditEvent editEvent) { //FindNext(true); } public bool ClearFlags(bool clearMatches, bool clearSelection) { uint8 mask = 0xFF; if (clearMatches) mask = (uint8)(mask & ~(uint8)(SourceElementFlags.Find_Matches)); if (clearSelection) mask = (uint8)(mask & ~(uint8)SourceElementFlags.Find_CurrentSelection); bool foundFlags = false; var text = mEditWidget.Content.mData.mText; for (int32 i = 0; i < text.Count; i++) { if ((text[i].mDisplayFlags & ~mask) != 0) { text[i].mDisplayFlags = (uint8)(text[i].mDisplayFlags & mask); foundFlags = true; } } return foundFlags; } public void FindAll() { mIsShowingMatches = true; mFoundMatches = false; mCurFindStart = mLastActiveCursorPos; mCurFindIdx = mCurFindStart - 1; mCurFindCount = 0; //mSearchDidWrap = false; ClearFlags(true, true); FindNext(1, true, ErrorReportType.None); int32 curFindIdx = mCurFindIdx; while (FindNext(1, false, ErrorReportType.None)) { } mCurFindIdx = curFindIdx; mCurFindCount = 0; } public void ShowCurrentSelection() { if (mCurFindIdx == -1) return; if (!mFoundMatches) return; String findText = scope String(); mFindEditWidget.GetText(findText); if (findText.Length == 0) return; var editWidgetContent = mEditWidget.Content; editWidgetContent.MoveCursorToIdx(mCurFindIdx + (int32)findText.Length, true); for (int32 idx = mCurFindIdx; idx < mCurFindIdx + findText.Length; idx++) { uint8 flags = (uint8)SourceElementFlags.Find_CurrentSelection; mEditWidget.Content.mData.mText[idx].mDisplayFlags = (uint8)(mEditWidget.Content.mData.mText[idx].mDisplayFlags | flags); } if ((mSelectionStart == null) || (mParent == null)) { /*if (mFoundMatches) editWidgetContent.mSelection = EditSelection(mCurFindIdx, mCurFindIdx + (int32)findText.Length); else if (!String.IsNullOrWhiteSpace(findText)) editWidgetContent.mSelection = null;*/ } if (mHasNewActiveCursorPos) { if (mPanel != null) mPanel.RecordHistoryLocation(); mHasNewActiveCursorPos = false; } } public void FindNext(int32 dir, bool showMessage) { var editWidgetContent = mEditWidget.Content; if (!mIsShowingMatches) { // This only happens after we close the quickfind and we need to reshow the matches if (sLastSearchString.Length > 0) mFindEditWidget.SetText(sLastSearchString); mCurFindIdx = (int32)editWidgetContent.CursorTextPos; int32 curFindIdx = mCurFindIdx; FindAll(); mCurFindIdx = curFindIdx; } if (!mFoundMatches) { IDEApp.sApp.Fail("The search text wasn't found."); return; } if (FindNext(dir, true, showMessage ? ErrorReportType.MessageBox : ErrorReportType.Sound)) { ClearFlags(false, true); ShowCurrentSelection(); } } public enum ErrorReportType { None, Sound, MessageBox } void ShowDoneError(ErrorReportType errorType) { if (errorType != ErrorReportType.None) { IDEApp.Beep(IDEApp.MessageBeepType.Information); if (errorType == ErrorReportType.MessageBox) IDEApp.sApp.MessageDialog("Search Done", "Find reached the starting point of the search."); } } public bool FindNext(int32 dir, bool isSelection, ErrorReportType errorType) { var editContent = mEditWidget.Content; String findText = scope String(); mFindEditWidget.GetText(findText); sLastSearchString.Set(findText); if (findText.Length == 0) return false; String findTextLower = scope String(findText); findTextLower.ToLower(); String findTextUpper = scope String(findText); findTextUpper.ToUpper(); if ((mCurFindIdx == -1) && (mSelectionStart != null)) { mCurFindIdx = mSelectionStart.mIndex - 1; } int32 selStart = (mSelectionStart != null) ? mSelectionStart.mIndex : 0; int32 selEnd = (mSelectionEnd != null) ? mSelectionEnd.mIndex : editContent.mData.mTextLength; mCurFindStart = Math.Max(mCurFindStart, selStart); int32 nextIdx = -1; int32 searchStartIdx; if (dir < 0) { if (mCurFindIdx == -1) { searchStartIdx = selEnd + dir - (int32)findText.Length; } else searchStartIdx = mCurFindIdx + dir; /*if (searchStartIdx < selStart) searchStartIdx = selStart;*/ } else { searchStartIdx = mCurFindIdx + dir; /*if (searchStartIdx < selStart) searchStartIdx = selStart;*/ } /*if ((searchStartIdx == mCurFindStart) && (mCurFindCount > 0)) { mCurFindCount = 0; return false; }*/ //for (int startIdx = searchStartIdx; startIdx <= selEnd - findText.Length; startIdx++) for (int32 startIdx = searchStartIdx; true; startIdx += dir) { if (startIdx > selEnd - findText.Length) break; if (startIdx < selStart) break; bool isEqual = true; for (int32 i = 0; i < findText.Length; i++) { if ((editContent.mData.mText[i + startIdx].mChar != findTextLower[i]) && (editContent.mData.mText[i + startIdx].mChar != findTextUpper[i])) { isEqual = false; break; } } if (isEqual) { nextIdx = startIdx; break; } } /*if (nextIdx == mCurFindIdx - 1) { int a = 0; }*/ if (mCurFindDir != dir) { mCurFindDir = dir; mCurFindCount = 0; mCurFindStart = mCurFindIdx; } if (dir < 0) { int32 checkOfs = (mCurFindCount == 0) ? 1 : 0; if ((searchStartIdx >= mCurFindStart + checkOfs) && ((nextIdx == -1) || (nextIdx <= mCurFindStart))) { if (isSelection) ShowDoneError(errorType); mCurFindIdx = mCurFindStart + 1; mCurFindCount = 0; return false; } } else { int32 checkOfs = (mCurFindCount == 0) ? -1 : 0; if ((searchStartIdx <= mCurFindStart + checkOfs) && ((nextIdx == -1) || (nextIdx >= mCurFindStart))) { if (isSelection) ShowDoneError(errorType); mCurFindIdx = mCurFindStart - 1; mCurFindCount = 0; return false; } } /*if ((mSelectionEnd != null) && (nextIdx + findText.Length > mSelectionEnd.mIndex)) return false;*/ //nextIdx = -1; if (nextIdx != -1) { mCurFindCount++; mFoundMatches = true; for (int32 idx = nextIdx; idx < nextIdx + findText.Length; idx++) { uint8 flags = (uint8)SourceElementFlags.Find_Matches; /*if (isSelection) flags |= (uint8)SourceElementFlags.Find_CurrentSelection;*/ mEditWidget.Content.mData.mText[idx].mDisplayFlags = (uint8)(mEditWidget.Content.mData.mText[idx].mDisplayFlags | flags); } mCurFindIdx = nextIdx; return true; } else { //if (isSelection) { //if (doError) //IDEApp.MessageBeep(IDEApp.MessageBeepType.Information); //return false; } } mCurFindIdx = -1; if (mCurFindStart == selStart) { // Special case for searching from start if (isSelection) { ShowDoneError(errorType); mCurFindCount = 0; } return false; } mCurFindIdx = -1; sReentryCount++; if (sReentryCount > 10) { Runtime.FatalError("Too many iterations!"); } var result = FindNext(dir, isSelection, errorType); sReentryCount--; return result; } static int32 sReentryCount; public int32 Replace() { scope AutoBeefPerf("QuickFind.Replace"); int32 searchCount = 0; int32 searchStart = 0; String findText = scope String(); mFindEditWidget.GetText(findText); String replaceText = scope String(); mReplaceEditWidget.GetText(replaceText); UndoBatchStart undoBatchStart = null; var ewc = mEditWidget.Content; //Profiler.StartSampling(); while (true) { int32 selEnd = -1; int32 selStart = -1; var text = mEditWidget.Content.mData.mText; for (int32 i = searchStart; i < mEditWidget.Content.mData.mTextLength; i++) { if ((text[i].mDisplayFlags & (uint8)SourceElementFlags.Find_Matches) != 0) { if (selStart == -1) selStart = i; selEnd = i; } else if (selEnd != -1) break; } if (selStart == -1) break; int32 selLen = selEnd - selStart + 1; Debug.Assert(selLen % findText.Length == 0); selEnd = selStart + (int32)findText.Length - 1; if (searchCount == 0) { undoBatchStart = new UndoBatchStart("replace"); ewc.mData.mUndoManager.Add(undoBatchStart); } EditSelection selection = EditSelection(); selection.mStartPos = selStart; selection.mEndPos = selEnd + 1; ewc.mSelection = selection; EditWidgetContent.InsertFlags insertFlags = .NoMoveCursor | .NoRestoreSelectionOnUndo | .IsGroupPart; if (searchCount == 0) insertFlags |= .IsGroupStart; ewc.InsertAtCursor(replaceText, insertFlags); searchStart = selStart + (int32)replaceText.Length; searchCount++; /*if (flags == (byte)SourceElementFlags.Find_CurrentSelection) { mLastTextVersion = mEditWidget.Content.mCurTextVersionId; mCurFindIdx = searchStart; FindNext(true, ErrorReportType.MessageBox); break; }*/ } //Profiler.StopSampling(); if (undoBatchStart != null) ewc.mData.mUndoManager.Add(undoBatchStart.mBatchEnd); return searchCount; } public void UpdateData() { if (mLastTextVersion != mEditWidget.Content.mData.mCurTextVersionId) { if (mIsShowingMatches) FindAll(); mLastTextVersion = mEditWidget.Content.mData.mCurTextVersionId; } UpdateCursorPos(); } public override void Draw(Beefy.gfx.Graphics g) { base.Draw(g); using (g.PushColor(0xFFFFFFFF)) g.DrawBox(DarkTheme.sDarkTheme.GetImage(DarkTheme.ImageIdx.Menu), 0, 0, mWidth - GS!(8), mHeight - GS!(8)); } public override void DrawAll(Beefy.gfx.Graphics g) { base.DrawAll(g); var findText = scope String(); mFindEditWidget.GetText(findText); var replaceText = scope String(); if (mReplaceEditWidget != null) mReplaceEditWidget.GetText(replaceText); if ((!mFoundMatches) && (!String.IsNullOrWhiteSpace(findText))) { using (g.PushColor(0xFFFF0000)) g.OutlineRect(mFindEditWidget.mX, mFindEditWidget.mY, mFindEditWidget.mWidth, mFindEditWidget.mHeight); } if ((!mFindEditWidget.mHasFocus) && (findText.Length == 0)) { using (g.PushColor(0xFF606060)) g.DrawString("Find...", mFindEditWidget.mX + GS!(4), mFindEditWidget.mY); } if ((mReplaceEditWidget != null) && (!mReplaceEditWidget.mHasFocus) && (replaceText.Length == 0)) { using (g.PushColor(0xFF606060)) g.DrawString("Replace...", mReplaceEditWidget.mX + 4, mReplaceEditWidget.mY); } using (g.PushColor(0xFF000000)) { g.DrawString((mSelectionStart != null) ? "Selection" : "Current Document", GS!(6), mHeight - GS!(30)); } } // We leave a pointer to this in the TextPanel after we close it so we can still use F3 after its closed public bool Close() { bool didSomething = false; var sourceContent = mEditWidget.Content as SourceEditWidgetContent; if (mSelectionStart != null) { if (sourceContent != null) sourceContent.PersistentTextPositions.Remove(mSelectionStart); DeleteAndNullify!(mSelectionStart); } if (mSelectionEnd != null) { if (sourceContent != null) sourceContent.PersistentTextPositions.Remove(mSelectionEnd); DeleteAndNullify!(mSelectionEnd); } mIsShowingMatches = false; didSomething |= ClearFlags(true, true); if (mWidgetWindow != null) { bool hadFocus = (mWidgetWindow.mFocusWidget != null) && (mWidgetWindow.mFocusWidget.HasParent(this)); didSomething = true; if (hadFocus) mWidgetWindow.SetFocus(mEditWidget); RemoveSelf(); } return didSomething; } public override void Resize(float x, float y, float width, float height) { base.Resize(x, y, width, height); mFindEditWidget.Resize(GS!(6), GS!(6), width - GS!(20), GS!(22)); if (mReplaceEditWidget != null) mReplaceEditWidget.Resize(GS!(6), GS!(6 + 22 + 4), width - GS!(20), GS!(22)); } public void ResizeSelf() { if (mParent == null) return; float findWidth = GS!(200); float findHeight = mIsReplace ? GS!(80) : GS!(56); var editWidget = mEditWidget; float x = mParent.mWidth - findWidth; if (mEditWidget.mVertScrollbar != null) x -= GS!(10); Resize(x, editWidget.mY + 2, findWidth, findHeight); } public bool HasFocus() { if (mHasFocus) return true; if (mFindEditWidget.mHasFocus) return true; if ((mReplaceEditWidget != null) && (mReplaceEditWidget.mHasFocus)) return true; return false; } } }