Describes complete context of the text change including text ranges, affected editor tree and changed AST node.
Example #1
0
        private static TextChangeType CheckChangeInsideString(TextChangeContext context, out IAstNode node, out PositionType positionType)
        {
            positionType = context.EditorTree.AstRoot.GetPositionNode(context.NewStart, out node);

            if (positionType == PositionType.Token)
            {
                TokenNode tokenNode = node as TokenNode;
                Debug.Assert(tokenNode != null);

                if (tokenNode.Token.TokenType == RTokenType.String)
                {
                    if (context.OldText.IndexOfAny(_stringSensitiveCharacters) >= 0)
                    {
                        return(TextChangeType.Structure);
                    }

                    if (context.NewText.IndexOfAny(_stringSensitiveCharacters) >= 0)
                    {
                        return(TextChangeType.Structure);
                    }

                    context.ChangedNode = node;
                    return(TextChangeType.Token);
                }
            }

            return(TextChangeType.Trivial);
        }
Example #2
0
        public static void DetermineChangeType(TextChangeContext change)
        {
            var changeType = CheckChangeInsideComment(change);

            change.PendingChanges.TextChangeType = MathExtensions.Max(changeType, change.PendingChanges.TextChangeType);

            switch (change.PendingChanges.TextChangeType)
            {
            case TextChangeType.Comment:
                return;

            case TextChangeType.Trivial:
                changeType = CheckChangeInsideString(change, out var node, out var positionType);
                change.PendingChanges.TextChangeType = MathExtensions.Max(changeType, change.PendingChanges.TextChangeType);
                if (change.PendingChanges.TextChangeType == TextChangeType.Token)
                {
                    return;
                }

                if (node != null && change.PendingChanges.TextChangeType == TextChangeType.Trivial)
                {
                    changeType = CheckWhiteSpaceChange(change, node, positionType);
                    change.PendingChanges.TextChangeType = MathExtensions.Max(changeType, change.PendingChanges.TextChangeType);
                    if (change.PendingChanges.TextChangeType == TextChangeType.Trivial)
                    {
                        return;
                    }
                }
                break;
            }

            change.PendingChanges.TextChangeType    = TextChangeType.Structure;
            change.PendingChanges.FullParseRequired = true;
        }
Example #3
0
        public static void DetermineChangeType(TextChangeContext change)
        {
            change.PendingChanges.TextChangeType |= CheckChangeInsideComment(change);
            if (change.PendingChanges.TextChangeType == TextChangeType.Comment)
            {
                return;
            }
            else if (change.PendingChanges.TextChangeType == TextChangeType.Trivial)
            {
                IAstNode     node;
                PositionType positionType;

                change.PendingChanges.TextChangeType |= CheckChangeInsideString(change, out node, out positionType);
                if (change.PendingChanges.TextChangeType == TextChangeType.Token)
                {
                    return;
                }
                else if (node != null && change.PendingChanges.TextChangeType == TextChangeType.Trivial)
                {
                    change.PendingChanges.TextChangeType |= CheckWhiteSpaceChange(change, node, positionType);
                    if (change.PendingChanges.TextChangeType == TextChangeType.Trivial)
                    {
                        return;
                    }
                }
            }

            change.PendingChanges.TextChangeType    = TextChangeType.Structure;
            change.PendingChanges.FullParseRequired = true;
        }
Example #4
0
        /// <summary>
        /// Text buffer change event handler. Performs analysis of the change.
        /// If change is trivial, such as change in whitespace (excluding line
        /// breaks that in R may be sensistive), simply applies the changes
        /// by shifting tree elements. If some elements get deleted or otherwise
        /// damaged, removes them from the tree right away. Non-trivial changes
        /// are queued for background parsing which starts on next on idle.
        /// Methond must be called on a main thread only, typically from an event
        /// handler that receives text buffer change events.
        /// </summary>
        internal void OnTextChanges(IReadOnlyCollection <TextChangeEventArgs> textChanges)
        {
            if (Thread.CurrentThread.ManagedThreadId != _ownerThreadId)
            {
                throw new ThreadStateException("Method should only be called on the main thread");
            }

            _editorTree.FireOnUpdatesPending(textChanges);
            if (UpdatesSuspended)
            {
                this.TextBufferChangedSinceSuspend = true;
                _pendingChanges.FullParseRequired  = true;
            }
            else
            {
                foreach (TextChangeEventArgs change in textChanges)
                {
                    _lastChangeTime = DateTime.UtcNow;
                    var context = new TextChangeContext(_editorTree, change, _pendingChanges);

                    // No need to analyze changes if full parse is already pending
                    if (!_pendingChanges.FullParseRequired)
                    {
                        TextChangeAnalyzer.DetermineChangeType(context);
                    }

                    ProcessChange(context);
                }
            }
        }
