using System; using System.Collections.Generic; using System.Text; using System.Threading.Tasks; using System.Threading; using System.Diagnostics; using Beefy; using Beefy.widgets; using Beefy.theme; using Beefy.gfx; using Beefy.theme.dark; using Beefy.utils; using IDE.Debugger; using IDE.Compiler; namespace IDE.ui { public class SourceEditBatchHelper { SourceEditWidget mEditWidget; UndoBatchStart mUndoBatchStart; PersistentTextPosition mTrackedCursorPosition; PersistentTextPosition mSelStartPostion; PersistentTextPosition mSelEndPostion; public this(SourceEditWidget editWidget, String batchName, UndoAction firstUndoAction = null) { mEditWidget = editWidget; var editWidgetContent = (SourceEditWidgetContent)mEditWidget.Content; mUndoBatchStart = new UndoBatchStart(batchName); editWidgetContent.mData.mUndoManager.Add(mUndoBatchStart); if (firstUndoAction != null) editWidgetContent.mData.mUndoManager.Add(firstUndoAction); editWidgetContent.mData.mUndoManager.Add(new EditWidgetContent.SetCursorAction(editWidgetContent)); mTrackedCursorPosition = new PersistentTextPosition((int32)editWidgetContent.CursorTextPos); editWidgetContent.PersistentTextPositions.Add(mTrackedCursorPosition); if (editWidgetContent.HasSelection()) { mSelStartPostion = new PersistentTextPosition(editWidgetContent.mSelection.Value.mStartPos); editWidgetContent.PersistentTextPositions.Add(mSelStartPostion); mSelEndPostion = new PersistentTextPosition(editWidgetContent.mSelection.Value.mEndPos); editWidgetContent.PersistentTextPositions.Add(mSelEndPostion); } editWidgetContent.mSelection = null; } public void Finish(UndoAction lastUndoAction = null) { var editWidgetContent = (SourceEditWidgetContent)mEditWidget.Content; editWidgetContent.CursorTextPos = mTrackedCursorPosition.mIndex; editWidgetContent.PersistentTextPositions.Remove(mTrackedCursorPosition); delete mTrackedCursorPosition; if (mSelStartPostion != null) { editWidgetContent.mSelection = EditSelection(mSelStartPostion.mIndex, mSelEndPostion.mIndex); editWidgetContent.PersistentTextPositions.Remove(mSelEndPostion); delete mSelEndPostion; editWidgetContent.PersistentTextPositions.Remove(mSelStartPostion); delete mSelStartPostion; } editWidgetContent.mData.mUndoManager.Add(new EditWidgetContent.SetCursorAction(editWidgetContent)); if (lastUndoAction != null) editWidgetContent.mData.mUndoManager.Add(lastUndoAction); editWidgetContent.mData.mUndoManager.Add(mUndoBatchStart.mBatchEnd); } } public class ReplaceSourceAction : EditWidgetContent.TextAction { public String mOldText ~ delete _; public String mNewText ~ delete _; public this(EditWidgetContent editWidget, String newText, bool restoreSelectionOnUndo = true) : base(editWidget) { mNewText = newText; mOldText = new String(); editWidget.ExtractString(0, editWidget.mData.mTextLength, mOldText); mRestoreSelectionOnUndo = restoreSelectionOnUndo; } public override bool Undo() { var editWidgetContent = EditWidgetContent; var sourceEditWidgetContent = (SourceEditWidgetContent)editWidgetContent; sourceEditWidgetContent.mSourceViewPanel.ClearTrackedElements(); //int startIdx = (mSelection != null) ? mSelection.Value.MinPos : mCursorTextPos; editWidgetContent.RemoveText(0, mEditWidgetContentData.mTextLength); editWidgetContent.InsertText(0, mOldText); editWidgetContent.ContentChanged(); base.Undo(); editWidgetContent.ContentChanged(); mEditWidgetContentData.mCurTextVersionId = mPrevTextVersionId; editWidgetContent.ClampCursor(); return true; } public override bool Redo() { base.Redo(); var editWidgetContent = EditWidgetContent; var sourceEditWidgetContent = (SourceEditWidgetContent)editWidgetContent; sourceEditWidgetContent.mSourceViewPanel.ClearTrackedElements(); //int startIdx = (mSelection != null) ? mSelection.Value.MinPos : mCursorTextPos; editWidgetContent.RemoveText(0, mEditWidgetContentData.mTextLength); editWidgetContent.InsertText(0, mNewText); editWidgetContent.ContentChanged(); editWidgetContent.ClampCursor(); return true; } } public class SourceEditWidgetContent : DarkEditWidgetContent { public class Data : EditWidgetContent.Data { public List mPersistentTextPositions = new List() ~ DeleteContainerAndItems!(_); public QuickFind mCurQuickFind; // Only allow one QuickFind on this buffer at a time } struct QueuedTextEntry { public String mString; public float mX; public float mY; public uint16 mTypeIdAndFlags; public void Dispose() { delete mString; } } struct QueuedUnderlineEntry { public float mX; public float mY; public float mWidth; public uint8 mFlags; } class FastCursorState { public double mX; public double mY; public struct DrawSect { public double mX; public double mY; public float mPct; } public List mDrawSects = new List() ~ delete _; public int mUpdateCnt; } public enum AutoCompleteOptions { None = 0, HighPriority = 1, UserRequested = 2, OnlyShowInvoke = 4 } public Action mOnGenerateAutocomplete ~ delete _; public Action mOnFinishAsyncAutocomplete ~ delete _; public Action mOnCancelAsyncAutocomplete ~ delete _; public Func mOnEscape ~ delete _; // returns 'true' if did work public AutoComplete mAutoComplete ~ delete _; List mQueuedText = new List() ~ delete _; List mQueuedUnderlines = new List() ~ delete _; public bool mHadPersistentTextPositionDeletes; public SourceViewPanel mSourceViewPanel; //public bool mAsyncAutocomplete; public bool mIsInKeyChar; public bool mDbgDoTest; public int32 mCursorStillTicks; public static bool sReadOnlyErrorShown; public bool mIgnoreKeyChar; // This fixes cases where a KeyDown changes focus to us but then we get a KeyChar that doesn't belong to us public bool mIgnoreSetHistory; public static uint32[] sTextColors = new uint32[] { 0xFFFFFFFF, // Normal 0xFFE1AE9A, // Keyword 0XFFC8A0FF, // Literal 0xFFFFFFFF, // Identifier 0XFF66D9EF, // Type 0XFF75715E, // Comment 0XFFA6E22A, // Method 0XFF66D9EF, // TypeRef 0xFF7BEEB7, // Namespace 0xFFB0B0B0, // Disassembly_Text 0XFFFF0000, // Disassembly_FileName 0xFFFF0000, // Error 0xFFFF8080, // BuildError 0xFFFFFF80, // BuildWarning 0xFF9090C0, // VisibleWhiteSpace } ~ delete _; bool mHasCustomColors; FastCursorState mFastCursorState ~ delete _; public this(EditWidgetContent refContent = null) : base(refContent) { mAllowVirtualCursor = true; SetFont(IDEApp.sApp.mCodeFont, true, true); //SetFont(DarkTheme.sDarkTheme.mSmallFont, false, false); mTabSize = mFont.GetWidth(" "); mTextColors = sTextColors; mExtendDisplayFlags = (uint8)(SourceElementFlags.SpellingError | SourceElementFlags.SymbolReference); mShowLineBottomPadding = 2; } public ~this() { ClearColors(); } public override void RehupScale(float oldScale, float newScale) { base.RehupScale(oldScale, newScale); mTabSize = mFont.GetWidth(" "); } protected override EditWidgetContent.Data CreateEditData() { return new Data(); } public List PersistentTextPositions { get { return ((Data)mData).mPersistentTextPositions; } } public Data Data { get { return (Data)mData; } } [StdCall, CLink] static extern char8* BfDiff_DiffText(char8* text1, char8* text2); struct TextLineSegment { public int32 mIndex; public int32 mLength; public String mLine; } public bool Reload(String filePath, String queuedContent = null) { //TODO: Only do the 'return false' if the file doesn't actually exist. // Otherwise keep trying to reload (for a while) before giving up var text = scope String(); if (queuedContent != null) { queuedContent.MoveTo(text); } else { if (gApp.LoadTextFile(filePath, text) case .Err) { //ClearText(); return false; } } // var editWidgetContent = this; String curText = scope String(); mEditWidget.GetText(curText); char8* diffCmdsPtr = BfDiff_DiffText(curText, text); String diffCmds = scope String(); diffCmds.Append(diffCmdsPtr); UndoBatchStart undoBatchStart = new UndoBatchStart("applyDiff"); editWidgetContent.mData.mUndoManager.Add(undoBatchStart); editWidgetContent.mSelection = null; int32 curSrcLineIdx = -1; List deletedLineSegments = scope List(); String cmd = scope String(); List cmdResults = scope List(diffCmds.Split('\n')); List cmdParts = scope List(); String val = scope String(); for (var cmdView in cmdResults) { cmd.Set(cmdView); if (cmd == "") continue; //String[] cmdPart = String.StackSplit!(cmd, ' '); cmdParts.Clear(); for (var strView in cmd.Split(' ')) cmdParts.Add(strView); val.Set(cmdParts[0]); if (val == "-") { int32 pos; int32 len; val.Set(cmdParts[1]); pos = int32.Parse(val); val.Set(cmdParts[2]); len = int32.Parse(val); if (cmdParts.Count >= 4) { val.Set(cmdParts[3]); int32 deleteLineIdx= int32.Parse(val); if (deleteLineIdx == curSrcLineIdx) { curSrcLineIdx++; } else { deletedLineSegments.Clear(); } } editWidgetContent.mHadPersistentTextPositionDeletes = false; editWidgetContent.mSelection = EditSelection(pos, pos + len); editWidgetContent.DeleteSelection(); // If we have modified a section of text containing breakpoint (for example), this gets encoded by // 'adds' of the new lines and then 'removes' of the old lines. We do a Levenshtein search for // the most applicable line to bind to. if (editWidgetContent.mHadPersistentTextPositionDeletes) { var deleteSelectionAction = (EditWidgetContent.DeleteSelectionAction)editWidgetContent.mData.mUndoManager.GetLastUndoAction(); String oldLineText = deleteSelectionAction.mSelectionText; for (int32 persistentIdx = 0; persistentIdx < editWidgetContent.PersistentTextPositions.Count; persistentIdx++) { var persistentTextPosition = editWidgetContent.PersistentTextPositions[persistentIdx]; if (persistentTextPosition.mWasDeleted) { int bestDist = Int32.MaxValue; int bestSegIdx = -1; for (int segIdx = 0; segIdx < deletedLineSegments.Count; segIdx++) { var textSegment = deletedLineSegments[segIdx]; if (textSegment.mLine == null) { textSegment.mLine = scope:: String(); editWidgetContent.ExtractString(textSegment.mIndex, textSegment.mLength, textSegment.mLine); deletedLineSegments[segIdx] = textSegment; } int dist = Utils.LevenshteinDistance(oldLineText, textSegment.mLine); if (dist < bestDist) { bestDist = dist; bestSegIdx = segIdx; } } if (bestSegIdx != -1) { var textSegment = deletedLineSegments[bestSegIdx]; persistentTextPosition.mWasDeleted = false; persistentTextPosition.mIndex = textSegment.mIndex; // Find first non-whitespace for (int32 char8Idx = 0; char8Idx < textSegment.mLength; char8Idx++) { if (!((char8)editWidgetContent.mData.mText[persistentTextPosition.mIndex].mChar).IsWhiteSpace) break; persistentTextPosition.mIndex++; } deletedLineSegments.RemoveAt(bestSegIdx); } } } } } else if (val == "+") { val.Set(cmdParts[1]); int32 posTo = int32.Parse(val).Get(); val.Set(cmdParts[2]); int32 posFrom = int32.Parse(val).Get(); val.Set(cmdParts[3]); int32 len = int32.Parse(val).Get(); editWidgetContent.CursorTextPos = posTo; var subStr = scope String(); subStr.Append(text, posFrom, len); editWidgetContent.InsertAtCursor(subStr); if (cmdParts.Count >= 5) { val.Set(cmdParts[4]); int32 insertLineIdx = int32.Parse(val); if (curSrcLineIdx != insertLineIdx) { curSrcLineIdx = insertLineIdx; deletedLineSegments.Clear(); } var lineSeg = TextLineSegment(); lineSeg.mIndex = posTo; lineSeg.mLength = len; deletedLineSegments.Add(lineSeg); } } } editWidgetContent.mData.mUndoManager.Add(undoBatchStart.mBatchEnd); //QueueFullRefresh(false); var endingText = scope String(); mEditWidget.GetText(endingText); if (endingText != text) { Utils.WriteTextFile("c:\\temp\\dbg_curText.txt", curText); Utils.WriteTextFile("c:\\temp\\dbg_text.txt", text); Utils.WriteTextFile("c:\\temp\\dbg_endingText.txt", endingText); } Debug.Assert(endingText == text); //mClangSourceChanged = false; //mLastFileTextVersion = mEditWidget.Content.mData.mCurTextVersionId; //if ((mProjectSource != null) && (mProjectSource.mEditData != null)) //mProjectSource.mEditData.mLastFileTextVersion = mLastFileTextVersion; return true; } void ClearColors() { if (mHasCustomColors) { delete mTextColors; mTextColors = null; mHasCustomColors = false; } } public void SetOldVersionColors(bool old = true) { ClearColors(); mTextColors = sTextColors; mHasCustomColors = false; if (!old) { return; } var newTextColor = new uint32[mTextColors.Count]; for (int32 colorIdx = 0; colorIdx < mTextColors.Count; colorIdx++) { uint32 color = sTextColors[colorIdx]; float h; float s; float v; Color.ToHSV(color, out h, out s, out v); s = s * 0.5f; v = Math.Min(1.0f, v * 1.0f); uint32 newColor = Color.FromHSV(h, s, v); newTextColor[colorIdx] = newColor; } ClearColors(); mTextColors = newTextColor; mHasCustomColors = true; } public override bool CheckReadOnly() { if ((gApp.mSymbolReferenceHelper != null) && (gApp.mSymbolReferenceHelper.mKind == .Rename)) gApp.mSymbolReferenceHelper.EnsureWorkStarted(); if (!base.CheckReadOnly()) { if ((mSourceViewPanel != null) && (mSourceViewPanel.IsReadOnly)) { IDEApp.Beep(IDEApp.MessageBeepType.Error); mSourceViewPanel.mLockFlashPct = 0.00001f; return true; } return false; } if (mSourceViewPanel != null) { if ((!sReadOnlyErrorShown) && (mSourceViewPanel.[Friend]mCurrentVersionPanel != null)) IDEApp.sApp.Fail("Switch to the current version of this file to edit it."); else IDEApp.Beep(IDEApp.MessageBeepType.Error); } sReadOnlyErrorShown = true; return true; } public override void ApplyTextFlags(int index, String text, uint8 typeNum, uint8 flags) { uint8 curTypeNum = typeNum; for (int i = 0; i < text.Length; i++) { char8 c = text[i]; if ((curTypeNum != (uint8)BfSourceElementType.Normal) && (curTypeNum != (uint8)BfSourceElementType.Comment)) { if ((!c.IsLetterOrDigit) && (c != '_')) curTypeNum = (uint8)BfSourceElementType.Normal; } mData.mText[i + index].mChar = (char8)c; #if INCLUDE_CHARDATA_CHARID mText[i + index].mCharId = mNextCharId; #endif mData.mNextCharId++; mData.mText[i + index].mDisplayTypeId = curTypeNum; mData.mText[i + index].mDisplayFlags = flags; if (c.IsWhiteSpace) curTypeNum = 0; } } public override float DrawText(Graphics g, String str, float x, float y, uint16 typeIdAndFlags) { /*using (g.PushColor(mTextColors[typeIdAndFlags & 0xFF])) return DoDrawText(g, str, x, y);*/ QueuedTextEntry queuedTextEntry; queuedTextEntry.mString = new String(str); queuedTextEntry.mX = x; queuedTextEntry.mY = y; queuedTextEntry.mTypeIdAndFlags = typeIdAndFlags; mQueuedText.Add(queuedTextEntry); return DoDrawText(null, str, x, y); } public override void DrawSectionFlagsOver(Graphics g, float x, float y, float width, uint8 flags) { if ((flags & ~(uint8)SourceElementFlags.Skipped) == 0) return; /*if ((flags & (byte)SourceElementFlags.Find_CurrentSelection) != 0) { using (g.PushColor(0xFF706030)) g.FillRect(x, y, width, mFont.GetLineSpacing()); DrawSectionFlagsOver(g, x, y, width, (byte)(flags & ~(byte)(SourceElementFlags.Find_CurrentSelection | SourceElementFlags.Find_Matches))); return; } else*/ if ((flags & (uint8)SourceElementFlags.SymbolReference) != 0) { bool isRenameSymbol = (IDEApp.sApp.mSymbolReferenceHelper != null) && (IDEApp.sApp.mSymbolReferenceHelper.mKind == SymbolReferenceHelper.Kind.Rename); using (g.PushColor(isRenameSymbol ? (uint32)0x28FFFFFF : (uint32)0x18FFFFFF)) g.FillRect(x, y, width, mFont.GetLineSpacing()); DrawSectionFlagsOver(g, x, y, width, (uint8)(flags & ~(uint8)SourceElementFlags.SymbolReference)); return; } if ((flags & (uint8)SourceElementFlags.Find_Matches) != 0) { //using (g.PushColor(0xFF505050)) //using (g.PushColor(0x30B0B0B0)) //using (g.PushColor(0x28FFFFFF)) using (g.PushColor(0x34FFE0B0)) g.FillRect(x, y, width, mFont.GetLineSpacing()); DrawSectionFlagsOver(g, x, y, width, (uint8)(flags & ~(uint8)SourceElementFlags.Find_Matches)); return; } QueuedUnderlineEntry queuedUnderlineEntry; queuedUnderlineEntry.mX = x; queuedUnderlineEntry.mY = y; queuedUnderlineEntry.mWidth = width; queuedUnderlineEntry.mFlags = flags; mQueuedUnderlines.Add(queuedUnderlineEntry); } void DoDrawSectionFlags(Graphics g, float x, float y, float width, uint8 flags) { SourceElementFlags elementFlags = (SourceElementFlags)flags; if (elementFlags.HasFlag(SourceElementFlags.IsAfter)) { elementFlags = elementFlags & ~SourceElementFlags.IsAfter; DoDrawSectionFlags(g, x + width, y, GS!(6), (uint8)elementFlags); } if (elementFlags != 0) { uint32 underlineColor = 0; if (elementFlags.HasFlag(SourceElementFlags.Error)) { underlineColor = 0xFFFF0000; } else if (elementFlags.HasFlag(SourceElementFlags.Warning)) { underlineColor = 0xFFFFD200; } else if (elementFlags.HasFlag(SourceElementFlags.SpellingError)) { underlineColor = 0xE0FF3000; } if (underlineColor != 0) { using (g.PushColor(underlineColor)) gApp.DrawSquiggle(g, x, y, width); } } } public int32 GetActualLineStartColumn(int line) { String lineStr = scope String(); GetLineText(line, lineStr); int32 spaceCount = 0; for (int idx < lineStr.Length) { var c = lineStr[idx]; switch (c) { case ' ': spaceCount++; case '\t': spaceCount += GetTabSpaceCount(); default: return spaceCount; } } return spaceCount; } void GetBlockStart(int line, out int foundBlockStartIdx, out int blockOpenSpaceCount) { int lineStart; int lineEnd; GetLinePosition(line, out lineStart, out lineEnd); int parenDepth = 0; int blockDepth = 0; int checkLineNum = line; foundBlockStartIdx = -1; blockOpenSpaceCount = 0; // Go backwards and find block opening for (int checkIdx = lineStart - 1; checkIdx >= 0; checkIdx--) { char8 c = mData.mText[checkIdx].mChar; var elementType = (SourceElementType)mData.mText[checkIdx].mDisplayTypeId; if (elementType == SourceElementType.Comment) c = ' '; // Ignore if (foundBlockStartIdx != -1) { if (c == '\n') // Done? break; else if (c == ' ') blockOpenSpaceCount++; else if (c == '\t') blockOpenSpaceCount += 4; // SpacesInTab else { blockOpenSpaceCount = 0; } } else if (elementType == SourceElementType.Normal) { switch (c) { case '\n': checkLineNum--; case '}': blockDepth++; case '{': blockDepth--; if (blockDepth == -1) { foundBlockStartIdx = checkIdx; break; } case ')': parenDepth++; case '(': parenDepth--; } } } } bool IsInTypeDef(int line) { int foundBlockStartIdx; int blockOpenSpaceCount; GetBlockStart(line, out foundBlockStartIdx, out blockOpenSpaceCount); char8 prevC = 0; for (int checkIdx = foundBlockStartIdx - 1; checkIdx >= 0; checkIdx--) { char8 c = mData.mText[checkIdx].mChar; var elementType = (SourceElementType)mData.mText[checkIdx].mDisplayTypeId; if (elementType == SourceElementType.Normal) { if (c == '}') { } if ((c == ';') || (c == ')')) return false; } if (elementType == SourceElementType.Keyword) { if ((checkIdx == 0) || ((SourceElementType)mData.mText[checkIdx - 1].mDisplayTypeId != .Keyword)) { if (((c == 'c') && (prevC == 'l')) || // class ((c == 'e') && (prevC == 'n')) || // enum ((c == 's') && (prevC == 't')) || // struct ((c == 'e') && (prevC == 'x'))) // extension return true; } } prevC = c; } return false; } public int32 GetTabbedSpaceCount(String str) { int32 spaceCount = 0; for (let c in str.RawChars) { if (c == '\t') { spaceCount = (spaceCount / mTabLength + 1) * mTabLength; } else if (c == ' ') { spaceCount++; } else Runtime.FatalError(); } return spaceCount; } public int GetLineEndColumn(int line, bool openingBlock, bool force, bool ignoreLineText = false, bool insertingElseStmt = false, float* outWidth = null) { String curLineStr = scope String(); GetLineText(line, curLineStr); //int lineLen = curLineStr.Length; // 2 String curLineTrimmed = scope String(curLineStr); curLineTrimmed.TrimEnd(); int trimmedLineLen = curLineTrimmed.Length; //int semiCount = 0; float totalTabbedWidth = GetTabbedWidth(curLineStr, 0); int32 actualSpaceCount = (int32)(totalTabbedWidth / mCharWidth + 0.001f); if (openingBlock || ignoreLineText) actualSpaceCount = 0; if (((trimmedLineLen > 0) && (!force)) || (!mAllowVirtualCursor)) { if (outWidth != null) *outWidth = totalTabbedWidth; return actualSpaceCount; } /// int lineStart; int lineEnd; GetLinePosition(line, out lineStart, out lineEnd); int foundBlockStartIdx; int blockOpenSpaceCount; GetBlockStart(line, out foundBlockStartIdx, out blockOpenSpaceCount); if (foundBlockStartIdx == -1) return 0; int endingPos = lineEnd; if (ignoreLineText) endingPos = lineStart; int blockDepth = 0; int parenDepth = 0; String keywordStr = scope String(); int ifDepth = 0; bool isDoingCtl = false; bool justFinishedCtlCond = false; bool justFinishedCtl = false; int ctlDepth = 0; // for, while, using int extraTab = 1; bool hasUnterminatedStatement = false; bool doingIf = false; bool justFinishedIf = false; bool doingElse = false; bool isAttribute = false; int bracketDepth = 0; bool isLineStart = true; bool skippingLine = false; bool startedWithType = false; // to distinguish between a list like a data or enum value list vs a variable declaration with multiple names across multiple lines bool checkedStartedWithType = false; bool inCaseExpr = false; bool inCaseExprNext = false; // Got a comma bool inCaseExprNextLine = false; // Got a comma and then a newline int caseStartPos = -1; /*String debugStr = scope String(); mEditWidget.GetText(debugStr);*/ //TODO: Make List append allocate //List ifDepthStack = scope List(8); List ifCtlDepthStack = scope List(); bool keepBlockIndented = false; for (int checkIdx = foundBlockStartIdx + 1; checkIdx < endingPos; checkIdx++) { char8 c = mData.mText[checkIdx].mChar; var elementType = (SourceElementType)mData.mText[checkIdx].mDisplayTypeId; bool isWhitespace = c.IsWhiteSpace; if (isWhitespace) elementType = .Normal; if ((c == ':') && (elementType == .Method)) elementType = .Normal; if (c == '\n') { isLineStart = true; skippingLine = false; if (inCaseExprNext) inCaseExprNextLine = true; } else if ((c == '#') && (elementType == .Normal) && (isLineStart)) { isLineStart = false; skippingLine = true; } if ((inCaseExpr) && (c == ':') && (elementType == .Normal) && (parenDepth == 0)) { inCaseExpr = false; inCaseExprNext = false; inCaseExprNextLine = false; } if ((inCaseExpr) && (c == ',') && (parenDepth == 0) && (bracketDepth == 0)) { inCaseExprNext = true; inCaseExprNextLine = false; } if ((inCaseExprNextLine) && (!isWhitespace) && (elementType == .Normal)) inCaseExprNextLine = false; if (skippingLine) { continue; } if (elementType == SourceElementType.Keyword) { if (blockDepth == 0) keywordStr.Append(c); } else if (keywordStr.Length > 0) { if (blockDepth == 0) { if (((justFinishedIf) || (justFinishedCtl)) && (keywordStr != "else")) { // This catches the case of: // if (a) Thing(); // if (b) ... ifDepth = 0; justFinishedIf = false; ifCtlDepthStack.Clear(); ctlDepth = 0; isDoingCtl = false; } justFinishedCtl = false; justFinishedIf = false; switch (keywordStr) { case "if": if (isDoingCtl) { ctlDepth++; isDoingCtl = false; } doingIf = true; if (!doingElse) { ifDepth++; ifCtlDepthStack.Add((int32)ctlDepth); } case "else": isDoingCtl = false; justFinishedIf = false; doingElse = true; case "for", "while", "using": justFinishedCtlCond = false; if (isDoingCtl) ctlDepth++; isDoingCtl = true; case "case": if (parenDepth == 0) { caseStartPos = checkIdx - 4; inCaseExpr = true; } case "switch": ifDepth = 0; } doingElse = keywordStr == "else"; } keywordStr.Clear(); } if ((isWhitespace) && (!checkedStartedWithType) && (checkIdx > foundBlockStartIdx + 1)) { char8 prevC = mData.mText[checkIdx - 1].mChar; if (!prevC.IsWhiteSpace) { var prevElementType = (SourceElementType)mData.mText[checkIdx - 1].mDisplayTypeId; startedWithType = prevElementType == SourceElementType.TypeRef; checkedStartedWithType = true; } } if ((elementType == SourceElementType.Comment) || (isWhitespace)) continue; if ((!checkedStartedWithType) && (elementType != SourceElementType.Namespace) && (elementType != SourceElementType.TypeRef)) { checkedStartedWithType = true; } if (elementType == SourceElementType.Normal) { switch (c) { case '=', ':', '?', '!': keepBlockIndented = true; default: keepBlockIndented = false; } switch (c) { case ',': if ((!startedWithType) && (parenDepth == 0) && (blockDepth == 0)) hasUnterminatedStatement = false; case '{': blockDepth++; case '}': blockDepth--; case '(': parenDepth++; case ')': parenDepth--; if ((parenDepth == 0) && (blockDepth == 0)) { if (isDoingCtl) justFinishedCtlCond = true; if (doingElse) { if (!doingIf) { if (ifDepth > 0) { ifDepth--; ifCtlDepthStack.PopBack(); if (ifCtlDepthStack.Count > 0) ctlDepth = ifCtlDepthStack[ifCtlDepthStack.Count - 1]; else ctlDepth = 0; } } doingElse = false; } if (doingIf) { doingIf = false; hasUnterminatedStatement = false; } } case '[': if (!hasUnterminatedStatement) { isAttribute = true; } bracketDepth++; case ']': bracketDepth--; default: hasUnterminatedStatement = true; } if ((c == ']') && (isAttribute) && (bracketDepth == 0)) { hasUnterminatedStatement = false; } if (((c == '}') || (c == ';') || (c == ':')) && (blockDepth == 0) && (parenDepth == 0)) { if (ifCtlDepthStack.Count > 0) { // This is important for an 'for,if,for,for,stmt;,else' - so the 'else' properly sees a ctlDepth of 1 instead of 3 ctlDepth = ifCtlDepthStack[ifCtlDepthStack.Count - 1]; } justFinishedCtlCond = false; startedWithType = false; checkedStartedWithType = false; if (isDoingCtl) justFinishedCtl = true; if (ifDepth == 0) { isDoingCtl = false; ctlDepth = 0; } if (doingElse) { if (!doingIf) { if (ifDepth > 0) { ifDepth--; ifCtlDepthStack.PopBack(); if (ifCtlDepthStack.Count > 0) ctlDepth = ifCtlDepthStack[ifCtlDepthStack.Count - 1]; else ctlDepth = 0; } } doingElse = false; } if (justFinishedIf) { // We had a non-else statement ifDepth = 0; justFinishedIf = false; ctlDepth = 0; ifCtlDepthStack.Clear(); //isDoingCtl = false; } // Had statement hasUnterminatedStatement = false; if (ifDepth > 0) justFinishedIf = true; inCaseExpr = false; inCaseExprNext = false; inCaseExprNextLine = false; } } else { if ((isDoingCtl) && (justFinishedCtlCond)) { isDoingCtl = false; ctlDepth++; } keepBlockIndented = false; hasUnterminatedStatement = true; //if (blockDepth == 0) //doingIf = false; } if (!isWhitespace) isLineStart = false; } if (inCaseExpr) { int startIdx = -1; int checkIdx = caseStartPos - 1; while (checkIdx > 0) { char8 c = mData.mText[checkIdx].mChar; if (c == '\n') { startIdx = checkIdx + 1; break; } else if ((c != ' ') && (c != '\t')) { startIdx = -1; break; } checkIdx--; } if (startIdx != -1) { var str = scope String(); ExtractString(startIdx, caseStartPos - checkIdx - 1, str); return GetTabbedSpaceCount(str) + 5; } } if ((ctlDepth > 0) && ((!justFinishedIf) || (insertingElseStmt))) extraTab += ctlDepth; if ((ifDepth > 0) && ((!justFinishedIf) || (insertingElseStmt))) { extraTab += ifDepth - 0; if ((doingElse) || (insertingElseStmt)) extraTab--; } // If we have an opening block in a lambda statement like "handlers.Add(scope (evt) => \n{" then indent the block still if ((hasUnterminatedStatement) && (!doingIf)) extraTab++; if ((openingBlock) && (!keepBlockIndented) && (parenDepth == 0)) extraTab = Math.Max(1, extraTab - 1); int wantSpaceCount = blockOpenSpaceCount; //if (!openingBlock) wantSpaceCount += extraTab * 4; if (inCaseExprNextLine) wantSpaceCount++; if ((!curLineStr.IsEmpty) && (!ignoreLineText) && (!force)) { int tabbedSpaceCount = GetTabbedSpaceCount(curLineStr); if (tabbedSpaceCount >= wantSpaceCount) return tabbedSpaceCount; } return wantSpaceCount; } public HistoryEntry RecordHistoryLocation(bool ignoreIfClose = false) { if ((mSourceViewPanel != null) && (mSourceViewPanel.mFilePath != null)) { int line; int lineChar; GetCursorLineChar(out line, out lineChar); return IDEApp.sApp.mHistoryManager.CreateHistory(mSourceViewPanel, mSourceViewPanel.mFilePath, line, lineChar, ignoreIfClose); } return null; } public bool IsAtCurrentHistory() { if ((mSourceViewPanel == null) || (mSourceViewPanel.mFilePath == null)) return false; int line; int lineChar; GetCursorLineChar(out line, out lineChar); return IDEApp.sApp.mHistoryManager.IsAtCurrentHistory(mSourceViewPanel, mSourceViewPanel.mFilePath, line, lineChar, false); } public void PasteText(String str, bool forceMatchIndent = false) { String useString = str; int32 tabSpaceCount = GetTabSpaceCount(); var lineAndColumn = CursorLineAndColumn; var lineText = scope String(); if (HasSelection()) { int startPos; int endPos; mSelection.Value.GetAsForwardSelect(out startPos, out endPos); int line; int lineChar; GetLineCharAtIdx(startPos, out line, out lineChar); lineAndColumn.mLine = (int32)line; int column; GetColumnAtLineChar(line, lineChar, out column); lineAndColumn.mColumn = (int32)column; int lineStart; int lineEnd; GetLinePosition(line, out lineStart, out lineEnd); ExtractString(lineStart, startPos - lineStart, lineText); } else { GetLineText(lineAndColumn.mLine, lineText); } bool startsWithNewline = (forceMatchIndent) && (str.StartsWith("\n")); bool isMultiline = str.Contains("\n"); if (startsWithNewline || isMultiline) { var undoBatchStart = new UndoBatchStart("pasteText"); mData.mUndoManager.Add(undoBatchStart); defer(stack) mData.mUndoManager.Add(undoBatchStart.mBatchEnd); } if (startsWithNewline) { CursorLineAndColumn = LineAndColumn(lineAndColumn.mLine, 0); InsertAtCursor("\n"); CursorLineAndColumn = lineAndColumn; useString = scope:: String(str, 1, str.Length - 1); } bool didFormattedPaste = false; bool hadUnevenTabs = false; bool doDeepPaste = false; if (mAllowVirtualCursor) doDeepPaste = isMultiline || (str.Contains(':')); // We had a rule where we wouldn't format if we're pasting at the start of the line, but this // is not a valid check since we could be copying a whole class block outside a namespace if ((/*(lineAndColumn.mColumn > 0) &&*/ (doDeepPaste) && (String.IsNullOrWhiteSpace(lineText))) || (forceMatchIndent)) { didFormattedPaste = true; int32 minColumn = int32.MaxValue; int32 maxColumn = 0; int32 column = 0; bool atLineStart = false; bool isBlock = false; bool isFirstChar = true; for (int32 idx = 0; idx < str.Length; idx++) { char8 c = str[idx]; if (c == '\n') { column = 0; atLineStart = true; } else if (c == '\t') { column += tabSpaceCount; } else if (c == ' ') { column++; } else if (c != '\r') { if ((c == '#') && (column == 0) && (atLineStart)) { // Don't consider left-aligned preprocessor nodes in the minSpacing atLineStart = false; } if (isFirstChar) { if (c == '{') isBlock = true; isFirstChar = false; } if (atLineStart) { minColumn = Math.Min(minColumn, column); maxColumn = Math.Max(maxColumn, column); atLineStart = false; } } } hadUnevenTabs = minColumn != maxColumn; String sb = scope:: String(); int alignColumn = GetLineEndColumn(lineAndColumn.mLine, isBlock, true, true); if ((useString.StartsWith("case ")) || (useString.StartsWith("when ")) || (useString.StartsWith("default:")) || (useString.StartsWith("default "))) { //CursorLineAndColumn = LineAndColumn(lineAndColumn.mLine, alignColumn - tabSpaceCount); //alignColumn--; } String linePrefix = scope String('\t', alignColumn / tabSpaceCount); CursorLineAndColumn = LineAndColumn(lineAndColumn.mLine, alignColumn); bool isFullSwitch = false; if (useString.StartsWith("{")) isFullSwitch = true; if ((useString.StartsWith("case ")) || (useString.StartsWith("when ")) || (useString.StartsWith("default:")) || (useString.StartsWith("default "))) { CursorLineAndColumn = LineAndColumn(lineAndColumn.mLine, Math.Max(0, alignColumn - tabSpaceCount)); if (maxColumn > 0) { maxColumn += tabSpaceCount; minColumn += tabSpaceCount; } } column = 0; atLineStart = true; bool didLinePrefix = true; for (int32 idx = 0; idx < useString.Length; idx++) { bool allowChar = (!atLineStart) || (column >= minColumn); char8 c = useString[idx]; if (c == '\t') { column += tabSpaceCount; } else if (c == ' ') { column++; } else { if ((c == '#') && (column == 0)) didLinePrefix = true; allowChar = true; if (c == '\n') { column = 0; atLineStart = true; didLinePrefix = false; } else { atLineStart = false; } } if (allowChar) { if ((!didLinePrefix) && (c != '\n')) { String lineStartString = scope String(); lineStartString.Reference(useString); lineStartString.AdjustPtr(idx); if (lineStartString.StartsWith("{")) isFullSwitch = true; if ((lineStartString.StartsWith("case ")) || (lineStartString.StartsWith("when ")) || (lineStartString.StartsWith("default:")) || (lineStartString.StartsWith("default "))) { if ((linePrefix.StartsWith("\t")) && (!isFullSwitch)) sb.Append(linePrefix.CStr() + 1); else sb.Append(linePrefix); } else sb.Append(linePrefix); didLinePrefix = true; } sb.Append(c); } } useString = sb; } int prevCursorTextPos = CursorTextPos; InsertAtCursor(useString); // If we paste in "if (a)\n\tDoThing();", we want "DoThing();" to be indented, so we need to fix this. // This isn't a problem if we had "if (a)\n\tDoThing()\nDoOtherThing();" because minColumn would match // DoOtherThing so DoThing would indent properly (for example) if ((didFormattedPaste) && (lineAndColumn.mLine < GetLineCount() - 1)) { LineAndColumn endLineAndColumn = CursorLineAndColumn; String nextLineText = scope String(); GetLineText(lineAndColumn.mLine + 1, nextLineText); nextLineText.Trim(); bool isOpeningBlock = nextLineText.StartsWith("{"); int wantIndent1 = GetLineEndColumn(lineAndColumn.mLine, false, true, true); mSourceViewPanel.DoFastClassify(prevCursorTextPos, CursorTextPos - prevCursorTextPos); int wantIndent2 = GetLineEndColumn(lineAndColumn.mLine + 1, isOpeningBlock, true, true); int haveIndent2 = GetActualLineStartColumn(lineAndColumn.mLine + 1); if ((wantIndent2 == wantIndent1 + GetTabSpaceCount()) && (wantIndent1 == haveIndent2)) { for (int32 lineNum = lineAndColumn.mLine + 1; lineNum <= endLineAndColumn.mLine; lineNum++) { String line = scope String(); GetLineText(lineNum, line); line.Trim(); /*if (line.StartsWith("{")) // Fix for pasting "if (a)\n{\n}" break;*/ if (!line.IsWhiteSpace) { CursorLineAndColumn = LineAndColumn(lineNum, 0); InsertAtCursor("\t"); } } CursorToLineEnd(); } } } public override void PasteText(String theString) { PasteText(theString, false); } public override void InsertAtCursor(String theString, InsertFlags insertFlags) { var insertFlags; if ((HasSelection()) && (gApp.mSymbolReferenceHelper != null) && (gApp.mSymbolReferenceHelper.mKind == .Rename)) { bool hasSymbolSelection = true; mSelection.Value.GetAsForwardSelect(var startPos, var endPos); var text = mEditWidget.mEditWidgetContent.mData.mText; for (int i = startPos; i < endPos; i++) { if (!((SourceElementFlags)text[i].mDisplayFlags).HasFlag(.SymbolReference)) { hasSymbolSelection = false; } } if (hasSymbolSelection) mInsertDisplayFlags = (uint8)SourceElementFlags.SymbolReference; } if ((mSourceViewPanel != null) && (!insertFlags.HasFlag(.IsGroupPart))) { mSourceViewPanel.EnsureTrackedElementsValid(); } base.InsertAtCursor(theString, insertFlags); mInsertDisplayFlags = 0; if ((!mIgnoreSetHistory) && (!insertFlags.HasFlag(.NoMoveCursor))) RecordHistoryLocation(); } public override void CursorToLineEnd() { if (!mAllowVirtualCursor) { base.CursorToLineEnd(); return; } int line; int lineChar; GetCursorLineChar(out line, out lineChar); //CursorLineAndColumn = LineAndColumn(line, GetLineEndColumn(line, false, false)); //CursorMoved(); float x; float y; float wantWidth = 0; int column = GetLineEndColumn(line, false, false, false, false, &wantWidth); GetTextCoordAtLineAndColumn(line, column, out x, out y); if (wantWidth != 0) x = wantWidth + mTextInsets.mLeft; MoveCursorToCoord(x, y); return; } bool IsTextSpanEmpty(int32 start, int32 length) { for (int32 i = start; i < start + length; i++) { char8 c = mData.mText[i].mChar; if (!c.IsWhiteSpace) return false; } return true; } public bool OpenCodeBlock() { int lineIdx; if (HasSelection()) { int minLineIdx = 0; int minLineCharIdx = 0; int maxLineIdx = 0; int maxLineCharIdx = 0; var selection = mSelection.Value; GetLineCharAtIdx(selection.MinPos, out minLineIdx, out minLineCharIdx); GetLineCharAtIdx(selection.MaxPos, out maxLineIdx, out maxLineCharIdx); bool isMultilineSelection = minLineIdx != maxLineIdx; String lineText = scope String(); GetLineText(minLineIdx, lineText); lineText.Trim(); if (lineText.Length == 0) minLineIdx++; /*if ((lineText.Length > 0) && (lineText[lineText.Length - 1] == '{')) { minLineIdx++; }*/ //lineText = GetLineText(maxLineIdx).Trim(); int maxLineStartIdx; int maxLineEndIdx; GetLinePosition(maxLineIdx, out maxLineStartIdx, out maxLineEndIdx); int embeddedEndIdx = int32.MaxValue; UndoBatchStart undoBatchStart = null; if (!isMultilineSelection) { // Must have start of line selected for (int i = maxLineStartIdx; i < selection.MinPos; i++) if (!mData.mText[i].mChar.IsWhiteSpace) return false; for (int i = selection.MaxPos; i < maxLineEndIdx; i++) if (!mData.mText[i].mChar.IsWhiteSpace) { undoBatchStart = new UndoBatchStart("embeddedOpenCodeBlock"); mData.mUndoManager.Add(undoBatchStart); mSelection = null; CursorTextPos = i; InsertAtCursor("\n"); embeddedEndIdx = i; break; } } int maxSelPos = selection.MaxPos; if ((selection.MaxPos < mData.mTextLength) && (mData.mText[maxSelPos].mChar == '}')) maxSelPos++; lineText.Clear(); ExtractString(maxLineStartIdx, maxSelPos - maxLineStartIdx, lineText); lineText.Trim(); if (lineText.Length == 0) maxLineIdx--; /*if ((lineText.Length > 0) && (lineText[lineText.Length - 1] == '}')) maxLineIdx--;*/ if (minLineIdx > maxLineIdx) { minLineIdx--; maxLineIdx++; } int selectedIndentCount = int32.MaxValue; for (lineIdx = minLineIdx; lineIdx <= maxLineIdx; lineIdx++) { lineText.Clear(); GetLineText(lineIdx, lineText); int32 curIndentCount = 0; int32 curSpaceCount = 0; bool hadContent = false; for (int32 i = 0; i < lineText.Length; i++) { if (lineText[i] == ' ') { curSpaceCount++; continue; } if (lineText[i] != '\t') { hadContent = true; break; } curIndentCount++; } if (hadContent) { curIndentCount += curSpaceCount / GetTabSpaceCount(); selectedIndentCount = Math.Min(selectedIndentCount, curIndentCount); } } int indentCount = GetLineEndColumn(minLineIdx, true, true, true) / 4; bool wantsContentTab = indentCount == selectedIndentCount; /*if (mAllowVirtualCursor) { // Why did we have this? int expectedIndentCount = GetLineEndColumn(minLineIdx, true, true) / 4; if ((!isMultilineSelection) && (indentCount == expectedIndentCount + 1)) wantsContentTab = false; indentCount = expectedIndentCount; }*/ var indentTextAction = new EditWidgetContent.IndentTextAction(this); EditSelection newSel = selection; selection.MakeForwardSelect(); mSelection = selection; int startAdjust = 0; int endAdjust = 0; //GetTextData(); //Debug.Assert(mData.mLineStarts != null); int32[] lineStarts = scope int32[maxLineIdx - minLineIdx + 2]; int32[] lineEnds = scope int32[maxLineIdx - minLineIdx + 2]; for (lineIdx = minLineIdx; lineIdx <= maxLineIdx + 1; lineIdx++) { int lineStart; int lineEnd; GetLinePosition(lineIdx, out lineStart, out lineEnd); lineStarts[lineIdx - minLineIdx] = (int32)lineStart; lineEnds[lineIdx - minLineIdx] = (int32)lineEnd; } startAdjust += indentCount + 3; for (lineIdx = minLineIdx; lineIdx <= maxLineIdx + 1; lineIdx++) { int lineStart = lineStarts[lineIdx - minLineIdx]; int lineEnd = lineEnds[lineIdx - minLineIdx]; bool isEmbeddedEnd = false; if (lineStart > embeddedEndIdx) { isEmbeddedEnd = true; //lineStart = maxLineEndIdx; //lineEnd = maxLineEndIdx; } if (lineEnd != mData.mTextLength) { lineStart += endAdjust; lineEnd += endAdjust; } if (lineIdx == minLineIdx) { int32 i; for (i = 0; i < indentCount; i++) { InsertText(lineStart + i, "\t"); indentTextAction.mInsertCharList.Add(Tuple((int32)(lineStart + i), '\t')); endAdjust++; } newSel.mStartPos = (int32)(lineStart + i); if (wantsContentTab) { InsertText(lineStart + i, "{\n\t"); indentTextAction.mInsertCharList.Add(Tuple((int32)(lineStart + i), '{')); indentTextAction.mInsertCharList.Add(Tuple((int32)(lineStart + i + 1), '\n')); indentTextAction.mInsertCharList.Add(Tuple((int32)(lineStart + i + 2), '\t')); endAdjust += 3; } else { InsertText(lineStart + i, "{\n"); indentTextAction.mInsertCharList.Add(Tuple((int32)(lineStart + i), '{')); indentTextAction.mInsertCharList.Add(Tuple((int32)(lineStart + i + 1), '\n')); startAdjust -= 1; endAdjust += 2; } // Make sure those InsertText's don't clear our mLineStarts! That would slow things down // and it would screw up our endAdjust calculation //Debug.Assert(mData.mLineStarts != null); newSel.mEndPos = (int32)(lineStart + i + 1); } else if (lineIdx == maxLineIdx + 1) { if (lineEnd == mData.mTextLength) { InsertText(lineStart, "\n"); lineStart++; } int32 i; for (i = 0; i < indentCount; i++) { InsertText(lineStart + i, "\t"); indentTextAction.mInsertCharList.Add(Tuple((int32)(lineStart + i), '\t')); } if (isEmbeddedEnd) { InsertText(lineStart + i, "}"); indentTextAction.mInsertCharList.Add(Tuple((int32)(lineStart + i), '}')); } else { InsertText(lineStart + i, "}\n"); indentTextAction.mInsertCharList.Add(Tuple((int32)(lineStart + i), '}')); indentTextAction.mInsertCharList.Add(Tuple((int32)(lineStart + i + 1), '\n')); } newSel.mEndPos = (int32)(lineStart + i + 1); } else if (wantsContentTab) { if (lineEnd - lineStart >= 1) { char8 c = mData.mText[lineStart].mChar; if (c != '#') { InsertText(lineStart, "\t"); indentTextAction.mInsertCharList.Add(Tuple((int32)(lineStart), '\t')); endAdjust++; } } } } // We shouldn't do ContentChanged until the end, otherwise we thrash on calculating mLineStarts in GetTextData ContentChanged(); ResetWantX(); mCursorBlinkTicks = 0; CursorTextPos = newSel.mEndPos; indentTextAction.mNewSelection = newSel; if ((indentTextAction.mInsertCharList.Count > 0) || (indentTextAction.mRemoveCharList.Count > 0)) mData.mUndoManager.Add(indentTextAction); if (undoBatchStart != null) mData.mUndoManager.Add(undoBatchStart.mBatchEnd); mSelection = newSel; return true; } int lineCharIdx; GetLineCharAtIdx(CursorTextPos, out lineIdx, out lineCharIdx); // Duplicate the tab start from the previous line if (lineIdx > 0) { int indentCount = 0; String lineText = scope String(); GetLineText(lineIdx, lineText); if (String.IsNullOrWhiteSpace(lineText)) // Is on new line? { GetLineText(lineIdx - 1, lineText); for (int32 i = 0; i < lineText.Length; i++) { if (lineText[i] != '\t') break; indentCount++; } lineText.Trim(); if ((lineText.Length > 0) && (lineText[lineText.Length - 1] == '{')) indentCount++; if (mAllowVirtualCursor) { int column = GetLineEndColumn(lineIdx, true, false, true); CursorLineAndColumn = LineAndColumn(lineIdx, column); indentCount = column / 4; } if (lineIdx < GetLineCount() - 1) { String nextLineText = scope String(); GetLineText(lineIdx + 1, nextLineText); nextLineText.TrimEnd(); int32 spaceCount = 0; for (int32 i = 0; i < nextLineText.Length; i++) { if (nextLineText[i] == '\t') spaceCount += 4; else if (nextLineText[i] == ' ') spaceCount++; else break; } if (spaceCount > indentCount * 4) { // Next line is indented? Just simple open InsertAtCursor("{"); return true; } } String sb = scope String(); if (mAllowVirtualCursor) { GetLineText(lineIdx, lineText); if ((lineText.Length > 0) && (String.IsNullOrWhiteSpace(lineText))) { ClearLine(); CursorLineAndColumn = LineAndColumn(lineIdx, indentCount * 4); } // This will already insert at correct position InsertAtCursor("{"); sb.Append("\n"); sb.Append("\n"); for (int32 i = 0; i < indentCount; i++) sb.Append("\t"); sb.Append("}"); InsertAtCursor(sb); } else { lineText.Clear(); GetLineText(lineIdx, lineText); for (int i = lineText.Length; i > indentCount; i--) { CursorTextPos--; DeleteChar(); lineText.Remove(i - 1); } for (int i = lineText.Length; i < indentCount; i++) sb.Append("\t"); sb.Append("{\n"); for (int i = 0; i < indentCount; i++) sb.Append("\t"); sb.Append("\t\n"); for (int i = 0; i < indentCount; i++) sb.Append("\t"); sb.Append("}"); InsertAtCursor(sb); } CursorTextPos -= indentCount + 2; CursorToLineEnd(); return true; } } return false; } public bool CloseCodeBlock() { if (!HasSelection()) return false; int minIdx = mSelection.Value.MinPos; int maxIdx = mSelection.Value.MaxPos; while (true) { if (minIdx >= mData.mTextLength) return false; char8 c = mData.mText[minIdx].mChar; if (c.IsWhiteSpace) { minIdx++; continue; } if (c != '{') return false; break; } while (true) { if (maxIdx == 0) break; char8 c = mData.mText[maxIdx - 1].mChar; if (c.IsWhiteSpace) { maxIdx--; continue; } if (c != '}') return false; break; } UndoBatchStart undoBatchStart = new UndoBatchStart("closeCodeBlock"); mData.mUndoManager.Add(undoBatchStart); mSelection = EditSelection(maxIdx - 1, maxIdx); GetLineCharAtIdx(maxIdx - 1, var endLine, var endLineChar); String endStr = scope .(); ExtractLine(endLine, endStr); endStr.Trim(); if (endStr == "}") { CursorLineAndColumn = .(endLine, 0); DeleteLine(); endLine--; } else DeleteSelection(); mSelection = EditSelection(minIdx, minIdx + 1); GetLineCharAtIdx(minIdx, var startLine, var startLineChar); String startStr = scope .(); ExtractLine(startLine, startStr); startStr.Trim(); if (startStr == "{") { CursorLineAndColumn = .(startLine, 0); DeleteLine(); endLine--; } else DeleteSelection(); GetLinePosition(startLine, var startLineStart, var startLineEnd); GetLinePosition(endLine, var endLineStart, var endLineEnd); while (startLineStart < startLineEnd) { char8 c = mData.mText[startLineStart].mChar; if (!c.IsWhiteSpace) break; startLineStart++; } CursorTextPos = startLineStart; mSelection = EditSelection(startLineStart, endLineEnd); if (undoBatchStart != null) mData.mUndoManager.Add(undoBatchStart.mBatchEnd); return true; } public bool ToggleComment() { if (HasSelection()) { var startLineAndCol = CursorLineAndColumn ; UndoBatchStart undoBatchStart = new UndoBatchStart("embeddedToggleComment"); mData.mUndoManager.Add(undoBatchStart); int minPos = mSelection.mValue.MinPos; int maxPos = mSelection.mValue.MaxPos; mSelection = null; var str = scope String(); ExtractString(minPos, maxPos - minPos, str); var trimmedStr = scope String(); trimmedStr.Append(str); int32 startLen = (int32)trimmedStr.Length; trimmedStr.TrimStart(); int32 afterTrimStart = (int32)trimmedStr.Length; trimmedStr.TrimEnd(); int32 afterTrimEnd = (int32)trimmedStr.Length; int firstCharPos = minPos + (startLen - afterTrimStart); int lastCharPos = maxPos - (afterTrimStart - afterTrimEnd); if (trimmedStr.StartsWith("/*")) { if (trimmedStr.EndsWith("*/")) { mSelection = EditSelection(firstCharPos, firstCharPos + 2); DeleteChar(); mSelection = EditSelection(lastCharPos - 4, lastCharPos - 2); DeleteChar(); } } else { CursorTextPos = firstCharPos; InsertAtCursor("/*"); CursorTextPos = lastCharPos + 2; InsertAtCursor("*/"); } if (undoBatchStart != null) mData.mUndoManager.Add(undoBatchStart.mBatchEnd); CursorLineAndColumn = startLineAndCol; mSelection = null; return true; } return false; } public override void ContentChanged() { base.ContentChanged(); mCursorStillTicks = 0; if (mSourceViewPanel != null) { if (mSourceViewPanel.mProjectSource != null) mSourceViewPanel.mProjectSource.HasChangedSinceLastCompile = true; } //if ((IDEApp.sApp.mSymbolReferenceHelper != null) && (IDEApp.sApp.mSymbolReferenceHelper.mKind != )) /*if ((!mIsInKeyChar) && (mAutoComplete != null)) mAutoComplete.CloseListWindow();*/ } public override void KeyChar(char32 theChar) { scope AutoBeefPerf("SEWC.KeyChar"); if (mIgnoreKeyChar) { mIgnoreKeyChar = false; return; } if (mIsReadOnly) { base.KeyChar(theChar); return; } if ((gApp.mSymbolReferenceHelper != null) && (gApp.mSymbolReferenceHelper.IsRenaming)) { if ((theChar == '\r') || (theChar == '\n')) { gApp.mSymbolReferenceHelper.Close(); return; } else if (theChar == '\t') { if (HasSelection()) { mSelection = null; return; } } else if (theChar == '\b') { if (HasSelection()) mSelection = null; } } int32 startRevision = mData.mCurTextVersionId; bool doAutocomplete = (theChar == '\t'); if ((mAutoComplete != null) && (theChar == '\r') && ((!mIsMultiline) || (mAutoComplete.mIsUserRequested))) doAutocomplete = true; bool hasEmptyAutocompleteReplace = true; if (mAutoComplete != null) hasEmptyAutocompleteReplace = mAutoComplete.mInsertEndIdx == -1; bool isEndingChar = (theChar >= (char8)32) && !theChar.IsLetterOrDigit && (theChar != '_') && (theChar != '~') && (theChar != '=') && (theChar != '!') && (theChar != ':'); if (gApp.mSettings.mEditorSettings.mAutoCompleteRequireTab) { doAutocomplete = theChar == '\t'; if (theChar == '\r') { if (mAutoComplete != null) mAutoComplete.Close(); } isEndingChar = false; } if (isEndingChar) { bool forceAsyncFinish = false; if (mCursorTextPos > 0) { char8 c = mData.mText[mCursorTextPos - 1].mChar; var displayType = (SourceElementType)mData.mText[mCursorTextPos - 1].mDisplayTypeId; if ((displayType != .Comment) && (displayType != .Literal)) { if ((c.IsLetterOrDigit) || (c == '_')) { // Could be a symbol autocomplete? forceAsyncFinish = true; } } } if (forceAsyncFinish) { if (mOnFinishAsyncAutocomplete != null) mOnFinishAsyncAutocomplete(); doAutocomplete = true; } } else { if ((doAutocomplete) && (mOnFinishAsyncAutocomplete != null)) mOnFinishAsyncAutocomplete(); } if ((mAutoComplete != null) && (mAutoComplete.mAutoCompleteListWidget != null)) { if ((mAutoComplete.mInsertEndIdx != -1) && (mAutoComplete.mInsertEndIdx != mCursorTextPos) && (theChar != '\t') && (theChar != '\r') && (theChar != '\n')) doAutocomplete = false; if ((mAutoComplete.IsInsertEmpty()) && (!mAutoComplete.mIsFixit) && (theChar != '.') && (theChar != '\t')) { // Require a '.' or tab to insert autocomplete when we don't have any insert section (ie: after an 'enumVal = ') doAutocomplete = false; } if ((theChar == '[') && (mAutoComplete.mInsertStartIdx >= 0) && (mData.mText[mAutoComplete.mInsertStartIdx].mChar != '.')) { // Don't autocomplete for ".[" (member access attributes) doAutocomplete = false; } if (((theChar == '.') || (theChar == '*')) && ((mAutoComplete.mInsertEndIdx == -1) || (mAutoComplete.IsInsertEmpty()))) { // Don't autocomplete for object allocation when we haven't typed anything yet but then we press '.' (for 'inferred type') doAutocomplete = false; } if ((mAutoComplete.mUncertain) && (theChar != '\t')) doAutocomplete = false; if (doAutocomplete) { if (mOnFinishAsyncAutocomplete != null) mOnFinishAsyncAutocomplete(); UndoBatchStart undoBatchStart = new UndoBatchStart("autocomplete"); mData.mUndoManager.Add(undoBatchStart); bool allowChar = true; mIsInKeyChar = true; String insertType = scope String(); String insertStr = scope String(); mAutoComplete.InsertSelection(theChar, insertType, insertStr); mIsInKeyChar = false; if (insertType != null) { //mGenerateAutocompleteHandler(false, false); if (((insertType == "method") && (theChar == '(')) || ((insertType == "token") && (insertStr == "override"))) { if (IsCursorVisible(false)) mOnGenerateAutocomplete('\0', default); } else { //Debug.WriteLine("Autocomplete InsertSelection {0}", mAutoComplete); if (mAutoComplete != null) mAutoComplete.CloseListWindow(); if ((mAutoComplete != null) && (mAutoComplete.mInvokeWindow != null)) { // Update invoke since the autocompletion may have selected a different overload if (IsCursorVisible(false)) mOnGenerateAutocomplete(theChar, .OnlyShowInvoke); } } if ((insertType == "override") || (insertType == "fixit")) { allowChar = false; } if ((insertType == "mixin") && (theChar == '!')) { // It already ends with the char8 that we have allowChar = false; } } else mAutoComplete.CloseListWindow(); if ((theChar == '\t') || (theChar == '\r')) // Let other chars besides explicit-insert chrars pass through { allowChar = false; } mData.mUndoManager.Add(undoBatchStart.mBatchEnd); if (!allowChar) return; } } SourceElementType prevElementType = SourceElementType.Normal; char8 prevChar = 0; int cursorTextPos = CursorTextPos; if (cursorTextPos != 0) { prevElementType = (SourceElementType)mData.mText[cursorTextPos - 1].mDisplayTypeId; prevChar = mData.mText[cursorTextPos - 1].mChar; } if (((theChar == '\n') || (theChar == '\r')) && (mIsMultiline) && (!CheckReadOnly())) { UndoBatchStart undoBatchStart = new UndoBatchStart("newline"); mData.mUndoManager.Add(undoBatchStart); var lineAndColumn = CursorLineAndColumn; int lineStart; int lineEnd; GetLinePosition(lineAndColumn.mLine, out lineStart, out lineEnd); String beforeCursorLineText = scope String(); ExtractString(lineStart, CursorTextPos - lineStart, beforeCursorLineText); String fullCursorLineText = scope String(); ExtractString(lineStart, lineEnd - lineStart, fullCursorLineText); if ((String.IsNullOrWhiteSpace(beforeCursorLineText)) && (!String.IsNullOrWhiteSpace(fullCursorLineText))) { // Cursor is within whitesapce at line start, insert newline at actual start of line InsertAtCursor(""); // To save cursor position var newLineAndColumn = lineAndColumn; newLineAndColumn.mColumn = 0; CursorLineAndColumn = newLineAndColumn; InsertAtCursor("\n"); CursorLineAndColumn = LineAndColumn(lineAndColumn.mLine + 1, lineAndColumn.mColumn); } else { EditWidgetContent.InsertFlags insertFlags = .None; if (!HasSelection()) { // Select whitespace at the end of the line so we trim it as we InsertAtCursor mSelection = EditSelection(CursorTextPos, CursorTextPos); for (int checkIdx = beforeCursorLineText.Length - 1; checkIdx >= 0; checkIdx--) { char8 c = beforeCursorLineText[checkIdx]; if (!c.IsWhiteSpace) break; mSelection.mValue.mStartPos--; } insertFlags |= .NoRestoreSelectionOnUndo; } InsertAtCursor("\n", insertFlags); int line; int lineChar; GetCursorLineChar(out line, out lineChar); String lineText = scope String(); GetLineText(line, lineText); if (!String.IsNullOrWhiteSpace(lineText)) { // If we're breaking a line, clear the previous line if we just have whitespace on it String prevLineText = scope String(); GetLineText(line - 1, prevLineText); prevLineText.Trim(); if (prevLineText.Length == 0) { lineAndColumn = CursorLineAndColumn; CursorLineAndColumn = LineAndColumn(line - 1, 0); ClearLine(); CursorLineAndColumn = lineAndColumn; } String lineTextAtCursor = scope String(); GetLineText(line, lineTextAtCursor); // Only treat as block open if it's a lonely '{', not a complete one-line block bool isBlockOpen = (lineTextAtCursor.Length > 0) && (lineTextAtCursor[0] == '{') && (!lineTextAtCursor.Contains("}")); int column = GetLineEndColumn(line, isBlockOpen, true, true); if (lineTextAtCursor.StartsWith("}")) { column -= 4; // SpacesInTab } if (column > 0) { String tabStr = scope String(); tabStr.Append('\t', column / 4); tabStr.Append(' ', column % 4); InsertAtCursor(tabStr); } // Insert extra blank line if we're breaking between a { and a } if ((lineTextAtCursor.StartsWith("}")) && (prevLineText.EndsWith("{"))) { CursorLineAndColumn = LineAndColumn(line - 1, 0); CursorToLineEnd(); InsertAtCursor("\n"); CursorLineAndColumn = LineAndColumn(line, 0); CursorToLineEnd(); } } else CursorToLineEnd(); } if ((mAutoComplete != null) && (mAutoComplete.mInvokeWidget != null)) { // Update the position of the invoke widget if (IsCursorVisible(false)) mOnGenerateAutocomplete('\0', default); } mData.mUndoManager.Add(undoBatchStart.mBatchEnd); if ((prevElementType == .Normal) && ((prevChar == '.') || (prevChar == '('))) { // Leave it be } else { if (mAutoComplete != null) mAutoComplete.CloseListWindow(); } mAutoComplete?.UpdateAsyncInfo(); return; } if ((theChar == '/') && (ToggleComment())) { return; } if (prevElementType == SourceElementType.Comment) { if ((cursorTextPos < mData.mTextLength - 1) && (mData.mText[cursorTextPos - 1].mChar == '\n')) prevElementType = (SourceElementType)mData.mText[cursorTextPos].mDisplayTypeId; if (theChar == '*') { if (cursorTextPos >= 3) { if ((mData.mText[cursorTextPos - 2].mChar == '/') && (mData.mText[cursorTextPos - 1].mChar == '*') && (mData.mText[cursorTextPos].mChar == '\n')) { InsertAtCursor("*"); let prevLineAndColumn = mEditWidget.mEditWidgetContent.CursorLineAndColumn; int column = GetLineEndColumn(prevLineAndColumn.mLine, false, true, true); var str = scope String(); str.Append("\n"); str.Append('\t', column / mTabLength); str.Append("*/"); InsertAtCursor(str); mEditWidget.mEditWidgetContent.CursorLineAndColumn = prevLineAndColumn; return; } } } } bool doChar = true; if (prevElementType != SourceElementType.Comment) { doChar = false; char8 char8UnderCursor = (char8)0; bool cursorInOpenSpace = false; //int cursorTextPos = CursorTextPos; if (cursorTextPos < mData.mTextLength) { char8UnderCursor = (char8)mData.mText[cursorTextPos].mChar; cursorInOpenSpace = ((char8UnderCursor == ')') || (char8UnderCursor == ']') || (char8UnderCursor == (char8)0) || (char8UnderCursor.IsWhiteSpace)); if ((char8UnderCursor == theChar) && (!HasSelection())) { var wantElementType = SourceElementType.Normal; bool ignore = false; int checkPos = cursorTextPos; if ((theChar == '"') || (theChar == '\'')) { checkPos = Math.Max(cursorTextPos - 1, 0); wantElementType = .Literal; if (cursorTextPos > 0) { if (mData.mText[cursorTextPos - 1].mChar == '\\') ignore = true; } } if (!ignore) { if ((mData.mText[checkPos].mDisplayTypeId == (int32)wantElementType) && ((theChar == '"') || (theChar == '\'') || (theChar == ')') || (theChar == ']') || (theChar == '>') || (theChar == '}'))) { mJustInsertedCharPair = false; CursorTextPos++; return; } else { if ((theChar == '"') || (theChar == '\'')) cursorInOpenSpace = true; } } } else if ((theChar == '}') && (!HasSelection())) // Jump down to block close, as long as it's just whitespace between us and there { int checkPos = cursorTextPos; while (checkPos < mData.mTextLength - 1) { char8 checkC = mData.mText[checkPos].mChar; if (checkC == '}') { int closeLine; int closeLineChar; GetLineCharAtIdx(checkPos, out closeLine, out closeLineChar); int expectedColumn = GetLineEndColumn(closeLine, true, true, true) - 4; int actualColumn = GetActualLineStartColumn(closeLine); if (expectedColumn == actualColumn) { CursorTextPos = checkPos + 1; return; } } if (!checkC.IsWhiteSpace) break; checkPos++; } } } else cursorInOpenSpace = true; bool cursorAfterText = false; if (cursorTextPos > 0) { char8 char8Before = (char8)mData.mText[cursorTextPos - 1].mChar; if (char8Before.IsLetterOrDigit) cursorAfterText = true; } if (HasSelection()) { if ((theChar == '{') && (OpenCodeBlock())) { // OpenCodeBlock handled this char } else if ((theChar == '{') || (theChar == '(')) { UndoBatchStart undoBatchStart = new UndoBatchStart("blockSurround"); mData.mUndoManager.Add(undoBatchStart); int minPos = mSelection.mValue.MinPos; int maxPos = mSelection.mValue.MaxPos; mSelection = null; CursorTextPos = minPos; String insertStr = scope String(); insertStr.Append(theChar); InsertAtCursor(insertStr); CursorTextPos = maxPos + 1; insertStr.Clear(); if (theChar == '(') insertStr.Append(')'); else if (theChar == '{') insertStr.Append('}'); InsertAtCursor(insertStr); mData.mUndoManager.Add(undoBatchStart.mBatchEnd); } else if ((theChar == '}') && (CloseCodeBlock())) { // CloseCodeBlock handled this char } else doChar = true; } else if (prevElementType == SourceElementType.Literal) doChar = true; else if (theChar == '#') { int32 line = CursorLineAndColumn.mLine; String lineText = scope String(); GetLineText(line, lineText); lineText.Trim(); if ((lineText.Length == 0) && (mAllowVirtualCursor)) CursorLineAndColumn = LineAndColumn(line, 0); doChar = true; } else if ((theChar == '{') && (OpenCodeBlock())) { // OpenCodeBlock handled this char8 } else if (theChar == '}') { int32 line = CursorLineAndColumn.mLine; String lineText = scope String(); GetLineText(line, lineText); lineText.Trim(); if (lineText.Length == 0) { ClearLine(); CursorLineAndColumn = LineAndColumn(line, Math.Max(0, GetLineEndColumn(line, false, false) - 4)); CursorMoved(); } base.KeyChar(theChar); } else if ((theChar == '(') && (cursorInOpenSpace)) InsertCharPair("()"); else if ((theChar == '{') && (cursorInOpenSpace)) { /*int lineStart; int lineEnd; GetLinePosition(CursorLineAndColumn.mLine, out lineStart, out lineEnd); String endingStr = scope String(); ExtractString(CursorTextPos, lineEnd - CursorTextPos, endingStr); endingStr.Trim(); // Only do the "{}" pair if it's inside a a param call or something already, IE: // handler.Add(new () => {}); if (endingStr.Length != 0) InsertCharPair("{}"); else doChar = true;*/ InsertCharPair("{}"); } else if ((theChar == '[') && (cursorInOpenSpace)) InsertCharPair("[]"); else if ((theChar == '\"') && (cursorInOpenSpace) && (!cursorAfterText)) InsertCharPair("\"\""); else if ((theChar == '\'') && (cursorInOpenSpace) && (!cursorAfterText)) InsertCharPair("\'\'"); else doChar = true; } if (doChar) { mIsInKeyChar = true; base.KeyChar(theChar); mIsInKeyChar = false; } if ((theChar == '\b') || (theChar == '\r') || (theChar >= (char8)32)) { bool isHighPri = (theChar == '(') || (theChar == '.'); bool needsFreshAutoComplete = ((isHighPri) /*|| (!mAsyncAutocomplete)*/ || (mAutoComplete == null) || (mAutoComplete.mAutoCompleteListWidget == null)); if ((needsFreshAutoComplete) && (theChar == '\b')) { if ((prevChar != 0) && (prevChar.IsWhiteSpace) && (prevElementType != .Comment)) { needsFreshAutoComplete = false; } } if (mAutoComplete != null) { mAutoComplete.UpdateAsyncInfo(); /*if (mAutoComplete != null) { String filter = scope String(); mAutoComplete.GetFilter(filter); if ((filter.Length < mAutoComplete.mInfoFilter.Length) || (mAutoComplete.mInfoFilter.Length == 1)) { needsFreshAutoComplete = true; } }*/ needsFreshAutoComplete = true; } if (needsFreshAutoComplete) { //Profiler.StartSampling(); if (IsCursorVisible(false)) mOnGenerateAutocomplete(theChar, isHighPri ? .HighPriority : default); //Profiler.StopSampling(); } } else if (mData.mCurTextVersionId != startRevision) { if (mAutoComplete != null) mAutoComplete.CloseListWindow(); } if ((theChar.IsLower) || (theChar == ' ') || (theChar == ':')) { int cursorTextIdx = CursorTextPos; int line; int lineChar; GetLineCharAtIdx(cursorTextIdx, out line, out lineChar); String lineText = scope String(); GetLineText(line, lineText); String trimmedLineText = scope String(lineText); trimmedLineText.TrimStart(); if ((theChar == ' ') || (theChar == ':')) { bool isLabel = false; if (theChar == ':') { isLabel = trimmedLineText != "scope:"; for (var c in trimmedLineText.RawChars) { if (c.IsWhiteSpace) isLabel = false; } } if (((trimmedLineText == "case ") || (trimmedLineText == "when ") || isLabel) && (prevElementType != .Comment) && (!IsInTypeDef(line))) { int wantLineColumn = 0; if (line > 0) { if ((isLabel) && (trimmedLineText != "default:")) wantLineColumn = GetLineEndColumn(line, true, true, true); else wantLineColumn = GetLineEndColumn(line, false, true, true) - 4; } // Move "case" back to be inline with switch statement if (lineChar > trimmedLineText.Length) { String tabStartStr = scope String(); tabStartStr.Append(lineText, 0, lineChar - trimmedLineText.Length); int32 columnPos = (int32)(GetTabbedWidth(tabStartStr, 0) / mCharWidth + 0.001f); if (columnPos >= wantLineColumn + 4) { mSelection = EditSelection(); mSelection.ValueRef.mEndPos = (int32)(cursorTextIdx - trimmedLineText.Length); if (lineText.EndsWith(scope String(" ", trimmedLineText), StringComparison.Ordinal)) mSelection.ValueRef.mStartPos = mSelection.Value.mEndPos - 4; else if (lineText.EndsWith(scope String("\t", trimmedLineText), StringComparison.Ordinal)) mSelection.ValueRef.mStartPos = mSelection.Value.mEndPos - 1; if (mSelection.Value.mStartPos > 0) { DeleteSelection(); CursorToLineEnd(); } mSelection = null; } } } } else { if (trimmedLineText == "else") { int wantLineColumn = GetLineEndColumn(line, false, true, true, true); //Debug.WriteLine("ElsePos: {0}", wantLineColumn); int textStartPos = lineChar - (int32)trimmedLineText.Length; String tabStartStr = scope String(); tabStartStr.Append(lineText, 0, textStartPos); int32 columnPos = (int32)(GetTabbedWidth(tabStartStr, 0) / mCharWidth + 0.001f); if (wantLineColumn > columnPos) { String insertStr = scope String(' ', wantLineColumn - columnPos); insertStr.Append("else"); var cursorPos = CursorTextPos; mSelection = EditSelection(); mSelection.ValueRef.mStartPos = (int32)cursorPos - 4; mSelection.ValueRef.mEndPos = (int32)cursorPos; InsertAtCursor(insertStr, .NoRestoreSelectionOnUndo); //var indentTextAction = new EditWidgetContent.IndentTextAction(this); /*for (int i = 0; i < wantLineColumn - columnPos; i++) { InsertText(insertPos, " "); //indentTextAction.mInsertCharList.Add(new Tuple(textStartPos, ' ')); }*/ //mUndoManager.Add(indentTextAction); } } } } mCursorImplicitlyMoved = true; } public void ShowAutoComplete(bool isUserRequested) { if (gApp.mSettings.mEditorSettings.mAutoCompleteShowKind != .None) { if (IsCursorVisible(false)) mOnGenerateAutocomplete('\0', isUserRequested ? .UserRequested : .None); } } /// summary = "Hey This is a summary" /// param.keyCode = "Keycode of pressed key" /// param.isRepeat = "Whether the key is repeated" public override void KeyDown(KeyCode keyCode, bool isRepeat) { mIgnoreKeyChar = false; if (((keyCode == .Up) || (keyCode == .Down)) && (mAutoComplete != null) && (mAutoComplete.IsShowing()) && (mAutoComplete.mListWindow != null) && (!mAutoComplete.IsInPanel()) && (!gApp.mSettings.mTutorialsFinished.mCtrlCursor)) { if (mWidgetWindow.IsKeyDown(.Control)) { if ((DarkTooltipManager.sTooltip != null) && (DarkTooltipManager.sTooltip.mAllowMouseOutside)) DarkTooltipManager.CloseTooltip(); gApp.mSettings.mTutorialsFinished.mCtrlCursor = true; } else { GetTextCoordAtCursor(var cursorX, var cursorY); let tooltip = DarkTooltipManager.ShowTooltip("Hold CTRL when using UP and DOWN", this, cursorX - GS!(24), cursorY - GS!(40)); if (tooltip != null) tooltip.mAllowMouseOutside = true; return; } } /*if (keyCode == .Tilde) { Thread.Sleep(300); }*/ /*if ((keyCode == KeyCode.Space) && (mWidgetWindow.IsKeyDown(KeyCode.Control))) { //Debug.WriteLine("CursorPos: {0}", CursorTextPos); ShowAutoComplete(); return; }*/ if (keyCode == KeyCode.Apps) { GetTextCoordAtCursor(var x, var y); MouseClicked(x, y, 1); return; } if ((keyCode == KeyCode.Escape) && (mAutoComplete != null) && (mAutoComplete.IsShowing())) { mAutoComplete.Close(); return; } if ((keyCode == KeyCode.Escape) && (mOnEscape != null) && (mOnEscape())) { return; } if ((keyCode == KeyCode.Escape) && (mSelection != null) && (mSelection.Value.HasSelection)) { mSelection = null; //return; } /*if (keyCode == KeyCode.F2) { int wantCount = 10; if (mWidgetWindow.IsKeyDown(KeyCode.Shift)) wantCount = 100; for (int i = 0; i < wantCount; i++) mPanel.DoClassify(true, false, true); } if ((mPanel.mUseDebugKeyboard) && (keyCode == KeyCode.Menu)) { if (mPanel.mClassifyPaused) mPanel.mClassifyPaused = false; else mPanel.Classify(true, false, true); return; }*/ /*if ((keyCode == KeyCode.Left) && (mWidgetWindow.IsKeyDown(KeyCode.Alt))) { if (!IsAtCurrentHistory()) IDEApp.sApp.mHistoryManager.GoToCurrentHistory(); else IDEApp.sApp.mHistoryManager.PrevHistory(); return; } if ((keyCode == KeyCode.Right) && (mWidgetWindow.IsKeyDown(KeyCode.Alt))) { IDEApp.sApp.mHistoryManager.NextHistory(); return; }*/ if (((keyCode == KeyCode.Up) || (keyCode == KeyCode.Down) || (keyCode == KeyCode.Left) || (keyCode == KeyCode.Right)) && (mWidgetWindow.IsKeyDown(KeyCode.Control) && ((mWidgetWindow.IsKeyDown(KeyCode.Alt))))) { // Fast cursor! /*if (mFastCursorState == null) { mFastCursorState = new FastCursorState(); float x; float y; GetTextCoordAtCursor(out x, out y); mFastCursorState.mX = x; mFastCursorState.mY = y; }*/ return; } if (((keyCode == KeyCode.Up) || (keyCode == KeyCode.Down) || (keyCode == KeyCode.PageUp) || (keyCode == KeyCode.PageDown)) && (mWidgetWindow.IsKeyDown(KeyCode.Control))) { if (mAutoComplete != null) { if (mAutoComplete.mAutoCompleteListWidget != null) { int32 pageSize = (int32)(mAutoComplete.mAutoCompleteListWidget.mScrollContentContainer.mHeight / mAutoComplete.mAutoCompleteListWidget.mItemSpacing - 0.5f); int32 moveDir = 0; switch (keyCode) { case KeyCode.Up: moveDir = -1; break; case KeyCode.Down: moveDir = 1; break; case KeyCode.PageUp: moveDir = -pageSize; break; case KeyCode.PageDown: moveDir = pageSize; break; default: } mAutoComplete.mAutoCompleteListWidget.SelectDirection(moveDir); } else if (mAutoComplete.mInvokeWidget != null) { mAutoComplete.mInvokeWidget.SelectDirection(((keyCode == KeyCode.Up) || (keyCode == KeyCode.PageUp)) ? -1 : 1); } } // Disabled window-scroll code for ctrl+up/ctrl+down when autocomplete is not up return; } //var lineAndColumn = CursorLineAndColumn; int prevCursorPos; TryGetCursorTextPos(out prevCursorPos); int prevTextLength = mData.mTextLength; base.KeyDown(keyCode, isRepeat); if ((mAutoComplete != null) && (keyCode != .Control) && (keyCode != .Shift)) { mAutoComplete.MarkDirty(); bool isCursorInRange = prevCursorPos == CursorTextPos; if (mAutoComplete.mInvokeSrcPositions != null) { isCursorInRange = (CursorTextPos > mAutoComplete.mInvokeSrcPositions[0]) && (CursorTextPos <= mAutoComplete.mInvokeSrcPositions[mAutoComplete.mInvokeSrcPositions.Count - 1]); } bool wasNormalTyping = ((isCursorInRange) && (prevTextLength == mData.mTextLength))/* || ((prevCursorPos + 1 == CursorTextPos) && (prevTextLength + 1 == mTextLength)) || ((prevCursorPos - 1 == CursorTextPos) && (prevTextLength - 1 == mTextLength))*/; /*if ((lineAndColumn.mColumn != CursorLineAndColumn.mColumn) && (prevTextLength == mTextLength)) wasNormalTyping = false; // Moved into virtual space*/ if (!wasNormalTyping) { mAutoComplete.CloseInvoke(); } else if ((keyCode == .Right) || (keyCode == .Left)) { mAutoComplete.CloseListWindow(); } } } void ReplaceWord(int leftIdx, int rightIdx, String origWord, String newWord) { mSelection.ValueRef.mStartPos = (int32)leftIdx; mSelection.ValueRef.mEndPos = (int32)rightIdx; InsertAtCursor(newWord, .NoRestoreSelectionOnUndo); } public bool DoSpellingPopup(Menu menu) { var spellChecker = IDEApp.sApp.mSpellChecker; if (spellChecker == null) return false; spellChecker.CancelBackground(); int leftIdx = CursorTextPos; int rightIdx = leftIdx; while (leftIdx >= 1) { if ((mData.mText[leftIdx - 1].mDisplayFlags & (uint8)SourceElementFlags.SpellingError) == 0) break; leftIdx--; } while (rightIdx < mData.mTextLength) { if ((mData.mText[rightIdx].mDisplayFlags & (uint8)SourceElementFlags.SpellingError) == 0) break; rightIdx++; } if (rightIdx - leftIdx == 0) return false; String word = scope String(); for (int i = leftIdx; i < rightIdx; i++) word.Append((char8)mData.mText[i].mChar); Menu item; bool hadSuggestion = false; List suggestions = scope List(); defer (scope) ClearAndDeleteItems(suggestions); spellChecker.GetSuggestions(word, suggestions); for (var suggestion in suggestions) { if (suggestion.Length == 0) break; hadSuggestion = true; item = menu.AddItem(suggestion); String replaceStr = new String(suggestion); item.mOnMenuItemSelected.Add(new (evt) => { ReplaceWord(leftIdx, rightIdx, word, replaceStr); } ~ { delete replaceStr; }); } if (hadSuggestion) menu.AddItem(); { item = menu.AddItem("Add word to dictionary"); String wordCopy = new String(word); item.mOnMenuItemSelected.Add(new (evt) => { using (spellChecker.mMonitor.Enter()) { spellChecker.mCustomDictionaryWordList.Add(new String(wordCopy)); } spellChecker.CancelBackground(); spellChecker.AddWord(wordCopy); mSourceViewPanel.mWantsSpellCheck = true; } ~ delete wordCopy ); } { item = menu.AddItem("Ignore all occurrences"); String wordCopy = new String(word); item.mOnMenuItemSelected.Add(new (evt) => { using (spellChecker.mMonitor.Enter()) { word.ToLower(); spellChecker.mIgnoreWordList.Add(word); } mSourceViewPanel.mWantsSpellCheck = true; } ~ delete wordCopy ); } return true; } protected void ClampMenuCoords(ref float x, ref float y) { IDEUtils.ClampMenuCoords(ref x, ref y, mEditWidget, scope .(0, 0, GetLineHeight(0), GS!(32))); } public override void MouseClicked(float x, float y, int32 btn) { base.MouseClicked(x, y, btn); var useX = x; var useY = y; if ((btn == 1) && (mSourceViewPanel != null)) { #unwarn ClampMenuCoords(ref useX, ref useY); Menu menu = new Menu(); if (!DoSpellingPopup(menu)) { bool hasText = false; bool hasSelection = HasSelection(); if (hasSelection) hasText = true; else if ((GetLineCharAtCoord(x, y, var line, var lineChar, var overflowX)) || (overflowX < 2)) { int textIdx = GetTextIdx(line, lineChar); for (int checkIdx = textIdx; checkIdx < Math.Min(textIdx + 1, mData.mTextLength); checkIdx++) { if (mData.mText[checkIdx].mDisplayTypeId != (uint8)BfSourceElementType.Comment) { char8 c = mData.mText[checkIdx].mChar; if (!c.IsWhiteSpace) hasText = true; } } } if (mSourceViewPanel.mIsSourceCode) { Menu menuItem; menuItem = menu.AddItem("Go to Definition"); menuItem.SetDisabled(!hasText); menuItem.mOnMenuItemSelected.Add(new (evt) => gApp.GoToDefinition()); menuItem = menu.AddItem("Rename Symbol"); menuItem.SetDisabled(!hasText); menuItem.mOnMenuItemSelected.Add(new (evt) => gApp.Cmd_RenameSymbol()); menuItem = menu.AddItem("Add Watch"); menuItem.SetDisabled(!hasText); menuItem.mOnMenuItemSelected.Add(new (evt) => { int line, lineChar; float overflowX; bool addedWatch = false; if (GetLineCharAtCoord(x, y, out line, out lineChar, out overflowX)) { int textIdx = GetTextIdx(line, lineChar); String debugExpr = scope String(); BfSystem bfSystem = IDEApp.sApp.mBfResolveSystem; BfPassInstance passInstance = null; BfParser parser = null; if ((mSelection != null) && (textIdx >= mSelection.Value.MinPos) && (textIdx < mSelection.Value.MaxPos)) { GetSelectionText(debugExpr); } else if (bfSystem != null) { parser = bfSystem.CreateEmptyParser(null); defer(stack) delete parser; var text = scope String(); mEditWidget.GetText(text); parser.SetSource(text, mSourceViewPanel.mFilePath); parser.SetAutocomplete(textIdx); passInstance = bfSystem.CreatePassInstance(); parser.Parse(passInstance, !mSourceViewPanel.mIsBeefSource); parser.Reduce(passInstance); debugExpr = scope:: String(); parser.GetDebugExpressionAt(textIdx, debugExpr); } if (!String.IsNullOrEmpty(debugExpr)) { IDEApp.sApp.AddWatch(debugExpr); addedWatch = true; } } if (!addedWatch) { gApp.Fail("No watch text selected"); } }); // Fixits { ResolveParams resolveParams = scope .(); mSourceViewPanel.DoClassify(ResolveType.GetFixits, resolveParams, true); menuItem = menu.AddItem("Fixit"); if (resolveParams.mNavigationData != null) { int32 fixitIdx = 0; for (let str in resolveParams.mNavigationData.Split('\n')) { var strItr = str.Split('\t'); let cmd = strItr.GetNext().Value; if (cmd != "fixit") continue; let arg = strItr.GetNext().Value; var fixitItem = menuItem.AddItem(arg); var infoCopy = new String(resolveParams.mNavigationData); fixitItem.mOnMenuItemSelected.Add(new (menu) => { mAutoComplete?.Close(); var autoComplete = new AutoComplete(mEditWidget); autoComplete.SetInfo(infoCopy); autoComplete.mAutoCompleteListWidget.mSelectIdx = fixitIdx; autoComplete.InsertSelection(0); autoComplete.Close(); } ~ { delete infoCopy; }); fixitIdx++; } } if (!menuItem.IsParent) { menuItem.IsParent = true; menuItem.SetDisabled(true); } } menu.AddItem(); menuItem = menu.AddItem("Cut|Ctrl+X"); menuItem.mOnMenuItemSelected.Add(new (menu) => { CutText(); }); menuItem.SetDisabled(!hasSelection); menuItem = menu.AddItem("Copy|Ctrl+C"); menuItem.mOnMenuItemSelected.Add(new (menu) => { CopyText(); }); menuItem.SetDisabled(!hasSelection); menuItem = menu.AddItem("Paste|Ctrl+V"); menuItem.mOnMenuItemSelected.Add(new (menu) => { PasteText(); }); // Debugger options menu.AddItem(); var debugger = IDEApp.sApp.mDebugger; bool isPaused = debugger.IsPaused(); menuItem = menu.AddItem("Show Disassembly"); menuItem.SetDisabled(!isPaused); menuItem.mOnMenuItemSelected.Add(new (evt) => IDEApp.sApp.ShowDisassemblyAtCursor()); var stepIntoSpecificMenu = menu.AddItem("Step into Specific"); stepIntoSpecificMenu.SetDisabled(!isPaused); stepIntoSpecificMenu.IsParent = true; var stepFilterMenu = menu.AddItem("Step Filter"); stepFilterMenu.SetDisabled(!isPaused); stepFilterMenu.IsParent = true; if (isPaused) { int addr; String file = scope String(); String stackFrameInfo = scope String(); debugger.GetStackFrameInfo(debugger.mActiveCallStackIdx, out addr, file, stackFrameInfo); if (addr != (int)0) { HashSet foundFilters = scope HashSet(); String lineCallAddrs = scope String(); var checkAddr = addr; if (debugger.mActiveCallStackIdx > 0) checkAddr--; // Bump back to an address in a calling instruction if (debugger.mActiveCallStackIdx > 0) checkAddr = debugger.GetStackFrameCalleeAddr(debugger.mActiveCallStackIdx); debugger.FindLineCallAddresses(checkAddr, lineCallAddrs); for (var callStr in String.StackSplit!(lineCallAddrs, '\n')) { if (!String.IsNullOrEmpty(callStr)) { Menu callMenuItem; var callData = String.StackSplit!(callStr, '\t'); String callInstLocStr = callData[0]; bool isPastAddr = false; if (callInstLocStr[0] == '-') { callInstLocStr.Remove(0); isPastAddr = true; } int callInstLoc = (int)int64.Parse(callInstLocStr, System.Globalization.NumberStyles.HexNumber); if (callData.Count == 1) { callMenuItem = stepIntoSpecificMenu.AddItem(StackStringFormat!("Indirect call at 0x{0:X}", callInstLoc)); } else { String name = callData[1]; StepFilter stepFilter = null; debugger.mStepFilterList.TryGetValue(name, out stepFilter); bool isDefaultFiltered = false; if (callData.Count >= 3) { if (callData[2].Contains('d')) isDefaultFiltered = true; } callMenuItem = stepIntoSpecificMenu.AddItem(name); if (!foundFilters.Contains(name)) { foundFilters.Add(scope:: String(name)); var filteredItem = stepFilterMenu.AddItem(name); for (int32 scopeIdx = 0; scopeIdx < 2; scopeIdx++) { bool isGlobal = scopeIdx != 0; var scopeItem = filteredItem.AddItem((scopeIdx == 0) ? "Workspace" : "Global"); if ((stepFilter != null) && (stepFilter.mIsGlobal == isGlobal)) { if (stepFilter.mKind == .Filtered) scopeItem.mIconImage = DarkTheme.sDarkTheme.GetImage(.StepFilter); else scopeItem.mIconImage = DarkTheme.sDarkTheme.GetImage(.LinePointer); filteredItem.mIconImage = scopeItem.mIconImage; scopeItem.mOnMenuItemSelected.Add(new (evt) => { debugger.DeleteStepFilter(stepFilter); }); } else { if (isDefaultFiltered) { scopeItem.mIconImage = DarkTheme.sDarkTheme.GetImage(.StepFilteredDefault); filteredItem.mIconImage = scopeItem.mIconImage; } String nameCopy = new String(name); scopeItem.mOnMenuItemSelected.Add(new (evt) => { debugger.CreateStepFilter(nameCopy, isGlobal, isDefaultFiltered ? .NotFiltered : .Filtered); } ~ delete nameCopy ); } } } } if (isPastAddr) callMenuItem.mDisabled = true ; callMenuItem.mOnMenuItemSelected.Add(new (evt) => { IDEApp.sApp.StepIntoSpecific(callInstLoc); }); } } } } stepIntoSpecificMenu.mDisabled |= stepIntoSpecificMenu.mItems.Count == 0; stepFilterMenu.mDisabled |= stepFilterMenu.mItems.Count == 0; } else // (!mSourceViewPanel.mIsSourceCode) { var menuItem = menu.AddItem("Word Wrap"); if (mWordWrap) menuItem.mIconImage = DarkTheme.sDarkTheme.GetImage(.Check); menuItem.mOnMenuItemSelected.Add(new (evt) => { mWordWrap = !mWordWrap; ContentChanged(); }); } /*menu.AddItem("Item 2"); menu.AddItem(); menu.AddItem("Item 3");*/ } MenuWidget menuWidget = DarkTheme.sDarkTheme.CreateMenuWidget(menu); menuWidget.Init(this, useX, useY); //menuWidget.mWidgetWindow.mWindowClosedHandler += MenuClosed; } } public override void Undo() { var symbolReferenceHelper = IDEApp.sApp.mSymbolReferenceHelper; if ((symbolReferenceHelper != null) && (symbolReferenceHelper.mKind == SymbolReferenceHelper.Kind.Rename)) { symbolReferenceHelper.Revert(); return; } base.Undo(); } public override void Redo() { base.Redo(); } public bool IsSymbolChar(char8 c) { return (c.IsLetterOrDigit) || (c == '_'); } public override void GetInsertFlags(int index, ref uint8 typeId, ref uint8 flags) { if ((index > 0) && (IsSymbolChar((char8)mData.mText[index - 1].mChar))) { typeId = mData.mText[index - 1].mDisplayTypeId; // Copy attr from prev attr flags = (uint8)(mData.mText[index - 1].mDisplayFlags & mExtendDisplayFlags) | mInsertDisplayFlags; } else if (index < mData.mTextLength - 1) { if (IsSymbolChar((char8)mData.mText[index].mChar)) { typeId = mData.mText[index].mDisplayTypeId; // Copy attr from prev attr flags = (uint8)(mData.mText[index].mDisplayFlags & mExtendDisplayFlags) | mInsertDisplayFlags; } else { let displayTypeId = (SourceElementType)mData.mText[index].mDisplayTypeId; if ((displayTypeId == .Literal) || (displayTypeId == .Comment)) { typeId = (uint8)displayTypeId; } } } } public override void InsertText(int index, String text) { if (IDEApp.sApp.mSymbolReferenceHelper != null) IDEApp.sApp.mSymbolReferenceHelper.SourcePreInsertText(this, index, text); for (var persistentTextPosition in PersistentTextPositions) { if (index <= persistentTextPosition.mIndex) persistentTextPosition.mIndex += (int32)text.Length; } base.InsertText(index, text); //if (mAsyncAutocomplete) { // Try to copy "skipped" flags int prevIdx = index; bool prevWasSkipped = false; while (prevIdx > 0) { prevIdx--; if (!((char8)mData.mText[prevIdx].mChar).IsWhiteSpace) { prevWasSkipped = ((mData.mText[prevIdx].mDisplayFlags & (uint8)SourceElementFlags.Skipped) != 0); break; } } if (prevWasSkipped) { for (int32 ofs = 0; ofs < text.Length; ofs++) mData.mText[index + ofs].mDisplayFlags |= (uint8)SourceElementFlags.Skipped; } } if ((text.Length > 1) && (mSourceViewPanel != null)) { // We need to do a full refresh after a paste mSourceViewPanel.QueueFullRefresh(false); } if ((mSourceViewPanel != null) && (IDEApp.sApp.mSymbolReferenceHelper != null)) IDEApp.sApp.mSymbolReferenceHelper.SourceUpdateText(this, index); } public override void RemoveText(int index, int length) { if (IDEApp.sApp.mSymbolReferenceHelper != null) IDEApp.sApp.mSymbolReferenceHelper.SourcePreRemoveText(this, index, length); for (var persistentTextPosition in PersistentTextPositions) { if (persistentTextPosition.mIndex >= index) { if (persistentTextPosition.mIndex <= index + length) { int32 lineRight = persistentTextPosition.mIndex; while ((lineRight < mData.mText.Count - 1) && (mData.mText[lineRight + 1].mChar != '\n')) lineRight++; if (lineRight < index + length) { // Even end of line is deleted mHadPersistentTextPositionDeletes = true; persistentTextPosition.mWasDeleted = true; persistentTextPosition.mIndex = (int32)index; continue; } else { persistentTextPosition.mIndex = (int32)index; continue; } } persistentTextPosition.mIndex -= (int32)length; } } base.RemoveText(index, length); if ((mSourceViewPanel != null) && (IDEApp.sApp.mSymbolReferenceHelper != null)) IDEApp.sApp.mSymbolReferenceHelper.SourceUpdateText(this, index); } public override void PhysCursorMoved() { //Debug.WriteLine("Cursor moved {0} {1}", CursorLineAndColumn.mLine, CursorLineAndColumn.mColumn); base.PhysCursorMoved(); mCursorStillTicks = 0; if ((mSourceViewPanel != null) && (mSourceViewPanel.mHoverWatch != null)) mSourceViewPanel.mHoverWatch.Close(); } public override void Update() { base.Update(); if (mAutoComplete != null) { mAutoComplete.Update(); } if ((mAutoComplete != null) && (mAutoComplete.mAutoCompleteListWidget != null)) { if ((mAutoComplete.mAutoCompleteListWidget.mDocumentationDelay == 0) && ((mSourceViewPanel == null) || (!mSourceViewPanel.ResolveCompiler.mThreadWorkerHi.mThreadRunning))) { mAutoComplete.mIsDocumentationPass = true; mAutoComplete.mAutoCompleteListWidget.mDocumentationDelay = -1; //Debug.WriteLine("Rehup for documentation"); ShowAutoComplete(mAutoComplete.mIsUserRequested); if (mAutoComplete != null) mAutoComplete.mIsDocumentationPass = false; } } if (mDbgDoTest) { if (mUpdateCnt % 8 == 0) { if ((mUpdateCnt / 8) % 2 == 0) KeyChar('z'); else KeyChar('\b'); } } if (mFastCursorState != null) { float dirX = 0; float dirY = 0; if (mWidgetWindow.IsKeyDown(KeyCode.Left)) dirX--; if (mWidgetWindow.IsKeyDown(KeyCode.Right)) dirX++; if (mWidgetWindow.IsKeyDown(KeyCode.Up)) dirY--; if (mWidgetWindow.IsKeyDown(KeyCode.Down)) dirY++; mFastCursorState.mX += dirX * 8.0; mFastCursorState.mY += dirY * 8.0; int line; int column; GetLineAndColumnAtCoord((float)mFastCursorState.mX, (float)mFastCursorState.mY, out line, out column); CursorLineAndColumn = LineAndColumn(line, column); if ((dirX == 0) && (dirY == 0)) { float x; float y; GetTextCoordAtCursor(out x, out y); mFastCursorState.mX = x; mFastCursorState.mY = y; } if ((mFastCursorState.mUpdateCnt % 4 == 0) || (dirX != 0) || (dirY != 0)) { mFastCursorState.mDrawSects.Add(default(FastCursorState.DrawSect)); } mFastCursorState.mUpdateCnt++; var lastDrawSect = ref mFastCursorState.mDrawSects[mFastCursorState.mDrawSects.Count - 1]; lastDrawSect.mX = mFastCursorState.mX; lastDrawSect.mY = mFastCursorState.mY; for (var drawSect in ref mFastCursorState.mDrawSects) { drawSect.mPct += 0.2f; if (drawSect.mPct >= 1.0) { @drawSect.Remove(); continue; } } if ((!mWidgetWindow.IsKeyDown(KeyCode.Control)) || ((!mWidgetWindow.IsKeyDown(KeyCode.Alt)))) { DeleteAndNullify!(mFastCursorState); } } } public override uint32 GetSelectionColor(uint8 flags) { bool hasFocus = mEditWidget.mHasFocus; if ((mSourceViewPanel != null) && (mSourceViewPanel.mQuickFind != null)) hasFocus |= mSourceViewPanel.mQuickFind.mFindEditWidget.mHasFocus; if ((flags & (uint8)SourceElementFlags.Find_Matches) != 0) { return 0x50FFE0B0; } return hasFocus ? mHiliteColor : mUnfocusedHiliteColor; } public override void Draw(Graphics g) { base.Draw(g); using (g.PushTranslate(mTextInsets.mLeft, mTextInsets.mTop)) { for (var queuedUnderline in mQueuedUnderlines) { DoDrawSectionFlags(g, queuedUnderline.mX, queuedUnderline.mY, queuedUnderline.mWidth, queuedUnderline.mFlags); } mQueuedUnderlines.Clear(); for (var queuedTextEntry in mQueuedText) { uint32 textColor = mTextColors[queuedTextEntry.mTypeIdAndFlags & 0xFF]; if (((queuedTextEntry.mTypeIdAndFlags >> 8) & (uint8)SourceElementFlags.Skipped) != 0) textColor = mTextColors[(int32)SourceElementType.Comment]; using (g.PushColor(textColor)) { DoDrawText(g, queuedTextEntry.mString, queuedTextEntry.mX, queuedTextEntry.mY); } queuedTextEntry.Dispose(); } mQueuedText.Clear(); } if (mFastCursorState != null) { for (var drawSect in mFastCursorState.mDrawSects) { using (g.PushTranslate((float)drawSect.mX - 32, (float)drawSect.mY - 24)) { //float showPct = (float)Math.Sin(drawSect.mPct * Math.PI) * 0.35f; float showPct = (drawSect.mPct + 0.1f) * 0.3f; using (g.PushColor(Color.Get(1.0f - drawSect.mPct))) using (g.PushScale(showPct, showPct, 32, 32)) g.Draw(IDEApp.sApp.mCircleImage); } } } } } }