/// <summary>
        /// Determines if element is a self-closing element (i.e. like &lt;br /&gt;
        /// </summary>
        /// <param name="textProvider">Text provider</param>
        /// <param name="prefixRange">Text range of the element prefix</param>
        /// <param name="nameRange">Text range of the element name</param>
        /// <returns>True if element is a self-closing element.</returns>
        public bool IsSelfClosing(ITextProvider textProvider, ITextRange prefixRange, ITextRange nameRange) {
            if (nameRange.Length == 0)
                return false;

            string name = textProvider.GetText(nameRange);
            if (name[0] == '!')
                return true; // bang tags are always self-closing

            if (prefixRange.Length == 0)
                return _defaultProvider.IsSelfClosing(textProvider, nameRange);

            string prefix = textProvider.GetText(prefixRange);

            IHtmlClosureProvider provider; ;
            _providers.TryGetValue(prefix, out provider);

            var textRangeProvider = provider as IHtmlClosureProviderTextRange;
            if (textRangeProvider != null)
                return textRangeProvider.IsSelfClosing(textProvider, nameRange);

            if (provider != null)
                return provider.IsSelfClosing(name);

            return false;
        }
Beispiel #2
0
        public override bool Parse(IItemFactory itemFactory, ITextProvider text, ITokenStream stream)
        {
            if (stream.Current.Type == TokenType.Identifier && IsModifier(text.GetText(stream.Current.Start, stream.Current.Length)))
                Modifier = Children.AddCurrentAndAdvance(stream);

            ParseItem mediaType;
            if (itemFactory.TryCreateParsedOrDefault(this, text, stream, out mediaType))
            {
                MediaType = mediaType;
                Children.Add(mediaType);
            }

            while (!IsTerminator(text, stream))
            {
                var expression = itemFactory.CreateSpecific<MediaQueryExpression>(this, text, stream);
                if (expression.Parse(itemFactory, text, stream))
                {
                    _Expressions.Add(expression);
                    Children.Add(expression);
                }
                else
                {
                    Children.AddCurrentAndAdvance(stream);
                }
            }

            if (stream.Current.Type == TokenType.Comma)
                Comma = Children.AddCurrentAndAdvance(stream, SassClassifierType.Punctuation);

            return Children.Count > 0;
        }
        public string GetName(ITextProvider text)
        {
            if (IsValid)
                return text.GetText(Name.Start, Name.Length);

            return null;
        }
Beispiel #4
0
        public static string ResolvePath(StringValue item, ITextProvider text, DirectoryInfo currentDirectory)
        {
            var relativePath = text.GetText(item.Start, item.Length).Trim('\'', '"');
            var segments = relativePath.Split('/');
            if (segments.Length == 0)
                return null;

            var path = currentDirectory.FullName;
            for (int i = 0; i < (segments.Length - 1); i++)
                path = Path.Combine(path, segments[i]);

            var directory = new DirectoryInfo(Path.GetFullPath(path));
            if (!directory.Exists)
                return null;

            var filename = segments[segments.Length - 1];
            if (string.IsNullOrEmpty(Path.GetExtension(filename)))
                filename += ".scss";

            var files = directory.GetFiles("*" + filename);

            var comparer = StringComparer.OrdinalIgnoreCase;
            return files.Where(x => comparer.Equals(x.Name, filename) || comparer.Equals(x.Name, "_" + filename))
                .Select(x => x.FullName)
                .FirstOrDefault();
        }
Beispiel #5
0
        public string GetName(ITextProvider text)
        {
            if (Fragments.Count == 0 || !Fragments.All(x => x is TokenItem))
                return "";

            var start = Fragments[0];
            var end = Fragments[Fragments.Count - 1];

            return text.GetText(start.Start, end.End - start.Start);
        }