Example #5
0
        private static TextChangeType CheckWhiteSpaceChange(TextChangeContext context, IAstNode node, PositionType positionType)
        {
            context.ChangedNode = node;

            if (string.IsNullOrWhiteSpace(context.OldText) && string.IsNullOrWhiteSpace(context.NewText))
            {
                // In R there is no line continuation so expression may change when user adds or deletes line breaks.
                bool lineBreakSensitive = node is If;
                if (lineBreakSensitive)
                {
                    string oldLineText = context.OldTextProvider.GetText(new TextRange(context.OldStart, context.OldLength));
                    string newLineText = context.NewTextProvider.GetText(new TextRange(context.NewStart, context.NewLength));

                    if (oldLineText.IndexOfAny(CharExtensions.LineBreakChars) >= 0 || newLineText.IndexOfAny(CharExtensions.LineBreakChars) >= 0)
                    {
                        return(TextChangeType.Structure);
                    }
                }

                // Change inside token node is destructive: consider adding space inside an indentifier
                if (!IsChangeDestructiveForChildNodes(node, context.OldRange))
                {
                    return(TextChangeType.Trivial);
                }
            }

            return(TextChangeType.Structure);
        }
Example #6
0
        private static TextChangeType CheckChangeInsideComment(TextChangeContext context)
        {
            var comments = context.EditorTree.AstRoot.Comments;

            IReadOnlyList <int> affectedComments = comments.GetItemsContainingInclusiveEnd(context.NewStart);

            if (affectedComments.Count == 0)
            {
                return(TextChangeType.Trivial);
            }

            if (affectedComments.Count > 1)
            {
                return(TextChangeType.Structure);
            }

            // Make sure user is not deleting leading # effectively
            // destroying the comment
            RToken comment = comments[affectedComments[0]];

            if (comment.Start == context.NewStart && context.OldLength > 0)
            {
                return(TextChangeType.Structure);
            }

            // The collection will return a match if the comment starts
            // at the requested location. However, the change is not
            // inside the comment if it's at the comment start and
            // the old length of the change is zero.

            if (comment.Start == context.NewStart && context.OldLength == 0)
            {
                if (context.NewText.IndexOf('#') < 0)
                {
                    context.ChangedComment = comment;
                    return(TextChangeType.Comment);
                }
            }

            if (context.NewText.IndexOfAny(CharExtensions.LineBreakChars) >= 0)
            {
                return(TextChangeType.Structure);
            }

            // The change is not safe if old or new text contains line breaks
            // as in R comments runs to the end of the line and deleting
            // line break at the end of the comment may bring code into
            // the comment range and change the entire file structure.

            if (context.OldText.IndexOfAny(CharExtensions.LineBreakChars) >= 0)
            {
                return(TextChangeType.Structure);
            }

            context.ChangedComment = comment;
            return(TextChangeType.Comment);
        }
