Inheritance: ICloneable
Example #1
0
        public TextChangeContext(EditorTree editorTree, TextChangeEventArgs change, TextChange pendingChanges)
        {
            EditorTree = editorTree;
            NewStart = change.Start;
            OldStart = change.OldStart;
            OldLength = change.OldLength;
            NewLength = change.NewLength;

            OldTextProvider = change.OldText != null ? change.OldText : editorTree.AstRoot.TextProvider;
            NewTextProvider = change.NewText != null ? change.NewText : new TextProvider(editorTree.TextBuffer.CurrentSnapshot, partial: true);

            PendingChanges = pendingChanges;

            TextChange textChange = new TextChange();

            textChange.OldRange = this.OldRange;
            textChange.OldTextProvider = this.OldTextProvider;

            textChange.NewRange = this.NewRange;
            textChange.NewTextProvider = this.NewTextProvider;

            textChange.Version = this.NewTextProvider.Version;

            PendingChanges.Combine(textChange);
        }
Example #2
0
        public static EditorTree ApplyTextChange(string expression, int start, int oldLength, int newLength, string newText)
        {
            TextBufferMock textBuffer = new TextBufferMock(expression, RContentTypeDefinition.ContentType);

            EditorTree tree = new EditorTree(textBuffer);
            tree.Build();

            TextChange tc = new TextChange();
            tc.OldRange = new TextRange(start, oldLength);
            tc.NewRange = new TextRange(start, newLength);
            tc.OldTextProvider = new TextProvider(textBuffer.CurrentSnapshot);

            if (oldLength == 0 && newText.Length > 0)
            {
                textBuffer.Insert(start, newText);
            }
            else if (oldLength > 0 && newText.Length > 0)
            {
                textBuffer.Replace(new Span(start, oldLength), newText);
            }
            else
            {
                textBuffer.Delete(new Span(start, oldLength));
            }

            return tree;
        }
        /// <summary>
        /// Processes a single text change incrementally. Enqueues resulting 
        /// tree changes in the supplied queue. Does not modify the tree. 
        /// Changes are to be sent to the main thread and applied from there.
        /// Caller is responsible for the tree read lock acquisition. 
        /// </summary>
        /// <param name="start">Start position of the change</param>
        /// <param name="oldLength">Length of the original text (0 if insertion)</param>
        /// <param name="newLength">Length of the new text (0 if deletion)</param>
        /// <param name="oldSnapshot">Text snapshot before the change</param>
        /// <param name="newSnapshot">Text snapshot after the change</param>
        /// <param name="treeChanges">Collection of tree changes to apply 
        /// from the main thread</param>
        public void ProcessChange(TextChange textChange, EditorTreeChangeCollection treeChanges) {
            IAstNode startNode = null, endNode = null;
            PositionType startPositionType = PositionType.Undefined;
            PositionType endPositionType = PositionType.Undefined;
            IAstNode commonParent = null;

            int start = textChange.OldRange.Start;
            int oldLength = textChange.OldRange.Length;
            int newLength = textChange.NewRange.Length;
            int offset = newLength - oldLength;

            ITextProvider oldSnapshot = textChange.OldTextProvider;
            ITextProvider newSnapshot = textChange.NewTextProvider;

            // Find position type and the enclosing element node. Note that element 
            // positions have been adjusted already (it happens immediately in OnTextChange) 
            // so we should be looking at the new range even that tree hasn't 
            // been fully updated yet. For example,if we delete a node, subsequent 
            // elements were already shifted up and damaged nodes have been removed 
            // so current node positions reflect text buffer state after the change.

            _astRoot.GetElementsEnclosingRange(start, newLength, out startNode,
                          out startPositionType, out endNode, out endPositionType);

            if (startNode is AstRoot) {
                commonParent = _astRoot;
            } else if (startNode == endNode) {
                if (startPositionType == PositionType.Token) {
                    // Change in comment or string content. 
                    commonParent = OnTokenNodeChange(startNode as TokenNode, start, oldLength, newLength);
                }
            } else {
                //if (commonParent == null)
                //{
                //    // Find parent that still has well formed curly braces.
                //    commonParent = FindWellFormedOuterScope(startNode);
                //}

                if (commonParent == null) {
                    commonParent = _astRoot;
                }
            }

            if (IsCancellationRequested())
                return;

            if (!(commonParent is AstRoot)) {
                Debug.Assert(commonParent is IScope);
                AstRoot subTree = RParser.Parse(newSnapshot, commonParent);
                return;
            }

            AstRoot newTree = RParser.Parse(newSnapshot);
            treeChanges.ChangeQueue.Enqueue(new EditorTreeChange_NewTree(newTree));
        }
        public static void ApplyTextChange(ITextBuffer textBuffer, int start, int oldLength, int newLength, string newText)
        {
            TextChange tc = new TextChange();
            tc.OldRange = new TextRange(start, oldLength);
            tc.NewRange = new TextRange(start, newLength);
            tc.OldTextProvider = new TextProvider(textBuffer.CurrentSnapshot);

            if (oldLength == 0 && newText.Length > 0)
            {
                textBuffer.Insert(start, newText);
            }
            else if (oldLength > 0 && newText.Length > 0)
            {
                textBuffer.Replace(new Span(start, oldLength), newText);
            }
            else
            {
                textBuffer.Delete(new Span(start, oldLength));
            }
        }
