/// <summary> /// Determines if a change will cause a structural change to the document and if not, applies it to the existing tree. /// If a structural change would occur, automatically starts a reparse /// </summary> /// <remarks> /// NOTE: The initial incremental parsing check and actual incremental parsing (if possible) occurs /// on the callers thread. However, if a full reparse is needed, this occurs on a background thread. /// </remarks> /// <param name="change">The change to apply to the parse tree</param> /// <returns>A PartialParseResult value indicating the result of the incremental parse</returns> public virtual PartialParseResult CheckForStructureChanges(TextChange change) { // Validate the change long?elapsedMs = null; #if EDITOR_TRACING Stopwatch sw = new Stopwatch(); sw.Start(); #endif RazorEditorTrace.TraceLine(RazorResources.Trace_EditorReceivedChange, Path.GetFileName(FileName), change); if (change.NewBuffer == null) { throw new ArgumentException(String.Format(CultureInfo.CurrentUICulture, RazorResources.Structure_Member_CannotBeNull, "Buffer", "TextChange"), "change"); } PartialParseResult result = PartialParseResult.Rejected; // If there isn't already a parse underway, try partial-parsing string changeString = String.Empty; using (_parser.SynchronizeMainThreadState()) { // Capture the string value of the change while we're synchronized changeString = change.ToString(); // Check if we can partial-parse if (CurrentParseTree != null && _parser.IsIdle) { result = TryPartialParse(change); } } // If partial parsing failed or there were outstanding parser tasks, start a full reparse if (result.HasFlag(PartialParseResult.Rejected)) { _parser.QueueChange(change); } // Otherwise, remember if this was provisionally accepted for next partial parse LastResultProvisional = result.HasFlag(PartialParseResult.Provisional); VerifyFlagsAreValid(result); #if EDITOR_TRACING sw.Stop(); elapsedMs = sw.ElapsedMilliseconds; sw.Reset(); #endif RazorEditorTrace.TraceLine(RazorResources.Trace_EditorProcessedChange, Path.GetFileName(FileName), changeString, elapsedMs.HasValue ? elapsedMs.Value.ToString() : "?", result.ToString()); return(result); }
public void QueueChange(TextChange change) { #if EDITOR_TRACING RazorEditorTrace.TraceLine(RazorResources.Trace_QueuingParse, Path.GetFileName(_fileName), change); #endif EnsureOnThread(); lock (_stateLock) { // CurrentParcel token source is not null ==> There's a parse underway if (_currentParcelCancelSource != null) { _currentParcelCancelSource.Cancel(); } _changes.Add(change); _hasParcel.Set(); } }
// **** BACKGROUND THREAD **** private void WorkerLoop() { long? elapsedMs = null; string fileNameOnly = Path.GetFileName(_fileName); #if EDITOR_TRACING Stopwatch sw = new Stopwatch(); #endif try { RazorEditorTrace.TraceLine(RazorResources.Trace_BackgroundThreadStart, fileNameOnly); EnsureOnThread(); while (!_shutdownToken.IsCancellationRequested) { // Grab the parcel of work to do WorkParcel parcel = _main.GetParcel(); if (parcel.Changes.Any()) { RazorEditorTrace.TraceLine(RazorResources.Trace_ChangesArrived, fileNameOnly, parcel.Changes.Count); try { DocumentParseCompleteEventArgs args = null; using (var linkedCancel = CancellationTokenSource.CreateLinkedTokenSource(_shutdownToken, parcel.CancelToken)) { if (parcel != null && !linkedCancel.IsCancellationRequested) { // Collect ALL changes #if EDITOR_TRACING if (_previouslyDiscarded != null && _previouslyDiscarded.Any()) { RazorEditorTrace.TraceLine(RazorResources.Trace_CollectedDiscardedChanges, fileNameOnly, _previouslyDiscarded.Count); } #endif var allChanges = Enumerable.Concat( _previouslyDiscarded ?? Enumerable.Empty <TextChange>(), parcel.Changes).ToList(); var finalChange = allChanges.LastOrDefault(); if (finalChange != default(TextChange)) { #if EDITOR_TRACING sw.Start(); #endif GeneratorResults results = ParseChange(finalChange.NewBuffer, linkedCancel.Token); #if EDITOR_TRACING sw.Stop(); elapsedMs = sw.ElapsedMilliseconds; sw.Reset(); #endif RazorEditorTrace.TraceLine( RazorResources.Trace_ParseComplete, fileNameOnly, elapsedMs.HasValue ? elapsedMs.Value.ToString() : "?"); if (results != null && !linkedCancel.IsCancellationRequested) { // Clear discarded changes list _previouslyDiscarded = null; // Take the current tree and check for differences #if EDITOR_TRACING sw.Start(); #endif bool treeStructureChanged = _currentParseTree == null || TreesAreDifferent(_currentParseTree, results.Document, allChanges, parcel.CancelToken); #if EDITOR_TRACING sw.Stop(); elapsedMs = sw.ElapsedMilliseconds; sw.Reset(); #endif _currentParseTree = results.Document; RazorEditorTrace.TraceLine(RazorResources.Trace_TreesCompared, fileNameOnly, elapsedMs.HasValue ? elapsedMs.Value.ToString() : "?", treeStructureChanged); // Build Arguments args = new DocumentParseCompleteEventArgs() { GeneratorResults = results, SourceChange = finalChange, TreeStructureChanged = treeStructureChanged }; } else { // Parse completed but we were cancelled in the mean time. Add these to the discarded changes set RazorEditorTrace.TraceLine(RazorResources.Trace_ChangesDiscarded, fileNameOnly, allChanges.Count); _previouslyDiscarded = allChanges; } #if CHECK_TREE if (args != null) { // Rewind the buffer and sanity check the line mappings finalChange.NewBuffer.Position = 0; int lineCount = finalChange.NewBuffer.ReadToEnd().Split(new string[] { Environment.NewLine, "\r", "\n" }, StringSplitOptions.None).Count(); Debug.Assert( !args.GeneratorResults.DesignTimeLineMappings.Any(pair => pair.Value.StartLine > lineCount), "Found a design-time line mapping referring to a line outside the source file!"); Debug.Assert( !args.GeneratorResults.Document.Flatten().Any(span => span.Start.LineIndex > lineCount), "Found a span with a line number outside the source file"); Debug.Assert( !args.GeneratorResults.Document.Flatten().Any(span => span.Start.AbsoluteIndex > parcel.NewBuffer.Length), "Found a span with an absolute offset outside the source file"); } #endif } } } if (args != null) { _main.ReturnParcel(args); } } catch (OperationCanceledException) { } } else { RazorEditorTrace.TraceLine(RazorResources.Trace_NoChangesArrived, fileNameOnly, parcel.Changes.Count); Thread.Yield(); } } } catch (OperationCanceledException) { // Do nothing. Just shut down. } catch (Exception ex) { MonoDevelop.Core.LoggingService.LogInternalError("Internal error in Razor parser", ex); } finally { RazorEditorTrace.TraceLine(RazorResources.Trace_BackgroundThreadShutdown, fileNameOnly); // Clean up main thread resources _main.Dispose(); } }