Example #7
0
        /// <summary>
        /// Handles non-trivial changes like changes that delete elements,
        /// change identifier names, introducing new braces: changes
        /// that cannot be handled without background parse.
        /// </summary>
        private void ProcessComplexChange(TextChangeContext context)
        {
            // Cancel background parse if it is running
            Cancel();

            TextChange textChange = new TextChange()
            {
                OldTextProvider = context.OldTextProvider,
                NewTextProvider = context.NewTextProvider
            };

            try {
                // Get write lock since there may be concurrent readers
                // of the tree. Note that there are no concurrent writers
                // since changes can only come from a background parser
                // and are always applied from the main thread.
                _editorTree.AcquireWriteLock();

                if (_pendingChanges.FullParseRequired)
                {
                    // When full parse is required, change is like replace the entire file
                    textChange.OldRange = TextRange.FromBounds(0, context.OldText.Length);
                    textChange.NewRange = TextRange.FromBounds(0, context.NewText.Length);

                    // Remove damaged elements if any and reflect text change.
                    // Although we are invalidating the AST next, old copy will
                    // be kept for operations that may need it such as smart indent.
                    bool elementsChanged;
                    _editorTree.InvalidateInRange(_editorTree.AstRoot, context.OldRange, out elementsChanged);
                    _editorTree.NotifyTextChange(context.NewStart, context.OldLength, context.NewLength);
                    // Invalidate will store existing AST as previous snapshot
                    // and create temporary empty AST until the next async parse.
                    _editorTree.Invalidate();
                }
                else
                {
                    textChange.OldRange = context.OldRange;
                    textChange.NewRange = context.NewRange;

                    DeleteAndShiftElements(context);
                    Debug.Assert(_editorTree.AstRoot.Children.Count > 0);
                }

                _pendingChanges.Combine(textChange);
                _pendingChanges.Version = TextBuffer != null ? TextBuffer.CurrentSnapshot.Version.VersionNumber : 1;

                UpdateTreeTextSnapshot();
            } finally {
                // Lock must be released before firing events otherwise we may hang
                _editorTree.ReleaseWriteLock();
            }

            _editorTree.FireOnUpdateCompleted(TreeUpdateType.NodesRemoved);
        }
Example #8
0
        private void ProcessChange(TextChangeContext context)
        {
            _editorTree.FireOnUpdateBegin();

            if (_pendingChanges.IsSimpleChange)
            {
                ProcessSimpleChange(context);
            }
            else
            {
                ProcessComplexChange(context);
            }
        }
Example #9
0
        /// <summary>
        /// Handles non-trivial changes like changes that delete elements,
        /// change identifier names, introducing new braces: changes
        /// that cannot be handled without background parse.
        /// </summary>
        private void ProcessComplexChange(TextChangeContext context)
        {
            // Cancel background parse if it is running
            Cancel();
            var c = context.PendingChanges;

            try {
                // Get write lock since there may be concurrent readers
                // of the tree. Note that there are no concurrent writers
                // since changes can only come from a background parser
                // and are always applied from the main thread.
                _editorTree.AcquireWriteLock();
                int start, oldLength, newLength;

                if (Changes.FullParseRequired)
                {
                    // When full parse is required, change is like replace the entire file
                    start     = 0;
                    oldLength = c.OldTextProvider.Length;
                    newLength = c.NewTextProvider.Length;

                    // Remove damaged elements if any and reflect text change.
                    // the tree remains usable outside of the damaged scope.
                    _editorTree.InvalidateInRange(c.OldRange);
                    _editorTree.NotifyTextChange(c.Start, c.OldLength, c.NewLength);
                }
                else
                {
                    start     = c.Start;
                    oldLength = c.OldLength;
                    newLength = c.NewLength;

                    DeleteAndShiftElements(context);
                    Debug.Assert(_editorTree.AstRoot.Children.Count > 0);
                }

                var ttc = new TreeTextChange(start, oldLength, newLength, _editorTree.BufferSnapshot, EditorBuffer.CurrentSnapshot);
                Changes.Combine(ttc);
                Changes.Version = EditorBuffer?.CurrentSnapshot?.Version ?? 1;

                _editorTree.BufferSnapshot = EditorBuffer.CurrentSnapshot;
            } finally {
                // Lock must be released before firing events otherwise we may hang
                _editorTree.ReleaseWriteLock();
            }

            _editorTree.FireOnUpdateCompleted(TreeUpdateType.NodesRemoved);
        }
Example #10
0
        /// <summary>
        /// Handles non-trivial changes like changes that delete elements,
        /// change identifier names, introducing new braces: changes
        /// that cannot be handled without background parse.
        /// </summary>
        private void ProcessComplexChange(TextChangeContext context)
        {
            // Cancel background parse if it is running
            Cancel();

            TextChange textChange = new TextChange()
            {
                OldTextProvider = context.OldTextProvider,
                NewTextProvider = context.NewTextProvider
            };

            try {
                // Get write lock since there may be concurrent readers
                // of the tree. Note that there are no concurrent writers
                // since changes can only come from a background parser
                // and are always applied from the main thread.
                _editorTree.AcquireWriteLock();

                if (_pendingChanges.FullParseRequired)
                {
                    // When full parse is required, change is like replace the entire file
                    textChange.OldRange = TextRange.FromBounds(0, context.OldText.Length);
                    textChange.NewRange = TextRange.FromBounds(0, context.NewText.Length);

                    // Remove all elements from the tree
                    _editorTree.Invalidate();
                }
                else
                {
                    textChange.OldRange = context.OldRange;
                    textChange.NewRange = context.NewRange;

                    DeleteAndShiftElements(context);
                    Debug.Assert(_editorTree.AstRoot.Children.Count > 0);
                }

                _pendingChanges.Combine(textChange);
                _pendingChanges.Version = TextBuffer != null ? TextBuffer.CurrentSnapshot.Version.VersionNumber : 1;

                UpdateTreeTextSnapshot();
            } finally {
                // Lock must be released before firing events otherwise we may hang
                _editorTree.ReleaseWriteLock();
            }

            _editorTree.FireOnUpdateCompleted(TreeUpdateType.NodesRemoved);
        }