Example #5
0
        /// <summary>
        /// Main asyncronous task body
        /// </summary>
        void ProcessTextChanges(TextChange changeToProcess, bool async, Func<bool> isCancelledCallback) {
            lock (_disposeLock) {
                if (_editorTree == null || _disposed || isCancelledCallback())
                    return;

                EditorTreeChangeCollection treeChanges = null;
                // Cache id since it can change if task is canceled
                long taskId = TaskId;

                try {
                    AstRoot rootNode;

                    // We only need read lock since changes will be applied 
                    // from the main thread
                    if (async) {
                        rootNode = _editorTree.AcquireReadLock(_treeUserId);
                    } else {
                        rootNode = _editorTree.GetAstRootUnsafe();
                    }

                    treeChanges = new EditorTreeChangeCollection(changeToProcess.Version, changeToProcess.FullParseRequired);
                    TextChangeProcessor changeProcessor = new TextChangeProcessor(_editorTree, rootNode, isCancelledCallback);

                    bool fullParseRequired = changeToProcess.FullParseRequired;
                    if (fullParseRequired) {
                        changeProcessor.FullParse(treeChanges, changeToProcess.NewTextProvider);
                    } else {
                        changeProcessor.ProcessChange(changeToProcess, treeChanges);
                    }
                } finally {
                    if (async && _editorTree != null)
                        _editorTree.ReleaseReadLock(_treeUserId);
                }

                // Lock should be released at this point since actual application
                // of tree changes is going to be happen from the main thread.

                if (!isCancelledCallback() && treeChanges != null) {
                    // Queue results for the main thread application. This must be done before 
                    // signaling that the task is complete since if EnsureProcessingComplete 
                    // is waiting it will want to apply changes itself rather than wait for 
                    // the DispatchOnUIThread to go though and hence it will need all changes
                    // stored and ready for application.

                    _backgroundParsingResults.Enqueue(treeChanges);
                }

                // Signal task complete now so if main thread is waiting
                // it can proceed and appy the changes immediately.
                SignalTaskComplete(taskId);

                if (_backgroundParsingResults.Count > 0) {
                    _uiThreadTransitionRequestTime = DateTime.UtcNow;

                    // It is OK to post results while main thread might be working
                    // on them since if if it does, by the time posted request comes
                    // queue will already be empty.
                    if (async) {
                        // Post request to apply tree changes to the main thread.
                        // This must NOT block or else task will never enter 'RanToCompletion' state.
                        EditorShell.DispatchOnUIThread(() => ApplyBackgroundProcessingResults());
                    } else {
                        // When processing is synchronous, apply changes and fire events right away.
                        ApplyBackgroundProcessingResults();
                    }
                }
            }
        }