Beispiel #6
0
        public static bool IsRule(ITextProvider text, ITokenStream stream, string name)
        {
            if (stream.Current.Type == TokenType.At)
            {
                var nameToken = stream.Peek(1);
                if (nameToken.Type == TokenType.Identifier)
                    return text.GetText(nameToken.Start, name.Length) == name;
            }

            return false;
        }
        public override void Add(ParseItem item, ITextProvider text)
        {
            if (item is SassImportDirective)
            {
                var directive = item as SassImportDirective;
                foreach (var file in directive.Files.Where(x => x.IsValid))
                    Containers.Add(new ImportContainer(directive, IntellisenseManager.Get(file.Document)));
            }
            else if (item is XmlDocumentationComment)
            {
                var comment = item as XmlDocumentationComment;
                foreach (var tag in comment.Children.OfType<FileReferenceTag>().Where(x => x.Document != null))
                    Containers.Add(new ImportContainer(tag, IntellisenseManager.Get(tag.Document)));
            }
            else if (item is MixinDefinition)
            {
                var definition = item as MixinDefinition;
                if (definition.Name != null && definition.Name.Name != null)
                {
                    Parse(new MixinContainer(definition, text), definition.Children, text);

                    var name = text.GetText(definition.Name.Name.Start, definition.Name.Name.Length);
                    _Mixins.Add(definition.End, new MixinCompletionValue(name));
                }
            }
            else if (item is UserFunctionDefinition)
            {
                var definition = item as UserFunctionDefinition;
                if (definition.Name != null)
                {
                    Parse(new FunctionContainer(definition, text), definition.Children, text);

                    var name = text.GetText(definition.Name.Start, definition.Name.Length);
                    _Functions.Add(definition.End, new UserFunctionCompletionValue(name));
                }
            }
            else
            {
                base.Add(item, text);
            }
        }
        static ParseItem CreateDocumentationTag(ComplexItem parent, IItemFactory itemFactory, ITextProvider text, ITokenStream stream)
        {
            if (!(parent is XmlDocumentationComment))
                return null;

            if (stream.Current.Type == TokenType.LessThan)
            {
                var next = stream.Peek(1);
                if (next.Type != TokenType.Identifier)
                    return null;

                switch (text.GetText(next.Start, next.Length))
                {
                    case "reference": return new FileReferenceTag();
                }
            }

            return null;
        }
        protected void AddVariable(VariableDefinition variable, ITextProvider text)
        {
            if (variable != null && variable.IsValid)
            {
                var variableName = variable.Name;
                var builder = new StringBuilder(variableName.Length);
                builder.Append(variableName.Prefix.SourceType == TokenType.Dollar ? '$' : '!');
                builder.Append(text.GetText(variableName.Name.Start, variableName.Name.Length));

                var name = builder.ToString();
                _Variables.Add(variable.Start, new VariableCompletionValue
                {
                    DisplayText = name,
                    CompletionText = name,
                    Start = variableName.Start,
                    End = variableName.End,
                    Length = variableName.Length
                });
            }
        }
        public override bool Parse(IItemFactory itemFactory, ITextProvider text, ITokenStream stream)
        {
            if (stream.Current.Type == TokenType.Identifier && IsValidNamedRange(text.GetText(stream.Current.Start, stream.Current.Length)))
            {
                AnimationBegin = Children.AddCurrentAndAdvance(stream, SassClassifierType.Keyword);
            }
            else if (stream.Current.Type == TokenType.Number && stream.Peek(1).Type == TokenType.PercentSign)
            {
                ParseItem begin = itemFactory.Create<PercentageUnit>(this, text, stream);
                if (begin.Parse(itemFactory, text, stream))
                {
                    AnimationBegin = begin;
                    Children.Add(begin);

                    if (stream.Current.Type == TokenType.Comma)
                        Comma = Children.AddCurrentAndAdvance(stream, SassClassifierType.Punctuation);

                    ParseItem end = itemFactory.Create<PercentageUnit>(this, text, stream);
                    if (end.Parse(itemFactory, text, stream))
                    {
                        AnimationEnd = end;
                        Children.Add(end);
                    }
                }
            }

            if (AnimationBegin != null)
            {
                var block = itemFactory.CreateSpecific<RuleBlock>(this, text, stream);
                if (block.Parse(itemFactory, text, stream))
                {
                    Body = block;
                    Children.Add(block);
                }
            }

            return Children.Count > 0;
        }
        /// <summary>
        /// Incrementally applies whitespace change to the buffer 
        /// having old and new tokens produced from the 'before formatting' 
        /// and 'after formatting' versions of the same text.
        /// </summary>
        /// <param name="textBuffer">Text buffer to apply changes to</param>
        /// <param name="newTextProvider">Text provider of the text fragment before formatting</param>
        /// <param name="newTextProvider">Text provider of the formatted text</param>
        /// <param name="oldTokens">Tokens from the 'before' text fragment</param>
        /// <param name="newTokens">Tokens from the 'after' text fragment</param>
        /// <param name="formatRange">Range that is being formatted in the text buffer</param>
        /// <param name="transactionName">Name of the undo transaction to open</param>
        /// <param name="selectionTracker">Selection tracker object that will save, track
        /// <param name="additionalAction">Action to perform after changes are applies by undo unit is not yet closed.</param>
        /// and restore selection after changes have been applied.</param>
        public static void ApplyChangeByTokens(
            ITextBuffer textBuffer,
            ITextProvider oldTextProvider,
            ITextProvider newTextProvider,
            IReadOnlyList<ITextRange> oldTokens,
            IReadOnlyList<ITextRange> newTokens,
            ITextRange formatRange,
            string transactionName,
            ISelectionTracker selectionTracker,
            IEditorShell editorShell,
            Action additionalAction = null) {

            Debug.Assert(oldTokens.Count == newTokens.Count);
            if (oldTokens.Count == newTokens.Count) {
                ITextSnapshot snapshot = textBuffer.CurrentSnapshot;
                using (CreateSelectionUndo(selectionTracker, editorShell, transactionName)) {
                    using (ITextEdit edit = textBuffer.CreateEdit()) {
                        if (oldTokens.Count > 0) {
                            // Replace whitespace between tokens in reverse so relative positions match
                            int oldEnd = oldTextProvider.Length;
                            int newEnd = newTextProvider.Length;
                            string oldText, newText;
                            for (int i = newTokens.Count - 1; i >= 0; i--) {
                                oldText = oldTextProvider.GetText(TextRange.FromBounds(oldTokens[i].End, oldEnd));
                                newText = newTextProvider.GetText(TextRange.FromBounds(newTokens[i].End, newEnd));
                                if (oldText != newText) {
                                    edit.Replace(formatRange.Start + oldTokens[i].End, oldEnd - oldTokens[i].End, newText);
                                }
                                oldEnd = oldTokens[i].Start;
                                newEnd = newTokens[i].Start;
                            }
                            newText = newTextProvider.GetText(TextRange.FromBounds(0, newEnd));
                            edit.Replace(formatRange.Start, oldEnd, newText);
                        } else {
                            string newText = newTextProvider.GetText(TextRange.FromBounds(0, newTextProvider.Length));
                            edit.Replace(formatRange.Start, formatRange.Length, newText);
                        }
                        edit.Apply();
                        additionalAction?.Invoke();
                    }
                }
            }
        }
        /// <summary>
        /// Determines if given element can be implicitly closed like &lt;li&gt; or &lt;td&gt; in HTML"
        /// </summary>
        /// <param name="text">Text provider</param>
        /// <param name="name">Element name</param>
        /// <returns>True if element can be implictly closed</returns>
        public bool IsImplicitlyClosed(ITextProvider text, ITextRange name, out string[] containerElementNames) {
            containerElementNames = null;

            if (name.Length < _minCharCountImplicitClosing || name.Length > _maxCharCountImplicitClosing)
                return false;

            bool found = FindElementName(text, name, _implicitlyClosingElementNameIndex, ignoreCase: true);
            if (found) {
                string elementName = text.GetText(name);
                containerElementNames = GetContainerElements(elementName);
            }
            return found;
        }
        private bool IsDestructiveChangeForSeparator(
            ISensitiveFragmentSeparatorsInfo separatorInfo,
            IEnumerable<IArtifact> itemsInRange,
            int start,
            int oldLength,
            int newLength,
            ITextProvider oldText,
            ITextProvider newText
        ) {
            if (separatorInfo == null || (separatorInfo.LeftSeparator.Length == 0 && separatorInfo.RightSeparator.Length == 0)) {
                return false;
            }

            // Find out if one of the existing fragments contains position 
            // and if change damages fragment start or end separators

            string leftSeparator = separatorInfo.LeftSeparator;
            string rightSeparator = separatorInfo.RightSeparator;

            var firstTwoItems = itemsInRange.Take(2).ToList();
            var item = firstTwoItems.FirstOrDefault();

            // If no items are affected, change is unsafe only if new region contains left side separators.
            if (item == null) {
                // Simple optimization for whitespace insertion
                if (oldLength == 0 && string.IsNullOrWhiteSpace(newText.GetText(new TextRange(start, newLength)))) {
                    return false;
                }

                // Take into account that user could have deleted space between existing 
                // { and % or added { to the existing % so extend search range accordingly.
                int fragmentStart = Math.Max(0, start - leftSeparator.Length + 1);
                int fragmentEnd = Math.Min(newText.Length, start + newLength + leftSeparator.Length - 1);
                return newText.IndexOf(leftSeparator, TextRange.FromBounds(fragmentStart, fragmentEnd), true) >= 0;
            }

            // Is change completely inside an existing item?
            if (firstTwoItems.Count == 1 && (item.Contains(start) && item.Contains(start + oldLength))) {
                // Check that change does not affect item left separator
                if (TextRange.Contains(item.Start, leftSeparator.Length, start)) {
                    return true;
                }

                // Check that change does not affect item right separator. Note that we should not be using 
                // TextRange.Intersect since in case oldLength is zero (like when user is typing right before %} or }})
                // TextRange.Intersect will determine that zero-length range intersects with the right separator
                // which is incorrect. Typing at position 10 does not change separator at position 10. Similarly,
                // deleting text right before %} or }} does not make change destructive.

                var htmlToken = item as IHtmlToken;
                if (htmlToken == null || htmlToken.IsWellFormed) {
                    int rightSeparatorStart = item.End - rightSeparator.Length;
                    if (start + oldLength > rightSeparatorStart) {
                        if (TextRange.Intersect(rightSeparatorStart, rightSeparator.Length, start, oldLength)) {
                            return true;
                        }
                    }
                }

                // Touching left separator is destructive too, like when changing {{ to {{@
                // Check that change does not affect item left separator (whitespace is fine)
                if (item.Start + leftSeparator.Length == start) {
                    if (oldLength == 0) {
                        string text = newText.GetText(new TextRange(start, newLength));
                        if (String.IsNullOrWhiteSpace(text)) {
                            return false;
                        }
                    }

                    return true;
                }

                int fragmentStart = item.Start + separatorInfo.LeftSeparator.Length;
                fragmentStart = Math.Max(fragmentStart, start - separatorInfo.RightSeparator.Length + 1);
                int changeLength = newLength - oldLength;
                int fragmentEnd = item.End + changeLength;
                fragmentEnd = Math.Min(fragmentEnd, start + newLength + separatorInfo.RightSeparator.Length - 1);

                if (newText.IndexOf(separatorInfo.RightSeparator, TextRange.FromBounds(fragmentStart, fragmentEnd), true) >= 0) {
                    return true;
                }

                return false;
            }

            return true;
        }
        static bool AllowsExpresion(ITextProvider text, TokenItem statement)
        {
            var name = text.GetText(statement.Start, statement.Length);

            return name == "if" || name == "else if";
        }