Example #11
0
        private static TextChangeType CheckChangeInsideComment(TextChangeContext context) {
            var comments = context.EditorTree.AstRoot.Comments;

            IReadOnlyList<int> affectedComments = comments.GetItemsContainingInclusiveEnd(context.NewStart);
            if (affectedComments.Count == 0) {
                return TextChangeType.Trivial;
            }

            if (affectedComments.Count > 1) {
                return TextChangeType.Structure;
            }

            // Make sure user is not deleting leading # effectively 
            // destroying the comment
            RToken comment = comments[affectedComments[0]];
            if (comment.Start == context.NewStart && context.OldLength > 0) {
                return TextChangeType.Structure;
            }

            // The collection will return a match if the comment starts 
            // at the requested location. However, the change is not 
            // inside the comment if it's at the comment start and 
            // the old length of the change is zero.

            if (comment.Start == context.NewStart && context.OldLength == 0) {
                if (context.NewText.IndexOf('#') < 0) {
                    context.ChangedComment = comment;
                    return TextChangeType.Comment;
                }
            }

            if (context.NewText.IndexOfAny(CharExtensions.LineBreakChars) >= 0) {
                return TextChangeType.Structure;
            }

            // The change is not safe if old or new text contains line breaks
            // as in R comments runs to the end of the line and deleting
            // line break at the end of the comment may bring code into 
            // the comment range and change the entire file structure.

            if (context.OldText.IndexOfAny(CharExtensions.LineBreakChars) >= 0) {
                return TextChangeType.Structure;
            }

            context.ChangedComment = comment;
            return TextChangeType.Comment;
        }
Example #12
0
        // internal for unit tests
        internal bool DeleteAndShiftElements(TextChangeContext context)
        {
            if (Thread.CurrentThread.ManagedThreadId != _ownerThreadId)
            {
                throw new ThreadStateException("Method should only be called on the main thread");
            }

            TextChange textChange      = context.PendingChanges;
            var        changeType      = textChange.TextChangeType;
            bool       elementsChanged = false;

            if (changeType == TextChangeType.Structure)
            {
                IAstNode changedElement = context.ChangedNode;
                int      start          = context.NewStart;

                // We delete change nodes unless node is a token node
                // which range can be modified such as string or comment
                var positionType = PositionType.Undefined;

                if (changedElement != null)
                {
                    IAstNode node;
                    positionType = changedElement.GetPositionNode(context.NewStart, out node);
                }

                bool deleteElements = (context.OldLength > 0) || (positionType != PositionType.Token);

                // In case of delete or replace we need to invalidate elements that were
                // damaged by the delete operation. We need to remove elements and their keys
                // so they won't be found by validator and incremental change analysis
                // will not be looking at zombies.

                if (deleteElements)
                {
                    _pendingChanges.FullParseRequired =
                        _editorTree.InvalidateInRange(_editorTree.AstRoot, context.OldRange, out elementsChanged);
                }
            }

            _editorTree.NotifyTextChange(context.NewStart, context.OldLength, context.NewLength);

            return(elementsChanged);
        }