Example #6
0
        /// <summary>
        /// Processes text buffer changed accumulated so far. 
        /// Typically called on idle.
        /// </summary>
        /// <param name="newTextProvider">New text buffer content</param>
        /// <param name="async">True if processing is to be done asynchronously.
        /// Non-async processing is typically used in unit tests only.</param>
        internal void ProcessPendingTextBufferChanges(ITextProvider newTextProvider, bool async) {
            if (Thread.CurrentThread.ManagedThreadId != _ownerThreadId)
                throw new ThreadStateException("Method should only be called on the main thread");

            if (ChangesPending) {
                if (async && (IsTaskRunning() || _backgroundParsingResults.Count > 0)) {
                    // Try next time or we may end up spawning a lot of tasks
                    return;
                }

                // Combine changes in processing with pending changes.
                var changesToProcess = new TextChange(_pendingChanges, newTextProvider);

                // We need to signal task start here, in the main thread since it takes
                // some time before task is created and when it actually starts.
                // Therefore setting task state in the task body creates a gap
                // where we may end up spawning another task.

                base.Run((isCancelledCallback) => ProcessTextChanges(changesToProcess, async, isCancelledCallback), async);
            }
        }
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 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 #8
0
        public void TextChange_Test() {
            TextChange tc = new TextChange();
            tc.IsEmpty.Should().BeTrue();
            tc.TextChangeType.Should().Be(TextChangeType.Trivial);

            string content = "23456789";
            TextBufferMock textBuffer = new TextBufferMock(content, RContentTypeDefinition.ContentType);
            ITextSnapshot oldSnapshot = textBuffer.CurrentSnapshot;

            textBuffer.Insert(0, "1");
            ITextSnapshot newSnapshot1 = textBuffer.CurrentSnapshot;

            textBuffer.Insert(0, "0");
            ITextSnapshot newSnapshot2 = textBuffer.CurrentSnapshot;

            tc.OldTextProvider = new TextProvider(oldSnapshot);
            tc.NewTextProvider = new TextProvider(newSnapshot1);

            tc.OldRange = new TextRange(0, 0);
            tc.NewRange = new TextRange(0, 1);

            var tc1 = new TextChange(tc, new TextProvider(newSnapshot2));

            tc1.ShouldBeEquivalentTo(new {
                OldRange = new { Length = 0 },
                NewRange = new { Length = 2 },
                Version = 2,
                FullParseRequired = false,
                IsEmpty = false,
                IsSimpleChange = true
            }, o => o.ExcludingMissingMembers());

            var tc2 = tc1.Clone() as TextChange;

            tc2.ShouldBeEquivalentTo(new {
                OldRange = new { Length = 0 },
                NewRange = new { Length = 2 },
                Version = 2,
                FullParseRequired = false,
                IsEmpty = false,
                IsSimpleChange = true
            }, o => o.ExcludingMissingMembers());

            tc1.Clear();

            tc1.ShouldBeEquivalentTo(new {
                OldRange = new { Length = 0 },
                NewRange = new { Length = 0 },
                OldTextProvider = (ITextProvider)null,
                NewTextProvider = (ITextProvider)null,
                IsEmpty = true,
                IsSimpleChange = true
            }, o => o.ExcludingMissingMembers());

            tc2.ShouldBeEquivalentTo(new {
                OldRange = new { Length = 0 },
                NewRange = new { Length = 2 },
                Version = 2,
                FullParseRequired = false,
                IsEmpty = false,
                IsSimpleChange = true
            }, o => o.ExcludingMissingMembers());
        }
