using System; using System.Collections.Generic; using System.Text; using System.Threading.Tasks; using System.Threading; using System.Diagnostics; using System.IO; using Beefy; using Beefy.widgets; using Beefy.theme; using Beefy.gfx; using Beefy.events; using Beefy.theme.dark; using Beefy.utils; using Beefy.geom; using IDE.Debugger; using IDE.Compiler; using System.Security.Cryptography; using IDE.util; using Beefy2D.utils; namespace IDE.ui { public enum SourceElementType { Normal, Keyword, Literal, Identifier, Type, Comment, Method, TypeRef, Namespace, Disassembly_Text, Disassembly_FileName, Error, BuildError, BuildWarning, VisibleWhiteSpace } //[Flags] public enum SourceElementFlags { Error = 1, Warning = 2, IsAfter = 4, Skipped = 8, CompilerFlags_Mask = 0x0F, SpellingError = 0x10, Find_Matches = 0x20, SymbolReference = 0x40, EditorFlags_Mask = 0xF0, MASK = 0xFF } public class SourceEditWidget : DarkEditWidget { public TextPanel mPanel; public this(SourceViewPanel panel, SourceEditWidget refEditWidget = null) : base(new SourceEditWidgetContent((refEditWidget != null) ? refEditWidget.mEditWidgetContent : null)) { mPanel = panel; } public this(SourceViewPanel panel, SourceEditWidgetContent content) : base(content) { mPanel = panel; } public override void DrawAll(Graphics g) { base.DrawAll(g); } public override void MouseDown(float x, float y, int32 btn, int32 btnCount) { base.MouseDown(x, y, btn, btnCount); } public override void GotFocus() { //Debug.WriteLine("SourceViewPanel.GotFocus {0}", this); if (var tabbedView = mParent.mParent as IDETabbedView) { if (tabbedView.mIsFillWidget) gApp.mActiveDocumentsTabbedView = tabbedView; } var sourceEditWidgetContent = (SourceEditWidgetContent)mEditWidgetContent; // Just got focus, flip this on until we get a KeyDown sourceEditWidgetContent.mIgnoreKeyChar = true; if (mPanel != null) { mPanel.mLastFocusTick = IDEApp.sApp.mUpdateCnt; mPanel.EditGotFocus(); } base.GotFocus(); } public override void LostFocus() { if (mPanel != null) mPanel.EditLostFocus(); base.LostFocus(); } protected override bool WantsUnfocus() { if ((mWidgetWindow != null) && (mWidgetWindow.mOverWidget is PanelSplitter)) return false; return base.WantsUnfocus(); } } public class TrackedTextElementView { public TrackedTextElement mTrackedElement; public PersistentTextPosition mTextPosition; public int32 mLastBoundLine = -1; public int32 mLastMoveIdx; // Compare to mTrackedElement.mMoveIdx public this(TrackedTextElement trackedEntry) { mTrackedElement = trackedEntry; mTrackedElement.AddRef(); } public ~this() { mTrackedElement.Deref(); } } public class SourceFindTask { public WaitEvent mDoneEvent = new WaitEvent() ~ delete _; public SourceViewPanel mSourceViewPanel; public bool mCancelling; public String mFilePath ~ delete _; public String mFileName ~ delete _; public String mBackupFileName ~ delete _; // Didn't match hash public String mFoundPath ~ delete _; public Dictionary mRemapMap = new .() ~ DeleteDictionyAndKeysAndItems!(_); public ~this() { Cancel(); WaitFor(); } public void Cancel() { mCancelling = true; } public bool WaitFor(int waitMS = -1) { return mDoneEvent.WaitFor(waitMS); } void CheckFile(String filePath) { bool hashMatches = true; if (!(mSourceViewPanel.mLoadedHash case .None)) { SourceHash.Kind hashKind = mSourceViewPanel.mLoadedHash.GetKind(); var buffer = scope String(); SourceHash hash = ?; if (gApp.LoadTextFile(filePath, buffer, true, scope [&] () => { hash = SourceHash.Create(hashKind, buffer); }) case .Ok) { if (hash != mSourceViewPanel.mLoadedHash) hashMatches = false; } } if (!hashMatches) { if (mBackupFileName == null) mBackupFileName = new String(filePath); } else if (mFoundPath == null) mFoundPath = new String(filePath); } void SearchPath(String dirPath) { if (!dirPath.EndsWith('*')) { for (var file in Directory.EnumerateFiles(dirPath)) { var fileName = scope String(); file.GetFileName(fileName); if (fileName.Equals(mFileName, .OrdinalIgnoreCase)) { if (mFoundPath == null) { var filePath = scope String(); file.GetFilePath(filePath); CheckFile(filePath); } } } } String dirSearchStr = scope String(); dirSearchStr.Append(dirPath); if (!dirSearchStr.EndsWith('*')) dirSearchStr.Append("/*"); for (var dir in Directory.Enumerate(dirSearchStr, .Directories)) { var subPath = scope String(); dir.GetFilePath(subPath); SearchPath(subPath); } } public void Run() { let dir = scope String(); Path.GetDirectoryPath(mFilePath, dir); IDEUtils.FixFilePath(dir); if (!Environment.IsFileSystemCaseSensitive) dir.ToUpper(); // Check for files relative to manually-specified alias paths for (var (origDir, localDir) in mRemapMap) { if ((mFilePath.Length > origDir.Length) && (dir.StartsWith(origDir)) && (mFilePath[origDir.Length] == Path.DirectorySeparatorChar)) { let localPath = scope String(); localPath.Append(localDir); localPath.Append(mFilePath, origDir.Length); CheckFile(localPath); } } if (mFoundPath == null) { for (let searchPath in gApp.mSettings.mDebuggerSettings.mAutoFindPaths) { SearchPath(searchPath); } } mDoneEvent.Set(true); } } public enum SourceHash { public enum Kind { None, MD5, SHA256 } case None; case MD5(MD5Hash hash); case SHA256(SHA256Hash hash); public Kind GetKind() { switch (this) { case .MD5: return .MD5; case .SHA256: return .SHA256; default: return .None; } } public static SourceHash Create(Kind kind, StringView str) { switch (kind) { case .MD5: return .MD5(Security.Cryptography.MD5.Hash(.((uint8*)str.Ptr, str.Length))); case .SHA256: return .SHA256(Security.Cryptography.SHA256.Hash(.((uint8*)str.Ptr, str.Length))); default: return .None; } } public static bool operator==(SourceHash lhs, SourceHash rhs) { switch (lhs) { case .MD5(let lhsMD5): if (rhs case .MD5(let rhsMD5)) return lhsMD5 == rhsMD5; case .SHA256(let lhsSHA256): if (rhs case .SHA256(let rhsSHA256)) return lhsSHA256 == rhsSHA256; default: } return false; } } class QueuedAutoComplete { public char32 mKeyChar; public SourceEditWidgetContent.AutoCompleteOptions mOptions; } struct LinePointerDrawData { public Image mImage; public int32 mLine; public int32 mUpdateCnt; public int32 mDebuggerContinueIdx; } public class SourceViewPanel : TextPanel { enum SourceDisplayId { Cleared, AutoComplete, SpellCheck, FullClassify, SkipResult } public bool mAsyncAutocomplete = true; public SourceEditWidget mEditWidget; public ProjectSource mProjectSource; public FileEditData mEditData; public List mTrackedTextElementViewList ~ DeleteContainerAndItems!(_); public List mErrorList = new List() ~ DeleteContainerAndItems!(_); public List mDeferredResolveResults = new .() ~ DeleteContainerAndItems!(_); public bool mTrackedTextElementViewListDirty; public String mFilePath ~ delete _; public bool mIsBinary; public String mAliasFilePath ~ delete _; public SourceHash mLoadedHash; #if IDE_C_SUPPORT public ProjectSource mClangSource; // For headers, an implementing .cpp file #endif public int32 mLastMRUVersion; public int32 mLastTextVersionId; public int32 mAutocompleteTextVersionId; public int32 mClassifiedTextVersionId; public bool mLoadFailed; String mOldVerLoadCmd ~ delete _; HTTPRequest mOldVerHTTPRequest ~ delete _; IDEApp.ExecutionInstance mOldVerLoadExecutionInstance ~ { if (_ != null) _.mAutoDelete = true; }; SourceFindTask mSourceFindTask ~ delete _; bool mWantsFastClassify; bool mWantsFullClassify; // This triggers a classify bool mWantsFullRefresh; // If mWantsFullClassify is set, mWantsFullRefresh makes the whole thing refresh bool mRefireMouseOverAfterRefresh; bool mWantsBackgroundAutocomplete; QueuedAutoComplete mQueuedAutoComplete ~ delete _; public bool mWantsSpellCheck; int32 mTicksSinceTextChanged; int32 mErrorLookupTextIdx = -1; LinePointerDrawData mLinePointerDrawData; #if IDE_C_SUPPORT public String mClangHoverErrorData ~ delete mClangHoverErrorData; #endif public EditWidgetContent.CharData[] mProcessSpellCheckCharData ~ delete _; public IdSpan mProcessSpellCheckCharIdSpan ~ _.Dispose(); //public int mBackgroundCursorIdx; String mQueuedLocation ~ delete _; bool mWantsClassifyAutocomplete; bool mWantsCurrentLocation; public bool mIsPerformingBackgroundClassify; public bool mClassifyPaused = false; public bool mUseDebugKeyboard; //public int32 mLastFileTextVersion; public bool mHasChangedSinceLastCompile; public bool mIsSourceCode; public bool mIsBeefSource; public bool mIsClang; #if IDE_C_SUPPORT public bool mClangSourceChanged; //public ClangCompiler mClangHelper; bool mDidClangSource; #endif public bool mJustShown; public double mDesiredVertPos; public float mLockFlashPct; ResolveType mBackgroundResolveType; SourceViewPanel mOldVersionPanel; SourceViewPanel mCurrentVersionPanel; EditWidgetContent.LineAndColumn? mRequestedLineAndColumn; SourceViewPanel mSplitTopPanel; // This is only set if we are controlling a top panel PanelSplitter mSplitter; SourceViewPanel mSplitBottomPanel; // The primary owning panel is the bottom panel, this only set if we are the bottom panel int mHotFileIdx = -1; bool mIsOldCompiledVersion; static bool sPreviousVersionWarningShown; PanelHeader mPanelHeader; NavigationBar mNavigationBar; public SymbolReferenceHelper mRenameSymbolDialog; public int32 mBackgroundDelay = 0; public Monitor mMonitor = new Monitor() ~ delete _; bool mDidSpellCheck; int32 mSpellCheckJobCount; int32 mResolveJobCount; bool mWantsParserCleanup; bool mInPostRemoveUpdatePanels; // For multi-view files... PersistentTextPosition mTrackCursorPos; PersistentTextPosition mTrackSelStart; PersistentTextPosition mTrackSelEnds; public override float TabWidthOffset { get { return 30; } } public bool NeedsPostRemoveUpdate { get { return !mDeferredResolveResults.IsEmpty || (mResolveJobCount > 0) || (mSpellCheckJobCount > 0); } } public override SourceEditWidget EditWidget { get { return mEditWidget; } } public CompilerBase ResolveCompiler { get { #if IDE_C_SUPPORT if (mIsBeefSource) return IDEApp.sApp.mBfResolveCompiler; return IDEApp.sApp.mResolveClang; #else return IDEApp.sApp.mBfResolveCompiler; #endif } } public BfCompiler BfResolveCompiler { get { #if IDE_C_SUPPORT if (mIsBeefSource) return IDEApp.sApp.mBfResolveCompiler; return null; #else return IDEApp.sApp.mBfResolveCompiler; #endif } } #if IDE_C_SUPPORT public ClangCompiler ClangResolveCompiler { get { if (mIsClang) return IDEApp.sApp.mResolveClang; return null; } } #endif public BfSystem BfResolveSystem { get { #if IDE_C_SUPPORT if (!mIsClang) return IDEApp.sApp.mBfResolveSystem; return null; #else return IDEApp.sApp.mBfResolveSystem; #endif } } public NavigationBar PrimaryNavigationBar { get { if (mSplitBottomPanel != null) return mSplitBottomPanel.mNavigationBar; return mNavigationBar; } } public bool IsReadOnly { get { if (gApp.mSettings.mEditorSettings.mLockEditing) return true; if ((mProjectSource != null) && (mProjectSource.mProject.mLocked)) return true; if (gApp.mDebugger.mIsRunning) { switch (gApp.mSettings.mEditorSettings.mLockEditingWhenDebugging) { case .Always: return true; case .Never: break; case .WhenNotHotSwappable: if ((!mIsBeefSource) || (!gApp.mDebugger.mIsRunningCompiled)) { return true; } } } return false; } } ProjectSource FilteredProjectSource { get { if (mProjectSource == null) return null; if (!gApp.IsProjectSourceEnabled(mProjectSource)) return null; return mProjectSource; } } public this() { DebugManager debugManager = IDEApp.sApp.mDebugger; debugManager.mBreakpointsChangedDelegate.Add(new => BreakpointsChanged); mNavigationBar = new NavigationBar(this); AddWidget(mNavigationBar); } public ~this() { if (mInPostRemoveUpdatePanels) { //Debug.WriteLine("Removing sourceViewPanel from mPostRemoveUpdatePanel {0} in ~this ", this); gApp.mPostRemoveUpdatePanels.Remove(this); } if (!mDisposed) Dispose(); /*if (mOldVersionPanel != null) { Widget.RemoveAndDelete(mOldVersionPanel); mOldVersionPanel = null; }*/ } SourceEditWidgetContent Content { get { return (SourceEditWidgetContent)mEditWidget.Content; } } void DoAutoComplete(char32 keyChar, SourceEditWidgetContent.AutoCompleteOptions options) { var editWidgetContent = (SourceEditWidgetContent)mEditWidget.Content; /*bool isValid = false; int cursorPos = editWidgetContent.mCursorTextPos; if (cursorPos > 0) { char8 c = editWidgetContent.mData.mText[cursorPos].mChar; if ((c.IsLetterOrDigit) || (c == '') } if (!isValid) { Debug.WriteLine("DoAutoComplete ignored"); return; }*/ if (ResolveCompiler.mThreadWorkerHi.mThreadRunning) { //Debug.WriteLine("Deferred DoAutoComplete"); DeleteAndNullify!(mQueuedAutoComplete); mQueuedAutoComplete = new .(); mQueuedAutoComplete.mKeyChar = keyChar; mQueuedAutoComplete.mOptions = options; return; } //Debug.WriteLine("DoAutoComplete"); if (editWidgetContent.mAutoComplete != null) { editWidgetContent.mAutoComplete.mInvokeOnly = options.HasFlag(.OnlyShowInvoke); //Debug.WriteLine("Setting invokeonly {0} {1}", editWidgetContent.mAutoComplete, editWidgetContent.mAutoComplete.mInvokeOnly); } //Classify(options.HasFlag(.HighPriority) ? ResolveType.Autocomplete_HighPri : ResolveType.Autocomplete); ResolveParams resolveParams = new ResolveParams(); resolveParams.mIsUserRequested = options.HasFlag(.UserRequested); Classify(.Autocomplete, resolveParams); if (!resolveParams.mInDeferredList) delete resolveParams; if (mIsClang) { // We classify afterwards so we can attempt to detect any bound functions mWantsFullClassify = true; mWantsClassifyAutocomplete = options.HasFlag(.UserRequested); } } public void CancelResolve(ResolveType resolveType) { for (var resolveResults in mDeferredResolveResults) { if (resolveResults.mResolveType == resolveType) { resolveResults.mCancelled = true; } } } void CancelAutocomplete() { CancelResolve(.Autocomplete); DeleteAndNullify!(mQueuedAutoComplete); } void SetupEditWidget() { if (mEditWidget.mParent == null) AddWidget(mEditWidget); mEditWidget.mPanel = this; var editWidgetContent = (SourceEditWidgetContent)mEditWidget.Content; delete editWidgetContent.mOnGenerateAutocomplete; editWidgetContent.mOnGenerateAutocomplete = new => DoAutoComplete; editWidgetContent.mSourceViewPanel = this; delete editWidgetContent.mOnEscape; editWidgetContent.mOnEscape = new () => EscapeHandler(); delete editWidgetContent.mOnFinishAsyncAutocomplete; editWidgetContent.mOnFinishAsyncAutocomplete = new () => { scope AutoBeefPerf("editWidgetContent.mOnFinishAsyncAutocomplete"); ProcessDeferredResolveResults(-1, true); if (mQueuedAutoComplete != null) { DoAutoComplete(mQueuedAutoComplete.mKeyChar, mQueuedAutoComplete.mOptions); DeleteAndNullify!(mQueuedAutoComplete); ProcessDeferredResolveResults(-1, true); } }; delete editWidgetContent.mOnCancelAsyncAutocomplete; editWidgetContent.mOnCancelAsyncAutocomplete = new () => { CancelAutocomplete(); }; } AutoComplete GetAutoComplete() { return ((SourceEditWidgetContent)mEditWidget.Content).mAutoComplete; } public override void Serialize(StructuredData data) { base.Serialize(data); if (mFilePath == null) return; data.Add("Type", "SourceViewPanel"); data.Add("FilePath", mFilePath); if (mAliasFilePath != null) data.Add("AliasFilePath", mAliasFilePath); data.ConditionalAdd("CursorPos", mEditWidget.Content.CursorTextPos, 0); data.ConditionalAdd("VertPos", mEditWidget.mVertScrollbar.mContentPos, 0); if (mProjectSource != null) data.Add("ProjectName", mProjectSource.mProject.mProjectName); } public override bool Deserialize(StructuredData data) { base.Deserialize(data); String filePath = scope String(); data.GetString("FilePath", filePath); String projectName = scope String(); data.GetString("ProjectName", projectName); var aliasFilePath = scope String(); data.GetString("AliasFilePath", aliasFilePath); if (!aliasFilePath.IsEmpty) mAliasFilePath = new String(aliasFilePath); bool foundProjectSource = false; if (projectName != null) { var project = IDEApp.sApp.FindProjectByName(projectName); if (project != null) { String relPath = scope String(); project.GetProjectRelPath(filePath, relPath); var projectItem = IDEApp.sApp.FindProjectItem(project.mRootFolder, relPath); if (projectItem != null) { if (!Show(projectItem, true)) return false; foundProjectSource = true; } } } if (!foundProjectSource) { if (!Show(filePath, true)) return false; } int32 cursorPos = data.GetInt("CursorPos"); mEditWidget.Content.mCursorTextPos = Math.Min(cursorPos, mEditWidget.mEditWidgetContent.mData.mTextLength); mDesiredVertPos = data.GetFloat("VertPos"); return true; } public void QueueFullRefresh(bool configMayHaveChanged) { #if IDE_C_SUPPORT if ((mIsClang) && (configMayHaveChanged)) { // Force file to be recreated with potentially new Clang settings using (mMonitor.Enter()) { var clangCompiler = ClangResolveCompiler; clangCompiler.QueueFileRemoved(mFilePath); if (mClangSource != null) mClangSource = null; mDidClangSource = false; } } #endif if ((configMayHaveChanged) && (mSplitTopPanel != null)) mSplitTopPanel.QueueFullRefresh(configMayHaveChanged); mWantsFullClassify = true; mWantsFullRefresh = true; // We do this every time we autocomplete, so we don't want to set mDidClangSource to false here. Why did we have that? //if (mIsClang) //mDidClangSource = false; } public override bool EscapeHandler() { if (IDEApp.sApp.mSymbolReferenceHelper != null) { IDEApp.sApp.mSymbolReferenceHelper.Cancel(); return true; } if (base.EscapeHandler()) return true; if (HasFocus(true)) { if ((mSplitTopPanel != null) && (mSplitTopPanel.EscapeHandler())) return true; if ((mSplitBottomPanel != null) && (mSplitBottomPanel.EscapeHandler())) return true; } // Remove F4-selection from output panels IDEApp.sApp.mOutputPanel.EscapeHandler(); IDEApp.sApp.mFindResultsPanel.EscapeHandler(); return false; } public bool IsControllingEditData() { // We are controlling if we have focus or, if none have focus, if we're the most recently added var users = Content.mData.mUsers; for (var user in users) { if (user.mEditWidget.mHasFocus) return mEditWidget == user.mEditWidget; } return mEditWidget == users[users.Count - 1].mEditWidget; } public bool HasUnsavedChanges() { //return mLastFileTextVersion != mEditWidget.Content.mData.mCurTextVersionId; if (mFilePath == null) return true; if (mEditData == null) return false; if (mEditData.mFileDeleted) return true; return mEditData.mLastFileTextVersion != mEditWidget.Content.mData.mCurTextVersionId; } public bool IsLastViewOfData() { int32 expectCount = 1; if (mSplitTopPanel != null) expectCount = 2; return Content.Data.mUsers.Count == expectCount; } public bool HasTextChangedSinceCompile(int defLineStart, int defLineEnd, int compileInstanceIdx) { /*if ((mProjectSource != null) && (!mProjectSource.mHasChangedSinceLastSuccessfulCompile)) return false;*/ if ((compileInstanceIdx == -1) && (!mHasChangedSinceLastCompile)) return false; let projectSource = FilteredProjectSource; if (projectSource == null) return false; int32 char8IdStart; int32 char8IdEnd; IdSpan char8IdData = IDEApp.sApp.mWorkspace.GetProjectSourceSelectionCharIds(projectSource, compileInstanceIdx, defLineStart, defLineEnd, out char8IdStart, out char8IdEnd); if (char8IdData.IsEmpty) return false; bool doDebug = false; if (doDebug) { char8IdData.Dump(); mEditWidget.mEditWidgetContent.mData.mTextIdData.Dump(); } return !char8IdData.IsRangeEqual(mEditWidget.mEditWidgetContent.mData.mTextIdData.GetPrepared(), char8IdStart, char8IdEnd); } public void FileSaved() { ClearLoadFailed(); #if IDE_C_SUPPORT mClangSourceChanged = false; #endif //mLastFileTextVersion = mEditWidget.Content.mData.mCurTextVersionId; if (mEditData != null) { mEditData.mLastFileTextVersion = mEditWidget.Content.mData.mCurTextVersionId; mEditData.mHadRefusedFileChange = false; } if (mProjectSource != null) { //mProjectSource.ClearUnsavedData(); var editText = scope String(); mEditWidget.GetText(editText); using (gApp.mMonitor.Enter()) { mEditData.SetSavedData(editText, mEditWidget.mEditWidgetContent.mData.mTextIdData.GetPrepared()); } } gApp.FileChanged(mFilePath); } void ClassifyThreadDone() { /*if (mProcessingPassInstance != null) { //Debug.WriteLine("Pass Instance {0} Done. Dispose? {1}", mProcessingPassInstance.mId, mWidgetWindow == null); // If we've closed the window by the time our pass instance is completed then just drop the results if (mWidgetWindow == null) { delete mProcessingPassInstance; mProcessingPassInstance = null; } }*/ mIsPerformingBackgroundClassify = false; mResolveJobCount--; } void BackgroundResolve(ThreadStart threadStart) { } public bool Classify(ResolveType resolveType, ResolveParams resolveParams = null) { if (resolveType == .Autocomplete) { NOP!(); } // Don't allow other classify calls interrupt a symbol rename if ((IDEApp.sApp.mSymbolReferenceHelper != null) && (IDEApp.sApp.mSymbolReferenceHelper.HasStarted) && (IDEApp.sApp.mSymbolReferenceHelper.mKind == SymbolReferenceHelper.Kind.Rename)) return false; //Debug.WriteLine("Classify({0})", resolveType); if (!mIsSourceCode) return true; if (resolveParams != null) resolveParams.mResolveType = resolveType; /*if (mWidgetWindow.IsKeyDown(.Control)) { NOP!(); }*/ //Debug.WriteLine("Classify {0} {1}", this, resolveType); scope AutoBeefPerf("SourceViewPanel.Classify"); /*if ((mIsClang) && (!mDidClangSource)) { mDidClangSource = true; var clangCompiler = ClangResolveCompiler; if (mProjectSource != null) { clangCompiler.QueueProjectSource(mProjectSource); // Return because now we need to wait for this to finish return false; } }*/ var useResolveType = resolveType; var compiler = ResolveCompiler; var bfCompiler = BfResolveCompiler; BfSystem bfSystem = null; if (bfCompiler != null) bfSystem = IDEApp.sApp.mBfResolveSystem; /*if ((!mIsBeefSource) || (!bfSystem.HasParser(mFilePath))) { //DoFastClassify(); //return; }*/ if (bfSystem == null) return false; if (bfSystem != null) bfSystem.PerfZoneStart("Classify"); let projectSource = FilteredProjectSource; //Debug.WriteLine("Classify {0}", doAutocomplete); //bfSystem.PerfZoneStart("CancelBackground"); bool hasValidProjectSource = false; if (projectSource != null) { if (mIsBeefSource) { var bfProject = bfSystem.GetBfProject(projectSource.mProject); if (!bfProject.mDisabled) hasValidProjectSource = true; } else hasValidProjectSource = true; } mClassifyPaused = false; if (mIsClang) { if (resolveType == .Autocomplete) return true; // Don't do anything // Clang always just does a 'fast classify' DoFastClassify(); } if (useResolveType == ResolveType.Autocomplete) mAutocompleteTextVersionId = mEditWidget.Content.mData.mCurTextVersionId; bool wasHighPri = false; if (useResolveType == ResolveType.Autocomplete_HighPri) { wasHighPri = true; if (mIsClang) { compiler.CancelBackground(); //delete mQueuedAutocompleteInfo; //mQueuedAutocompleteInfo = null; mWantsBackgroundAutocomplete = false; } useResolveType = ResolveType.Autocomplete; } bool doBackground = (useResolveType == ResolveType.Classify) || (useResolveType == ResolveType.ClassifyFullRefresh); if (mAsyncAutocomplete) { if ((useResolveType == .Autocomplete) || (useResolveType == .GetCurrentLocation) || (useResolveType == .GetSymbolInfo)) doBackground = true; } if (mIsClang) { #if !IDE_C_SUPPORT return true; #else if (useResolveType == ResolveType.GetCurrentLocation) doBackground = (useResolveType == ResolveType.GetCurrentLocation); if (useResolveType == ResolveType.Autocomplete) { mWantsClassifyAutocomplete = GetAutoComplete() != null; var autocomplete = GetAutoComplete(); if (autocomplete != null) autocomplete.UpdateAsyncInfo(); if ((compiler.IsPerformingBackgroundOperation()) || (mQueuedAutocompleteInfo != null)) { if ((mBackgroundResolveType == ResolveType.Autocomplete) && (!mIgnorePendingAsyncAutocomplete)) { // We already have a relevant pending autocomplete return true; } Debug.Assert(!wasHighPri); mWantsBackgroundAutocomplete = true; return true; } mIgnorePendingAsyncAutocomplete = false; doBackground = true; } else if (useResolveType == ResolveType.GetNavigationData) { #if IDE_C_SUPPORT DoClangClassify(useResolveType, resolveParams); doBackground = false; #else return false; #endif } #endif } if (doBackground) { ProcessDeferredResolveResults(0); var resolveParams; if (resolveParams == null) { resolveParams = new ResolveParams(); } resolveParams.mResolveType = resolveType; resolveParams.mWaitEvent = new WaitEvent(); resolveParams.mInDeferredList = true; mDeferredResolveResults.Add(resolveParams); var autoComplete = GetAutoComplete(); if ((autoComplete != null) && (autoComplete.mIsDocumentationPass)) { let ewc = (SourceEditWidgetContent)mEditWidget.mEditWidgetContent; Debug.Assert(ewc.mAutoComplete != null); let selectedEntry = ewc.mAutoComplete.mAutoCompleteListWidget.mEntryList[ewc.mAutoComplete.mAutoCompleteListWidget.mSelectIdx]; resolveParams.mDocumentationName = new String(selectedEntry.mEntryDisplay); } resolveParams.mTextVersion = Content.mData.mCurTextVersionId; compiler.CheckThreadDone(); // Process any pending thread done callbacks bool isHi = (resolveType != .ClassifyFullRefresh) && (resolveType != .Classify); if (isHi) Debug.Assert(!bfCompiler.mThreadWorkerHi.mThreadRunning); else Debug.Assert(!bfCompiler.mThreadWorker.mThreadRunning); if (bfSystem != null) bfSystem.PerfZoneStart("DoBackground"); //ProcessResolveData(); //Debug.Assert(mProcessResolveCharData == null); DuplicateEditState(out resolveParams.mCharData, out resolveParams.mCharIdSpan); //Debug.WriteLine("Edit State: {0}", mProcessResolveCharData); if ((useResolveType == .Autocomplete) || (useResolveType == .GetSymbolInfo) || (mIsClang)) { resolveParams.mOverrideCursorPos = (.)mEditWidget.Content.CursorTextPos; /*if (useResolveType == .Autocomplete) resolveParams.mOverrideCursorPos--;*/ } //Debug.Assert(mCurParser == null); if ((mIsBeefSource) && (hasValidProjectSource) && (!isHi)) resolveParams.mParser = bfSystem.FindParser(projectSource); //if (mCurParser != null) { if (!isHi) Debug.Assert(!mIsPerformingBackgroundClassify); mBackgroundResolveType = useResolveType; mIsPerformingBackgroundClassify = true; mResolveJobCount++; if (useResolveType == .Autocomplete) compiler.DoBackgroundHi(new () => { DoClassify(.Autocomplete, resolveParams, true); }, new => ClassifyThreadDone); //BackgroundResolve(new () => { DoClassify(.Autocomplete, resolveParams); }); else if (useResolveType == .ClassifyFullRefresh) { // To avoid "flashing" on proper colorization vs FastClassify, we wait a bit for the proper classifying to finish // on initial show int maxWait = (mUpdateCnt <= 1) ? 50 : 0; compiler.DoBackground(new () => { DoClassify(.ClassifyFullRefresh, resolveParams, false); }, new () => { ClassifyThreadDone(); }, maxWait); } else if (useResolveType == .GetCurrentLocation) compiler.DoBackgroundHi(new () => { DoClassify(.GetCurrentLocation, resolveParams, true); }, new => ClassifyThreadDone); else if (useResolveType == .GetSymbolInfo) compiler.DoBackgroundHi(new () => { DoClassify(.GetSymbolInfo, resolveParams, true); }, new => ClassifyThreadDone); else compiler.DoBackgroundHi(new () => { DoFullClassify(resolveParams); }, new => ClassifyThreadDone); } /*else { // Not part of project DoFastClassify(); }*/ if (bfSystem != null) bfSystem.PerfZoneEnd(); } else if ((mIsBeefSource) && (hasValidProjectSource)) { /*if (useResolveType == ResolveType.Autocomplete) { Profiler.StartSampling(); DoClassify(useResolveType, resolveParams); Profiler.StopSampling(); } else*/ DoClassify(useResolveType, resolveParams, true); MarkDirty(); } if (bfSystem != null) bfSystem.PerfZoneEnd(); return true; } public void DoParserCleanup() { var bfSystem = IDEApp.sApp.mBfResolveSystem; bfSystem.Lock(0); //bfSystem.RemoveDeletedParsers(); bfSystem.RemoveOldData(); bfSystem.Unlock(); } public void DoFullClassify(ResolveParams resolveParams) { var bfCompiler = BfResolveCompiler; //var bfSystem = IDEApp.sApp.mBfResolveSystem; //bfCompiler.StartTiming(); DoClassify(ResolveType.Classify, resolveParams); //bfCompiler.StopTiming(); if (bfCompiler != null) bfCompiler.QueueDeferredResolveAll(); } //[StdCall, CLink] //static extern char8* BfDiff_DiffText(char8* text1, char8* text2); public void DoFastClassify() { if (!mIsSourceCode) return; //Debug.WriteLine("DoFastClassify"); scope AutoBeefPerf("SourceViewPanel.DoFastClassify"); let projectSource = FilteredProjectSource; var bfSystem = IDEApp.sApp.mBfResolveSystem; if (bfSystem == null) return; //var compiler = ResolveCompiler; var char8Data = mEditWidget.Content.mData.mText; int char8Len = Math.Min(char8Data.Count, mEditWidget.Content.mData.mTextLength); char8[] chars = new char8[char8Len]; defer delete chars; for (int32 i = 0; i < char8Len; i++) chars[i] = (char8)char8Data[i].mChar; String text = scope String(); text.Append(chars, 0, chars.Count); BfProject bfProject = null; if ((projectSource != null) && (mIsBeefSource)) { bfProject = bfSystem.GetBfProject(projectSource.mProject); } var parser = bfSystem.CreateEmptyParser(bfProject); defer delete parser; var resolvePassData = parser.CreateResolvePassData(); defer delete resolvePassData; var passInstance = bfSystem.CreatePassInstance("DoFastClassify"); defer delete passInstance; parser.SetSource(text, mFilePath); parser.SetIsClassifying(); parser.Parse(passInstance, !mIsBeefSource); if (mIsBeefSource) parser.Reduce(passInstance); parser.ClassifySource(char8Data, !mIsBeefSource); mWantsParserCleanup = true; } public void DoFastClassify(int start, int length) { if (!mIsSourceCode) return; var bfSystem = IDEApp.sApp.mBfResolveSystem; if (bfSystem == null) return; //var compiler = ResolveCompiler; //var char8Data = mEditWidget.Content.mData.mText; let projectSource = FilteredProjectSource; int32 char8Len = mEditWidget.Content.mData.mTextLength; var char8Data = new EditWidgetContent.CharData[char8Len]; defer delete char8Data; var curCharData = mEditWidget.Content.mData.mText; char8[] chars = new char8[char8Len]; defer delete chars; for (int32 i = 0; i < char8Len; i++) { char8Data[i] = curCharData[i]; chars[i] = (char8)char8Data[i].mChar; } String text = scope String(chars, 0, chars.Count); BfProject bfProject = null; if ((projectSource != null) && (mIsBeefSource)) { bfProject = bfSystem.GetBfProject(projectSource.mProject); } var parser = bfSystem.CreateEmptyParser(bfProject); defer delete parser; var resolvePassData = parser.CreateResolvePassData(); defer delete resolvePassData; var passInstance = bfSystem.CreatePassInstance("DoFastClassify"); defer delete passInstance; parser.SetSource(text, mFilePath); parser.SetIsClassifying(); parser.Parse(passInstance, !mIsBeefSource); if (mIsBeefSource) parser.Reduce(passInstance); parser.ClassifySource(char8Data, !mIsBeefSource); mWantsParserCleanup = true; for (int i = start; i < start + length; i++) { curCharData[i] = char8Data[i]; } } void HandleAutocompleteInfo(ResolveType resolveType, String autocompleteInfo, bool clearList, bool changedAfterInfo) { var editWidgetContent = (SourceEditWidgetContent)mEditWidget.Content; if (mWidgetWindow == null) return; bool wantOpen = (autocompleteInfo.Length > 0); if ((editWidgetContent.mAutoComplete != null) && (editWidgetContent.mAutoComplete.mIsAsync) && (editWidgetContent.mAutoComplete.mInvokeWidget != null)) { // Leave the existing invoke widget open wantOpen = true; } if (wantOpen) { if (editWidgetContent.mAutoComplete == null) { editWidgetContent.mAutoComplete = new AutoComplete(mEditWidget); //if (mIsClang) //editWidgetContent.mAutoComplete.mIsAsync = true; editWidgetContent.mAutoComplete.mOnAutoCompleteInserted.Add(new () => { Classify(resolveType); }); editWidgetContent.mAutoComplete.mOnClosed.Add(new () => { //Debug.WriteLine("Autocomplete Closed"); if (editWidgetContent.mAutoComplete != null) { editWidgetContent.mAutoComplete = null; } // Any pending autocomplete is no longer valid CancelAutocomplete(); }); } if (editWidgetContent.mAutoComplete.mIsDocumentationPass) editWidgetContent.mAutoComplete.UpdateInfo(autocompleteInfo); else editWidgetContent.mAutoComplete.SetInfo(autocompleteInfo, clearList, 0, changedAfterInfo); } else if ((editWidgetContent.mAutoComplete != null) && (!editWidgetContent.mAutoComplete.mIsDocumentationPass)) editWidgetContent.mAutoComplete.Close(); } #if IDE_C_SUPPORT public void DoClangClassify(ResolveType resolveType, ResolveParams resolveParams = null) { var buildClang = IDEApp.sApp.mDepClang; var resolveClang = (ClangCompiler)ResolveCompiler; bool isBackground = (resolveType == ResolveType.Classify) || (resolveType == ResolveType.ClassifyFullRefresh) || (resolveType == ResolveType.Autocomplete) || (resolveType == ResolveType.Autocomplete_HighPri) || (resolveType == ResolveType.GetCurrentLocation); if (!isBackground) { resolveClang.CancelBackground(); } if (resolveType == ResolveType.GetCurrentLocation) { NOP!(); } if (isBackground) Debug.Assert(mIsPerformingBackgroundClassify); var char8Data = (!isBackground) ? mEditWidget.Content.mData.mText : mProcessResolveCharData; Debug.Assert(char8Data != null); int char8Len = 0; if (char8Data != null) char8Len = (!isBackground) ? mEditWidget.Content.mData.mTextLength : mProcessResolveCharData.Length; mBackgroundCursorIdx = (int32)Math.Min(mBackgroundCursorIdx, char8Len); bool didClangSource = false; using (mMonitor.Enter()) { didClangSource = mDidClangSource; mDidClangSource = true; } //bool needsNewClangSource = IDEUtils.IsHeaderFile(mFilePath) && (mClangSource == null); //if ((!didClangSource) || (needsNewClangSource)) if (!didClangSource) { bool handled = false; //if (needsNewClangSource) if (IDEUtils.IsHeaderFile(mFilePath)) { // Wait for buildClang to stop buildClang.CancelBackground(); // Process the buildClang commands in this thread (if needed) buildClang.ProcessQueue(); ProjectSource projectSource = null; String findStr = scope String("/"); Path.GetFileNameWithoutExtension(mFilePath, findStr); findStr.Append("."); String findFilePath = scope String(mFilePath); if (!Utils.IsFileSystemCaseSensitive) findFilePath.ToUpper; //foreach (var checkProjectSource in resolveClang.mSourceMRUList) for (int mruIdx = resolveClang.mSourceMRUList.Count - 1; mruIdx >= 0; mruIdx--) { var checkProjectSource = resolveClang.mSourceMRUList[mruIdx]; ClangCompiler.FileEntry fileEntry = null; buildClang.mProjectFileSet.TryGetValue(checkProjectSource, out fileEntry); if (fileEntry != null) { if (fileEntry.mCDepFileRefs.Contains(findFilePath)) { projectSource = checkProjectSource; break; } } } // First try to find a file with the same base name, then try to for (int32 pass = 0; pass < 2; pass++) { if (projectSource != null) break; for (var projectPair in buildClang.mProjectFileSet) { var checkProjectSource = projectPair.Key; var fileData = projectPair.Value; if ((fileData.mCDepFileRefs != null) && (((pass == 1) || (checkProjectSource.mPath.IndexOf(findStr, true) != -1)))) { if (fileData.mCDepFileRefs.Contains(findFilePath)) projectSource = checkProjectSource; } //projectPair.Value.mCDepFileRefs.Contains(); } } String headerPrefix = scope String(); if (projectSource != null) { mClangSource = projectSource; IdSpan idSpan; String fileContent = scope String(); IDEApp.sApp.FindProjectSourceContent(mClangSource, out idSpan, true, fileContent); idSpan.Dispose(); String clangArgs = scope String(); resolveClang.GetClangArgs(mClangSource, clangArgs); String fullImportPath = scope String(); mClangSource.GetFullImportPath(fullImportPath); int32 includePos = resolveClang.CDepGetIncludePosition(fullImportPath, fileContent, mFilePath, clangArgs); if (includePos != -1) fileContent.Substring(0, includePos, headerPrefix); resolveClang.AddTranslationUnit(mFilePath, headerPrefix, clangArgs); } else { resolveClang.AddTranslationUnit(mFilePath, "", ""); } handled = true; } if ((!handled) && (!didClangSource)) { Debug.Assert(char8Data != null); if (mProjectSource != null) { resolveClang.AddTranslationUnit(mProjectSource, null, char8Data, char8Len); } } } if (resolveType == ResolveType.GetNavigationData) { resolveClang.CancelBackground(); resolveParams.mNavigationData = new String(); resolveClang.GetNavigationData(mFilePath, resolveParams.mNavigationData); return; } if (resolveType == ResolveType.GetCurrentLocation) { String result = new String(); resolveClang.GetCurrentLocation(mFilePath, result, mBackgroundCursorIdx); delete mQueuedLocation; mQueuedLocation = result; return; } //string fileName; //string headerFileName = null; if (resolveType == ResolveType.Autocomplete) { Debug.Assert(mQueuedAutocompleteInfo == null); mQueuedAutocompleteInfo = new String(); resolveClang.Autocomplete(mFilePath, char8Data, char8Len, mBackgroundCursorIdx, mQueuedAutocompleteInfo); if ((mBackgroundCursorIdx != -1) && (mQueuedAutocompleteInfo == null)) mQueuedAutocompleteInfo = ""; if (mIgnorePendingAsyncAutocomplete) { delete mQueuedAutocompleteInfo; mQueuedAutocompleteInfo = null; } } else { bool ignoreErrors = (mProjectSource == null) && (mClangSource == null); /*if (IDEUtils.IsHeaderFile(mFilePath)) ignoreErrors = mClangSource == null; else ignoreErrors = mProjectSource == null;*/ String results = scope String(); resolveClang.Classify(mFilePath, char8Data, char8Len, mBackgroundCursorIdx, mErrorLookupTextIdx, ignoreErrors, results); if (results != null) { String autocompleteResult = scope String(); for (var resultLine in String.StackSplit!(results, '\n')) { var resultLineSplit = scope List(); resultLine.Split(resultLineSplit, scope char8[] {'\t'}, 2); if (scope String(resultLineSplit[0]) == "diag") { delete mClangHoverErrorData; mClangHoverErrorData = new String(resultLineSplit[1]); } else if (resultLine != "") { autocompleteResult.Append(resultLine, "\n"); } } if ((mWantsClassifyAutocomplete) && (autocompleteResult.Length > 0)) { Debug.Assert(mQueuedClassifyInfo == null); mQueuedClassifyInfo = new String(); autocompleteResult.MoveTo(mQueuedClassifyInfo); } } mErrorLookupTextIdx = -1; } var bfSystem = IDEApp.sApp.mBfResolveSystem; bfSystem.RemoveDeletedParsers(); if (isBackground) Debug.Assert(mIsPerformingBackgroundClassify); } #endif void HandleResolveResult(ResolveType resolveType, String autocompleteInfo, ResolveParams resolveParams) { if (resolveType == ResolveType.GetSymbolInfo) { if (!resolveParams.mCancelled) gApp.mSymbolReferenceHelper.SetSymbolInfo(autocompleteInfo); } else if (resolveType == ResolveType.GoToDefinition) { var autocompleteLines = String.StackSplit!(autocompleteInfo, '\n'); for (var autocompleteLine in autocompleteLines) { var lineData = String.StackSplit!(autocompleteLine, '\t'); if (scope String(lineData[0]) == "defLoc") { resolveParams.mOutLine = int32.Parse(lineData[2]); resolveParams.mOutLineChar = int32.Parse(lineData[3]); resolveParams.mOutFileName = new String(lineData[1]); } } } else if (resolveType == ResolveType.GetNavigationData) { resolveParams.mNavigationData = new String(autocompleteInfo); } else if (resolveType == ResolveType.GetVarType) { resolveParams.mTypeDef = new String(autocompleteInfo); } else if (resolveType == ResolveType.GetCurrentLocation) { PrimaryNavigationBar.SetLocation(autocompleteInfo); } else if ((resolveType == .Autocomplete) || (resolveType == .GetFixits)) { if ((resolveParams == null) || (!resolveParams.mCancelled)) { bool changedAfterInfo = (resolveParams != null) && (resolveParams.mTextVersion != Content.mData.mCurTextVersionId); var autoComplete = GetAutoComplete(); if ((autoComplete != null) && (resolveParams != null)) autoComplete.mIsDocumentationPass = resolveParams.mDocumentationName != null; HandleAutocompleteInfo(resolveType, autocompleteInfo, true, changedAfterInfo); autoComplete = GetAutoComplete(); if (autoComplete != null) { autoComplete.mIsDocumentationPass = false; if (resolveParams != null) autoComplete.mIsUserRequested = resolveParams.mIsUserRequested; if (resolveType == .GetFixits) autoComplete.mIsUserRequested = true; } } } } // fullRefresh means we rebuild types even if the hashes haven't changed - this is required for // a classifier pass on a newly-opened file public void DoClassify(ResolveType resolveType, ResolveParams resolveParams = null, bool isInterrupt = false) { scope AutoBeefPerf("SourceViewPanel.DoClassify"); if (gApp.mDbgDelayedAutocomplete) Thread.Sleep(250); /*if (resolveType == .Autocomplete) { Thread.Sleep(250); }*/ #if IDE_C_SUPPORT if (mIsClang) { DoClangClassify(resolveType, resolveParams); return; } #endif var bfSystem = IDEApp.sApp.mBfResolveSystem; if (bfSystem == null) return; var bfCompiler = BfResolveCompiler; //var compiler = ResolveCompiler; bool isBackground = (resolveType == ResolveType.Classify) || (resolveType == ResolveType.ClassifyFullRefresh); bool fullRefresh = resolveType == ResolveType.ClassifyFullRefresh; if (!isBackground) Debug.Assert(isInterrupt); if (mAsyncAutocomplete) { if ((resolveType == .Autocomplete) || (resolveType == .GetCurrentLocation) || (resolveType == .GetSymbolInfo)) { isBackground = true; } } /*if ((mFilePath.Contains("mainA.bf")) && (isBackground)) { Thread.Sleep(1000); }*/ // If we think we're not running in the background, make sure Debug.Assert((Thread.CurrentThread == gApp.mMainThread) == !isBackground); if (isInterrupt) bfSystem.NotifyWillRequestLock(1); ProjectSource projectSource = FilteredProjectSource; bool isFastClassify = false; BfParser parser; if (!isBackground) { bfSystem.PerfZoneStart("CreateParser"); parser = bfSystem.CreateParser(projectSource, false); bfSystem.PerfZoneEnd(); } else if ((resolveParams != null) && (resolveParams.mParser != null)) { parser = bfSystem.CreateNewParserRevision(resolveParams.mParser); } else if (projectSource != null) { bfSystem.PerfZoneStart("CreateParser"); parser = bfSystem.CreateParser(projectSource, false); bfSystem.PerfZoneEnd(); } else { // This only happens when we're editing source that isn't in our project isFastClassify = true; parser = bfSystem.CreateEmptyParser((BfProject)null); } EditWidgetContent.CharData[] char8Data = null; int char8Len = 0; if ((resolveParams != null) && (resolveParams.mCharData != null)) { char8Data = resolveParams.mCharData; char8Len = resolveParams.mCharData.Count; } if (char8Data == null) { Debug.Assert(!isBackground); char8Data = mEditWidget.Content.mData.mText; char8Len = mEditWidget.Content.mData.mTextLength; } /*var char8Data = (!isBackground) ? mEditWidget.Content.mData.mText : mProcessResolveCharData; int char8Len = Math.Min(char8Data.Count, mEditWidget.Content.mData.mTextLength);*/ String passInstanceName = scope String("DoClassify "); resolveType.ToString(passInstanceName); if (projectSource != null) passInstanceName.Append(":", projectSource.mName); var passInstance = bfSystem.CreatePassInstance(passInstanceName); passInstance.SetClassifierPassId(!isBackground ? (uint8)SourceDisplayId.AutoComplete : (uint8)SourceDisplayId.FullClassify); if (isBackground) { //Debug.Assert(mProcessingPassInstance == null); //mProcessingPassInstance = passInstance; Debug.Assert(resolveParams.mPassInstance == null); resolveParams.mPassInstance = passInstance; } if (!isBackground) bfSystem.PerfZoneStart("DoClassify.CreateChars"); char8[] chars = new char8[char8Len]; defer delete chars; for (int32 i = 0; i < char8Len; i++) { char8Data[i].mDisplayPassId = (int32)SourceDisplayId.Cleared; chars[i] = (char8)char8Data[i].mChar; } String text = scope String(); text.Append(chars, 0, chars.Count); if (!isBackground) { bfSystem.PerfZoneEnd(); bfSystem.PerfZoneStart("SetSource"); } parser.SetIsClassifying(); parser.SetSource(text, mFilePath); if (!isBackground) { bfSystem.PerfZoneEnd(); bfSystem.PerfZoneStart("DoClassify.DoWork"); } int cursorPos = mEditWidget.mEditWidgetContent.CursorTextPos; /*if (resolveType == ResolveType.Autocomplete) cursorPos--;*/ if ((resolveParams != null) && (resolveParams.mOverrideCursorPos != -1)) cursorPos = resolveParams.mOverrideCursorPos; if ((resolveType == ResolveType.GetNavigationData) || (resolveType == ResolveType.GetFixits)) parser.SetAutocomplete(-1); else { bool setAutocomplete = ((!isBackground) && (resolveType != ResolveType.RenameSymbol)); if ((resolveType == .Autocomplete) || (resolveType == .GetCurrentLocation) || (resolveType == .GetSymbolInfo)) setAutocomplete = true; if (setAutocomplete) parser.SetAutocomplete(Math.Max(0, cursorPos)); } /*else (!isFullClassify) -- do we ever need to do this? parser.SetCursorIdx(mEditWidget.mEditWidgetContent.CursorTextPos);*/ var resolvePassData = parser.CreateResolvePassData(resolveType); if (resolveParams != null) { if (resolveParams.mLocalId != -1) resolvePassData.SetLocalId(resolveParams.mLocalId); if (resolveParams.mTypeDef != null) resolvePassData.SetSymbolReferenceTypeDef(resolveParams.mTypeDef); if (resolveParams.mFieldIdx != -1) resolvePassData.SetSymbolReferenceFieldIdx(resolveParams.mFieldIdx); if (resolveParams.mMethodIdx != -1) resolvePassData.SetSymbolReferenceMethodIdx(resolveParams.mMethodIdx); if (resolveParams.mPropertyIdx != -1) resolvePassData.SetSymbolReferencePropertyIdx(resolveParams.mPropertyIdx); } if ((resolveParams != null) && (resolveParams.mDocumentationName != null)) resolvePassData.SetDocumentationRequest(resolveParams.mDocumentationName); parser.Parse(passInstance, !mIsBeefSource); parser.Reduce(passInstance); if (isInterrupt) { bfSystem.PerfZoneEnd(); bfSystem.PerfZoneStart("Lock"); var sw = scope Stopwatch(true); sw.Start(); bfSystem.Lock(1); if (sw.ElapsedMicroseconds > 100) { NOP!(); } bfSystem.PerfZoneEnd(); } else { bfSystem.Lock(0); } if (!isFastClassify) parser.BuildDefs(passInstance, resolvePassData, fullRefresh); // For Fixits we do want to parse the whole file but we need the cursorIdx bound still to // locate the correct fixit info if (resolveType == ResolveType.GetFixits) parser.SetCursorIdx(Math.Max(0, cursorPos)); if ((resolveType == ResolveType.ClassifyFullRefresh) && (mUseDebugKeyboard)) { //Debug.WriteLine("Classify Paused..."); mClassifyPaused = true; while (mClassifyPaused) Thread.Sleep(20); //Debug.WriteLine("Classify Continuing."); } /*if (resolveType == ResolveType.RenameLocalSymbol) { // We do want cursor info for replacing the symbol, we just didn't want it for reducing // and building defs parser.SetAutocomplete(Math.Max(0, mEditWidget.mEditWidgetContent.CursorTextPos - 1)); }*/ if ((!isFastClassify) && (bfCompiler != null)) { if (!bfCompiler.ClassifySource(passInstance, parser, resolvePassData, char8Data)) { //DeleteAndNullify!(mProcessResolveCharData); //mProcessResolveCharIdSpan.Dispose(); resolveParams.mCancelled = true; if (resolveType == ResolveType.ClassifyFullRefresh) QueueFullRefresh(false); bfCompiler.QueueDeferredResolveAll(); } } else { parser.ClassifySource(char8Data, !mIsBeefSource); } if (resolveType == .Autocomplete) { NOP!(); } if (!isBackground) { String autocompleteInfo = scope String(); bfCompiler.GetAutocompleteInfo(autocompleteInfo); HandleResolveResult(resolveType, autocompleteInfo, resolveParams); } else if (resolveParams != null) { resolveParams.mAutocompleteInfo = new String(); bfCompiler.GetAutocompleteInfo(resolveParams.mAutocompleteInfo); } if (!isBackground) bfSystem.PerfZoneStart("Cleanup"); delete resolvePassData; if ((!isBackground) || (isFastClassify)) { //bool isAutocomplete = (resolveType == ResolveType.Autocomplete) || (resolveType == ResolveType.Autocomplete_HighPri); //if (isAutocomplete) if ((!isBackground) && (resolveType == ResolveType.Autocomplete)) InjectErrors(passInstance, mEditWidget.mEditWidgetContent.mData.mText, mEditWidget.mEditWidgetContent.mData.mTextIdData.GetPrepared(), true); //IDEApp.sApp.ShowPassOutput(passInstance); delete passInstance; if ((resolveParams != null) && (resolveParams.mPassInstance == passInstance)) resolveParams.mPassInstance = null; } else { if (!isInterrupt) bfSystem.RemoveOldData(); } if ((resolveParams == null) || (resolveParams.mParser == null)) { delete parser; mWantsParserCleanup = true; } bfSystem.Unlock(); if (!isBackground) bfSystem.PerfZoneEnd(); if ((resolveParams != null) && (resolveParams.mWaitEvent != null)) resolveParams.mWaitEvent.Set(true); //Debug.WriteLine("Classify Done {0}", !isBackground); /*for (int i = 0; i < text.Length; i++) mEditWidget.Content.mText[i].mTypeNum = (ushort)elementTypeArray[i]; */ } int32 GetBraceNum(EditWidgetContent.CharData c) { if (c.mDisplayTypeId != 0) return 0; switch ((char8)c.mChar) { case '{': return 1; case '[': return 2; case '(': return 3; case '<': return 4; case '}': return -1; case ']': return -2; case ')': return -3; case '>': return -4; } return 0; } public void MatchBrace() { var textData = mEditWidget.Content.mData.mText; int32[] braceCounts = scope int32[4]; int cursorIdx = mEditWidget.Content.CursorTextPos; int braceIdx = cursorIdx; int braceNum = GetBraceNum(textData[braceIdx]); if ((braceNum == 0) && (braceIdx > 0)) { braceIdx--; cursorIdx--; braceNum = GetBraceNum(textData[braceIdx]); } if (braceNum != 0) { int bracePairNum = -braceNum; int searchIdx = braceIdx; int searchDir = braceNum / Math.Abs(braceNum); int braceDepth = 0; while ((searchIdx >= 0) && (searchIdx < textData.Count)) { int32 checkBraceNum = GetBraceNum(textData[searchIdx]); if ((checkBraceNum != 0) && ((braceNum == 4) || (braceNum == -4))) { if (checkBraceNum > 0) braceCounts[checkBraceNum - 1]++; else if (checkBraceNum < 0) { int32 decBraceNum = -checkBraceNum - 1; braceCounts[decBraceNum]--; if ((decBraceNum != 3) && (braceNum != checkBraceNum) && (braceCounts[decBraceNum] < 0)) return; } } if (textData[searchIdx].mDisplayTypeId != 0) checkBraceNum = 0; if ((checkBraceNum == braceNum) || (checkBraceNum == bracePairNum)) { braceDepth += checkBraceNum; if (braceDepth == 0) { if (searchDir > 0) searchIdx++; // Put cursor after close if ((braceNum == 4) || (braceNum == -4)) { // If we're matching <>'s then make sure the ) and } counts are zero if ((braceCounts[0] != 0) || (braceCounts[1] != 0) || (braceCounts[2] != 0)) break; } int cursorStartPos = cursorIdx; if (searchDir < 0) cursorStartPos++; mEditWidget.Content.CursorTextPos = searchIdx; if (mWidgetWindow.IsKeyDown(KeyCode.Shift)) { mEditWidget.Content.mSelection = EditSelection(cursorStartPos, mEditWidget.Content.CursorTextPos); } else mEditWidget.Content.mSelection = null; mEditWidget.Content.CursorMoved(); mEditWidget.Content.EnsureCursorVisible(); break; } } searchIdx += searchDir; } } } public bool FileNameMatches(String fileName) { if (mFilePath == null) return false; if ((mAliasFilePath != null) && (Path.Equals(fileName, mAliasFilePath))) return true; return Path.Equals(fileName, mFilePath); } void HilitePosition(LocatorType hilitePosition, int32 prevLine = -1) { if (hilitePosition == LocatorType.None) return; if ((hilitePosition == LocatorType.Smart) && (prevLine != -1) && (!mJustShown)) { int32 newLine = mEditWidget.Content.CursorLineAndColumn.mLine; if (Math.Abs(prevLine - newLine) < 16) return; } float x; float y; mEditWidget.Content.GetTextCoordAtCursor(out x, out y); var sourceEditWidgetContent = (SourceEditWidgetContent)mEditWidget.Content; LocatorAnim.Show(mEditWidget.Content, x, y + sourceEditWidgetContent.mFont.GetLineSpacing() / 2); } public void ShowFileLocation(int cursorIdx, LocatorType hilitePosition) { var activePanel = GetActivePanel(); if (activePanel != this) { activePanel.ShowFileLocation(cursorIdx, hilitePosition); return; } if (mEditWidget == null) return; int32 prevLine = mEditWidget.Content.CursorLineAndColumn.mLine; mEditWidget.Content.mSelection = null; mEditWidget.Content.CursorTextPos = cursorIdx; mEditWidget.Content.CursorMoved(); mEditWidget.Content.EnsureCursorVisible(true, true); mEditWidget.Content.mCursorImplicitlyMoved = true; if (mJustShown) // Jump to whatever position we're scrolling to { mEditWidget.mVertPos.mPct = 1.0f; mEditWidget.UpdateContentPosition(); } HilitePosition(hilitePosition, prevLine); } public void ShowFileLocation(int refHotIdx, int line, int column, LocatorType hilitePosition) { mRequestedLineAndColumn = EditWidgetContent.LineAndColumn(line, column); //int prevLine = mEditWidget.Content.CursorLineAndColumn.mLine; var content = mEditWidget.Content; var useLine = line; ProjectSource projectSource = FilteredProjectSource; if (projectSource != null) { bool allowThrough = false; bool worked = false; if (refHotIdx == -1) { int char8Idx = content.GetTextIdx(useLine, column); ShowFileLocation(char8Idx, hilitePosition); worked = true; } else { int32 char8Id = IDEApp.sApp.mWorkspace.GetProjectSourceCharId(projectSource, refHotIdx, useLine, column); if (char8Id != 0) { int char8Idx = content.GetCharIdIdx(char8Id); if (char8Idx != -1) { ShowFileLocation(char8Idx, hilitePosition); worked = true; } else if (IDEApp.sApp.mWorkspace.GetProjectSourceCompileInstance(projectSource, refHotIdx) == null) { allowThrough = true; } } } if (mOldVersionPanel != null) { float scrollTopDelta = mEditWidget.Content.GetCursorScreenRelY(); mOldVersionPanel.ShowFileLocation(refHotIdx, useLine, column, hilitePosition); if (mOldVersionPanel.mJustShown) { mOldVersionPanel.mEditWidget.Content.SetCursorScreenRelY(scrollTopDelta - mOldVersionPanel.mPanelHeader.mHeight); mOldVersionPanel.mEditWidget.Content.EnsureCursorVisible(true, true); } return; } if (!allowThrough) { if (!worked) IDEApp.Beep(IDEApp.MessageBeepType.Error); return; } } useLine = Math.Min(useLine, content.GetLineCount() - 1); int cursorIdx = content.GetTextIdx(useLine, column); ShowFileLocation(cursorIdx, hilitePosition); /*content.MoveCursorTo(line, column, true); //content.EnsureCursorVisible(true, true); if (mJustShown) // Jump to whatever position we're scrolling to { mEditWidget.mVertPos.mPct = 1.0f; mEditWidget.UpdateContentPosition(); } if (hilitePosition) HilitePosition(prevLine);*/ } void RemoveEditWidget() { } public void AttachToProjectSource(ProjectSource projectSource) { mProjectSource = projectSource; if (mEditData != null) { if (mProjectSource.mEditData == null) { mEditData.Ref(); mProjectSource.mEditData = mEditData; mEditData.mProjectSources.Add(mProjectSource); } } QueueFullRefresh(true); } public void DetachFromProjectItem() { if (mProjectSource == null) return; ProcessDeferredResolveResults(-1); //Debug.Assert(mEditData != null); //gApp.mFileEditData.Add(mEditData); //mProjectSource.mEditData = null; mProjectSource = null; QueueFullRefresh(true); if (mOldVersionPanel != null) mOldVersionPanel.DetachFromProjectItem(); if (mSplitTopPanel != null) mSplitTopPanel.DetachFromProjectItem(); } public override void Dispose() { if (mDisposed) return; ProcessDeferredResolveResults(-1); if (IDEApp.sApp.mLastActiveSourceViewPanel == this) IDEApp.sApp.mLastActiveSourceViewPanel = null; if (IDEApp.sApp.mLastActivePanel == this) IDEApp.sApp.mLastActivePanel = null; if (Content.Data.mCurQuickFind != null) { //Content.Data.mCurQuickFind.Close(); Content.Data.mCurQuickFind = null; } mEditWidget.mPanel = null; if (mFilePath != null) IDEApp.sApp.mFileWatcher.RemoveWatch(mFilePath); var editWidgetContent = (SourceEditWidgetContent)mEditWidget.Content; if (editWidgetContent.mAutoComplete != null) editWidgetContent.mAutoComplete.Close(); DeleteAndNullify!(editWidgetContent.mOnGenerateAutocomplete); DeleteAndNullify!(editWidgetContent.mOnEscape); DeleteAndNullify!(editWidgetContent.mOnFinishAsyncAutocomplete); DeleteAndNullify!(editWidgetContent.mOnCancelAsyncAutocomplete); editWidgetContent.mSourceViewPanel = null; mEditWidget.RemoveSelf(); if ((mEditData != null) && (mEditData.mEditWidget == mEditWidget)) { // if there's another view of this window open, remap the mEditData.mEditWidget to that one // That allows us to delete our current edit widget for (var user in mEditData.mEditWidget.mEditWidgetContent.mData.mUsers) { if ((user != mEditWidget.mEditWidgetContent) && (var sourceEditWidget = user.mEditWidget as SourceEditWidget)) { mEditData.mEditWidget = sourceEditWidget; } } } if ((mEditData != null) && (mEditData.mEditWidget == mEditWidget)) { //Debug.WriteLine("Dispose mEditData.mEditWidget = {0}", mEditWidget); Debug.Assert(mEditWidget.mParent == null); mEditData.mOwnsEditWidget = true; } else { //Debug.WriteLine("Dispose deleting mEditWidget {0}", mEditWidget); delete mEditWidget; } mEditWidget = null; if ((mProjectSource == null) && (mEditData != null) && (mEditData.mOwnsEditWidget) && (mEditData.mEditWidget.mEditWidgetContent.mData.mUsers.Count == 1)) { // This isn't needed anymore... gApp.DeleteEditData(mEditData); } /*if (mProjectSource != null) mProjectSource.ClearUnsavedData();*/ if (mOldVersionPanel != null) mOldVersionPanel.Dispose(); if (mSplitTopPanel != null) mSplitTopPanel.Dispose(); DebugManager debugManager = IDEApp.sApp.mDebugger; debugManager.mBreakpointsChangedDelegate.Remove(scope => BreakpointsChanged, true); #if IDE_C_SUPPORT if (mIsClang) { var clangCompiler = ClangResolveCompiler; clangCompiler.QueueFileRemoved(mFilePsath); } #endif if (IDEApp.sApp.mBfResolveHelper != null) IDEApp.sApp.mBfResolveHelper.SourceViewPanelClosed(this); if (mResolveJobCount > 0) { //TODO: Make a way we can cancel only our specific job ResolveCompiler.CancelBackground(); Debug.Assert(mResolveJobCount == 0); } if (mSpellCheckJobCount > 0) { IDEApp.sApp.mSpellChecker.CancelBackground(); Debug.Assert(mSpellCheckJobCount == 0); } if (mRenameSymbolDialog != null) mRenameSymbolDialog.Close(); base.Dispose(); } public void Close() { } SourceViewPanel GetOldVersionPanel() { if (mSplitBottomPanel != null) return mSplitBottomPanel.mOldVersionPanel; return mOldVersionPanel; } public SourceViewPanel GetActivePanel() { if (mSplitTopPanel != null) { if (mSplitTopPanel.mLastFocusTick > mLastFocusTick) { return mSplitTopPanel.GetActivePanel(); } } if (mOldVersionPanel != null) { return mOldVersionPanel.GetActivePanel(); } return this; } public void FocusEdit() { let activePanel = GetActivePanel(); activePanel.mEditWidget.SetFocus(); if (!mWidgetWindow.mHasFocus) EditGotFocus(); } public override void SetFocus() { //mEditWidget.SetFocus(); FocusEdit(); } public bool HasFocus(bool selfOnly = false) { if (!selfOnly) { if ((mSplitTopPanel != null) && (mSplitTopPanel.mEditWidget.mHasFocus)) return true; if ((mSplitBottomPanel != null) && (mSplitBottomPanel.mEditWidget.mHasFocus)) return true; if ((mOldVersionPanel != null) && (mOldVersionPanel.mEditWidget.mHasFocus)) return true; } if (mEditWidget.mHasFocus) return true; if (mQuickFind != null) return mQuickFind.HasFocus(); return false; } public override void EditGotFocus() { if (mFilePath != null) gApp.AddToRecentDisplayedFilesList(mFilePath); if (mLoadFailed) return; #if IDE_C_SUPPORT if (mIsClang) { if (IDEUtils.IsHeaderFile(mFilePath)) { // If a different cpp file has gotten focus since we have last been here, then we want to // re-evaluate the mClangSource so we can potentially switch to using that //if (mClangSource != null) { var resolveClang = IDEApp.sApp.mResolveClang; using (resolveClang.mMonitor.Enter()) { if (mLastMRUVersion != resolveClang.mProjectSourceVersion) { //mClangSource = null; QueueFullRefresh(true); mLastMRUVersion = resolveClang.mProjectSourceVersion; } } } } else if (mProjectSource != null) IDEApp.sApp.mResolveClang.UpdateMRU(mProjectSource); } #endif var sourceEditWidgetContent = (SourceEditWidgetContent)mEditWidget.Content; sourceEditWidgetContent.mCursorStillTicks = 0; sourceEditWidgetContent.mCursorBlinkTicks = 0; if (mProjectSource != null) { var editData = gApp.GetEditData(mProjectSource, true); editData.mEditWidget = mEditWidget; } gApp.mLastActiveSourceViewPanel = this; gApp.mLastActivePanel = this; } public override void EditLostFocus() { #if IDE_C_SUPPORT if (mClangSourceChanged) { IDEApp.sApp.mDepClang.FileSaved(mFilePath); mClangSourceChanged = false; } #endif } public override void RemovedFromParent(Widget previousParent, WidgetWindow window) { if (mHoverWatch != null) mHoverWatch.Close(); if (mRenameSymbolDialog != null) mRenameSymbolDialog.Cancel(); if (mEditWidget != null) { var sourceEditWidgetContent = (SourceEditWidgetContent)mEditWidget.Content; if (sourceEditWidgetContent.mAutoComplete != null) sourceEditWidgetContent.mAutoComplete.Close(); } base.RemovedFromParent(previousParent, window); CloseOldVersion(); if (NeedsPostRemoveUpdate) { //Debug.WriteLine("Adding sourceViewPanel to mPostRemoveUpdatePanel {0}", this); mInPostRemoveUpdatePanels = true; gApp.mPostRemoveUpdatePanels.Add(this); } } public override void AddedToParent() { base.AddedToParent(); mWantsFullClassify = true; mWantsFullRefresh = true; mJustShown = true; mWantsClassifyAutocomplete = false; if (mUpdateCnt == 0) { //DoFastClassify(); mWantsFastClassify = true; mWantsFullClassify = true; } if (mDesiredVertPos != 0) { mEditWidget.Content.RecalcSize(); mEditWidget.mVertPos.Set(mDesiredVertPos, true); mEditWidget.UpdateContentPosition(); mDesiredVertPos = 0; } //IDEApp.sApp.mInDisassemblyView = false; if ((mProjectSource != null) && (IDEApp.sApp.mWakaTime != null)) { IDEApp.sApp.mWakaTime.QueueFile(mFilePath, mProjectSource.mProject.mProjectName, false); } IDEApp.sApp.mLastActiveSourceViewPanel = this; IDEApp.sApp.mLastActivePanel = this; } void CheckTrackedElementChanges() { if (mHotFileIdx != -1) return; bool hadBreakpointChanges = false; // Update tracked positions if (mTrackedTextElementViewList != null) { var editContent = (SourceEditWidgetContent)mEditWidget.Content; for (var trackedElementView in mTrackedTextElementViewList) { int32 startContentIdx = -1; if (trackedElementView.mTrackedElement.mIsDead) continue; var breakpoint = trackedElementView.mTrackedElement as Breakpoint; var trackedElement = trackedElementView.mTrackedElement; if (trackedElement.mSnapToLineStart) { int32 lineLeft = trackedElementView.mTextPosition.mIndex; repeat { if (!((char8)editContent.mData.mText[lineLeft].mChar).IsWhiteSpace) startContentIdx = lineLeft; lineLeft--; } while ((lineLeft > 0) && (editContent.mData.mText[lineLeft].mChar != '\n')); if (startContentIdx == -1) { lineLeft = trackedElementView.mTextPosition.mIndex; repeat { if (!((char8)editContent.mData.mText[lineLeft].mChar).IsWhiteSpace) { startContentIdx = lineLeft; break; } lineLeft++; } while ((lineLeft > 0) && (editContent.mData.mText[lineLeft].mChar != '\n')); } } else startContentIdx = trackedElementView.mTextPosition.mIndex; if ((startContentIdx == -1) || (trackedElementView.mTextPosition.mWasDeleted)) { if (breakpoint != null) { BfLog.LogDbg("Tracked element deleted. Deleting breakpoint\n"); IDEApp.sApp.mDebugger.DeleteBreakpoint(breakpoint); continue; } else { trackedElementView.mTextPosition.mWasDeleted = false; if (startContentIdx == -1) startContentIdx = trackedElementView.mTextPosition.mIndex; } } if (trackedElementView.mLastMoveIdx != trackedElement.mMoveIdx) { UpdateTrackedElementView(trackedElementView); continue; } trackedElementView.mTextPosition.mIndex = startContentIdx; int line; int lineCharIdx; mEditWidget.Content.GetLineCharAtIdx(trackedElementView.mTextPosition.mIndex, out line, out lineCharIdx); if ((breakpoint != null) && (breakpoint.mLineNum != line)) hadBreakpointChanges = true; //RemapActiveToCompiledLine(0, ref line, ref lineCharIdx); trackedElementView.mTrackedElement.Move(line, lineCharIdx); } } if (hadBreakpointChanges) IDEApp.sApp.mDebugger.mBreakpointsChangedDelegate(); } void UpdateTrackedElements() { if (mHotFileIdx != -1) return; if (!mIsBeefSource) return; for (var breakpointView in GetTrackedElementList()) { var trackedElement = breakpointView.mTrackedElement; var breakpoint = trackedElement as Breakpoint; //if ((breakpoint != null) && (breakpoint.mNativeBreakpoint != null)) //TODO: we're only doing this for BOUND breakpoints. Otherwise if we create a new method and set a // breakpoint on it but the new method hasn't actually been compile yet, then it causes the breakpoint // to go crazy if ((breakpoint != null) && (breakpoint.mNativeBreakpoint != null)) { int32 breakpointLineNum = breakpoint.GetLineNum(); if (breakpointLineNum != breakpointView.mLastBoundLine) { breakpointView.mLastBoundLine = breakpointLineNum; UpdateTrackedElementView(breakpointView); int compileIdx = IDEApp.sApp.mWorkspace.GetHighestCompileIdx(); if (compileIdx != -1) { int column = -1; breakpoint.mLineNum = breakpointLineNum; int line = breakpoint.mLineNum; RemapCompiledToActiveLine(compileIdx, ref line, ref column); breakpoint.mLineNum = (int32)line; } } } } } void BreakpointsChanged() { mTrackedTextElementViewListDirty = true; } public void EnsureTrackedElementsValid() { if (mTrackedTextElementViewListDirty) { // Update the old one and then clear it and then update the new one //TODO: The old one could contain deleted breakpoints and such... Under what circumstances should be do this? CheckTrackedElementChanges(); GetTrackedElementList(); } CheckTrackedElementChanges(); } public void ClearTrackedElements() { if (mTrackedTextElementViewList != null) { var editWidgetContent = (SourceEditWidgetContent)mEditWidget.Content; for (var breakpoint in mTrackedTextElementViewList) { editWidgetContent.PersistentTextPositions.Remove(breakpoint.mTextPosition); delete breakpoint.mTextPosition; } DeleteContainerAndItems!(mTrackedTextElementViewList); } mTrackedTextElementViewList = null; } public void UpdateTrackedElementView(TrackedTextElementView trackedElementView) { var editWidgetContent = (SourceEditWidgetContent)mEditWidget.Content; var trackedElement = trackedElementView.mTrackedElement; if (trackedElementView.mTextPosition != null) { editWidgetContent.PersistentTextPositions.Remove(trackedElementView.mTextPosition); delete trackedElementView.mTextPosition; } int lineStart; int lineEnd; mEditWidget.Content.GetLinePosition(trackedElement.mLineNum, out lineStart, out lineEnd); trackedElementView.mTextPosition = new PersistentTextPosition((int32)Math.Min(editWidgetContent.mData.mTextLength, lineStart + trackedElement.mColumn)); editWidgetContent.PersistentTextPositions.Add(trackedElementView.mTextPosition); trackedElementView.mLastMoveIdx = trackedElement.mMoveIdx; } List GetTrackedElementList() { if (mTrackedTextElementViewListDirty) { ClearTrackedElements(); mTrackedTextElementViewListDirty = false; } DebugManager debugManager = IDEApp.sApp.mDebugger; if (mTrackedTextElementViewList == null) { String findFileName = mFilePath; mTrackedTextElementViewList = new List(); if (mFilePath == null) return mTrackedTextElementViewList; for (var bookmark in IDEApp.sApp.mBookmarkManager.mBookmarkList) { if (Path.Equals(bookmark.mFileName, findFileName)) { var bookmarkView = new TrackedTextElementView(bookmark); UpdateTrackedElementView(bookmarkView); mTrackedTextElementViewList.Add(bookmarkView); } } for (var breakpoint in debugManager.mBreakpointList) { if ((breakpoint.mFileName != null) && (Path.Equals(breakpoint.mFileName, findFileName))) { var breakpointView = new TrackedTextElementView(breakpoint); UpdateTrackedElementView(breakpointView); mTrackedTextElementViewList.Add(breakpointView); } } ////// for (var historyEntry in IDEApp.sApp.mHistoryManager.mHistoryList) { if (Path.Equals(historyEntry.mFileName, findFileName)) { var historyView = new TrackedTextElementView(historyEntry); UpdateTrackedElementView(historyView); mTrackedTextElementViewList.Add(historyView); } } } return mTrackedTextElementViewList; } void CloseHeader() { if (mPanelHeader != null) { mPanelHeader.RemoveSelf(); gApp.DeferDelete(mPanelHeader); mPanelHeader = null; ResizeComponents(); } } void ClearLoadFailed() { if (mLoadFailed) { mLoadFailed = false; CloseHeader(); } } public void CheckBinary() { mIsBinary = false; let data = mEditWidget.mEditWidgetContent.mData; for (int i < data.mTextLength) { if (data.mText[i].mChar <= '\x08') { data.mText[i].mChar = ' '; mIsBinary = true; } } if (mIsBinary) { for (int i < data.mTextLength) { if (data.mText[i].mChar >= '\x80') { data.mText[i].mChar = ' '; mIsBinary = true; } } } if (mIsBinary) mEditWidget.mEditWidgetContent.mIsReadOnly = true; } public bool Show(String filePath, bool silentFail = false, FileEditData fileEditData = null) { scope AutoBeefPerf("SourceViewPanel.Show"); ClearLoadFailed(); Debug.Assert(!mDisposed); String useFilePath = null; var useFileEditData = fileEditData; if (filePath != null) { Debug.Assert(!filePath.Contains("//")); useFilePath = scope:: String(filePath); IDEUtils.FixFilePath(useFilePath); if (mProjectSource == null) { mProjectSource = IDEApp.sApp.FindProjectSourceItem(useFilePath); } } if ((useFileEditData == null) && (useFilePath != null)) { if (mProjectSource != null) { useFileEditData = IDEApp.sApp.GetEditData(mProjectSource, true); } else { SourceHash.Kind hashKind = mLoadedHash.GetKind(); if (hashKind == .None) hashKind = .MD5; useFileEditData = gApp.GetEditData(useFilePath, true, true, hashKind); } if (useFileEditData.mEditWidget == null) useFileEditData = null; } mEditData = useFileEditData; if (useFileEditData != null) { if (useFileEditData.mEditWidget != null) { if (mEditWidget != null) { mEditWidget.RemoveSelf(); delete mEditWidget; mEditWidget = null; } //Debug.Assert(projectSourceEditData.mOwnsEditWidget); if (useFileEditData.mOwnsEditWidget) { mEditWidget = useFileEditData.mEditWidget; useFileEditData.mOwnsEditWidget = false; //Debug.WriteLine("Taking over EditWidget {0} Parent: {1}", mEditWidget, mEditWidget.mParent); Debug.Assert(mEditWidget.mParent == null); } else { mEditWidget = IDEApp.sApp.CreateSourceEditWidget(useFileEditData.mEditWidget); mEditWidget.Content.RecalcSize(); //Debug.WriteLine("Creating new EditWidget {0}", mEditWidget); } } //mLastFileTextVersion = useFileEditData.mLastFileTextVersion; } if (mEditWidget == null) { mEditWidget = IDEApp.sApp.CreateSourceEditWidget(); } SetupEditWidget(); DeleteAndNullify!(mFilePath); if (useFilePath != null) { mFilePath = new String(useFilePath); IDEApp.sApp.mFileWatcher.WatchFile(mFilePath); var ext = scope String(); Path.GetExtension(useFilePath, ext); /*if (ext.Length == 0) return false;*/ ext.ToLower(); mIsSourceCode = IDEApp.IsSourceCode(useFilePath); mIsBeefSource = IDEApp.IsBeefFile(useFilePath); } //TODO: Find mProjectSource if (mIsSourceCode && !mIsBeefSource) { mIsClang = true; //((SourceEditWidgetContent)mEditWidget.Content).mAsyncAutocomplete = true; } mJustShown = true; mWantsFullRefresh = true; mWantsFullClassify = true; //mCurBfParser = IDEApp.sApp.mBfResolveSystem.CreateParser(mFilePath); if (useFileEditData == null) { var text = scope String(); SourceHash hash; if (mFilePath == null) { // Nothing } else if (gApp.LoadTextFile(mFilePath, text, true, scope [&] () => { hash = SourceHash.Create(.MD5, text); } ) case .Err) { if (silentFail) { Close(); return false; } mEditWidget.mEditWidgetContent.mIsReadOnly = true; FileOpenFailed(); } else { IDEApp.sApp.mFileWatcher.FileIsValid(mFilePath); mEditWidget.Content.AppendText(text); using (gApp.mMonitor.Enter()) { if ((mEditData != null) && (!mEditData.mSavedCharIdData.IsEmpty)) { int char8IdDataLength = mEditData.mSavedCharIdData.GetTotalLength(); if (char8IdDataLength == mEditWidget.Content.mData.mTextLength) { // The saved char8IdData matches the length of our text, so we can use it // This is important for text mapping after a tab has been closed and then // the files is reopened mEditWidget.Content.mData.mTextIdData.DuplicateFrom(ref mEditData.mSavedCharIdData); } } } //mLastFileTextVersion = mEditWidget.Content.mData.mCurTextVersionId; } } else { mLoadedHash = useFileEditData.mLoadedHash; // Sanity check for when we have the saved data cached already if (useFileEditData.IsFileDeleted()) { if (silentFail) { Close(); return false; } FileOpenFailed(); } } CheckBinary(); InitSplitter(); if ((mEditData != null) && (mFilePath != null)) Debug.Assert(Path.Equals(mFilePath, mEditData.mFilePath)); return true; } public void RehupAlias() { if ((gApp.mDebugger.mIsRunning) && (mAliasFilePath != null)) { gApp.mDebugger.SetAliasPath(mAliasFilePath, mFilePath); } } void BrowseForFile() { #if !CLI var fileDialog = scope System.IO.OpenFileDialog(); var initialDir = scope String(IDEApp.sApp.mWorkspace.mDir); //initialDir.Replace('/', '\\'); var fileName = scope String(); Path.GetFileName(mFilePath, fileName); var title = scope String(); title.AppendF("Open {0}", fileName); fileDialog.Title = title; fileDialog.Multiselect = false; var ext = scope String(); Path.GetExtension(mFilePath, ext); fileDialog.InitialDirectory = initialDir; fileDialog.ValidateNames = true; fileDialog.DefaultExt = ext; var filter = scope String(); filter.AppendF("{0}|{0}|All files (*.*)|*.*", fileName, ext); fileDialog.SetFilter(filter); mWidgetWindow.PreModalChild(); if (fileDialog.ShowDialog(gApp.GetActiveWindow()).GetValueOrDefault() == .OK) { for (String filePath in fileDialog.FileNames) { delete mAliasFilePath; mAliasFilePath = mFilePath; mFilePath = new String(filePath); RehupAlias(); RetryLoad(); break; } } #endif } void FileOpenFailed() { mLoadFailed = true; //mLastFileTextVersion = mEditWidget.Content.mData.mCurTextVersionId; mPanelHeader = new PanelHeader(); String fileName = scope String(); Path.GetFileName(mFilePath, fileName); String headerStr = scope String(); headerStr.AppendF("Unable to load file '{0}'. Requested path is '{1}'", fileName, mFilePath); mPanelHeader.Label = headerStr; if (!sPreviousVersionWarningShown) { mPanelHeader.Flash(); sPreviousVersionWarningShown = true; } bool hasBookmarks = false; bool hasBreakpoints = false; var trackedElements = GetTrackedElementList(); for (var element in trackedElements) { hasBookmarks |= element.mTrackedElement is Bookmark; hasBreakpoints |= element.mTrackedElement is Breakpoint; } if (hasBookmarks) { var button = mPanelHeader.AddButton("Remove Bookmarks"); button.mOnMouseClick.Add(new (evt) => { button.mVisible = false; var trackedElements = GetTrackedElementList(); for (var element in trackedElements) { if (var bookmark = element.mTrackedElement as Bookmark) { gApp.mBookmarkManager.DeleteBookmark(bookmark); } } }); } var button = mPanelHeader.AddButton("Retry"); button.mOnMouseClick.Add(new (evt) => { Reload(); }); button = mPanelHeader.AddButton("Auto Find"); button.mOnMouseClick.Add(new (evt) => { AutoFind(); }); button = mPanelHeader.AddButton("Browse..."); button.mOnMouseClick.Add(new (evt) => { BrowseForFile(); }); mPanelHeader.Flash(); AddWidget(mPanelHeader); } void AutoFind() { delete mSourceFindTask; mSourceFindTask = new SourceFindTask(); mSourceFindTask.mSourceViewPanel = this; mSourceFindTask.mFilePath = new String(mFilePath); mSourceFindTask.mFileName = new String(); Path.GetFileName(mFilePath, mSourceFindTask.mFileName); gApp.WithSourceViewPanels(scope (sourceViewPanel) => { if (sourceViewPanel.mAliasFilePath != null) { var origDir = scope String(); Path.GetDirectoryPath(sourceViewPanel.mAliasFilePath, origDir); IDEUtils.FixFilePath(origDir); if (!Environment.IsFileSystemCaseSensitive) origDir.ToUpper(); var localDir = scope String(); Path.GetDirectoryPath(sourceViewPanel.mFilePath, localDir); if (mSourceFindTask.mRemapMap.TryAdd(origDir, var keyPtr, var valuePtr)) { *keyPtr = new String(origDir); *valuePtr = new String(localDir); } } }); ThreadPool.QueueUserWorkItem(new => mSourceFindTask.Run); CloseHeader(); mPanelHeader = new PanelHeader(); String fileName = scope String(); Path.GetFileName(mFilePath, fileName); String headerStr = scope String(); headerStr.AppendF("Finding {0}...", fileName); mPanelHeader.Label = headerStr; var button = mPanelHeader.AddButton("Cancel"); button.mOnMouseClick.Add(new (evt) => { mSourceFindTask.Cancel(); }); mPanelHeader.mButtonsOnBottom = true; AddWidget(mPanelHeader); ResizeComponents(); } void ShowWrongHash() { CloseHeader(); mPanelHeader = new PanelHeader(); String fileName = scope String(); Path.GetFileName(mFilePath, fileName); String headerStr = scope String(); headerStr.AppendF("Warning: This file is not an exact match for the file this program was built with", fileName); mPanelHeader.Label = headerStr; var button = mPanelHeader.AddButton("Ok"); button.mOnMouseClick.Add(new (evt) => { CloseHeader(); }); button = mPanelHeader.AddButton("Browse..."); button.mOnMouseClick.Add(new (evt) => { BrowseForFile(); }); mPanelHeader.mButtonsOnBottom = true; AddWidget(mPanelHeader); ResizeComponents(); } public void SetLoadCmd(String loadCmd) { bool isRepeat = mOldVerLoadCmd != null; CloseHeader(); if (mOldVerLoadCmd == null) mOldVerLoadCmd = new String(loadCmd); if (loadCmd.StartsWith("http", .OrdinalIgnoreCase)) { LoadOldVer(); return; } // For testing a long command... //mOldVerLoadCmd.Set("/bin/sleep.exe 10"); mPanelHeader = new PanelHeader(); String fileName = scope String(); Path.GetFileName(mFilePath, fileName); String headerStr = scope String(); if (isRepeat) headerStr.AppendF("{0} Failed to retrieve file '{1}'.{2} The following command can be rerun to retry:\n{3}\nWARNING: This is a security risk if this PDB comes from an untrusted source.", Font.EncodeColor(0xFFFF8080), fileName, Font.EncodePopColor(), mOldVerLoadCmd); else headerStr.AppendF("The file '{0}' can be loaded from a source server by executing the embedded command:\n{1}\nWARNING: This is a security risk if this PDB comes from an untrusted source.", fileName, mOldVerLoadCmd); mPanelHeader.Label = headerStr; mPanelHeader.mTooltipText = new String(mOldVerLoadCmd); var button = mPanelHeader.AddButton("Run"); button.mOnMouseClick.Add(new (evt) => { LoadOldVer(); }); button = mPanelHeader.AddButton("Always Run"); button.mOnMouseClick.Add(new (evt) => { LoadOldVer(); }); let checkbox = new BoundCheckbox(ref gApp.mStepOverExternalFiles); checkbox.Label = "Step over external files"; mPanelHeader.AddWidget(checkbox); checkbox.mOnValueChanged.Add(new () => { gApp.RehupStepFilters(); }); mPanelHeader.mOnResized.Add(new (widget) => { checkbox.Resize(GS!(10), GS!(60), checkbox.CalcWidth(), GS!(20)); }); //button = mPanelHeader.AddButton("Run On This "); //button = mPanelHeader.AddButton("Never Run"); mPanelHeader.mButtonsOnBottom = true; mPanelHeader.mBaseHeight = 84; AddWidget(mPanelHeader); ResizeComponents(); } void LoadOldVer() { if (mOldVerLoadCmd.StartsWith("http", .OrdinalIgnoreCase)) { DeleteAndNullify!(mOldVerHTTPRequest); mOldVerHTTPRequest = new HTTPRequest(); mOldVerHTTPRequest.GetFile(mOldVerLoadCmd, mFilePath); } else { Debug.Assert(mOldVerLoadExecutionInstance == null); mOldVerLoadExecutionInstance = gApp.DoRun(null, mOldVerLoadCmd, gApp.mInstallDir, .None); mOldVerLoadExecutionInstance.mAutoDelete = false; } CloseHeader(); mPanelHeader = new PanelHeader(); String fileName = scope String(); Path.GetFileName(mFilePath, fileName); String headerStr = scope String(); headerStr.AppendF("Retrieving {0} via command: {1}", fileName, mOldVerLoadCmd); mPanelHeader.Label = headerStr; mPanelHeader.mTooltipText = new String(mOldVerLoadCmd); var button = mPanelHeader.AddButton("Cancel"); button.mOnMouseClick.Add(new (evt) => { if (mOldVerLoadExecutionInstance != null) mOldVerLoadExecutionInstance.Cancel(); if (mOldVerHTTPRequest != null) { DeleteAndNullify!(mOldVerHTTPRequest); } }); button = mPanelHeader.AddButton("Always Run"); button.mOnMouseClick.Add(new (evt) => { LoadOldVer(); }); //button = mPanelHeader.AddButton("Run On This "); //button = mPanelHeader.AddButton("Never Run"); mPanelHeader.mButtonsOnBottom = true; //mPanelHeader.mBaseHeight = 72; AddWidget(mPanelHeader); ResizeComponents(); } void InitSplitter() { mSplitter = new PanelSplitter(mSplitTopPanel, this); mSplitter.mSplitAction = new => SplitView; mSplitter.mUnsplitAction = new => UnsplitView; AddWidget(mSplitter); } public void Reload_Old() { var text = scope String(); if (gApp.LoadTextFile(mFilePath, text) case .Err) { gApp.Fail(StackStringFormat!("Failed to open file '{0}'", mFilePath)); return; } //mEditWidget.Content.ClearText(); int line; int lineChar; mEditWidget.Content.GetCursorLineChar(out line, out lineChar); //float vertPos = mEditWidget.mVertPos.v; text.Replace("\r", ""); var replaceSourceAction = new ReplaceSourceAction(mEditWidget.Content, text, true); mEditWidget.Content.mData.mUndoManager.Add(replaceSourceAction); replaceSourceAction.Redo(); //replaceSourceActionClearTrackedElements(); //UndoBatchStart undoBatchStart = new UndoBatchStart("reload"); //mEditWidget.Content.mUndoManager.Add(undoBatchStart); //mEditWidget.Content.SelectAll(); //mEditWidget.Content.DeleteSelection(); //mEditWidget.Content.InsertAtCursor(text); /*mEditWidget.Content.MoveCursorTo(line, lineChar, true); mEditWidget.VertScrollTo(vertPos); mEditWidget.mVertPos.mPct = 1.0f; mEditWidget.UpdateContentPosition();*/ QueueFullRefresh(false); //mLastFileTextVersion = mEditWidget.Content.mData.mCurTextVersionId; } /*struct TextLineSegment { public int mIndex; public int mLength; public String mLine; }*/ public void Reload() { //TODO: Why did we ClearTrackedElements here? It caused reloads to not move breakpoints and stuff... //ClearTrackedElements(); if (mLoadFailed) { Show(mFilePath); ResizeComponents(); return; } var editWidgetContent = (SourceEditWidgetContent)mEditWidget.mEditWidgetContent; Debug.Assert(!editWidgetContent.mIgnoreSetHistory); editWidgetContent.mIgnoreSetHistory = true; if (mEditData != null) { mEditData.Reload(); gApp.FileChanged(mEditData); } else { editWidgetContent.Reload(mFilePath); } editWidgetContent.mIgnoreSetHistory = false; QueueFullRefresh(false); #if IDE_C_SUPPORT mClangSourceChanged = false; #endif //mLastFileTextVersion = mEditWidget.Content.mData.mCurTextVersionId; if (mEditData != null) mEditData.mLastFileTextVersion = mEditWidget.Content.mData.mCurTextVersionId; CheckBinary(); } void RetryLoad() { var prevHash = mLoadedHash; Reload(); if (mRequestedLineAndColumn != null) ShowFileLocation(-1, mRequestedLineAndColumn.mValue.mLine, mRequestedLineAndColumn.mValue.mColumn, .Always); FocusEdit(); if ((!(prevHash case .None)) && (prevHash != mLoadedHash)) ShowWrongHash(); if (!mLoadFailed) { gApp.RehupStepFilters(); } } public void RefusedReload() { if (mEditData != null) { mEditData.mHadRefusedFileChange = true; DeleteAndNullify!(mEditData.mQueuedContent); } } public bool Show(ProjectItem projectItem, bool silentFail = false) { mProjectSource = (ProjectSource)projectItem; if (projectItem is ProjectSource) { var fullPath = scope String(); mProjectSource.GetFullImportPath(fullPath); return Show(fullPath, silentFail); } return false; } public void PathChanged(String path) { if (mFilePath != null) IDEApp.sApp.mFileWatcher.RemoveWatch(mFilePath); mFilePath.Set(path); IDEApp.sApp.mFileWatcher.WatchFile(path); mWantsFullRefresh = true; bool wasSource = mIsBeefSource; mIsBeefSource = IDEApp.IsBeefFile(mFilePath); mIsSourceCode = IDEApp.IsSourceCode(mFilePath); if ((!mIsSourceCode) && (wasSource)) { var ewd = mEditWidget.mEditWidgetContent.mData; for (int i < ewd.mTextLength) { ewd.mText[i].mDisplayFlags = 0; ewd.mText[i].mDisplayTypeId = 0; } } MarkDirty(); } public void SplitView() { if (mPanelHeader != null) return; if (mSplitTopPanel != null) return; // User requested from menu if (mSplitter.mSplitPct <= 0) mSplitter.mSplitPct = 0.3f; mSplitTopPanel = new SourceViewPanel(); mSplitTopPanel.mSplitBottomPanel = this; mSplitter.mTopPanel = mSplitTopPanel; if (mProjectSource != null) mSplitTopPanel.Show(mProjectSource); else mSplitTopPanel.Show(mFilePath, false, mEditData); mSplitTopPanel.mDesiredVertPos = mEditWidget.mVertPos.v; mSplitTopPanel.Content.CursorLineAndColumn = Content.CursorLineAndColumn; QueueFullRefresh(false); mSplitTopPanel.QueueFullRefresh(false); AddWidget(mSplitTopPanel); ResizeComponents(); // Match scroll positions //mSplitTopPanel.mEditWidget.UpdateScrollbars(); //mSplitTopPanel.mEditWidget.mVertScrollbar.ScrollTo(mEditWidget.mVertPos.v); //mSplitTopPanel.Content.CursorLineAndColumn = Content.CursorLineAndColumn; } public void UnsplitView() { //Debug.WriteLine("UnsplitView {0}\n", this); mSplitter.mTopPanel = null; mSplitTopPanel.Dispose(); Widget.RemoveAndDelete(mSplitTopPanel); mSplitTopPanel = null; ResizeComponents(); } void ShowOld(SourceViewPanel sourceViewPanel, int hotFileIdx) { mEditWidget = IDEApp.sApp.CreateSourceEditWidget(); SetupEditWidget(); var sourceEditWidgetContent = (SourceEditWidgetContent)mEditWidget.Content; sourceEditWidgetContent.SetOldVersionColors(true); String.NewOrSet!(mFilePath, sourceViewPanel.mFilePath); // We don't set mProjectSource because we don't want to actually perform a Classify, since // this old version will reference NEW class info, which is a version mismatch // mProjectSource = sourceViewPanel.mProjectSource; mIsSourceCode = sourceViewPanel.mIsSourceCode; mIsBeefSource = sourceViewPanel.mIsBeefSource; mJustShown = true; mWantsFullClassify = true; mWantsFullClassify = true; var projectSourceCompileInstance = IDEApp.sApp.mWorkspace.GetProjectSourceCompileInstance(sourceViewPanel.mProjectSource, hotFileIdx); mEditWidget.Content.AppendText(projectSourceCompileInstance.mSource); Debug.Assert(mEditWidget.Content.mData.mTextLength == projectSourceCompileInstance.mSource.Length); mEditWidget.Content.mData.mTextIdData.DuplicateFrom(ref projectSourceCompileInstance.mSourceCharIdData); mEditWidget.Content.mIsReadOnly = true; mHotFileIdx = hotFileIdx; mIsOldCompiledVersion = mHotFileIdx < IDEApp.sApp.mWorkspace.GetHighestCompileIdx(); mCurrentVersionPanel = sourceViewPanel; mPanelHeader = new PanelHeader(); if (mIsOldCompiledVersion) mPanelHeader.Label = "A previous version of this method is currently executing. The new version will be used when next called."; else mPanelHeader.Label = "This method has changed since compiling. Recompile to hot swap changes."; if (!sPreviousVersionWarningShown) { mPanelHeader.Flash(); sPreviousVersionWarningShown = true; } var button = mPanelHeader.AddButton("Show &Current"); button.mOnMouseClick.Add(new (evt) => { ShowCurrent(); /*var app = IDEApp.sApp; int callStackIdx = app.mDebugger.mSelectedCallStackIdx; float scrollTopDelta = mEditWidget.Content.GetCursorScreenRelY(); bool isPaused = app.mDebugger.IsPaused(); // Do another setPCLocation to make sure our cursor is at the PC position if (isPaused) app.ShowPCLocation(callStackIdx, true); sourceViewPanel.mJustShown = true; sourceViewPanel.CloseOldVersion(); if (isPaused) app.ShowPCLocation(callStackIdx, true); sourceViewPanel.mEditWidget.Content.SetCursorScreenRelY(scrollTopDelta + mPanelHeader.mHeight);*/ }); AddWidget(mPanelHeader); InitSplitter(); } public void ShowCurrent() { if (mOldVersionPanel != null) { mOldVersionPanel.ShowCurrent(); return; } if (mCurrentVersionPanel == null) return; var app = IDEApp.sApp; int32 callStackIdx = app.mDebugger.mActiveCallStackIdx; float scrollTopDelta = mEditWidget.Content.GetCursorScreenRelY(); bool isPaused = app.mDebugger.IsPaused(); // Do another setPCLocation to make sure our cursor is at the PC position if (isPaused) app.ShowPCLocation(callStackIdx, true); mCurrentVersionPanel.mJustShown = true; mCurrentVersionPanel.CloseOldVersion(); if (isPaused) app.ShowPCLocation(callStackIdx, true); mCurrentVersionPanel.mEditWidget.Content.SetCursorScreenRelY(scrollTopDelta + mPanelHeader.mHeight); } void CloseOldVersion() { if (mEditWidget == null) { // What to do? } if (mOldVersionPanel != null) { mEditWidget.mVisible = true; if (mOldVersionPanel.mParent != null) mOldVersionPanel.RemoveSelf(); mOldVersionPanel.Dispose(); BFApp.sApp.DeferDelete(mOldVersionPanel); //delete mOldVersionPanel; mOldVersionPanel = null; if (mWidgetWindow != null) FocusEdit(); ResizeComponents(); } } public void ShowHotFileIdx(int hotFileIdx) { if (mLoadFailed) return; bool isOldCompiledVersion = (hotFileIdx != -1) && (hotFileIdx < IDEApp.sApp.mWorkspace.GetHighestCompileIdx()); if ((mOldVersionPanel != null) && (mOldVersionPanel.mHotFileIdx == hotFileIdx) && (mOldVersionPanel.mIsOldCompiledVersion == isOldCompiledVersion)) return; CloseOldVersion(); //if (hotFileIdx != -1) if (isOldCompiledVersion) { mEditWidget.mVisible = false; mOldVersionPanel = new SourceViewPanel(); if (mProjectSource != null) { mOldVersionPanel.ShowOld(this, hotFileIdx); AddWidget(mOldVersionPanel); mOldVersionPanel.FocusEdit(); ResizeComponents(); } } } public void GetCursorPosition(out int32 line, out int32 column) { var lineAndCol = mEditWidget.Content.CursorLineAndColumn; line = lineAndCol.mLine; column = lineAndCol.mColumn; } int GetDrawLineNum(Breakpoint breakpoint) { int breakpointLineNum; /*if (mIsBeefSource) breakpointLineNum = breakpoint.GetLineNum(); else breakpointLineNum = breakpoint.mLineNum;*/ // Why did we have "mIsBeefSource" check? This broke our ability to 'move' the breakpoint down // onto the actual executable line breakpointLineNum = breakpoint.GetLineNum(); int drawLineNum = breakpointLineNum; // We want to use "IsBound" instead of "HasNativeBreakpoint" because otherwise when we hot-create a new method // and then put a breakpoint on it then it'll remap as if it was bound if ((mIsBeefSource) && (breakpoint.mNativeBreakpoint != null)) { int compileIdx = gApp.mWorkspace.GetHighestSuccessfulCompileIdx(); //drawLineNum = RemapCompiledToActiveLine(/*breakpoint.mLineNum*/breakpointLineNum); if (compileIdx != -1) { int drawLineColumn = 0; RemapCompiledToActiveLine(compileIdx, ref drawLineNum, ref drawLineColumn); if (mHotFileIdx != -1) { drawLineNum = RemapActiveLineToHotLine(drawLineNum); } } } return drawLineNum; } public Breakpoint ToggleBreakpointAtCursor(bool forceSet = false, int threadId = -1) { var activePanel = GetActivePanel(); if (activePanel != this) return activePanel.ToggleBreakpointAtCursor(forceSet, threadId); if (mOldVersionPanel != null) { return null; } DebugManager debugManager = IDEApp.sApp.mDebugger; int lineIdx; int lineCharIdx; mEditWidget.Content.GetLineCharAtIdx(mEditWidget.Content.CursorTextPos, out lineIdx, out lineCharIdx); bool hadBreakpoint = false; if (!forceSet) { /*WithTrackedElementsAtCursor(IDEApp.sApp.mDebugger.mBreakpointList, scope [&] (breakpoint) => { BfLog.LogDbg("SourceViewPanel.ToggleBreakpointAtCursor deleting breakpoint\n"); debugManager.DeleteBreakpoint(breakpoint); hadBreakpoint = true; });*/ for (var breakpointView in GetTrackedElementList()) { var trackedElement = breakpointView.mTrackedElement; if (var breakpoint = trackedElement as Breakpoint) { int drawLineNum = GetDrawLineNum(breakpoint); if (drawLineNum == lineIdx) { BfLog.LogDbg("SourceViewPanel.ToggleBreakpointAtCursor deleting breakpoint\n"); debugManager.DeleteBreakpoint(breakpoint); hadBreakpoint = true; } } } } if (!hadBreakpoint) { RecordHistoryLocation(); var editWidgetContent = mEditWidget.Content; int textPos = mEditWidget.Content.CursorTextPos - lineCharIdx; lineCharIdx = 0; // Find first non-space char8 while ((textPos < editWidgetContent.mData.mTextLength) && (((char8)editWidgetContent.mData.mText[textPos].mChar).IsWhiteSpace)) { textPos++; lineCharIdx++; } int requestedActiveLineIdx = lineIdx; int curCompileIdx = IDEApp.sApp.mWorkspace.GetHighestCompileIdx(); bool foundPosition = false; if (gApp.mDebugger.mIsRunning) foundPosition = RemapActiveToCompiledLine(curCompileIdx, ref lineIdx, ref lineCharIdx); bool createNow = foundPosition || !mIsBeefSource; // Only be strict about Beef source Breakpoint newBreakpoint = debugManager.CreateBreakpoint_Create(mFilePath, lineIdx, lineCharIdx, -1); newBreakpoint.mThreadId = threadId; debugManager.CreateBreakpoint_Finish(newBreakpoint, createNow); int newDrawLineNum = GetDrawLineNum(newBreakpoint); if (!forceSet) { for (int32 breakIdx = 0; breakIdx < IDEApp.sApp.mDebugger.mBreakpointList.Count; breakIdx++) { var checkBreakpoint = IDEApp.sApp.mDebugger.mBreakpointList[breakIdx]; if ((checkBreakpoint != newBreakpoint) && (checkBreakpoint.mFileName == newBreakpoint.mFileName)) { int checkDrawLineNum = GetDrawLineNum(checkBreakpoint); if (checkDrawLineNum == newDrawLineNum) { BfLog.LogDbg("SourceViewPanel.ToggleBreakpointAtCursor duplicate breakpoint. Deleting breakpoint\n"); // This ended up on the same line as another breakpoint after binding. Hilite the other breakpoint to show there's already one there. debugManager.DeleteBreakpoint(newBreakpoint); newBreakpoint = null; var ewc = mEditWidget.mEditWidgetContent; LocatorAnim.Show(mEditWidget, ewc.mX + -GS!(15), ewc.mY + newDrawLineNum * ewc.GetLineHeight(0) + GS!(12)); break; } } } } // If we are hot compiling, and the binding of the breakpoint moves the breakpoint (down a few lines presumably), but the text between // the requested position and the bound position has had changes that haven't been compiled in yet, then we undo the binding so we can // rebind when we complete the hot compile if ((newBreakpoint != null) && (gApp.mDebugger.mIsRunning) && (mIsBeefSource) && (mProjectSource != null)) { int boundLineNum = newBreakpoint.GetLineNum(); int boundColumn = 0; RemapCompiledToActiveLine(curCompileIdx, ref boundLineNum, ref boundColumn); if (requestedActiveLineIdx != boundLineNum) { int startCheckIdx = editWidgetContent.GetTextIdx(requestedActiveLineIdx, 0); int endCheckIdx = editWidgetContent.GetTextIdx(boundLineNum, 0); var textIdData = editWidgetContent.mData.mTextIdData.GetPrepared(); int32 startId = textIdData.GetIdAtIndex(startCheckIdx); int32 endId = textIdData.GetIdAtIndex(endCheckIdx); if (var projectSourceInstance = gApp.mWorkspace.GetProjectSourceCompileInstance(mProjectSource, curCompileIdx)) { if (!textIdData.IsRangeEqual(projectSourceInstance.mSourceCharIdData, startId, endId)) { newBreakpoint.DisposeNative(); newBreakpoint.mLineNum = (.)requestedActiveLineIdx; } } } } return newBreakpoint; } return null; } public void ToggleBookmarkAtCursor() { if ((mHotFileIdx != -1) || (mOldVersionPanel != null)) return; if (mFilePath == null) return; var activePanel = GetActivePanel(); if (activePanel != this) { activePanel.ToggleBookmarkAtCursor(); return; } BookmarkManager bookmarkManager = IDEApp.sApp.mBookmarkManager; int lineIdx; int lineCharIdx; mEditWidget.Content.GetLineCharAtIdx(mEditWidget.Content.CursorTextPos, out lineIdx, out lineCharIdx); bool hadBookmark = false; WithTrackedElementsAtCursor(IDEApp.sApp.mBookmarkManager.mBookmarkList, scope [&] (bookmark) => { bookmarkManager.DeleteBookmark(bookmark); hadBookmark = true; }); if (!hadBookmark) { var editWidgetContent = mEditWidget.Content; int textPos = mEditWidget.Content.CursorTextPos - lineCharIdx; lineCharIdx = 0; // Find first non-space char8 while ((textPos < editWidgetContent.mData.mTextLength) && (((char8)editWidgetContent.mData.mText[textPos].mChar).IsWhiteSpace)) { textPos++; lineCharIdx++; } bookmarkManager.CreateBookmark(mFilePath, lineIdx, lineCharIdx); } } void RemapCompiledToActiveLine(int compileInstanceIdx, ref int lineNum, ref int column) { let projectSource = FilteredProjectSource; if (projectSource != null) { int32 char8Id = IDEApp.sApp.mWorkspace.GetProjectSourceCharId(projectSource, compileInstanceIdx, lineNum, column); int char8Idx = mEditWidget.Content.GetCharIdIdx(char8Id); if (char8Idx != -1) { mEditWidget.Content.GetLineCharAtIdx(char8Idx, out lineNum, out column); } } } bool RemapActiveToCompiledLine(int compileInstanceIdx, ref int lineNum, ref int column) { var projectSource = FilteredProjectSource; if (mIsOldCompiledVersion) projectSource = ((SourceViewPanel)mParent).FilteredProjectSource; if (projectSource != null) { int char8Id = mEditWidget.Content.GetSourceCharIdAtLineChar(lineNum, column); if (char8Id == 0) return false; return IDEApp.sApp.mWorkspace.GetProjectSourceCharIdPosition(projectSource, compileInstanceIdx, char8Id, ref lineNum, ref column); } return true; } int RemapActiveLineToHotLine(int line) { if (mHotFileIdx == -1) return line; var activePanel = (SourceViewPanel)mParent; int32 char8Id = activePanel.mEditWidget.Content.GetSourceCharIdAtLineChar(line, -1); if (char8Id == -1) return -1; int remapIdx = mEditWidget.Content.GetCharIdIdx(char8Id); if (remapIdx == -1) return -1; int remapLine; int remapLineChar; mEditWidget.Content.GetLineCharAtIdx(remapIdx, out remapLine, out remapLineChar); return remapLine; } enum LineFlags : uint8 { None, BreakpointCountMask = 0x7F, Boomkmark = 0x80 } public override void Draw(Graphics g) { base.Draw(g); // If we're trying to time autocomplete /*var bfSystem = IDEApp.sApp.mBfResolveSystem; if (bfSystem.mIsTiming) { bfSystem.StopTiming(); bfSystem.DbgPrintTimings(); }*/ if (GetOldVersionPanel() != null) return; if (mLoadFailed) return; DarkEditWidgetContent darkEditWidgetContent = (DarkEditWidgetContent)mEditWidget.Content; g.SetFont(IDEApp.sApp.mTinyCodeFont); using (g.PushClip(0, mEditWidget.mY, mWidth, mEditWidget.mHeight - GS!(20))) { using (g.PushTranslate(0, mEditWidget.mY + mEditWidget.Content.Y + GS!(2))) { float lineSpacing = darkEditWidgetContent.mFont.GetLineSpacing(); float editX = GetEditX(); int lineStart = (int)((-mEditWidget.Content.Y) / lineSpacing) - 1; int lineEnd = Math.Min(darkEditWidgetContent.GetLineCount(), lineStart + (int)(mHeight / lineSpacing) + 3); if (lineEnd <= lineStart) { return; } LineFlags[] lineFlags = scope LineFlags[lineEnd - lineStart]; for (var breakpointView in GetTrackedElementList()) { var trackedElement = breakpointView.mTrackedElement; var breakpoint = trackedElement as Breakpoint; var bookmark = trackedElement as Bookmark; if (breakpoint != null) { int drawLineNum = GetDrawLineNum(breakpoint); if ((drawLineNum < lineStart) || (drawLineNum >= lineEnd)) continue; var curLineFlags = ref lineFlags[drawLineNum - lineStart]; int breakpointCount = (.)(curLineFlags & .BreakpointCountMask); curLineFlags++; float iconX = Math.Max(GS!(-2), mEditWidget.mX - GS!(24)) + breakpointCount*-GS!(2); float iconY = 0 + drawLineNum * lineSpacing + (lineSpacing - DarkTheme.sUnitSize + GS!(5)) / 2; // Just leave last digit visible /*using (g.PushColor(0xFF595959)) g.FillRect(4, iconY, editX - 14, 20);*/ using (g.PushColor((breakpointCount % 2 == 0) ? 0xFFFFFFFF : 0xFFC0C0C0)) using (g.PushTranslate(iconX, iconY)) { breakpoint.Draw(g, mIsOldCompiledVersion); } } else if (bookmark != null) { if (mHotFileIdx == -1) { int32 drawLineNum = bookmark.mLineNum; if ((drawLineNum < lineStart) || (drawLineNum >= lineEnd)) continue; //hadLineIcon[drawLineNum - lineStart] = true; g.Draw(DarkTheme.sDarkTheme.GetImage(.IconBookmark), Math.Max(GS!(-5), mEditWidget.mX - GS!(30)), 0 + bookmark.mLineNum * lineSpacing); var curLineFlags = ref lineFlags[drawLineNum - lineStart]; curLineFlags |= .Boomkmark; //FAIL } } //// /*var historyEntry = trackedElement as HistoryEntry; if (historyEntry != null) { if (mHotFileIdx == -1) { float xPos; float yPos; darkEditWidgetContent.GetTextCoordAtLineChar(historyEntry.mLineNum, historyEntry.mColumn, out xPos, out yPos); using (g.PushColor(0x60FFFFFF)) g.Draw(DarkTheme.sDarkTheme.GetImage(DarkTheme.ImageIdx.IconBookmark), mEditWidget.mX + xPos, 0 + historyEntry.mLineNum * lineSpacing); } }*/ } if (gApp.mSettings.mEditorSettings.mShowLineNumbers) { String lineStr = scope String(16); using (g.PushColor(0x80FFFFFF)) { for (int lineIdx = lineStart; lineIdx < lineEnd; lineIdx++) { lineStr.Clear(); int maxLineChars = Int32.MaxValue; let curLineFlags = lineFlags[lineIdx - lineStart]; if ((uint8)(curLineFlags & .BreakpointCountMask) > 0) maxLineChars = 1; else if (curLineFlags.HasFlag(.Boomkmark)) maxLineChars = 2; switch (maxLineChars) { case 0: case 1: lineStr.AppendF("{0}", (lineIdx + 1) % 10); case 2: lineStr.AppendF("{0}", (lineIdx + 1) % 100); default: lineStr.AppendF("{0}", lineIdx + 1); } g.DrawString(lineStr, 0, GS!(2) + lineIdx * lineSpacing, FontAlign.Right, editX - GS!(2)); } } } if (IDEApp.sApp.mExecutionPaused) { int addr; String fileName = scope String(Path.MaxPath); int hotIdx; int defLineStart; int defLineEnd; int lineNum; int column; int language; int stackSize; DebugManager.FrameFlags flags; IDEApp.sApp.mDebugger.GetStackFrameInfo(IDEApp.sApp.mDebugger.mActiveCallStackIdx, null, out addr, fileName, out hotIdx, out defLineStart, out defLineEnd, out lineNum, out column, out language, out stackSize, out flags); IDEUtils.FixFilePath(fileName); int hashPos = fileName.IndexOf('#'); if (hashPos != -1) fileName.RemoveToEnd(hashPos); if (FileNameMatches(fileName)) { RemapCompiledToActiveLine(hotIdx, ref lineNum, ref column); Image img; if (IDEApp.sApp.mDebugger.mActiveCallStackIdx == 0) { if (flags.HasFlag(.Optimized)) img = DarkTheme.sDarkTheme.GetImage(.LinePointer_Opt); else img = DarkTheme.sDarkTheme.GetImage(.LinePointer); } else img = DarkTheme.sDarkTheme.GetImage(.LinePointer_Prev); // If our step/continue doesn't actually change the line-pointer position, then we // want to give just the littlest 'flash' of the cursor to indicate to the user // that something actually happened. The most common case is a breakpoint that // gets hit over and over F5 (continue). bool doDraw = false; if ((mLinePointerDrawData.mImage != img) || (mLinePointerDrawData.mLine != lineNum)) { mLinePointerDrawData.mImage = img; mLinePointerDrawData.mLine = (.)lineNum; doDraw = true; } else if ((mLinePointerDrawData.mDebuggerContinueIdx == gApp.mDebuggerContinueIdx) || (gApp.mUpdateCnt - mLinePointerDrawData.mUpdateCnt >= 3)) { doDraw = true; } if (doDraw) { mLinePointerDrawData.mUpdateCnt = gApp.mUpdateCnt; mLinePointerDrawData.mDebuggerContinueIdx = gApp.mDebuggerContinueIdx; g.Draw(img, mEditWidget.mX - GS!(20), 0 + lineNum * lineSpacing); } } } } } bool drawLock = mSplitBottomPanel == null; if (drawLock) { IDEUtils.DrawLock(g, mEditWidget.mX - GS!(20), mHeight - GS!(20), IsReadOnly, mLockFlashPct); } /*using (g.PushColor(0x80FF0000)) g.FillRect(0, 0, mWidth, mHeight);*/ } /*void UpdateCharData() { var bfSystem = IDEApp.sApp.mBfResolveSystem; bfSystem.PerfZoneStart("UpdateCharData"); // Inject new char8 attributes into text uint highestSrcCharId = 0; for (int i = 0; i < mProcessingCharData.Length; i++) { uint char8Id = mProcessingCharData[i].mCharId; if (char8Id > highestSrcCharId) highestSrcCharId = char8Id; } int srcIdx = 0; int destIdx = 0; var destText = mEditWidget.Content.mText; int destTextLength = mEditWidget.Content.mTextLength; while ((srcIdx < mProcessingCharData.Length) && (destIdx < destTextLength)) { if (destText[destIdx].mCharId > highestSrcCharId) { // This is new text since we did the background compile, skip destIdx++; continue; } if (mProcessingCharData[srcIdx].mCharId != destText[destIdx].mCharId) { // Id doesn't match, char8acter must have been deleted srcIdx++; continue; } Debug.Assert(destText[destIdx].mChar == mProcessingCharData[srcIdx].mChar); if (destText[destIdx].mDisplayPassId == (byte)SourceDisplayId.AutoComplete) { // Autocomplete beat us to it destText[destIdx].mDisplayPassId = (byte)SourceDisplayId.Cleared; } else { byte prevFlags = destText[destIdx].mDisplayFlags; destText[destIdx] = mProcessingCharData[srcIdx]; destText[destIdx].mDisplayFlags = (byte) ((prevFlags & (byte)SourceElementFlags.EditorFlags_Mask) | (destText[destIdx].mDisplayFlags & (byte)SourceElementFlags.CompilerFlags_Mask)); } srcIdx++; destIdx++; } mProcessingCharData = null; bfSystem.PerfZoneEnd(); }*/ void UpdateCharData(ref EditWidgetContent.CharData[] charData, ref IdSpan charIdData, uint8 replaceFlags, bool flagsOnly) { //Debug.WriteLine("UpdateCharData: {0}", char8Data); scope AutoBeefPerf("SourceViewPanel.UpdateCharData"); charIdData.Prepare(); var editTextIdData = ref mEditWidget.Content.mData.mTextIdData; editTextIdData.Prepare(); // Inject new char8 attributes into text int32 highestSrcCharId = 0; int32 srcCharId = 1; //string dbgStr = ""; int srcEncodeIdx = 0; //dbgStr += "Src: "; while (true) { int32 cmd = Utils.DecodeInt(charIdData.mData, ref srcEncodeIdx); if (cmd > 0) { srcCharId = cmd; } else { srcCharId += -cmd; highestSrcCharId = Math.Max(highestSrcCharId, srcCharId - 1); if (cmd == 0) break; } //dbgStr += " " + cmd; } int destEncodeIdx = 0; /*dbgStr += " Dest: "; while (true) { int cmd = Utils.DecodeInt(mProcessCharIdData, ref destEncodeIdx); if (cmd == 0) break; dbgStr += " " + cmd; }*/ //Debug.WriteLine(dbgStr); int32 srcIdx = 0; int32 destIdx = 0; var destText = mEditWidget.Content.mData.mText; int32 destTextLength = mEditWidget.Content.mData.mTextLength; srcEncodeIdx = 0; int32 srcSpanLeft = 0; srcCharId = 1; destEncodeIdx = 0; int32 destSpanLeft = 0; int32 destCharId = 1; while ((srcIdx < charData.Count) && (destIdx < destTextLength)) { while (srcSpanLeft == 0) { int32 cmd = Utils.DecodeInt(charIdData.mData, ref srcEncodeIdx); if (cmd > 0) srcCharId = cmd; else srcSpanLeft = -cmd; } while (destSpanLeft == 0) { int32 cmd = Utils.DecodeInt(editTextIdData.mData, ref destEncodeIdx); if (cmd > 0) destCharId = cmd; else destSpanLeft = -cmd; } if (destCharId > highestSrcCharId) { // This is new text since we did the background compile, skip destIdx++; destSpanLeft--; destCharId++; continue; } if (srcCharId != destCharId) { // Id doesn't match, character must have been deleted srcIdx++; srcSpanLeft--; srcCharId++; continue; } Debug.Assert(destCharId == srcCharId); Debug.Assert(destText[destIdx].mChar == charData[srcIdx].mChar); if (destText[destIdx].mDisplayPassId == (uint8)SourceDisplayId.AutoComplete) { // Autocomplete beat us to it destText[destIdx].mDisplayPassId = (uint8)SourceDisplayId.Cleared; } else if (charData[srcIdx].mDisplayTypeId == (uint8)SourceDisplayId.SkipResult) { // } else { uint8 prevFlags = destText[destIdx].mDisplayFlags; if (!flagsOnly) destText[destIdx] = charData[srcIdx]; destText[destIdx].mDisplayFlags = (uint8) ((prevFlags & ~replaceFlags) | (charData[srcIdx].mDisplayFlags & replaceFlags)); } srcIdx++; srcSpanLeft--; srcCharId++; destIdx++; destSpanLeft--; destCharId++; } delete charData; charData = null; charIdData.Dispose(); } public override void ShowQuickFind(bool isReplace) { //RecordHistoryLocation(); var activePanel = GetActivePanel(); if (Content.Data.mCurQuickFind != null) { Content.Data.mCurQuickFind.Close(); } if (activePanel != this) { activePanel.ShowQuickFind(isReplace); return; } /*if (mOldVersionPanel != null) { mOldVersionPanel.ShowQuickFind(isReplace); return; }*/ if (mRenameSymbolDialog != null) mRenameSymbolDialog.Close(); base.ShowQuickFind(isReplace); Content.Data.mCurQuickFind = mQuickFind; } public override void FindNext(int32 dir) { var activePanel = GetActivePanel(); if (activePanel != this) { activePanel.FindNext(); return; } if (mOldVersionPanel != null) { mOldVersionPanel.FindNext(dir); return; } base.FindNext(dir); } public void ReformatDocument() { if (!mIsBeefSource) return; var bfSystem = IDEApp.sApp.mBfResolveSystem; if (bfSystem == null) return; var parser = bfSystem.CreateEmptyParser(null); defer delete parser; var text = scope String(); mEditWidget.GetText(text); parser.SetSource(text, mFilePath); var passInstance = bfSystem.CreatePassInstance(); defer delete passInstance; parser.Parse(passInstance, false); parser.Reduce(passInstance); mWantsParserCleanup = true; bool performSanityCheck = false; #if !DEBUG performSanityCheck = false; #endif if (performSanityCheck) { int32[] char8Mapping; var newText = scope String(); parser.Reformat(-1, -1, out char8Mapping, newText); // Just reprint without reformatting first int32 lineNum = 0; int32 lineStart = 0; for (int32 i = 0; i < Math.Min(newText.Length, text.Length); i++) { if (text[i] == '\n') { lineNum++; lineStart = i + 1; } if (text[i] != newText[i]) { IDEApp.sApp.OutputLine("Reformat had a difference at line {0}", (lineNum + 1)); int nextCr = text.IndexOf('\n', lineStart); IDEApp.sApp.OutputLine(" {0}", scope String(text, lineStart, nextCr - lineStart)); nextCr = newText.IndexOf('\n', lineStart); IDEApp.sApp.OutputLine(" {0}", scope String(newText, lineStart, nextCr - lineStart)); break; } } } if (mEditWidget.Content.HasSelection()) parser.ReformatInto(mEditWidget, mEditWidget.Content.mSelection.Value.MinPos, mEditWidget.Content.mSelection.Value.MaxPos); else parser.ReformatInto(mEditWidget, 0, text.Length); //mEditWidget.SetText(newText); } public void GotoLine() { GoToLineDialog aDialog = new GoToLineDialog("Go To Line", StackStringFormat!("Line Number ({0}-{1})", 1, mEditWidget.Content.GetLineCount())); aDialog.Init(this); aDialog.PopupWindow(mWidgetWindow); } public void GotoMethod() { if (mSplitBottomPanel != null) mSplitBottomPanel.GotoMethod(); else mNavigationBar.ShowDropdown(); } public void FixitAtCursor() { if (!mIsBeefSource) return; //TODO: Make better, do async, etc... DoClassify(ResolveType.GetFixits, null, true); } public void ShowSymbolReferenceHelper(SymbolReferenceHelper.Kind symbolReferenceKind) { if (gApp.mSymbolReferenceHelper != null) { gApp.mSymbolReferenceHelper.Close(); } SymbolReferenceHelper symbolReferenceHelper = new SymbolReferenceHelper(); //symbolReferenceHelper.[Friend]GCMarkMembers(); //Debug.WriteLine("SymbolReferenceHelper {0} ResolveParams:{1}", symbolReferenceHelper, symbolReferenceHelper.[Friend]mResolveParams); mRenameSymbolDialog = symbolReferenceHelper; gApp.mSymbolReferenceHelper = symbolReferenceHelper; BfResolveCompiler.mThreadWorkerHi.WaitForBackground(); // We need to finish up anything on the hi thread worker so we can queue this symbolReferenceHelper.Init(this, symbolReferenceKind); if (!symbolReferenceHelper.mFailed) { AddWidget(symbolReferenceHelper); ResizeComponents(); } else { mRenameSymbolDialog.Close(); } if ((symbolReferenceKind == .Rename) && (let autoComplete = GetAutoComplete())) autoComplete.Close(); } public void RenameSymbol() { var sourceEditWidgetContent = (SourceEditWidgetContent)mEditWidget.Content; if (!sourceEditWidgetContent.CheckReadOnly()) ShowSymbolReferenceHelper(SymbolReferenceHelper.Kind.Rename); } public void FindAllReferences() { ShowSymbolReferenceHelper(SymbolReferenceHelper.Kind.FindAllReferences); } public override void SetVisible(bool visible) { base.SetVisible(visible); mWantsFullClassify = true; mWantsClassifyAutocomplete = false; } public override void RecordHistoryLocation(bool ignoreIfClose = false) { var sourceEditWidgetContent = (SourceEditWidgetContent)mEditWidget.Content; sourceEditWidgetContent.RecordHistoryLocation(ignoreIfClose); } bool CheckLeftMouseover() { if (mWidgetWindow == null) return false; if (!mWidgetWindow.mHasMouseInside) return false; Point mousePos; bool mouseoverFired = DarkTooltipManager.CheckMouseover(this, 10, out mousePos); String tooltipStr = scope String(); if ((DarkTooltipManager.sTooltip != null) && (DarkTooltipManager.sTooltip.mRelWidget == this)) mouseoverFired = true; if (!mouseoverFired) return false; float editX = GetEditX(); if ((mousePos.x < editX - 24) || (mousePos.x > editX - 5)) return false; DarkEditWidgetContent darkEditWidgetContent = (DarkEditWidgetContent)mEditWidget.Content; float lineSpacing = darkEditWidgetContent.mFont.GetLineSpacing(); float ofsY = mEditWidget.mY + darkEditWidgetContent.mY + (lineSpacing - DarkTheme.sUnitSize + GS!(5)) / 2; for (var breakpointView in GetTrackedElementList()) { var trackedElement = breakpointView.mTrackedElement; var breakpoint = trackedElement as Breakpoint; //var bookmark = trackedElement as Bookmark; if (breakpoint != null) { int breakpointLineNum; if (mIsBeefSource) breakpointLineNum = breakpoint.GetLineNum(); else breakpointLineNum = breakpoint.mLineNum; int drawLineNum = breakpointLineNum; float iconY = ofsY + drawLineNum * lineSpacing; if ((mousePos.y > iconY) && (mousePos.y < iconY + GS!(20))) { if (!tooltipStr.IsEmpty) tooltipStr.Append("\n\n"); breakpoint.ToString_Location(tooltipStr); if (breakpoint.mThreadId != -1) tooltipStr.AppendF("\nThread: {0}", breakpoint.mThreadId); tooltipStr.Append("\nHits: "); breakpoint.ToString_HitCount(tooltipStr); if (breakpoint.mLogging != null) tooltipStr.Append("\nLog: ", breakpoint.mLogging); } } } if (tooltipStr.IsEmpty) return false; if ((DarkTooltipManager.sTooltip != null) && (DarkTooltipManager.sTooltip.mText == tooltipStr)) return true; DarkTooltipManager.ShowTooltip(tooltipStr, this, mousePos.x, mousePos.y); return true; } public void GetDebugExpressionAt(int textIdx, String debugExpr) { BfSystem bfSystem = IDEApp.sApp.mBfResolveSystem; let parser = bfSystem.CreateEmptyParser(null); var text = scope String(); mEditWidget.GetText(text); parser.SetSource(text, mFilePath); parser.SetAutocomplete(textIdx); let passInstance = bfSystem.CreatePassInstance("Mouseover"); parser.SetCompleteParse(); parser.Parse(passInstance, !mIsBeefSource); parser.Reduce(passInstance); if (parser.GetDebugExpressionAt(textIdx, debugExpr)) { if (debugExpr.StartsWith("`")) debugExpr[0] = ':'; else if (debugExpr.StartsWith(":")) debugExpr.Clear(); } delete passInstance; delete parser; } public void UpdateMouseover() { if (mWidgetWindow == null) return; if (CheckLeftMouseover()) { return; } if ((DarkTooltipManager.sTooltip != null) && (DarkTooltipManager.sTooltip.mRelWidget == this)) DarkTooltipManager.CloseTooltip(); if (!CheckAllowHoverWatch()) { return; } /*if ((mHoverWatch != null) && (mHoverWatch.mCloseDelay > 0)) return;*/ var editWidgetContent = mEditWidget.Content; Point mousePos; bool mouseoverFired = DarkTooltipManager.CheckMouseover(editWidgetContent, 10, out mousePos); #unwarn CompilerBase compiler = ResolveCompiler; bool hasClangHoverErrorData = false; #if IDE_C_SUPPORT hasClangHoverErrorData = mClangHoverErrorData != null; #endif if (((mouseoverFired) || (mHoverWatch != null) || (hasClangHoverErrorData)) && (mousePos.x >= 0)) { int line; int lineChar; String debugExpr = null; BfSystem bfSystem = IDEApp.sApp.mBfResolveSystem; BfPassInstance passInstance = null; BfParser parser = null; int textIdx = -1; bool isOverMessage = false; float overflowX; if (editWidgetContent.GetLineCharAtCoord(mousePos.x, mousePos.y, out line, out lineChar, out overflowX)) { textIdx = editWidgetContent.GetTextIdx(line, lineChar); int startIdx = editWidgetContent.GetTextIdx(line, lineChar); uint8 checkFlags = (uint8)SourceElementFlags.Error; if (!IDEApp.sApp.mDebugger.mIsRunning) { // Prioritize debug info over warning when we are debugging checkFlags |= (uint8)SourceElementFlags.Warning; } if ((editWidgetContent.mData.mText[startIdx].mDisplayFlags & checkFlags) != 0) isOverMessage = true; bool doSimpleMouseover = false; if ((editWidgetContent.mSelection != null) && (textIdx >= editWidgetContent.mSelection.Value.MinPos) && (textIdx < editWidgetContent.mSelection.Value.MaxPos)) { debugExpr = scope:: String(); editWidgetContent.GetSelectionText(debugExpr); } else if (mIsBeefSource) { if (bfSystem != null) { parser = bfSystem.CreateEmptyParser(null); var text = scope String(); mEditWidget.GetText(text); parser.SetSource(text, mFilePath); parser.SetAutocomplete(textIdx); passInstance = bfSystem.CreatePassInstance("Mouseover"); parser.SetCompleteParse(); parser.Parse(passInstance, !mIsBeefSource); parser.Reduce(passInstance); debugExpr = scope:: String(); if (parser.GetDebugExpressionAt(textIdx, debugExpr)) { if (debugExpr.StartsWith("`")) debugExpr[0] = ':'; else if (debugExpr.StartsWith(":")) debugExpr = null; } } } else if (mIsClang) doSimpleMouseover = true; if (doSimpleMouseover) SimpleMouseover: do { int endIdx = startIdx; String sb = scope:: String(); bool isInvalid = false; bool prevWasSpace = false; if (editWidgetContent.mData.mText[startIdx].mChar.IsWhiteSpace) break; startIdx--; while (startIdx > 0) { var char8Data = editWidgetContent.mData.mText[startIdx]; if (char8Data.mDisplayTypeId == (uint8)SourceElementType.Comment) { if (startIdx == endIdx - 1) { // Inside comment isInvalid = true; break; } } else { char8 c = (char8)char8Data.mChar; if ((c == ' ') || (c == '\t')) { // Ignore prevWasSpace = true; } else if (c == '\n') { break; } else { if (c == '>') { // Is this "->"? if ((startIdx > 1) && ((char8)editWidgetContent.mData.mText[startIdx - 1].mChar == '-')) { sb.Insert(0, "->"); startIdx--; } else break; } else if (c == ':') { // Is this "::"? if ((startIdx > 1) && ((char8)editWidgetContent.mData.mText[startIdx - 1].mChar == ':')) { sb.Insert(0, "::"); startIdx--; } else break; } else if (c == '.') sb.Insert(0, c); else if ((c == '_') || (c.IsLetterOrDigit)) { if (prevWasSpace) break; sb.Insert(0, c); } else break; prevWasSpace = false; } } startIdx--; } prevWasSpace = false; while ((endIdx < editWidgetContent.mData.mTextLength) && (endIdx > startIdx)) { var char8Data = editWidgetContent.mData.mText[endIdx]; if (char8Data.mDisplayTypeId == (uint8)SourceElementType.Comment) { // Ignore prevWasSpace = true; } else { char8 c = (char8)char8Data.mChar; if ((c == ' ') || (c == '\t')) { // Ignore prevWasSpace = true; } else if ((c == '_') || (c.IsLetterOrDigit)) { if (prevWasSpace) break; sb.Append(c); } else break; prevWasSpace = false; } endIdx++; } if (!isInvalid) debugExpr = sb; } } bool triedShow = false; if (mHoverWatch != null) { if (debugExpr != null) { if (mHoverWatch.mEvalString != debugExpr) { mHoverWatch.Close(); mHoverWatch = null; } else triedShow = true; } } if (((mHoverWatch == null) && (mouseoverFired)) || (debugExpr == null) || (hasClangHoverErrorData)) { float x; float y; editWidgetContent.GetTextCoordAtLineChar(line, lineChar, out x, out y); bool hasHoverWatchOpen = (mHoverWatch != null) && (mHoverWatch.mListView != null); if (mHoverWatch == null) mHoverWatch = new HoverWatch(); if (debugExpr != null) triedShow = true; if ((debugExpr == null) || (isOverMessage) || (!mHoverWatch.Show(this, x, y, debugExpr))) { #if IDE_C_SUPPORT if ((mIsClang) && (textIdx != -1)) { bool hasErrorFlag = (mEditWidget.Content.mData.mText[textIdx].mDisplayFlags != 0); if (hasErrorFlag) { if (!compiler.IsPerformingBackgroundOperation()) { bool hadValidError = false; if (mClangHoverErrorData != null) { String[] stringParts = String.StackSplit!(mClangHoverErrorData, '\t'); int startIdx = (int32)int32.Parse(stringParts[0]); int endIdx = (int32)int32.Parse(stringParts[1]); if ((textIdx >= startIdx) && (textIdx < endIdx)) { hadValidError = true; triedShow = true; mHoverWatch.Show(this, x, y, scope String(":", stringParts[2])); if (debugExpr != null) mHoverWatch.mEvalString.Set(debugExpr); // Set to old debugStr for comparison else mHoverWatch.mEvalString.Clear(); } } if (!hadValidError) { mErrorLookupTextIdx = (int32)textIdx; Classify(ResolveType.Classify); triedShow = false; } } } else { triedShow = false; delete mClangHoverErrorData; mClangHoverErrorData = null; } } #endif if ((parser != null) && (mIsBeefSource)) ErrorScope: { //TODO: Needed this? /*var resolvePassData = parser.CreateResolvePassData(); defer (scope) delete resolvePassData; bfSystem.NotifyWillRequestLock(1); bfSystem.Lock(1); parser.BuildDefs(passInstance, resolvePassData, false); BfResolveCompiler.ClassifySource(passInstance, parser, resolvePassData, null);*/ BfPassInstance.BfError bestError = scope BfPassInstance.BfError(); for (var bfError in mErrorList) { if (bfError.mIsWhileSpecializing) continue; if ((textIdx >= bfError.mSrcStart) && (textIdx < bfError.mSrcEnd)) { if ((bestError.mError == null) || (bestError.mIsWarning) || (bestError.mIsPersistent)) bestError = bfError; } } String showMouseoverString = null; if (bestError.mError != null) { showMouseoverString = scope:: String(":", bestError.mError); if (bestError.mMoreInfo != null) { for (var moreInfo in bestError.mMoreInfo) { showMouseoverString.AppendF("\n@{0}\t{1}\t{2}", moreInfo.mFileName, moreInfo.mSrcStart, moreInfo.mError); } } } else { var flags = (SourceElementFlags)editWidgetContent.mData.mText[textIdx].mDisplayFlags; if ((flags.HasFlag(.Error)) || (flags.HasFlag(.Warning))) { mWantsFullRefresh = true; mRefireMouseOverAfterRefresh = true; //Debug.WriteLine("Full refresh..."); } } if (showMouseoverString != null) { triedShow = true; mHoverWatch.Show(this, x, y, showMouseoverString); if (debugExpr != null) mHoverWatch.mEvalString.Set(debugExpr); // Set to old debugStr for comparison } else triedShow = false; } } if (!hasHoverWatchOpen) mHoverWatch.mOpenMousePos = DarkTooltipManager.sLastRelMousePos; if ((debugExpr == "var") || (debugExpr == "let")) { let resolveParams = scope ResolveParams(); resolveParams.mOverrideCursorPos = (int32)textIdx; Classify(ResolveType.GetVarType, resolveParams); if (resolveParams.mTypeDef != null) { debugExpr.Set(resolveParams.mTypeDef); if (!debugExpr.IsEmpty) debugExpr.Insert(0, ":"); } if (!triedShow) { mHoverWatch.Show(this, x, y, debugExpr); triedShow = true; } } } // Not used? if ((mHoverWatch != null) && (mHoverWatch.mTextPanel != this)) { mHoverWatch.Close(); mHoverWatch = null; } if (mHoverWatch != null) { if ((!triedShow) && (!IDEApp.sApp.HasPopupMenus())) { if (mHoverWatch.mCloseDelay > 0) { //Debug.WriteLine("mHoverWatch.mCloseCountdown = 20"); mHoverWatch.mCloseDelay--; mHoverWatch.mCloseCountdown = 20; } else { mHoverWatch.Close(); mHoverWatch = null; #if IDE_C_SUPPORT delete mClangHoverErrorData; mClangHoverErrorData = null; #endif } } else { //Debug.WriteLine("mCloseCountdown = 0"); mHoverWatch.mCloseCountdown = 0; } } if (passInstance != null) delete passInstance; if (parser != null) { delete parser; mWantsParserCleanup = true; } } #if IDE_C_SUPPORT delete mClangHoverErrorData; mClangHoverErrorData = null; #endif /*if ((mIsClang) && (!compiler.IsPerformingBackgroundOperation())) { bool hadValidError = false; if (mClangHoverErrorData != null) { int textIdx = -1; int line; int lineChar; if (editWidgetContent.GetLineCharAtCoord(mousePos.x, mousePos.y, out line, out lineChar)) textIdx = editWidgetContent.GetTextIdx(line, lineChar); string[] stringParts = mClangHoverErrorData.Split('\t'); int startIdx = int.Parse(stringParts[0]); int endIdx = int.Parse(stringParts[1]); if ((textIdx >= startIdx) && (textIdx < endIdx)) { hadValidError = true; mHoverWatch.Show(this, x, y, ":" + stringParts[2]); mHoverWatch.mEvalString.Set(debugExpr); // Set to old debugStr for comparison } } }*/ } void DuplicateEditState(out EditWidgetContent.CharData[] char8Data, out IdSpan char8IdData) { var srcCharData = mEditWidget.Content.mData.mText; char8Data = new EditWidgetContent.CharData[mEditWidget.Content.mData.mTextLength]; var editIdData = ref mEditWidget.Content.mData.mTextIdData; editIdData.Prepare(); char8IdData = editIdData.Duplicate(); for (int32 i = 0; i < char8Data.Count; i++) { srcCharData[i].mDisplayPassId = (uint8)SourceDisplayId.Cleared; char8Data[i] = srcCharData[i]; } } void DoSpellCheck() { String sb = scope String(); var spellChecker = IDEApp.sApp.mSpellChecker; int32 wordStart = -1; bool skipWord = false; bool isSectionText = true; int32 spanSectionIdx = -1; bool skipNextChar = false; bool isVerbatimString = false; bool prevWasLetter = false; SourceElementType prevElementType = .Normal; for (int32 i = 0; i < mProcessSpellCheckCharData.Count; i++) { mProcessSpellCheckCharData[i].mDisplayFlags = 0; mProcessSpellCheckCharData[i].mDisplayPassId = (uint8)SourceDisplayId.SpellCheck; if (skipNextChar) { skipNextChar = false; continue; } if (spellChecker == null) continue; var char8Data = mProcessSpellCheckCharData[i]; char8 c = (char8)char8Data.mChar; var elementType = (SourceElementType)char8Data.mDisplayTypeId; bool endString = false; if (elementType == .Literal) { if (prevElementType != .Literal) isVerbatimString = (c == '@'); } else isVerbatimString = false; if ((elementType == SourceElementType.Comment) || (elementType == SourceElementType.Literal)) { if (i >= spanSectionIdx) { isSectionText = true; spanSectionIdx = i; while (spanSectionIdx < mProcessSpellCheckCharData.Count) { var checkCharData = mProcessSpellCheckCharData[spanSectionIdx]; var checkElementType = (SourceElementType)checkCharData.mDisplayTypeId; if (checkElementType != elementType) break; char8 checkC = (char8)checkCharData.mChar; if (checkC == '\n') break; if ((checkC == '*') || (checkC == ';')) isSectionText = false; if (checkC >= '\x80') // Don't process high characters isSectionText = false; spanSectionIdx++; } } if ((c == '\\') && (elementType == SourceElementType.Literal) && (!isVerbatimString)) { endString = true; skipNextChar = true; } bool isLetter = c.IsLetter; if ((isLetter) || ((c == '\'') && (prevWasLetter))) { if (i > 0) { char8 prevC = (char8)mProcessSpellCheckCharData[i - 1].mChar; if ((prevC == '.') || (prevC.IsNumber)) { // Looks like extension skipWord = true; } } if ((c.IsUpper) && (wordStart != -1)) skipWord = true; if (wordStart == -1) wordStart = i; } else if (c == '_') { skipWord = true; } else if (c == '.') { if (i < mProcessSpellCheckCharData.Count - 1) { char8 nextC = (char8)mProcessSpellCheckCharData[i + 1].mChar; if (nextC.IsLetter) { // Looks like a filename skipWord = true; } } endString = true; } else { endString = true; } if (c == '\n') spanSectionIdx = i; prevWasLetter = isLetter; } else { endString = true; prevWasLetter = false; } if ((i == mProcessSpellCheckCharData.Count - 1) && (!endString)) { // Process last word i++; endString = true; } if ((endString) && (wordStart != -1)) { int32 wordLen = i - wordStart; if (wordLen <= 1) skipWord = true; if (!isSectionText) skipWord = true; if (!skipWord) { sb.Clear(); if (mProcessSpellCheckCharData[i - 1].mChar == '\'') i--; for (int32 wordCharIdx = wordStart; wordCharIdx < i; wordCharIdx++) sb.Append(mProcessSpellCheckCharData[wordCharIdx].mChar); String word = sb; bool hasSpellingError = (word.Length > 1) && (!IDEApp.sApp.mSpellChecker.IsWord(word)); if (hasSpellingError) { word.ToLower(); using (spellChecker.mMonitor.Enter()) hasSpellingError = !spellChecker.mIgnoreWordList.Contains(word); } if (hasSpellingError) { for (int32 wordCharIdx = wordStart; wordCharIdx < i; wordCharIdx++) { mProcessSpellCheckCharData[wordCharIdx].mDisplayFlags = (uint8)SourceElementFlags.SpellingError; } } } skipWord = false; wordStart = -1; } prevElementType = elementType; } } void SpellCheckDone() { mSpellCheckJobCount--; } void StartSpellCheck() { if (gApp.mSpellChecker == null) { if (mDidSpellCheck) { var data = mEditWidget.Content.mData; for (int i < data.mTextLength) { data.mText[i].mDisplayFlags &= ~((uint8)SourceElementFlags.SpellingError); } } mDidSpellCheck = false; return; } DuplicateEditState(out mProcessSpellCheckCharData, out mProcessSpellCheckCharIdSpan); mDidSpellCheck = true; mSpellCheckJobCount++; IDEApp.sApp.mSpellChecker.DoBackground(new => DoSpellCheck, new => SpellCheckDone); } void AddHistory() { } /*bool ProcessResolveData() { scope AutoBeefPerf("SourceViewPanel.ProcessResolveData"); bool canDoBackground = true; if ((mProcessResolveCharData != null) && (mProcessingPassInstance != null)) { MarkDirty(); InjectErrors(mProcessingPassInstance, mProcessResolveCharData, mProcessResolveCharIdSpan, false); canDoBackground = false; } if (mProcessResolveCharData != null) { for (int i < mProcessResolveCharData.Count) { if (mProcessResolveCharData[i].mDisplayFlags == 1) { NOP!(); } } MarkDirty(); UpdateCharData(ref mProcessResolveCharData, ref mProcessResolveCharIdSpan, (uint8)SourceElementFlags.CompilerFlags_Mask); canDoBackground = false; } if (mProcessingPassInstance != null) { MarkDirty(); if (mProcessingPassInstance.HadSignatureChanges()) mWantsFullRefresh = true; delete mProcessingPassInstance; mProcessingPassInstance = null; } return canDoBackground; }*/ public void EnsureReady() { if (mWantsFastClassify) { DoFastClassify(); mWantsFastClassify = false; } } public bool HasDeferredResolveResults() { using (mMonitor.Enter()) { return !mDeferredResolveResults.IsEmpty; } } void ProcessDeferredResolveResults(int waitTime, bool autocompleteOnly = false) { //bool canDoBackground = true; int checkIdx = 0; while (true) { ResolveParams resolveResult = null; using (mMonitor.Enter()) { if (checkIdx >= mDeferredResolveResults.Count) break; resolveResult = mDeferredResolveResults[checkIdx]; if ((autocompleteOnly) && (resolveResult.mResolveType != .Autocomplete)) { checkIdx++; continue; } if (!resolveResult.mWaitEvent.WaitFor(waitTime)) { checkIdx++; continue; } mDeferredResolveResults.RemoveAt(checkIdx); } HandleResolveResult(resolveResult.mResolveType, resolveResult.mAutocompleteInfo, resolveResult); //Debug.WriteLine("ProcessDeferredResolveResults finished {0}", resolveResult.mResolveType); //bool checkIt = (mFilePath.Contains("Program.bf")) && (mEditWidget.mEditWidgetContent.mData.mCurTextVersionId > 3); /*var data = ref mEditWidget.Content.mData.mText[10018]; if (checkIt) { Debug.Assert(resolveResult.mCharData[10018].mDisplayTypeId == 8); Debug.Assert(data.mDisplayTypeId == 8); } uint8* ptr = &data.mDisplayTypeId;*/ scope AutoBeefPerf("SourceViewPanel.ProcessResolveData"); bool canDoBackground = true; bool wantsData = (!resolveResult.mCancelled) && (resolveResult.mResolveType != .GetCurrentLocation) && (resolveResult.mResolveType != .GetSymbolInfo); if (wantsData) { if ((resolveResult.mCharData != null) && (resolveResult.mPassInstance != null)) { bool isAutocomplete = (resolveResult.mResolveType == .Autocomplete) || (resolveResult.mResolveType == .Autocomplete_HighPri); MarkDirty(); InjectErrors(resolveResult.mPassInstance, resolveResult.mCharData, resolveResult.mCharIdSpan, isAutocomplete); canDoBackground = false; } if (resolveResult.mCharData != null) { MarkDirty(); UpdateCharData(ref resolveResult.mCharData, ref resolveResult.mCharIdSpan, (uint8)SourceElementFlags.CompilerFlags_Mask, false); canDoBackground = false; } if (resolveResult.mPassInstance != null) { MarkDirty(); if (resolveResult.mPassInstance.HadSignatureChanges()) mWantsFullRefresh = true; } } /*if (checkIt) Debug.Assert(data.mDisplayTypeId == 8);*/ delete resolveResult; } } public override void Update() { base.Update(); scope AutoBeefPerf("SourceViewPanel.Update"); EnsureReady(); if (mProjectSource == null) { if (mEditData != null) Debug.Assert(mEditData.mProjectSources.IsEmpty); } else { if (mEditData != null) Debug.Assert(mEditData.mProjectSources.Contains(mProjectSource)); } let projectSource = FilteredProjectSource; /*if (mWidgetWindow.IsKeyDown(.Control) && mWidgetWindow.IsKeyDown(.Alt)) QueueFullRefresh(false);*/ if (mEditData != null) Debug.Assert(Path.Equals(mFilePath, mEditData.mFilePath)); bool hasFocus = HasFocus(false); bool selfHasFocus = HasFocus(true); if (mSourceFindTask != null) { if (mSourceFindTask.WaitFor(0)) { String foundPath = null; bool isWrongHash = false; if (mSourceFindTask.mFoundPath != null) { foundPath = mSourceFindTask.mFoundPath; } else if (mSourceFindTask.mBackupFileName != null) { foundPath = mSourceFindTask.mBackupFileName; isWrongHash = true; } if (foundPath != null) { delete mAliasFilePath; mAliasFilePath = mFilePath; mFilePath = new String(foundPath); RehupAlias(); } DeleteAndNullify!(mSourceFindTask); RetryLoad(); } } /*if (mEditData != null) { Debug.Assert(!mEditData.mOwnsEditWidget); Debug.Assert(!mEditData.mFilePath.Contains("\\\\")); if (mEditData.mFilePath.IndexOf("BfModule.cpp", true) != -1) { Debug.Assert(mProjectSource != null); } }*/ if (GetEditX() != mEditWidget.mX) ResizeComponents(); if (!IDEApp.sApp.mDebugger.mIsRunning) CloseOldVersion(); if (mOldVerLoadExecutionInstance != null) { if (mOldVerLoadExecutionInstance.mExitCode != null) { if ((int)mOldVerLoadExecutionInstance.mExitCode == 0) { RetryLoad(); } else SetLoadCmd(mOldVerLoadCmd); delete mOldVerLoadExecutionInstance; mOldVerLoadExecutionInstance = null; } } if (mOldVerHTTPRequest != null) { let result = mOldVerHTTPRequest.GetResult(); if (result != .NotDone) { if (result == .Failed) gApp.OutputErrorLine("Failed to retrieve source from {}", mOldVerLoadCmd); RetryLoad(); DeleteAndNullify!(mOldVerHTTPRequest); } } UpdateMouseover(); var compiler = ResolveCompiler; var bfSystem = BfResolveSystem; var sourceEditWidgetContent = (SourceEditWidgetContent)mEditWidget.Content; if (bfSystem != null) bfSystem.PerfZoneStart("Update"); if ((compiler != null) && (!compiler.IsPerformingBackgroundOperation())) mBackgroundResolveType = ResolveType.None; bool canDoBackground = (compiler != null) && (!compiler.IsPerformingBackgroundOperation()) && (!compiler.HasQueuedCommands()); #if IDE_C_SUPPORT if (mIsClang) { var buildClang = IDEApp.sApp.mDepClang; if ((buildClang.IsPerformingBackgroundOperation()) && (mWantsFullClassify)) { // Don't let buildClang get in the way of autocompletion buildClang.mThreadYieldCount = 20; canDoBackground = false; } } #endif if (mResolveJobCount > 0) { if (compiler != null) Debug.Assert((compiler.IsPerformingBackgroundOperation()) || (compiler.mThreadWorker.mOnThreadDone != null) || (compiler.mThreadWorkerHi.mOnThreadDone != null)); } if (mBackgroundDelay > 0) { --mBackgroundDelay; canDoBackground = false; } if (mIsPerformingBackgroundClassify) { canDoBackground = false; } else { // Handle finishing old backgrounds //canDoBackground &= ProcessResolveData(); } if (canDoBackground) { // Check for starting new backgrounds // Wait longer for Clang since it'll delay autocompletions whereas Beef can be interrupted int32 classifyDelayTicks = (mIsBeefSource || (mUpdateCnt < 40)) ? 2 : 40; if (mUpdateCnt <= 1) classifyDelayTicks = 0; if (mWantsBackgroundAutocomplete) { Classify(ResolveType.Autocomplete); mWantsBackgroundAutocomplete = false; canDoBackground = false; } else if ((mWantsFullClassify) && (mTicksSinceTextChanged >= classifyDelayTicks) //TODO: Debug, remove /*&& (mWidgetWindow.IsKeyDown(KeyCode.Alt))*/) { if (IsControllingEditData()) { if (Classify(mWantsFullRefresh ? ResolveType.ClassifyFullRefresh : ResolveType.Classify)) { mWantsFullClassify = false; mWantsFullRefresh = false; } canDoBackground = false; } } else if (mWantsParserCleanup) { if (!compiler.IsPerformingBackgroundOperation()) { mWantsParserCleanup = false; compiler.DoBackground(new => DoParserCleanup); canDoBackground = false; } } } else if ((mWantsFullClassify) && (selfHasFocus)) { // Already changed - cancel and restart if (compiler != null) compiler.RequestCancelBackground(); //Debug.WriteLine(String.Format("Cancel From: {0}", mFilePath)); } if ((IDEApp.sApp.mSpellChecker == null) || (!IDEApp.sApp.mSpellChecker.IsPerformingBackgroundOperation())) { if (mProcessSpellCheckCharData != null) { UpdateCharData(ref mProcessSpellCheckCharData, ref mProcessSpellCheckCharIdSpan, (uint8)SourceElementFlags.SpellingError, true); delete mProcessSpellCheckCharData; mProcessSpellCheckCharData = null; mProcessSpellCheckCharIdSpan.Dispose(); } if ((mTicksSinceTextChanged >= 60) && (mWantsSpellCheck)) { if (IsControllingEditData()) StartSpellCheck(); mWantsSpellCheck = false; } } #if IDE_C_SUPPORT if ((mAutocompleteTextVersionId != mEditWidget.Content.mData.mCurTextVersionId) && (mIsClang)) { // We used an edit that didn't fire the autocompletion, and thus didn't do a FastClassify if (IsControllingEditData()) DoFastClassify(); mAutocompleteTextVersionId = mEditWidget.Content.mData.mCurTextVersionId; } #endif if ((mLastTextVersionId != mEditWidget.Content.mData.mCurTextVersionId) || (mClassifiedTextVersionId != mEditWidget.Content.mData.mCurTextVersionId)) { if ((mIsBeefSource) && (projectSource != null)) { // If this file is included in multiple projects then we need to // reparse these contents in the context of the other projects if ((IsControllingEditData()) && (IDEApp.sApp.mBfResolveHelper != null)) IDEApp.sApp.mBfResolveHelper.DeferReparse(mFilePath, this); } #if IDE_C_SUPPORT if (mIsClang) { mClangSourceChanged = true; IDEApp.sApp.mResolveClang.mProjectSourceVersion++; } #endif if (mClassifiedTextVersionId != mEditWidget.Content.mData.mCurTextVersionId) { mWantsSpellCheck = true; mWantsFullClassify = true; mClassifiedTextVersionId = mEditWidget.Content.mData.mCurTextVersionId; if ((mProjectSource != null) && (IDEApp.sApp.mWakaTime != null) && (IsControllingEditData())) { IDEApp.sApp.mWakaTime.QueueFile(mFilePath, mProjectSource.mProject.mProjectName, false); } } mLastTextVersionId = mEditWidget.Content.mData.mCurTextVersionId; mTicksSinceTextChanged = 0; if (mProjectSource != null) mProjectSource.HasChangedSinceLastCompile = true; EnsureTrackedElementsValid(); UpdateHasChangedSinceLastCompile(); if (mHoverWatch != null) { mHoverWatch.Close(); Debug.Assert(mHoverWatch == null); } } else { // If we do a non-autocomple change that modifies a type signature, that will generate a Classify which detects // the signature change afterward, and then we need to do a full classify after that if (mWantsFullRefresh) mWantsFullClassify = true; if (IDEApp.sApp.mIsUpdateBatchStart) mTicksSinceTextChanged++; } //TODO: This is just a test! /*if (Rand.Float() = 0) && (cursorIdx + ofs < sourceEditWidgetContent.mData.mTextLength) && ((sourceEditWidgetContent.mData.mText[cursorIdx + ofs].mDisplayFlags & (uint8)(SourceElementFlags.SymbolReference)) != 0)) hasFlag = true; } } if ((!hasFlag) /*|| ((!mEditWidget.mHasFocus) && (!Utils.FileNameEquals(symbolReferenceHelper.mSourceViewPanel.mFilePath, mFilePath)))*/) { if ((symbolReferenceHelper.HasStarted) && (!symbolReferenceHelper.IsStarting)) { symbolReferenceHelper.Close(); } else { //symbolReferenceHelper.mWantsClose = true; //Debug.WriteLine("Queued close..."); } } } } if ((gApp.mSymbolReferenceHelper != null) && (gApp.mSymbolReferenceHelper.mWantsClose) && (gApp.mSymbolReferenceHelper.HasStarted) && (!gApp.mSymbolReferenceHelper.IsStarting)) { gApp.mSymbolReferenceHelper.Close(); } // Set this to 'false' for debugging to remove the hilighting of all symbol references under the cursor if (BFApp.sApp.mIsUpdateBatchStart) sourceEditWidgetContent.mCursorStillTicks++; if ((gApp.mSettings.mEditorSettings.mHiliteCursorReferences) && (HasFocus(true)) && (mProjectSource != null) /*&& (IDEApp.sApp.mSymbolReferenceHelper == null)*/) { if ((mEditWidget.mHasFocus) && (mIsBeefSource) && (sourceEditWidgetContent.mCursorStillTicks == 10) && (!sourceEditWidgetContent.mCursorImplicitlyMoved) && (!sourceEditWidgetContent.mVirtualCursorPos.HasValue)) { var symbolReferenceHelper = IDEApp.sApp.mSymbolReferenceHelper; if (symbolReferenceHelper == null) { if (!compiler.mThreadWorkerHi.mThreadRunning) { ShowSymbolReferenceHelper(SymbolReferenceHelper.Kind.ShowFileReferences); } else { sourceEditWidgetContent.mCursorStillTicks--; // Try again later (kindof a hack) } } else if (symbolReferenceHelper.mWantsClose) { //Debug.WriteLine("Delayed..."); sourceEditWidgetContent.mCursorStillTicks--; // Try again later (kindof a hack) } else { // Still trying to show the old location sourceEditWidgetContent.mCursorStillTicks--; // Try again later (kindof a hack) } } if (sourceEditWidgetContent.mCursorStillTicks == 5) { mWantsCurrentLocation = true; } if ((mWantsCurrentLocation) && (!compiler.mThreadWorkerHi.mThreadRunning)) { bool canClassify = true; #if IDE_C_SUPPORT if (mIsClang) canClassify = canDoBackground; #endif if (canClassify) { Classify(ResolveType.GetCurrentLocation); canDoBackground = false; mWantsCurrentLocation = false; } } if ((mQueuedLocation != null) && (!compiler.IsPerformingBackgroundOperation())) { PrimaryNavigationBar.SetLocation(mQueuedLocation); DeleteAndNullify!(mQueuedLocation); } } if ((mQueuedAutoComplete != null) & (!compiler.mThreadWorkerHi.mThreadRunning)) { DoAutoComplete(mQueuedAutoComplete.mKeyChar, mQueuedAutoComplete.mOptions); DeleteAndNullify!(mQueuedAutoComplete); } ProcessDeferredResolveResults(0); if (mLockFlashPct != 0) { mLockFlashPct += 0.02f; if (mLockFlashPct >= 1.0f) mLockFlashPct = 0; MarkDirty(); } } void InjectErrors(BfPassInstance processingPassInstance, EditWidgetContent.CharData[] processResolveCharData, IdSpan processCharIdSpan, bool keepPersistentErrors) { if (keepPersistentErrors) { for (int errorIdx = mErrorList.Count - 1; errorIdx >= 0; errorIdx--) { var bfError = mErrorList[errorIdx]; if (bfError.mIsPersistent) { } else { delete bfError; mErrorList.RemoveAt(errorIdx); } } } else { DeleteAndClearItems!(mErrorList); } int32 errorCount = processingPassInstance.GetErrorCount(); mErrorList.Capacity = mErrorList.Count + errorCount; for (int32 errorIdx = 0; errorIdx < errorCount; errorIdx++) { BfPassInstance.BfError bfError = new BfPassInstance.BfError(); processingPassInstance.GetErrorData(errorIdx, bfError); for (int32 moreInfoIdx < bfError.mMoreInfoCount) { BfPassInstance.BfError moreInfo = new BfPassInstance.BfError(); processingPassInstance.GetMoreInfoErrorData(errorIdx, moreInfoIdx, moreInfo); if (bfError.mMoreInfo == null) bfError.mMoreInfo = new List(); bfError.mMoreInfo.Add(moreInfo); } mErrorList.Add(bfError); } if (processResolveCharData != null) { IdSpan dupSpan = IdSpan(); for (var bfError in mErrorList) { int srcStart = bfError.mSrcStart; int srcEnd = bfError.mSrcEnd; if (bfError.mIsWhileSpecializing) continue; if (bfError.mIsPersistent) { if (bfError.mIdSpan.IsEmpty) { // New error - set current span if (dupSpan.IsEmpty) { dupSpan = processCharIdSpan.Duplicate(); bfError.mOwnsSpan = true; } bfError.mIdSpan = dupSpan; } else { // Old error - remap position int32 startId = bfError.mIdSpan.GetIdAtIndex(srcStart); int32 endId = bfError.mIdSpan.GetIdAtIndex(srcEnd); srcStart = processCharIdSpan.GetIndexFromId(startId); srcEnd = processCharIdSpan.GetIndexFromId(endId); } } if (srcStart == -1) continue; srcEnd = Math.Min(srcEnd, processResolveCharData.Count); uint8 flagsOr = bfError.mIsWarning ? (uint8)SourceElementFlags.Warning : (uint8)SourceElementFlags.Error; if (bfError.mIsAfter) flagsOr |= (uint8)SourceElementFlags.IsAfter; for (int i = srcStart; i < srcEnd; i++) processResolveCharData[i].mDisplayFlags |= flagsOr; } } if ((mRefireMouseOverAfterRefresh) && (!mWantsFullRefresh)) { mRefireMouseOverAfterRefresh = false; DarkTooltipManager.RefireMouseOver(); } } void UpdateHasChangedSinceLastCompile() { mHasChangedSinceLastCompile = false; if (mProjectSource == null) return; int compileIdx = IDEApp.sApp.mWorkspace.GetHighestSuccessfulCompileIdx(); var projectSourceCompileInstance = IDEApp.sApp.mWorkspace.GetProjectSourceCompileInstance(mProjectSource, compileIdx); if (projectSourceCompileInstance == null) return; //var sourceCharIdData = mEditWidget.Content.mTextIdData; mEditWidget.Content.mData.mTextIdData.Prepare(); mHasChangedSinceLastCompile = !mEditWidget.Content.mData.mTextIdData.Equals(projectSourceCompileInstance.mSourceCharIdData); } float GetEditX() { if (!gApp.mSettings.mEditorSettings.mShowLineNumbers) return GS!(24); var font = IDEApp.sApp.mTinyCodeFont; float lineWidth = Math.Max(font.GetWidth(ToStackString!(mEditWidget.Content.GetLineCount())) + GS!(8), GS!(32)); return Math.Max(GS!(24), lineWidth); } protected override void ResizeComponents() { float topY = 0; if (mSplitBottomPanel == null) { mNavigationBar.Resize(GS!(2), 0, Math.Max(mWidth - GS!(2), 0), GS!(22)); topY = GS!(24); } else { mNavigationBar.mVisible = false; } float splitterHeight = GS!(3); float splitTopHeight = 0; float splitBotHeight = Math.Max(mHeight - topY, 0); if (mSplitTopPanel != null) { float splitTotal = Math.Max(splitBotHeight - splitterHeight, 0); splitTopHeight = (int32)(splitTotal * mSplitter.mSplitPct); float minTopSize = 0; float minBotSize = GS!(64); splitTopHeight = Math.Clamp(splitTopHeight, minTopSize, Math.Max(splitTotal - minBotSize, 0)); splitBotHeight = Math.Max(splitTotal - splitTopHeight, 0); } if (mSplitTopPanel != null) { mSplitTopPanel.Resize(0, topY, mWidth, splitTopHeight); mSplitter.Resize(0, topY + splitTopHeight - 1, mWidth, splitterHeight + 2); topY += splitTopHeight + splitterHeight; } else if (mSplitTopPanel == null) { mSplitter.Resize(mWidth - GS!(20), GS!(22 - 1), GS!(20), splitterHeight + GS!(2)); } mSplitter.SetVisible(mPanelHeader == null); float editX = GetEditX(); if (mPanelHeader != null) { mPanelHeader.Resize(editX, topY, mWidth - editX, GS!(mPanelHeader.mBaseHeight)); mEditWidget.Resize(editX, topY + GS!(mPanelHeader.mBaseHeight), mWidth - editX, splitBotHeight - GS!(mPanelHeader.mBaseHeight)); } else mEditWidget.Resize(editX, topY, Math.Max(mWidth - editX, 0), splitBotHeight); mEditWidget.SetVisible(mEditWidget.mHeight > 0); mEditWidget.mClipGfx = mEditWidget.mHeight < GS!(20); float rightCornerY = mEditWidget.mY + GS!(2); if (mRenameSymbolDialog != null) { float renameWidth = GS!(240); float renameHeight = GS!(56); mRenameSymbolDialog.Resize(mWidth - renameWidth - GS!(10), mEditWidget.mY + GS!(2), renameWidth, renameHeight); rightCornerY += renameHeight + GS!(20); } if (mQuickFind != null) { /*float findWidth = GS!(200); float findHeight = mQuickFind.mIsReplace ? GS!(84) : GS!(58); mQuickFind.Resize(mWidth - findWidth - GS!(10), mEditWidget.mY + GS!(2), findWidth, findHeight);*/ mQuickFind.ResizeSelf(); //rightCornerY += findHeight + GS!(20); } if (mOldVersionPanel != null) { mOldVersionPanel.Resize(0, 0, mWidth, mHeight); if (mSplitTopPanel != null) mSplitTopPanel.mVisible = false; mEditWidget.mVisible = false; } else if (mSplitTopPanel != null) { mSplitTopPanel.mVisible = true; mEditWidget.mVisible = true; //Why did we have this? There were some cases where some text was "peeking through" something, but setting this to false // breaks the normal split-window functionality //mEditWidget.mVisible = false; } } public override void MouseDown(float x, float y, int32 btn, int32 btnCount) { base.MouseDown(x, y, btn, btnCount); if (mSplitBottomPanel != null) { return; } float lockX = mEditWidget.mX - GS!(20); float lockY = mHeight - GS!(20); if (Rect(lockX, lockY, GS!(20), GS!(20)).Contains(x, y)) { Menu menu = new Menu(); var menuItem = menu.AddItem("Lock Editing"); if (gApp.mSettings.mEditorSettings.mLockEditing) menuItem.mIconImage = DarkTheme.sDarkTheme.GetImage(.Check); menuItem.mOnMenuItemSelected.Add(new (evt) => { gApp.mSettings.mEditorSettings.mLockEditing = !gApp.mSettings.mEditorSettings.mLockEditing; }); if (mProjectSource != null) { menuItem = menu.AddItem("Lock Project"); if (mProjectSource.mProject.mLocked) menuItem.mIconImage = DarkTheme.sDarkTheme.GetImage(.Check); menuItem.mOnMenuItemSelected.Add(new (item) => { if (mProjectSource.mProject.mLocked && mProjectSource.mProject.mLockedDefault) { let dialog = new DarkDialog("Unlock Project?", "This project is locked because it may be a shared library, and editing shared libraries may have unwanted effects on other programs that use it.\n\nAre you sure you want to unlock it?", DarkTheme.sDarkTheme.mIconWarning); dialog.mWindowFlags |= .Modal; dialog.AddYesNoButtons(new (dlg) => { mProjectSource.mProject.mLocked = false; gApp.mWorkspace.SetChanged(); }); dialog.PopupWindow(mWidgetWindow); return; } mProjectSource.mProject.mLocked = !mProjectSource.mProject.mLocked; gApp.mWorkspace.SetChanged(); }); } menuItem = menu.AddItem("Lock while Debugging"); void AddLockType(String name, Settings.EditorSettings.LockWhileDebuggingKind kind) { var subItem = menuItem.AddItem(name); if (gApp.mSettings.mEditorSettings.mLockEditingWhenDebugging == kind) subItem.mIconImage = DarkTheme.sDarkTheme.GetImage(.Check); subItem.mOnMenuItemSelected.Add(new (evt) => { gApp.mSettings.mEditorSettings.mLockEditingWhenDebugging = kind; }); } AddLockType("Never", .Never); AddLockType("Always", .Always); AddLockType("When not Hot Swappable", .WhenNotHotSwappable); MenuWidget menuWidget = DarkTheme.sDarkTheme.CreateMenuWidget(menu); menuWidget.Init(this, x, y, true); } } public override void MouseClicked(float x, float y, int32 btn) { base.MouseClicked(x, y, btn); if (btn == 1) { DarkEditWidgetContent darkEditWidgetContent = (DarkEditWidgetContent)mEditWidget.Content; float lineSpacing = darkEditWidgetContent.mFont.GetLineSpacing(); HashSet selectedBreakpoints = scope HashSet(); for (var breakpointView in GetTrackedElementList()) { var trackedElement = breakpointView.mTrackedElement; var breakpoint = trackedElement as Breakpoint; if (breakpoint != null) { int drawLineNum = RemapActiveLineToHotLine(breakpoint.mLineNum); //float breakX = mEditWidget.mX - 20; float breakY = GS!(3) + drawLineNum * lineSpacing + mEditWidget.mY + mEditWidget.Content.Y; if ((x <= GS!(40)) && (y >= breakY) && (y < breakY + lineSpacing + GS!(1))) { selectedBreakpoints.Add(breakpoint); } } } if (selectedBreakpoints.Count > 0) { gApp.mBreakpointPanel.Update(); gApp.mBreakpointPanel.SelectBreakpoints(selectedBreakpoints); #unwarn var menuWidget = gApp.mBreakpointPanel.ShowRightClickMenu(this, x, y, true); //menuWidget.mDeletedHandler.Add(new (widget) => { gApp.mBreakpointPanel.mListView.GetRoot().SelectItemExclusively(null); }); } //float checkY = y + mEditWidget.mScrollContentContainer.m; } } public override void DrawAll(Graphics g) { DarkEditWidgetContent darkEditWidgetContent = (DarkEditWidgetContent)mEditWidget.Content; darkEditWidgetContent.mViewWhiteSpaceColor = gApp.mViewWhiteSpace.Bool ? darkEditWidgetContent.mTextColors[(int)SourceElementType.VisibleWhiteSpace] : 0; /*using (g.PushColor(0x50FFFFFF)) g.FillRect(0, 0, mWidth, mHeight);*/ //IDEApp.sApp.mBfResolveSystem.PerfZoneStart("SourceViewPanel.DrawAll"); base.DrawAll(g); if (mHotFileIdx != -1) { /*using (g.PushColor(0x50D0D0D0)) g.FillRect(0, 0, mWidth, mHeight);*/ } //using (g.PushClip(0, 0, mWidth, mHeight)) //{ // using (g.PushTranslate(0, mEditWidget.Content.Y)) // { // foreach (var breakpointView in GetTrackedElementList()) // { // /*var breakpoint = breakpointView.mTrackedElement as Breakpoint; // using (g.PushColor((breakpoint.GetAddress() != 0) ? Color.White : 0x8080FFFF)) // { // var sourceEditWidgetContent = (SourceEditWidgetContent)mEditWidget.Content; // float breakX; // float breakY; // sourceEditWidgetContent.GetTextCoordAtLineChar(breakpoint.mLineNum, breakpoint.mColumn, out breakX, out breakY); // g.Draw(DarkTheme.sDarkTheme.GetImage(DarkTheme.ImageIdx.RedDot), mEditWidget.mX + breakX, mEditWidget.mY + breakY); // }*/ // var historyEntry = breakpointView.mTrackedElement as HistoryEntry; // if (historyEntry != null) // { // using (g.PushColor(0x8080FFFF)) // { // var sourceEditWidgetContent = (SourceEditWidgetContent)mEditWidget.Content; // float breakX; // float breakY; // sourceEditWidgetContent.GetTextCoordAtLineChar(historyEntry.mLineNum, historyEntry.mColumn, out breakX, out breakY); // g.Draw(DarkTheme.sDarkTheme.GetImage(DarkTheme.ImageIdx.IconBookmark), mEditWidget.mX + breakX, mEditWidget.mY + breakY); // } // } // } // } //} //IDEApp.sApp.mBfResolveSystem.PerfZoneEnd(); } public void WithTrackedElementsAtCursor(List trackedElementList, Action func) where T : TrackedTextElement { int lineIdx; int lineCharIdx; mEditWidget.Content.GetLineCharAtIdx(mEditWidget.Content.CursorTextPos, out lineIdx, out lineCharIdx); int32 idx = 0; while (idx < trackedElementList.Count) { var trackedElement = trackedElementList[idx]; if (trackedElement.mLineNum == lineIdx) { int prevSize = trackedElementList.Count; func(trackedElement); if (trackedElementList.Count >= prevSize) idx++; } else idx++; } } } }