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;
                }
            }
        }
Exemple #2
0
        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);
        }