Beispiel #15
0
 public string GetSubstringAt(int position, int length) => Text.GetText(new TextRange(position, length));
Beispiel #16
0
        internal void UpdateValue(ITextProvider textProvider) {
            if (this.HasValue()) {
                QuotedValue = textProvider.GetText(ValueToken);

                int start = ValueToken.Start + (ValueToken.OpenQuote != '\0' ? 1 : 0);
                int end = ValueToken.CloseQuote != '\0' ? ValueToken.End - 1 : ValueToken.End;
                int length = end - start;

                Value = length > 0 ? textProvider.GetText(new TextRange(start, length)) : String.Empty;
            } else {
                QuotedValue = String.Empty;
                Value = String.Empty;
            }
        }
        public override bool Parse(IItemFactory itemFactory, ITextProvider text, ITokenStream stream)
        {
            if (AtRule.IsRule(text, stream, "function"))
            {
                Rule = AtRule.CreateParsed(itemFactory, text, stream);
                if (Rule != null)
                    Children.Add(Rule);

                if (stream.Current.Type == TokenType.Function)
                {
                    Name = Children.AddCurrentAndAdvance(stream, SassClassifierType.UserFunctionDefinition);
                    FunctionName = text.GetText(Name.Start, Name.Length);
                }

                if (stream.Current.Type == TokenType.OpenFunctionBrace)
                    Children.AddCurrentAndAdvance(stream, SassClassifierType.FunctionBrace);

                while (!IsArgumentTerminator(stream.Current.Type))
                {
                    var argument = itemFactory.CreateSpecific<FunctionArgumentDefinition>(this, text, stream);
                    if (argument == null || !argument.Parse(itemFactory, text, stream))
                        break;

                    Arguments.Add(argument);
                    Children.Add(argument);
                }

                if (stream.Current.Type == TokenType.CloseFunctionBrace)
                    Children.AddCurrentAndAdvance(stream, SassClassifierType.FunctionBrace);

                var body = new UserFunctionBody();
                if (body.Parse(itemFactory, text, stream))
                {
                    Body = body;
                    Children.Add(body);
                }
            }

            return Children.Count > 0;
        }