Example #13
0
        public static void DetermineChangeType(TextChangeContext change) {
            change.PendingChanges.TextChangeType |= CheckChangeInsideComment(change);
            if (change.PendingChanges.TextChangeType == TextChangeType.Comment) {
                return;
            } else if (change.PendingChanges.TextChangeType == TextChangeType.Trivial) {
                IAstNode node;
                PositionType positionType;

                change.PendingChanges.TextChangeType |= CheckChangeInsideString(change, out node, out positionType);
                if (change.PendingChanges.TextChangeType == TextChangeType.Token) {
                    return;
                } else if (node != null && change.PendingChanges.TextChangeType == TextChangeType.Trivial) {
                    change.PendingChanges.TextChangeType |= CheckWhiteSpaceChange(change, node, positionType);
                    if (change.PendingChanges.TextChangeType == TextChangeType.Trivial) {
                        return;
                    }
                }
            }

            change.PendingChanges.TextChangeType = TextChangeType.Structure;
            change.PendingChanges.FullParseRequired = true;
        }
Example #14
0
        private static TextChangeType CheckWhiteSpaceChange(TextChangeContext context, IAstNode node, PositionType positionType) {
            context.ChangedNode = node;

            if (string.IsNullOrWhiteSpace(context.OldText) && string.IsNullOrWhiteSpace(context.NewText)) {
                // In R there is no line continuation so expression may change when user adds or deletes line breaks.
                bool lineBreakSensitive = node is If;
                if (lineBreakSensitive) {
                    string oldLineText = context.OldTextProvider.GetText(new TextRange(context.OldStart, context.OldLength));
                    string newLineText = context.NewTextProvider.GetText(new TextRange(context.NewStart, context.NewLength));

                    if (oldLineText.IndexOfAny(CharExtensions.LineBreakChars) >= 0 || newLineText.IndexOfAny(CharExtensions.LineBreakChars) >= 0) {
                        return TextChangeType.Structure;
                    }
                }

                // Change inside token node is destructive: consider adding space inside an indentifier
                if (!IsChangeDestructiveForChildNodes(node, context.OldRange)) {
                    return TextChangeType.Trivial;
                }
            }

            return TextChangeType.Structure;
        }
Example #15
0
        // internal for unit tests
        internal bool DeleteAndShiftElements(TextChangeContext context)
        {
            Check.InvalidOperation(() => Thread.CurrentThread.ManagedThreadId == _ownerThreadId, _threadCheckMessage);

            var change          = context.PendingChanges;
            var changeType      = context.PendingChanges.TextChangeType;
            var elementsChanged = false;

            if (changeType == TextChangeType.Structure)
            {
                var changedElement = context.ChangedNode;

                // We delete change nodes unless node is a token node
                // which range can be modified such as string or comment
                var positionType = PositionType.Undefined;

                if (changedElement != null)
                {
                    positionType = changedElement.GetPositionNode(change.Start, out IAstNode node);
                }

                var deleteElements = change.OldLength > 0 || positionType != PositionType.Token;

                // In case of delete or replace we need to invalidate elements that were
                // damaged by the delete operation. We need to remove elements so they
                // won't be found by validator and it won't be looking at zombies.
                if (deleteElements)
                {
                    Changes.FullParseRequired = true;
                    elementsChanged           = _editorTree.InvalidateInRange(change.OldRange);
                }
            }

            _editorTree.NotifyTextChange(change.Start, change.OldLength, change.NewLength);
            return(elementsChanged);
        }
Example #16
0
        private static bool IsLineBreakSensitive(TextChangeContext context, IAstNode node)
        {
            if (node is If)
            {
                return(true);
            }

            var position  = context.PendingChanges.Start;
            var candidate = node.Root.GetNodeOfTypeFromPosition <If>(position);

            if (candidate != null)
            {
                return(true);
            }

            // Check if line break is added to removed on a line with closing } of 'if'
            // so we can full-parse if 'else' position changes relatively to the if
            var snapshot = context.EditorTree.EditorBuffer.CurrentSnapshot;
            var text     = snapshot.GetLineFromPosition(context.PendingChanges.Start).GetText();

            // We need to find if any position in the line belong to `if` scope
            // if there is 'else' the same line
            return(FindKeyword("if", text) || FindKeyword("else", text));
        }