Example #9
0
        public void TextChange_Test()
        {
            TextChange tc = new TextChange();

            tc.IsEmpty.Should().BeTrue();
            tc.TextChangeType.Should().Be(TextChangeType.Trivial);

            string         content     = "23456789";
            TextBufferMock textBuffer  = new TextBufferMock(content, RContentTypeDefinition.ContentType);
            ITextSnapshot  oldSnapshot = textBuffer.CurrentSnapshot;

            textBuffer.Insert(0, "1");
            ITextSnapshot newSnapshot1 = textBuffer.CurrentSnapshot;

            textBuffer.Insert(0, "0");
            ITextSnapshot newSnapshot2 = textBuffer.CurrentSnapshot;

            tc.OldTextProvider = new TextProvider(oldSnapshot);
            tc.NewTextProvider = new TextProvider(newSnapshot1);

            tc.OldRange = new TextRange(0, 0);
            tc.NewRange = new TextRange(0, 1);

            var tc1 = new TextChange(tc, new TextProvider(newSnapshot2));

            tc1.ShouldBeEquivalentTo(new {
                OldRange          = new { Length = 0 },
                NewRange          = new { Length = 2 },
                Version           = 2,
                FullParseRequired = false,
                IsEmpty           = false,
                IsSimpleChange    = true
            }, o => o.ExcludingMissingMembers());

            var tc2 = tc1.Clone() as TextChange;

            tc2.ShouldBeEquivalentTo(new {
                OldRange          = new { Length = 0 },
                NewRange          = new { Length = 2 },
                Version           = 2,
                FullParseRequired = false,
                IsEmpty           = false,
                IsSimpleChange    = true
            }, o => o.ExcludingMissingMembers());

            tc1.Clear();

            tc1.ShouldBeEquivalentTo(new {
                OldRange        = new { Length = 0 },
                NewRange        = new { Length = 0 },
                OldTextProvider = (ITextProvider)null,
                NewTextProvider = (ITextProvider)null,
                IsEmpty         = true,
                IsSimpleChange  = true
            }, o => o.ExcludingMissingMembers());

            tc2.ShouldBeEquivalentTo(new {
                OldRange          = new { Length = 0 },
                NewRange          = new { Length = 2 },
                Version           = 2,
                FullParseRequired = false,
                IsEmpty           = false,
                IsSimpleChange    = true
            }, o => o.ExcludingMissingMembers());
        }