Beispiel #18
0
        private static bool IsADestructiveChangeForSeparator(
            ISensitiveFragmentSeparatorsInfo separatorInfo,
            IEnumerable <IArtifact> itemsInRange,
            int start,
            int oldLength,
            int newLength,
            ITextProvider oldText,
            ITextProvider newText
            )
        {
            if (separatorInfo == null || (separatorInfo.LeftSeparator.Length == 0 && separatorInfo.RightSeparator.Length == 0))
            {
                return(false);
            }

            // Find out if one of the existing fragments contains position
            // and if change damages fragment start or end separators

            string leftSeparator  = separatorInfo.LeftSeparator;
            string rightSeparator = separatorInfo.RightSeparator;

            var firstTwoItems = itemsInRange.Take(2).ToList();
            var item          = firstTwoItems.FirstOrDefault();

            // If no items are affected, change is unsafe only if new region contains left side separators.
            if (item == null)
            {
                // Simple optimization for whitespace insertion
                if (oldLength == 0 && string.IsNullOrWhiteSpace(newText.GetText(start, newLength)))
                {
                    return(false);
                }

                // Take into account that user could have deleted space between existing
                // { and % or added { to the existing % so extend search range accordingly.
                int fragmentStart = Math.Max(0, start - leftSeparator.Length + 1);
                int fragmentEnd   = Math.Min(newText.Length, start + newLength + leftSeparator.Length - 1);
                return(newText.IndexOf(leftSeparator, fragmentStart, fragmentEnd - fragmentStart, true) >= 0);
            }

            // Is change completely inside an existing item?
            if (firstTwoItems.Count == 1 && (item.Contains(start) && item.Contains(start + oldLength)))
            {
                // Check that change does not affect item left separator
                if (TextRange.Contains(item.Start, leftSeparator.Length, start))
                {
                    return(true);
                }

                // Check that change does not affect item right separator. Note that we should not be using
                // TextRange.Intersect since in case oldLength is zero (like when user is typing right before %} or }})
                // TextRange.Intersect will determine that zero-length range intersects with the right separator
                // which is incorrect. Typing at position 10 does not change separator at position 10. Similarly,
                // deleting text right before %} or }} does not make change destructive.

                var htmlToken = item as IHtmlToken;
                if (htmlToken == null || htmlToken.IsWellFormed)
                {
                    int rightSeparatorStart = item.End - rightSeparator.Length;
                    if (start + oldLength > rightSeparatorStart)
                    {
                        if (TextRange.Intersect(rightSeparatorStart, rightSeparator.Length, start, oldLength))
                        {
                            return(true);
                        }
                    }
                }

                // Touching left separator is destructive too, like when changing {{ to {{@
                // Check that change does not affect item left separator (whitespace is fine)
                if (item.Start + leftSeparator.Length == start)
                {
                    if (oldLength == 0)
                    {
                        string text = newText.GetText(start, newLength);
                        if (String.IsNullOrWhiteSpace(text))
                        {
                            return(false);
                        }
                    }

                    return(true);
                }

                int fragmentStart = item.Start + separatorInfo.LeftSeparator.Length;
                fragmentStart = Math.Max(fragmentStart, start - separatorInfo.RightSeparator.Length + 1);
                int changeLength = newLength - oldLength;
                int fragmentEnd  = item.End + changeLength;
                fragmentEnd = Math.Min(fragmentEnd, start + newLength + separatorInfo.RightSeparator.Length - 1);

                if (newText.IndexOf(separatorInfo.RightSeparator, fragmentStart, fragmentEnd - fragmentStart, true) >= 0)
                {
                    return(true);
                }

                return(false);
            }

            return(true);
        }
 private static bool CheckRange(int surroundingLength, Func<string, bool> predicate, int rangeStart, int rangeLength, ITextProvider rangeText)
 {
     if (rangeLength == 0)
         return false;
     var surroundingText = rangeText.GetText(new Microsoft.Web.Core.Text.TextRange(
         rangeStart,
         Math.Min(rangeText.Length - rangeStart, rangeLength + surroundingLength)
     ));
     return predicate(surroundingText);
 }
        private string GetUserIndentString(ITextProvider textProvider, int position, RFormatOptions options) {
            for (int i = position - 1; i >= 0; i--) {
                char ch = textProvider[i];
                if (!char.IsWhiteSpace(ch)) {
                    break;
                }

                if (ch.IsLineBreak()) {
                    string userIndentString = textProvider.GetText(TextRange.FromBounds(i + 1, position));
                    int indentSize = IndentBuilder.TextIndentInSpaces(userIndentString, options.TabSize);
                    return IndentBuilder.GetIndentString(indentSize, options.IndentType, options.IndentSize);
                }
            }

            return string.Empty;
        }
