/// <summary> /// All items that can incrementally parse their children should run their /// full parse in the same way. This function does that work. /// </summary> public static bool FullParseIncrementalItem( IIncrementalParseItem item, ItemFactory itemFactory, ITextProvider text, TokenStream tokens) { ComplexItem complexItem = (ComplexItem)item; ParseItem prevChild = null; while (true) { ParseItem newChild = item.CreateNextChild(prevChild, itemFactory, text, tokens); if (newChild != null) { complexItem.Children.Add(newChild); prevChild = newChild; } else { break; } } item.UpdateCachedChildren(); item.UpdateParseErrors(); return(complexItem.Children.Count > 0); }
public void OnTextChange(ITextProvider fullNewText, int changeStart, int deletedLength, int insertedLength) { Debug.Assert(IsOwnerThread, "CssTree.OnTextChange must be called on the main thread"); if (StyleSheet == null || !IsOwnerThread) { return; } #if DEBUG_INCREMENTAL_PARSE DateTime startTime = DateTime.UtcNow; #endif // Figure out which tokens changed ICssParser parser = _parserFactory.CreateParser(); IncrementalTokenizer.Result tokenResult = IncrementalTokenizer.TokenizeChange( parser.TokenizerFactory, Tokens, TextProvider, fullNewText, changeStart, deletedLength, insertedLength); // Adjust the input to match what was actually tokenized changeStart = tokenResult.TokenizationStart; deletedLength = tokenResult.TextDeletedLength; insertedLength = tokenResult.TextInsertedLength; // Figure out where to start incrementally parsing IIncrementalParseItem parentItem = GetIncrementalParseParent(changeStart, deletedLength); ComplexItem parentComplexItem = (ComplexItem)parentItem; int firstReparseChild = FindFirstChildToReparse(parentComplexItem, tokenResult.OldTokenStart); int firstCleanChild = FindFirstCleanChild(parentComplexItem, tokenResult.OldTokenStart + tokenResult.OldTokenCount); int firstCleanTokenAfterParent = FindFirstCleanTokenAfterParent(parentComplexItem); using (CreateWriteLock()) { // Update the tokens and text IncrementalTokenizer.ApplyResult(tokenResult); firstCleanTokenAfterParent += tokenResult.NewTokens.Count - tokenResult.OldTokenCount; // Init the token stream for parsing TokenStream tokenStream = new TokenStream(Tokens); int streamPositionStart = FindTokenToStartParsing(parentComplexItem, firstReparseChild); tokenStream.Position = streamPositionStart; Debug.Assert(tokenStream.Position <= tokenResult.OldTokenStart); // Init parsing ItemFactory itemFactory = new ItemFactory(parser.ExternalItemFactory, fullNewText, tokenStream); tokenStream.SkipComments = true; // must be set after extracting comments // Init the old and new child lists ParseItemList oldChildren = parentComplexItem.Children; ParseItemList newChildren = new ParseItemList(); ParseItemList deletedChildren = new ParseItemList(); ParseItemList errorsChangedItems = new ParseItemList(); int deleteChildCount = oldChildren.Count - firstReparseChild; // CreateNextChild needs to know the previous child for context ParseItem prevChild = (firstReparseChild > 0) ? oldChildren[firstReparseChild - 1] : null; while (true) { ParseItem newChild = parentItem.CreateNextChild(prevChild, itemFactory, fullNewText, tokenStream); if (newChild != null) { // Are we done parsing yet? if (newChild.Start >= changeStart + insertedLength) { // See if this new child exactly matches an old child int oldChildIndex = oldChildren.FindInsertIndex(newChild.Start, beforeExisting: true); ParseItem oldChild = (oldChildIndex < oldChildren.Count) ? oldChildren[oldChildIndex] : null; if (oldChild != null && oldChildIndex >= firstCleanChild && oldChild.Start == newChild.Start && oldChild.Length == newChild.Length && oldChild.GetType() == newChild.GetType()) { // Found a match, stop parsing deleteChildCount = oldChildIndex - firstReparseChild; break; } } newChildren.Add(newChild); prevChild = newChild; } else if (tokenStream.Position != firstCleanTokenAfterParent) { // When the parse doesn't stop exactly on the first clean token after the parent, // then the tree structure changed too much. Just fall back to a full parse: ParseNewStyleSheet(fullNewText, Tokens); //Debug.WriteLine("CSS: Full parse:{0}ms", (DateTime.UtcNow - startTime).TotalMilliseconds); return; } else { break; } } // Replace items in the parent (saving the deleted items for later) oldChildren.RemoveRange(firstReparseChild, deleteChildCount, deletedChildren); oldChildren.AddRange(newChildren); if (oldChildren.Count == 0) { // The parent was deleted, currently can't deal with that as an incremental change ParseNewStyleSheet(fullNewText, Tokens); return; } // Collect comments over the parsed region tokenStream.SkipComments = false; int tokenCount = tokenStream.Position - streamPositionStart; tokenStream.Position = streamPositionStart; IList <Comment> comments = parser.ExtractComments(fullNewText, Tokens, tokenStream.Position, tokenCount); // All done parsing and updating the tree, so now update caches and fire the "on changed" event StyleSheet.TextProvider = fullNewText; parentItem.UpdateCachedChildren(); if (parentItem.UpdateParseErrors()) { errorsChangedItems.Add(parentComplexItem); } InsertComments(comments, newChildren); #if DEBUG_INCREMENTAL_PARSE Debug.WriteLine("CSS: Inc parse:{0}ms. Deleted:{1}, Inserted:{2}", (DateTime.UtcNow - startTime).TotalMilliseconds, deletedChildren.Count, newChildren.Count); VerifyTokensAfterIncrementalChange(parser.TokenizerFactory, fullNewText, Tokens); VerifyTreeAfterIncrementalParse(fullNewText, Tokens, StyleSheet); #endif FireOnItemsChanged(deletedChildren, newChildren, errorsChangedItems); // Clean up the deleted items (must be after the event is fired) foreach (ParseItem deletedItem in deletedChildren) { deletedItem.Parent = null; } } }