Example #10
0
        /// <summary>
        /// Combines one text change with another
        /// </summary>
        public void Combine(TextChange other)
        {
            if (other.IsEmpty)
            {
                return;
            }

            FullParseRequired |= other.FullParseRequired;
            TextChangeType    |= other.TextChangeType;

            if (OldRange == TextRange.EmptyRange || NewRange == TextRange.EmptyRange)
            {
                OldRange        = other.OldRange;
                NewRange        = other.NewRange;
                OldTextProvider = other.OldTextProvider;
                NewTextProvider = other.NewTextProvider;
            }
            else
            {
                ITextSnapshotProvider oldSnapshotProvider      = OldTextProvider as ITextSnapshotProvider;
                ITextSnapshotProvider newSnapshotProvider      = NewTextProvider as ITextSnapshotProvider;
                ITextSnapshotProvider otherOldSnapshotProvider = other.OldTextProvider as ITextSnapshotProvider;
                ITextSnapshotProvider otherNewSnapshotProvider = other.NewTextProvider as ITextSnapshotProvider;
                bool changesAreFromNextSnapshot = false;

                if ((oldSnapshotProvider != null) && (newSnapshotProvider != null) &&
                    (otherOldSnapshotProvider != null) && (otherNewSnapshotProvider != null))
                {
                    ITextSnapshot newSnapshot      = newSnapshotProvider.Snapshot;
                    ITextSnapshot otherOldSnapshot = otherOldSnapshotProvider.Snapshot;
                    if (newSnapshot.Version.ReiteratedVersionNumber == otherOldSnapshot.Version.ReiteratedVersionNumber)
                    {
                        changesAreFromNextSnapshot = true;
                    }
                }

                if (!changesAreFromNextSnapshot)
                {
                    // Assume these changes are from the same snapshot
                    int oldStart = Math.Min(other.OldRange.Start, this.OldRange.Start);
                    int oldEnd   = Math.Max(other.OldRange.End, this.OldRange.End);

                    int newStart = Math.Min(other.NewRange.Start, this.NewRange.Start);
                    int newEnd   = Math.Max(other.NewRange.End, this.NewRange.End);

                    OldRange = TextRange.FromBounds(oldStart, oldEnd);
                    NewRange = TextRange.FromBounds(newStart, newEnd);
                }
                else
                {
                    // the "other" change is from the subsequent snapshot. Merge the changes.
                    ITextSnapshot oldSnapshot      = oldSnapshotProvider.Snapshot;
                    ITextSnapshot newSnapshot      = newSnapshotProvider.Snapshot;
                    ITextSnapshot otherOldSnapshot = otherOldSnapshotProvider.Snapshot;
                    ITextSnapshot otherNewSnapshot = otherNewSnapshotProvider.Snapshot;

                    SnapshotSpan otherOldSpan = other.OldRange.ToSnapshotSpan(otherOldSnapshot);
                    SnapshotSpan oldSpan      = otherOldSpan.TranslateTo(oldSnapshot, SpanTrackingMode.EdgeInclusive);

                    SnapshotSpan newSpan      = NewRange.ToSnapshotSpan(newSnapshot);
                    SnapshotSpan otherNewSpan = newSpan.TranslateTo(otherNewSnapshot, SpanTrackingMode.EdgeInclusive);

                    OldRange        = new TextRange(TextRange.Union(OldRange, oldSpan.ToTextRange()));
                    NewRange        = new TextRange(TextRange.Union(other.NewRange, otherNewSpan.ToTextRange()));
                    NewTextProvider = other.NewTextProvider;
                }
            }

            Version = Math.Max(this.Version, other.Version);
        }
Example #11
0
        /// <summary>
        /// Processes a single text change incrementally. Enqueues resulting
        /// tree changes in the supplied queue. Does not modify the tree.
        /// Changes are to be sent to the main thread and applied from there.
        /// Caller is responsible for the tree read lock acquisition.
        /// </summary>
        /// <param name="start">Start position of the change</param>
        /// <param name="oldLength">Length of the original text (0 if insertion)</param>
        /// <param name="newLength">Length of the new text (0 if deletion)</param>
        /// <param name="oldSnapshot">Text snapshot before the change</param>
        /// <param name="newSnapshot">Text snapshot after the change</param>
        /// <param name="treeChanges">Collection of tree changes to apply
        /// from the main thread</param>
        public void ProcessChange(TextChange textChange, EditorTreeChangeCollection treeChanges)
        {
            IAstNode     startNode = null, endNode = null;
            PositionType startPositionType = PositionType.Undefined;
            PositionType endPositionType   = PositionType.Undefined;
            IAstNode     commonParent      = null;

            int start     = textChange.OldRange.Start;
            int oldLength = textChange.OldRange.Length;
            int newLength = textChange.NewRange.Length;
            int offset    = newLength - oldLength;

            ITextProvider oldSnapshot = textChange.OldTextProvider;
            ITextProvider newSnapshot = textChange.NewTextProvider;

            // Find position type and the enclosing element node. Note that element
            // positions have been adjusted already (it happens immediately in OnTextChange)
            // so we should be looking at the new range even that tree hasn't
            // been fully updated yet. For example,if we delete a node, subsequent
            // elements were already shifted up and damaged nodes have been removed
            // so current node positions reflect text buffer state after the change.

            _astRoot.GetElementsEnclosingRange(start, newLength, out startNode,
                                               out startPositionType, out endNode, out endPositionType);

            if (startNode is AstRoot)
            {
                commonParent = _astRoot;
            }
            else if (startNode == endNode)
            {
                if (startPositionType == PositionType.Token)
                {
                    // Change in comment or string content.
                    commonParent = OnTokenNodeChange(startNode as TokenNode, start, oldLength, newLength);
                }
            }
            else
            {
                //if (commonParent == null)
                //{
                //    // Find parent that still has well formed curly braces.
                //    commonParent = FindWellFormedOuterScope(startNode);
                //}

                if (commonParent == null)
                {
                    commonParent = _astRoot;
                }
            }

            if (IsCancellationRequested())
            {
                return;
            }

            if (!(commonParent is AstRoot))
            {
                Debug.Assert(commonParent is IScope);
                AstRoot subTree = RParser.Parse(newSnapshot, commonParent);
                return;
            }

            AstRoot newTree = RParser.Parse(newSnapshot);

            treeChanges.ChangeQueue.Enqueue(new EditorTreeChange_NewTree(newTree));
        }