Beispiel #21
0
        public static bool IsWellKnownFunction(ITextProvider text, ITokenStream stream)
        {
            if (stream.Current.Type == TokenType.Function || stream.Current.Type == TokenType.Identifier)
            {
                var functionName = text.GetText(stream.Current.Start, stream.Current.Length);
                return WellKnownFunctions.Contains(functionName);
            }

            return false;
        }
 static bool IsPseudoSelector(ITextProvider text, Token token)
 {
     switch (token.Type)
     {
         case TokenType.Identifier:
         case TokenType.Function:
             return WellKnownPseudoSelectors.Contains(text.GetText(token.Start, token.Length));
         default:
             return true;
     }
 }
Beispiel #23
0
        public override bool Parse(IItemFactory itemFactory, ITextProvider text, ITokenStream stream)
        {
            if (IsFunctionCall(stream) && IsFunctionNameValid(text, stream))
            {
                Name = Children.AddCurrentAndAdvance(stream, FunctionClassifierType);
                if (Name != null)
                    FunctionName = text.GetText(Name.Start, Name.Length);

                if (stream.Current.Type == TokenType.OpenFunctionBrace)
                    OpenBrace = Children.AddCurrentAndAdvance(stream, SassClassifierType.FunctionBrace);

                ParseArguments(itemFactory, text, stream);

                if (OpenBrace != null && stream.Current.Type == TokenType.CloseFunctionBrace)
                    CloseBrace = Children.AddCurrentAndAdvance(stream, SassClassifierType.FunctionBrace);
            }

            return Children.Count > 0;
        }