Example #17
0
        /// <summary>
        /// Handles simple (safe) changes.
        /// </summary>
        private void ProcessSimpleChange(TextChangeContext context)
        {
            bool elementsRemoved = false;

            try {
                _editorTree.AcquireWriteLock();

                elementsRemoved = DeleteAndShiftElements(context);
                UpdateTreeTextSnapshot();

                // If no elements were invalidated and full parse is not required, clear pending changes
                if (!elementsRemoved)
                {
                    ClearChanges();
                }
            } finally {
                _editorTree.ReleaseWriteLock();
            }

            if (!elementsRemoved)
            {
                if (context.ChangedNode != null || context.PendingChanges.TextChangeType == TextChangeType.Trivial)
                {
                    _editorTree.FireOnPositionsOnlyChanged();
                }

                _editorTree.FireOnUpdateCompleted(TreeUpdateType.PositionsOnly);
            }
            else
            {
                _editorTree.FireOnUpdateCompleted(TreeUpdateType.NodesRemoved);
            }

            DebugTree.VerifyTree(_editorTree);
            Debug.Assert(_editorTree.AstRoot.Children.Count > 0);
        }
Example #18
0
        /// <summary>
        /// Text buffer change event handler. Performs analysis of the change.
        /// If change is trivial, such as change in whitespace (excluding line
        /// breaks that in R may be sensistive), simply applies the changes
        /// by shifting tree elements. If some elements get deleted or otherwise
        /// damaged, removes them from the tree right away. Non-trivial changes
        /// are queued for background parsing which starts on next on idle.
        /// Methond must be called on a main thread only, typically from an event
        /// handler that receives text buffer change events.
        /// </summary>
        internal void OnTextChange(TextChangeEventArgs e)
        {
            Check.InvalidOperation(() => Thread.CurrentThread.ManagedThreadId == _ownerThreadId, _threadCheckMessage);

            _editorTree.FireOnUpdatesPending();
            if (UpdatesSuspended)
            {
                TextBufferChangedSinceSuspend = true;
                Changes.FullParseRequired     = true;
            }
            else
            {
                _lastChangeTime = DateTime.UtcNow;
                var context = new TextChangeContext(_editorTree, new TreeTextChange(e.Change), Changes);

                // No need to analyze changes if full parse is already pending
                if (!Changes.FullParseRequired)
                {
                    TextChangeAnalyzer.DetermineChangeType(context);
                }

                ProcessChange(context);
            }
        }
Example #19
0
        /// <summary>
        /// Text buffer change event handler. Performs analysis of the change.
        /// If change is trivial, such as change in whitespace (excluding line 
        /// breaks that in R may be sensistive), simply applies the changes 
        /// by shifting tree elements. If some elements get deleted or otherwise 
        /// damaged, removes them from the tree right away. Non-trivial changes 
        /// are queued for background parsing which starts on next on idle.
        /// Methond must be called on a main thread only, typically from an event 
        /// handler that receives text buffer change events. 
        /// </summary>
        internal void OnTextChanges(IReadOnlyCollection<TextChangeEventArgs> textChanges) {
            if (Thread.CurrentThread.ManagedThreadId != _ownerThreadId)
                throw new ThreadStateException("Method should only be called on the main thread");

            _editorTree.FireOnUpdatesPending(textChanges);
            if (UpdatesSuspended) {
                this.TextBufferChangedSinceSuspend = true;
                _pendingChanges.FullParseRequired = true;
            } else {
                foreach (TextChangeEventArgs change in textChanges) {
                    _lastChangeTime = DateTime.UtcNow;
                    var context = new TextChangeContext(_editorTree, change, _pendingChanges);

                    // No need to analyze changes if full parse is already pending
                    if (!_pendingChanges.FullParseRequired) {
                        TextChangeAnalyzer.DetermineChangeType(context);
                    }

                    ProcessChange(context);
                }
            }
        }
Example #20
0
        private static TextChangeType CheckChangeInsideString(TextChangeContext context, out IAstNode node, out PositionType positionType) {
            positionType = context.EditorTree.AstRoot.GetPositionNode(context.NewStart, out node);

            if (positionType == PositionType.Token) {
                TokenNode tokenNode = node as TokenNode;
                Debug.Assert(tokenNode != null);

                if (tokenNode.Token.TokenType == RTokenType.String) {

                    if (context.OldText.IndexOfAny(_stringSensitiveCharacters) >= 0) {
                        return TextChangeType.Structure;
                    }

                    if (context.NewText.IndexOfAny(_stringSensitiveCharacters) >= 0) {
                        return TextChangeType.Structure;
                    }

                    context.ChangedNode = node;
                    return TextChangeType.Token;
                }
            }

            return TextChangeType.Trivial;
        }
