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; } } }
public IEnumerable <ITagSpan <CssSmartTag> > GetTags(NormalizedSnapshotSpanCollection spans) { List <ITagSpan <CssSmartTag> > tags = new List <ITagSpan <CssSmartTag> >(); if (!EnsureInitialized()) { return(tags); } // Map view caret to the CSS buffer ProjectionSelectionHelper selectionHelpers = new ProjectionSelectionHelper(_textView, new[] { _textBuffer }); SnapshotPoint? bufferPoint = selectionHelpers.MapFromViewToBuffer(_textView.Caret.Position.BufferPosition); if (bufferPoint.HasValue) { for (ParseItem currentItem = GetContextItem(_tree.StyleSheet, bufferPoint.Value.Position); currentItem != null; currentItem = currentItem.Parent) { IEnumerable <ICssSmartTagProvider> providers = _smartTagProviders.GetHandlers(currentItem.GetType()); List <ISmartTagAction> actions = new List <ISmartTagAction>(); if (providers != null && _textBuffer.CurrentSnapshot.Length >= currentItem.AfterEnd) { ITrackingSpan trackingSpan = _textBuffer.CurrentSnapshot.CreateTrackingSpan(currentItem.Start, currentItem.Length, SpanTrackingMode.EdgeInclusive); foreach (ICssSmartTagProvider provider in providers) { IEnumerable <ISmartTagAction> newActions = provider.GetSmartTagActions(currentItem, bufferPoint.Value.Position, trackingSpan, _textView); if (newActions != null) { actions.AddRange(newActions); } } } if (actions.Count > 0) { SmartTagActionSet actionSet = new SmartTagActionSet(actions.AsReadOnly()); List <SmartTagActionSet> actionSets = new List <SmartTagActionSet>(); actionSets.Add(actionSet); SnapshotSpan itemSpan = new SnapshotSpan(_textBuffer.CurrentSnapshot, currentItem.Start, currentItem.Length); CssSmartTag smartTag = new CssSmartTag(actionSets.AsReadOnly()); tags.Add(new TagSpan <CssSmartTag>(itemSpan, smartTag)); } } } return(tags); }
internal static ParseItem ParsePropertyValue(ComplexItem parent, ItemFactory itemFactory, ITextProvider text, TokenStream tokens, bool callExternalFactory) { ParseItem pv = null; CssToken token = tokens.CurrentToken; // First give opportunity to override property value parsing if (callExternalFactory) { pv = itemFactory.Create <UnknownPropertyValue>(parent); } if (pv == null || pv.GetType() == typeof(UnknownPropertyValue)) { switch (token.TokenType) { case CssTokenType.HashName: pv = itemFactory.Create <HexColorValue>(parent); break; case CssTokenType.Number: pv = NumericalValue.ParseNumber(parent, itemFactory, text, tokens); return(pv); case CssTokenType.Url: case CssTokenType.UnquotedUrlString: pv = itemFactory.Create <UrlItem>(parent); break; case CssTokenType.Function: pv = Function.ParseFunction(parent, itemFactory, text, tokens); return(pv); case CssTokenType.UnicodeRange: pv = new TokenItem(tokens.CurrentToken, CssClassifierContextType.UnicodeRange); break; case CssTokenType.Comma: case CssTokenType.Slash: pv = new TokenItem(tokens.CurrentToken, CssClassifierContextType.Punctuation); break; case CssTokenType.String: case CssTokenType.MultilineString: case CssTokenType.InvalidString: pv = new TokenItem(tokens.CurrentToken, CssClassifierContextType.String); break; case CssTokenType.Identifier: pv = new TokenItem(tokens.CurrentToken, null); break; case CssTokenType.OpenSquareBracket: case CssTokenType.OpenFunctionBrace: case CssTokenType.OpenCurlyBrace: // "grid-rows" uses square brackets - http://dev.w3.org/csswg/css3-grid/ // And this is from a Win8 spec: -ms-grid-columns: (200px 10px)[3]; // Also, custom property values may have curly brace blocks pv = itemFactory.Create <PropertyValueBlock>(parent); break; default: if (callExternalFactory) { pv = itemFactory.Create <UnknownPropertyValue>(parent); if (pv.GetType() == typeof(UnknownPropertyValue)) { // UnknownPropertyValue is just a placeholder for plugins to use. // If one is actually created, discard it. pv = null; } } break; } } if (pv != null) { if (!pv.Parse(itemFactory, text, tokens)) { pv = null; } } return(pv); }