Beispiel #24
0
 public string GetSubstringAt(int position, int length)
 {
     return(_text.GetText(new TextRange(position, length)));
 }
 protected override void OnAttributeParsed(XmlAttribute attribute, ITextProvider text)
 {
     if (attribute.Name != null && IsPathAttribute(text.GetText(attribute.Name.Start, attribute.Name.Length)))
         Filename = attribute;
 }
        private bool IsDestructiveChangeForSeparator(ISensitiveFragmentSeparatorsInfo separatorInfo, IReadOnlyList <T> itemsInRange, int start, int oldLength, int newLength, ITextProvider oldText, ITextProvider newText)
        {
            if (separatorInfo == null)
            {
                return(false);
            }

            if (separatorInfo.LeftSeparator.Length == 0 && separatorInfo.RightSeparator.Length == 0)
            {
                return(false);
            }

            // Find out if one of the existing fragments contains position
            // and if change damages fragment start or end separators

            string leftSeparator  = separatorInfo.LeftSeparator;
            string rightSeparator = separatorInfo.RightSeparator;

            // If no items are affected, change is unsafe only if new region contains left side separators.
            if (itemsInRange.Count == 0)
            {
                // Simple optimization for whitespace insertion
                if (oldLength == 0 && String.IsNullOrWhiteSpace(newText.GetText(new TextRange(start, newLength))))
                {
                    return(false);
                }

                int fragmentStart = Math.Max(0, start - leftSeparator.Length + 1);
                int fragmentEnd;

                // Take into account that user could have deleted space between existing
                // <! and -- or added - to the existing <!- so extend search range accordingly.

                fragmentEnd = Math.Min(newText.Length, start + newLength + leftSeparator.Length - 1);

                int fragmentStartPosition = newText.IndexOf(leftSeparator, TextRange.FromBounds(fragmentStart, fragmentEnd), true);
                if (fragmentStartPosition >= 0)
                {
                    // We could've found the left separator only in the newly inserted text since we extended the range we examined
                    // by one less than the separator length. Return true, no further checks necessary.
                    return(true);
                }

                return(false);
            }

            // Is change completely inside an existing item?
            if (itemsInRange.Count == 1 && (itemsInRange[0].Contains(start) && itemsInRange[0].Contains(start + oldLength)))
            {
                // Check that change does not affect item left separator
                if (TextRange.Contains(itemsInRange[0].Start, leftSeparator.Length, start))
                {
                    return(true);
                }

                // Check that change does not affect item right separator. Note that we should not be using
                // TextRange.Intersect since in case oldLength is zero (like when user is typing right before %> or ?>)
                // TextRange.Intersect will determine that zero-length range intersects with the right separator
                // which is incorrect. Typing at position 10 does not change separator at position 10. Similarly,
                // deleting text right before %> or ?> does not make change destructive.

                IHtmlToken htmlToken = itemsInRange[0] as IHtmlToken;
                if (htmlToken == null || htmlToken.IsWellFormed)
                {
                    int rightSeparatorStart = itemsInRange[0].End - rightSeparator.Length;
                    if (start + oldLength > rightSeparatorStart)
                    {
                        if (TextRange.Intersect(rightSeparatorStart, rightSeparator.Length, start, oldLength))
                        {
                            return(true);
                        }
                    }
                }

                // Touching left separator is destructive too, like when changing <% to <%@
                // Check that change does not affect item left separator (whitespace is fine)
                if (itemsInRange[0].Start + leftSeparator.Length == start)
                {
                    if (oldLength == 0)
                    {
                        string text = newText.GetText(new TextRange(start, newLength));
                        if (String.IsNullOrWhiteSpace(text))
                        {
                            return(false);
                        }
                    }

                    return(true);
                }

                int fragmentStart = itemsInRange[0].Start + separatorInfo.LeftSeparator.Length;
                fragmentStart = Math.Max(fragmentStart, start - separatorInfo.RightSeparator.Length + 1);
                int changeLength = newLength - oldLength;
                int fragmentEnd  = itemsInRange[0].End + changeLength;
                fragmentEnd = Math.Min(fragmentEnd, start + newLength + separatorInfo.RightSeparator.Length - 1);

                if (newText.IndexOf(separatorInfo.RightSeparator, TextRange.FromBounds(fragmentStart, fragmentEnd), true) >= 0)
                {
                    return(true);
                }

                return(false);
            }

            return(true);
        }