Example #21
0
        // internal for unit tests
        internal bool DeleteAndShiftElements(TextChangeContext context) {
            if (Thread.CurrentThread.ManagedThreadId != _ownerThreadId)
                throw new ThreadStateException("Method should only be called on the main thread");

            TextChange textChange = context.PendingChanges;
            var changeType = textChange.TextChangeType;
            bool elementsChanged = false;

            if (changeType == TextChangeType.Structure) {
                IAstNode changedElement = context.ChangedNode;
                int start = context.NewStart;

                // We delete change nodes unless node is a token node 
                // which range can be modified such as string or comment
                var positionType = PositionType.Undefined;

                if (changedElement != null) {
                    IAstNode node;
                    positionType = changedElement.GetPositionNode(context.NewStart, out node);
                }

                bool deleteElements = (context.OldLength > 0) || (positionType != PositionType.Token);

                // In case of delete or replace we need to invalidate elements that were 
                // damaged by the delete operation. We need to remove elements and their keys 
                // so they won't be found by validator and incremental change analysis 
                // will not be looking at zombies.

                if (deleteElements) {
                    _pendingChanges.FullParseRequired =
                        _editorTree.InvalidateInRange(_editorTree.AstRoot, context.OldRange, out elementsChanged);
                }
            }

            _editorTree.NotifyTextChange(context.NewStart, context.OldLength, context.NewLength);

            return elementsChanged;
        }
Example #22
0
        /// <summary>
        /// Handles non-trivial changes like changes that delete elements, 
        /// change identifier names, introducing new braces: changes
        /// that cannot be handled without background parse.
        /// </summary>
        private void ProcessComplexChange(TextChangeContext context) {
            // Cancel background parse if it is running
            Cancel();

            TextChange textChange = new TextChange() {
                OldTextProvider = context.OldTextProvider,
                NewTextProvider = context.NewTextProvider
            };

            try {
                // Get write lock since there may be concurrent readers 
                // of the tree. Note that there are no concurrent writers 
                // since changes can only come from a background parser
                // and are always applied from the main thread.
                _editorTree.AcquireWriteLock();

                if (_pendingChanges.FullParseRequired) {
                    // When full parse is required, change is like replace the entire file
                    textChange.OldRange = TextRange.FromBounds(0, context.OldText.Length);
                    textChange.NewRange = TextRange.FromBounds(0, context.NewText.Length);

                    // Remove all elements from the tree
                    _editorTree.Invalidate();
                } else {
                    textChange.OldRange = context.OldRange;
                    textChange.NewRange = context.NewRange;

                    DeleteAndShiftElements(context);
                    Debug.Assert(_editorTree.AstRoot.Children.Count > 0);
                }

                _pendingChanges.Combine(textChange);
                _pendingChanges.Version = TextBuffer != null ? TextBuffer.CurrentSnapshot.Version.VersionNumber : 1;

                UpdateTreeTextSnapshot();
            } finally {
                // Lock must be released before firing events otherwise we may hang
                _editorTree.ReleaseWriteLock();
            }

            _editorTree.FireOnUpdateCompleted(TreeUpdateType.NodesRemoved);
        }
Example #23
0
        /// <summary>
        /// Handles simple (safe) changes.
        /// </summary>
        private void ProcessSimpleChange(TextChangeContext context) {
            bool elementsRemoved = false;

            try {
                _editorTree.AcquireWriteLock();

                elementsRemoved = DeleteAndShiftElements(context);
                UpdateTreeTextSnapshot();

                // If no elements were invalidated and full parse is not required, clear pending changes
                if (!elementsRemoved) {
                    ClearChanges();
                }
            } finally {
                _editorTree.ReleaseWriteLock();
            }

            if (!elementsRemoved) {
                if (context.ChangedNode != null || context.PendingChanges.TextChangeType == TextChangeType.Trivial) {
                    _editorTree.FireOnPositionsOnlyChanged();
                }

                _editorTree.FireOnUpdateCompleted(TreeUpdateType.PositionsOnly);
            } else {
                _editorTree.FireOnUpdateCompleted(TreeUpdateType.NodesRemoved);
            }

            DebugTree.VerifyTree(_editorTree);
            Debug.Assert(_editorTree.AstRoot.Children.Count > 0);
        }
Example #24
0
        private void ProcessChange(TextChangeContext context) {
            _editorTree.FireOnUpdateBegin();

            if (_pendingChanges.IsSimpleChange) {
                ProcessSimpleChange(context);
            } else {
                ProcessComplexChange(context);
            }
        }