Example #12
0
        /// <summary>
        /// Main asyncronous task body
        /// </summary>
        void ProcessTextChanges(TextChange changeToProcess, bool async, Func <bool> isCancelledCallback)
        {
            lock (_disposeLock) {
                if (_editorTree == null || _disposed || isCancelledCallback())
                {
                    return;
                }

                EditorTreeChangeCollection treeChanges = null;
                // Cache id since it can change if task is canceled
                long taskId = TaskId;

                try {
                    AstRoot rootNode;

                    // We only need read lock since changes will be applied
                    // from the main thread
                    if (async)
                    {
                        rootNode = _editorTree.AcquireReadLock(_treeUserId);
                    }
                    else
                    {
                        rootNode = _editorTree.GetAstRootUnsafe();
                    }

                    treeChanges = new EditorTreeChangeCollection(changeToProcess.Version, changeToProcess.FullParseRequired);
                    TextChangeProcessor changeProcessor = new TextChangeProcessor(_editorTree, rootNode, isCancelledCallback);

                    bool fullParseRequired = changeToProcess.FullParseRequired;
                    if (fullParseRequired)
                    {
                        changeProcessor.FullParse(treeChanges, changeToProcess.NewTextProvider);
                    }
                    else
                    {
                        changeProcessor.ProcessChange(changeToProcess, treeChanges);
                    }
                } finally {
                    if (async && _editorTree != null)
                    {
                        _editorTree.ReleaseReadLock(_treeUserId);
                    }
                }

                // Lock should be released at this point since actual application
                // of tree changes is going to be happen from the main thread.

                if (!isCancelledCallback() && treeChanges != null)
                {
                    // Queue results for the main thread application. This must be done before
                    // signaling that the task is complete since if EnsureProcessingComplete
                    // is waiting it will want to apply changes itself rather than wait for
                    // the DispatchOnUIThread to go though and hence it will need all changes
                    // stored and ready for application.

                    _backgroundParsingResults.Enqueue(treeChanges);
                }

                // Signal task complete now so if main thread is waiting
                // it can proceed and appy the changes immediately.
                SignalTaskComplete(taskId);

                if (_backgroundParsingResults.Count > 0)
                {
                    _uiThreadTransitionRequestTime = DateTime.UtcNow;

                    // It is OK to post results while main thread might be working
                    // on them since if if it does, by the time posted request comes
                    // queue will already be empty.
                    if (async)
                    {
                        // Post request to apply tree changes to the main thread.
                        // This must NOT block or else task will never enter 'RanToCompletion' state.
                        EditorShell.DispatchOnUIThread(() => ApplyBackgroundProcessingResults());
                    }
                    else
                    {
                        // When processing is synchronous, apply changes and fire events right away.
                        ApplyBackgroundProcessingResults();
                    }
                }
            }
        }