Пример #1
0
        //*************************************************************************
        /// <summary>
        /// Concatentates the two input DiffChange lists and returns the resulting
        /// list.
        /// </summary>
        /// <param name="left">The left changes</param>
        /// <param name="right">The right changes</param>
        /// <returns>The concatenated list</returns>
        //*************************************************************************
        private IDiffChange[] ConcatenateChanges(IDiffChange[] left, IDiffChange[] right)
        {
            IDiffChange mergedChange;

            if (left.Length == 0 || right.Length == 0)
            {
                return((right.Length > 0) ? right : left);
            }
            else if (ChangesOverlap(left[left.Length - 1], right[0], out mergedChange))
            {
                // Since we break the problem down recursively, it is possible that we
                // might recurse in the middle of a change thereby splitting it into
                // two changes. Here in the combining stage, we detect and fuse those
                // changes back together
                IDiffChange[] result = new IDiffChange[left.Length + right.Length - 1];
                Array.Copy(left, 0, result, 0, left.Length - 1);
                result[left.Length - 1] = mergedChange;
                Array.Copy(right, 1, result, left.Length, right.Length - 1);

                return(result);
            }
            else
            {
                IDiffChange[] result = new IDiffChange[left.Length + right.Length];
                Array.Copy(left, 0, result, 0, left.Length);
                Array.Copy(right, 0, result, left.Length, right.Length);

                return(result);
            }
        }
        /// <summary>
        /// This methods combines two <see cref="IDiffChange"/> objects into one.
        /// </summary>
        /// <param name="diffChange">The diff change to add.</param>
        /// <returns>A new instance of <see cref="IDiffChange"/> that represents <c>this</c> + <see cref="diffChange"/>.</returns>
        public IDiffChange Add(IDiffChange diffChange)
        {
            if (diffChange == null)
                return this;

            int originalStart = Math.Min(OriginalStart, diffChange.OriginalStart);
            int originalEnd = Math.Max(OriginalEnd, diffChange.OriginalEnd);
            int modifiedStart = Math.Min(ModifiedStart, diffChange.ModifiedStart);
            int modifiedEnd = Math.Max(ModifiedEnd, diffChange.ModifiedEnd);

            DiffChangeType changeType;
            if (ChangeType == diffChange.ChangeType && OriginalStart - diffChange.OriginalEnd == 0 && ModifiedStart - diffChange.ModifiedEnd == 0) 
                changeType = ChangeType;
            else 
                changeType = DiffChangeType.Change;

            return new DiffChange
            {
                ChangeType = changeType,
                OriginalStart = originalStart,
                OriginalEnd = originalEnd,
                OriginalLength = originalEnd - originalStart,
                ModifiedStart = modifiedStart,
                ModifiedEnd = modifiedEnd,
                ModifiedLength = modifiedEnd - modifiedStart
            };
        }
Пример #3
0
        //*************************************************************************
        /// <summary>
        /// Returns true if the two changes overlap and can be merged into a single
        /// change
        /// </summary>
        /// <param name="left">The left change</param>
        /// <param name="right">The right change</param>
        /// <param name="mergedChange">The merged change if the two overlap,
        /// null otherwise</param>
        /// <returns>True if the two changes overlap</returns>
        //*************************************************************************
        private bool ChangesOverlap(IDiffChange left, IDiffChange right, out IDiffChange mergedChange)
        {
            Debug.Assert(left.OriginalStart <= right.OriginalStart, "Left change is not less than or equal to right change");
            Debug.Assert(left.ModifiedStart <= right.ModifiedStart, "Left change is not less than or equal to right change");

            if (left.OriginalStart + left.OriginalLength >= right.OriginalStart ||
                left.ModifiedStart + left.ModifiedLength >= right.ModifiedStart)
            {
                int originalStart  = left.OriginalStart;
                int originalLength = left.OriginalLength;
                int modifiedStart  = left.ModifiedStart;
                int modifiedLength = left.ModifiedLength;

                if (left.OriginalStart + left.OriginalLength >= right.OriginalStart)
                {
                    originalLength = right.OriginalStart + right.OriginalLength - left.OriginalStart;
                }
                if (left.ModifiedStart + left.ModifiedLength >= right.ModifiedStart)
                {
                    modifiedLength = right.ModifiedStart + right.ModifiedLength - left.ModifiedStart;
                }

                mergedChange = new DiffChange(originalStart, originalLength, modifiedStart, modifiedLength);
                return(true);
            }
            else
            {
                mergedChange = null;
                return(false);
            }
        }
        /// <summary>
        /// This methods combines two <see cref="IDiffChange"/> objects into one.
        /// </summary>
        /// <param name="diffChange">The diff change to add.</param>
        /// <returns>A new instance of <see cref="IDiffChange"/> that represents <c>this</c> + <see cref="diffChange"/>.</returns>
        public IDiffChange Add(IDiffChange diffChange)
        {
            if (diffChange == null)
            {
                return(this);
            }

            int originalStart = Math.Min(OriginalStart, diffChange.OriginalStart);
            int originalEnd   = Math.Max(OriginalEnd, diffChange.OriginalEnd);
            int modifiedStart = Math.Min(ModifiedStart, diffChange.ModifiedStart);
            int modifiedEnd   = Math.Max(ModifiedEnd, diffChange.ModifiedEnd);

            DiffChangeType changeType;

            if (ChangeType == diffChange.ChangeType && OriginalStart - diffChange.OriginalEnd == 0 && ModifiedStart - diffChange.ModifiedEnd == 0)
            {
                changeType = ChangeType;
            }
            else
            {
                changeType = DiffChangeType.Change;
            }

            return(new DiffChange
            {
                ChangeType = changeType,
                OriginalStart = originalStart,
                OriginalEnd = originalEnd,
                OriginalLength = originalEnd - originalStart,
                ModifiedStart = modifiedStart,
                ModifiedEnd = modifiedEnd,
                ModifiedLength = modifiedEnd - modifiedStart
            });
        }
        /// <summary>
        /// Get a committed text from the original sequence of the <see cref="IDiffChange"/>.
        /// </summary>
        /// <param name="diffChange">Information about a specific difference between two sequences.</param>
        /// <param name="removeLastLineTerminator">Remove line terminator for the last line of the returned text, if line terminator is present.</param>
        /// <returns>Original text from the <see cref="IDiffChange"/>.</returns>
        public string GetOriginalText(IDiffChange diffChange, bool removeLastLineTerminator)
        {
            if (diffChange.ChangeType == DiffChangeType.Insert)
            {
                return(string.Empty);
            }

            return(GetOriginalText(diffChange.OriginalStart, diffChange.OriginalEnd, removeLastLineTerminator));
        }
        /// <summary>
        /// Get a local text from the modified sequence of the <see cref="IDiffChange"/>.
        /// </summary>
        /// <param name="diffChange">Information about a specific difference between two sequences.</param>
        /// <param name="removeLastLineTerminator">Remove line terminator for the last line of the returned text, if line terminator is present.</param>
        /// <returns>Modified text from the <see cref="IDiffChange"/>.</returns>
        public string GetModifiedText(IDiffChange diffChange, bool removeLastLineTerminator)
        {
            if (diffChange.ChangeType == DiffChangeType.Delete)
            {
                return(string.Empty);
            }

            return(GetModifiedText(diffChange.ModifiedStart, diffChange.ModifiedEnd, removeLastLineTerminator));
        }
Пример #7
0
        /// <summary>
        /// Rollback all modified text in the document except specified <see cref="IDiffChange"/>.
        /// </summary>
        /// <param name="diffChange">Information about a specific difference between two sequences.</param>
        private void RollbackAllButThisChange(IDiffChange diffChange)
        {
            ITextEdit edit = _textView.TextBuffer.CreateEdit();
            Span      viewSpan;

            try
            {
                string modifiedRegionText = _marginCore.GetModifiedText(diffChange, false);
                string originalText       = _marginCore.GetOriginalText();

                edit.Delete(0, edit.Snapshot.Length);
                edit.Insert(0, originalText);
                ApplyEdit(edit, "Undo Modified Text");

                edit = _textView.TextBuffer.CreateEdit();

                ITextSnapshotLine startLine = edit.Snapshot.GetLineFromLineNumber(diffChange.OriginalStart);
                ITextSnapshotLine endLine   = edit.Snapshot.GetLineFromLineNumber(diffChange.OriginalEnd);
                int start  = startLine.Start.Position;
                int length = endLine.EndIncludingLineBreak.Position - start;

                switch (diffChange.ChangeType)
                {
                case DiffChangeType.Insert:
                    edit.Insert(start, modifiedRegionText);
                    viewSpan = new Span(start, modifiedRegionText.Length);
                    break;

                case DiffChangeType.Delete:
                    edit.Delete(start, length);
                    viewSpan = new Span(start, 0);
                    break;

                case DiffChangeType.Change:
                    edit.Replace(start, length, modifiedRegionText);
                    viewSpan = new Span(start, modifiedRegionText.Length);
                    break;

                default:
                    throw new InvalidEnumArgumentException();
                }

                ApplyEdit(edit, "Restore Modified Region");
            }
            catch (Exception)
            {
                edit.Cancel();
                throw;
            }

            var viewSnapshotSpan = new SnapshotSpan(_textView.TextSnapshot, viewSpan);

            _textView.ViewScroller.EnsureSpanVisible(viewSnapshotSpan, EnsureSpanVisibleOptions.AlwaysCenter);
        }
Пример #8
0
        /// <summary>
        /// Event handler that occurs when the left mouse button is released while the mouse pointer is over the margin element.
        /// </summary>
        /// <param name="sender">Event sender (the margin element).</param>
        /// <param name="args">Event arguments.</param>
        private void OnMarginElementMouseLeftButtonUp(object sender, MouseButtonEventArgs args)
        {
            var element = (FrameworkElement)sender;

            // Disable copying commited text if diff inserted (otherwise an empty string is always copied).
            dynamic     data       = element.Tag;
            IDiffChange diffChange = data.DiffChangeInfo;

            _copyMenuItem.IsEnabled = diffChange.ChangeType != DiffChangeType.Insert;

            _contextMenu.PlacementTarget = element;
            _contextMenu.IsOpen          = true;
            args.Handled = true;
        }
Пример #9
0
        /// <summary>
        /// Event handler that occurs when the mouse pointer enters the bounds of the margin element.
        /// </summary>
        /// <param name="sender">Event sender (the margin element).</param>
        /// <param name="args">Event arguments.</param>
        private void OnMarginElementMouseEnter(object sender, MouseEventArgs args)
        {
            var         marginElement = (FrameworkElement)sender;
            dynamic     data          = marginElement.Tag;
            IDiffChange diffChange    = data.DiffChangeInfo;

            if (marginElement.ToolTip == null)
            {
                ToolTipService.SetShowDuration(marginElement, 3600000);
                string text = _marginCore.GetOriginalText(diffChange, true);
                marginElement.ToolTip = text;

                // TODO: Если регион свернут, то тултип будет только для первого изменения в нем, т.к. ContainsChanges возвращает первую пересекаемую строку.
                // Все пересекаемые строки не учитываются. Нужно добавить поддержку всех пересекаемых строк, но только для показа тултипа, чтобы не замедлять рендеринг.
            }
        }
        /// <summary>
        /// Add the changed line in the collection.
        /// </summary>
        /// <param name="line">A line of text from an <see cref="ITextSnapshot"/>.</param>
        /// <param name="diffChangeInfo">Information about the difference of line.</param>
        public void Add(ITextSnapshotLine line, IDiffChange diffChangeInfo)
        {
            ITextSnapshotLine existsLine = _diffDict.Keys.FirstOrDefault(x => x.LineNumber == line.LineNumber);

            if (existsLine != null)
            {
                IDiffChange oldDiffChangeInfo = _diffDict[existsLine];
                diffChangeInfo = oldDiffChangeInfo.Add(diffChangeInfo);

                _dict[oldDiffChangeInfo.ChangeType].Remove(existsLine);
                _diffDict.Remove(existsLine);
            }

            _dict[diffChangeInfo.ChangeType].Add(line);
            _diffDict[line] = diffChangeInfo;
        }
Пример #11
0
        /// <summary>
        /// This methods combines two DiffChange objects into one
        /// </summary>
        /// <param name="diffChange">The diff change to add</param>
        /// <returns>A IDiffChange object that represnets this + diffChange</returns>
        public IDiffChange Add(IDiffChange diffChange)
        {
            //If the diff change is null then just return this
            if (diffChange == null)
            {
                return(this);
            }

            int originalStart = Math.Min(this.OriginalStart, diffChange.OriginalStart);
            int originalEnd   = Math.Max(this.OriginalEnd, diffChange.OriginalEnd);

            int modifiedStart = Math.Min(this.ModifiedStart, diffChange.ModifiedStart);
            int modifiedEnd   = Math.Max(this.ModifiedEnd, diffChange.ModifiedEnd);

            return(new DiffChange(originalStart, originalEnd - originalStart, modifiedStart, modifiedEnd - modifiedStart));
        }
Пример #12
0
        /// <summary>
        /// Event handler that occurs when "Compare region with diff tool" menu item is clicked.
        /// </summary>
        /// <param name="sender">Event sender (the <see cref="MenuItem"/>).</param>
        /// <param name="routedEventArgs">Event arguments.</param>
        private void CompareChangeRegionMenuItemOnClick(object sender, RoutedEventArgs routedEventArgs)
        {
            try
            {
                var         marginElement = (FrameworkElement)_contextMenu.PlacementTarget;
                dynamic     data          = marginElement.Tag;
                IDiffChange diffChange    = data.DiffChangeInfo;

                CompareChangeRegion(diffChange);

                routedEventArgs.Handled = true;
            }
            catch (Exception ex)
            {
                ShowException(ex);
            }
        }
Пример #13
0
        /// <summary>
        /// Rollback modified text.
        /// </summary>
        /// <param name="diffChange">Information about a specific difference between two sequences.</param>
        private void RollbackChange(IDiffChange diffChange)
        {
            ITextEdit edit = _textView.TextBuffer.CreateEdit();

            try
            {
                ITextSnapshot     snapshot  = edit.Snapshot;
                ITextSnapshotLine startLine = snapshot.GetLineFromLineNumber(diffChange.ModifiedStart);
                ITextSnapshotLine endLine   = snapshot.GetLineFromLineNumber(diffChange.ModifiedEnd);

                int start;
                if (diffChange.ChangeType != DiffChangeType.Delete)
                {
                    start = startLine.Start.Position;
                    int length = endLine.EndIncludingLineBreak.Position - start;
                    edit.Delete(start, length);
                }
                else
                {
                    if (startLine.LineNumber == 0 && endLine.LineNumber == 0)
                    {
                        start = startLine.Start.Position;
                    }
                    else
                    {
                        start = startLine.EndIncludingLineBreak.Position;
                    }
                }

                if (diffChange.ChangeType != DiffChangeType.Insert)
                {
                    string text = _marginCore.GetOriginalText(diffChange, false);
                    edit.Insert(start, text);
                }

                ApplyEdit(edit, "Rollback Modified Region");
            }
            catch (Exception)
            {
                edit.Cancel();
                throw;
            }
        }
        /// <summary>
        /// Creates a <see cref="DiffChange"/> based on the <see cref="IDiffChange"/>.
        /// </summary>
        /// <param name="diffChange">Information about a specific difference between two sequences.</param>
        public DiffChange(IDiffChange diffChange)
        {
            ChangeType = diffChange.ChangeType;

            OriginalStart = diffChange.OriginalStart;
            OriginalEnd = Math.Max(0, diffChange.OriginalEnd - 1); // exclusive bound to inclusive.
            OriginalLength = diffChange.OriginalLength;

            if (ChangeType == DiffChangeType.Delete)
            {
                ModifiedStart = Math.Max(0, diffChange.ModifiedEnd - 1); // exclusive upper bound.
                ModifiedEnd = Math.Max(0, diffChange.ModifiedStart - 1); // exclusive lower bound.
            }
            else
            {
                ModifiedStart = diffChange.ModifiedStart;
                ModifiedEnd = Math.Max(0, diffChange.ModifiedEnd - 1); // exclusive bound to inclusive.
            }

            ModifiedLength = diffChange.ModifiedLength;
        }
        /// <summary>
        /// Creates a <see cref="DiffChange"/> based on the <see cref="IDiffChange"/>.
        /// </summary>
        /// <param name="diffChange">Information about a specific difference between two sequences.</param>
        public DiffChange(IDiffChange diffChange)
        {
            ChangeType = diffChange.ChangeType;

            OriginalStart  = diffChange.OriginalStart;
            OriginalEnd    = Math.Max(0, diffChange.OriginalEnd - 1); // exclusive bound to inclusive.
            OriginalLength = diffChange.OriginalLength;

            if (ChangeType == DiffChangeType.Delete)
            {
                ModifiedStart = Math.Max(0, diffChange.ModifiedEnd - 1);   // exclusive upper bound.
                ModifiedEnd   = Math.Max(0, diffChange.ModifiedStart - 1); // exclusive lower bound.
            }
            else
            {
                ModifiedStart = diffChange.ModifiedStart;
                ModifiedEnd   = Math.Max(0, diffChange.ModifiedEnd - 1); // exclusive bound to inclusive.
            }

            ModifiedLength = diffChange.ModifiedLength;
        }
Пример #16
0
        /// <summary>
        /// Copy commited text to the Clipboard.
        /// </summary>
        /// <param name="diffChange">Information about a specific difference between two sequences.</param>
        private void CopyCommitedText(IDiffChange diffChange)
        {
            string text = _marginCore.GetOriginalText(diffChange, true);

            Clipboard.SetText(text, TextDataFormat.UnicodeText);
        }
 /// <summary>
 /// Copy commited text to the Clipboard.
 /// </summary>
 /// <param name="diffChange">Information about a specific difference between two sequences.</param>
 private void CopyCommitedText(IDiffChange diffChange)
 {
     string text = _marginCore.GetOriginalText(diffChange, true);
     Clipboard.SetText(text, TextDataFormat.UnicodeText);
 }
Пример #18
0
        //*************************************************************************
        /// <summary>
        /// Computes the differences between the given original and modified sequences.
        /// </summary>
        /// <param name="original">The original sequence.</param>
        /// <param name="modified">The modified sequence.</param>
        /// <param name="elementComparer">The diff element comparer.</param>
        /// <returns>The set of differences between the sequences.</returns>
        //*************************************************************************
        public IDiffChange[] Diff(IList <T> original, IList <T> modified, IEqualityComparer <T> elementComparer,
                                  ContinueDifferencePredicate <T> predicate)
        {
            Debug.Assert(original != null, "original is null");
            Debug.Assert(modified != null, "modified is null");
            Debug.Assert(elementComparer != null, "elementComparer is null");

            m_original        = original;
            m_modified        = modified;
            m_elementComparer = elementComparer;
            m_predicate       = predicate;

            m_originalIds = new int[OriginalSequence.Count];
            m_modifiedIds = new int[ModifiedSequence.Count];

            int originalStart = 0;
            int originalEnd   = OriginalSequence.Count - 1;
            int modifiedStart = 0;
            int modifiedEnd   = ModifiedSequence.Count - 1;

            // Find the start of the differences
            while (originalStart <= originalEnd && modifiedStart <= modifiedEnd &&
                   ElementsAreEqual(originalStart, modifiedStart))
            {
                originalStart++;
                modifiedStart++;
            }

            // Find the end of the differences
            while (originalEnd >= originalStart && modifiedEnd >= modifiedStart &&
                   ElementsAreEqual(originalEnd, modifiedEnd))
            {
                originalEnd--;
                modifiedEnd--;
            }

            // In the very special case where one of the ranges is negative
            // Either the sequences are identical, or there is exactly one insertion
            // or there is exactly one deletion. We have no need to compute the diffs.
            if (originalStart > originalEnd || modifiedStart > modifiedEnd)
            {
                IDiffChange[] changes;

                if (modifiedStart <= modifiedEnd)
                {
                    Debug.Assert(originalStart == originalEnd + 1, "originalStart should only be one more than originalEnd");

                    // All insertions
                    changes    = new IDiffChange[1];
                    changes[0] = new DiffChange(originalStart, 0,
                                                modifiedStart,
                                                modifiedEnd - modifiedStart + 1);
                }
                else if (originalStart <= originalEnd)
                {
                    Debug.Assert(modifiedStart == modifiedEnd + 1, "modifiedStart should only be one more than modifiedEnd");

                    // All deletions
                    changes    = new IDiffChange[1];
                    changes[0] = new DiffChange(originalStart,
                                                originalEnd - originalStart + 1,
                                                modifiedStart, 0);
                }
                else
                {
                    Debug.Assert(originalStart == originalEnd + 1, "originalStart should only be one more than originalEnd");
                    Debug.Assert(modifiedStart == modifiedEnd + 1, "modifiedStart should only be one more than modifiedEnd");

                    // Identical sequences - No differences
                    changes = new IDiffChange[0];
                }

                return(changes);
            }

            // Now that we have our bounds, calculate unique ids for all
            // IDiffElements in the bounded range. That way, we speed up
            // element comparisons during the diff computation.
            ComputeUniqueIdentifiers(originalStart, originalEnd,
                                     modifiedStart, modifiedEnd);

            // Compute the diffences on the bounded range
            return(ComputeDiff(originalStart, originalEnd,
                               modifiedStart, modifiedEnd));
        }
        /// <summary>
        /// Rollback modified text.
        /// </summary>
        /// <param name="diffChange">Information about a specific difference between two sequences.</param>
        private void RollbackChange(IDiffChange diffChange)
        {
            ITextEdit edit = _textView.TextBuffer.CreateEdit();

            try
            {
                ITextSnapshot snapshot = edit.Snapshot;
                ITextSnapshotLine startLine = snapshot.GetLineFromLineNumber(diffChange.ModifiedStart);
                ITextSnapshotLine endLine = snapshot.GetLineFromLineNumber(diffChange.ModifiedEnd);

                int start;
                if (diffChange.ChangeType != DiffChangeType.Delete)
                {
                    start = startLine.Start.Position;
                    int length = endLine.EndIncludingLineBreak.Position - start;
                    edit.Delete(start, length);
                }
                else
                {
                    if (startLine.LineNumber == 0 && endLine.LineNumber == 0)
                        start = startLine.Start.Position;
                    else
                        start = startLine.EndIncludingLineBreak.Position;
                }

                if (diffChange.ChangeType != DiffChangeType.Insert)
                {
                    string text = _marginCore.GetOriginalText(diffChange, false);
                    edit.Insert(start, text);
                }

                ApplyEdit(edit, "Rollback Modified Region");
            }
            catch (Exception)
            {
                edit.Cancel();
                throw;
            }
        }
Пример #20
0
        //*************************************************************************
        /// <summary>
        /// Private helper method which computes the differences on the bounded range
        /// recursively.
        /// </summary>
        /// <returns>An array of the differences between the two input
        /// sequences.</returns>
        //*************************************************************************
        private IDiffChange[] ComputeDiffRecursive(int originalStart, int originalEnd,
                                                   int modifiedStart, int modifiedEnd,
                                                   out bool quitEarly)
        {
            quitEarly = false;

            // Find the start of the differences
            while (originalStart <= originalEnd && modifiedStart <= modifiedEnd &&
                   ElementsAreEqual(originalStart, modifiedStart))
            {
                originalStart++;
                modifiedStart++;
            }

            // Find the end of the differences
            while (originalEnd >= originalStart && modifiedEnd >= modifiedStart &&
                   ElementsAreEqual(originalEnd, modifiedEnd))
            {
                originalEnd--;
                modifiedEnd--;
            }

            // In the special case where we either have all insertions or all deletions or the sequences are identical
            if (originalStart > originalEnd || modifiedStart > modifiedEnd)
            {
                IDiffChange[] changes;

                if (modifiedStart <= modifiedEnd)
                {
                    Debug.Assert(originalStart == originalEnd + 1, "originalStart should only be one more than originalEnd");

                    // All insertions
                    changes    = new IDiffChange[1];
                    changes[0] = new DiffChange(originalStart, 0,
                                                modifiedStart,
                                                modifiedEnd - modifiedStart + 1);
                }
                else if (originalStart <= originalEnd)
                {
                    Debug.Assert(modifiedStart == modifiedEnd + 1, "modifiedStart should only be one more than modifiedEnd");

                    // All deletions
                    changes    = new IDiffChange[1];
                    changes[0] = new DiffChange(originalStart,
                                                originalEnd - originalStart + 1,
                                                modifiedStart, 0);
                }
                else
                {
                    Debug.Assert(originalStart == originalEnd + 1, "originalStart should only be one more than originalEnd");
                    Debug.Assert(modifiedStart == modifiedEnd + 1, "modifiedStart should only be one more than modifiedEnd");

                    // Identical sequences - No differences
                    changes = new IDiffChange[0];
                }

                return(changes);
            }

            // This problem can be solved using the Divide-And-Conquer technique.
            int midOriginal, midModified;

            IDiffChange[] result = ComputeRecursionPoint(originalStart, originalEnd, modifiedStart, modifiedEnd,
                                                         out midOriginal, out midModified, out quitEarly);

            if (result != null)
            {
                // Result is not-null when there was enough memory to compute the changes while
                // searching for the recursion point
                return(result);
            }
            else if (!quitEarly)
            {
                // We can break the problem down recursively by finding the changes in the
                // First Half:   (originalStart, modifiedStart) to (midOriginal, midModified)
                // Second Half:  (midOriginal + 1, minModified + 1) to (originalEnd, modifiedEnd)
                // NOTE: ComputeDiff() is inclusive, therefore the second range starts on the next point
                IDiffChange[] leftChanges  = ComputeDiffRecursive(originalStart, midOriginal, modifiedStart, midModified, out quitEarly);
                IDiffChange[] rightChanges = new IDiffChange[0];

                if (!quitEarly)
                {
                    rightChanges = ComputeDiffRecursive(midOriginal + 1, originalEnd, midModified + 1, modifiedEnd, out quitEarly);
                }
                else
                {
                    // We did't have time to finish the first half, so we don't have time to compute this half.
                    // Consider the entire rest of the sequence different.
                    rightChanges = new DiffChange[]
                    {
                        new DiffChange(midOriginal + 1, originalEnd - (midOriginal + 1) + 1,
                                       midModified + 1, modifiedEnd - (midModified + 1) + 1)
                    };
                }

                return(ConcatenateChanges(leftChanges, rightChanges));
            }

            // If we hit here, we quit early, and so can't return anything meaningful
            return(new DiffChange[]
            {
                new DiffChange(originalStart, originalEnd - originalStart + 1,
                               modifiedStart, modifiedEnd - modifiedStart + 1)
            });
        }
Пример #21
0
        //*************************************************************************
        /// <summary>
        /// Given the range to compute the diff on, this method finds the point:
        /// (midOriginal, midModified)
        /// that exists in the middle of the LCS of the two sequences and
        /// is the point at which the LCS problem may be broken down recursively.
        /// This method will try to keep the LCS trace in memory. If the LCS recursion
        /// point is calculated and the full trace is available in memory, then this method
        /// will return the change list.
        /// </summary>
        /// <param name="originalStart">The start bound of the original sequence range</param>
        /// <param name="originalEnd">The end bound of the original sequence range</param>
        /// <param name="modifiedStart">The start bound of the modified sequence range</param>
        /// <param name="modifiedEnd">The end bound of the modified sequence range</param>
        /// <param name="midOriginal">The middle point of the original sequence range</param>
        /// <param name="midModified">The middle point of the modified sequence range</param>
        /// <returns>The diff changes, if available, otherwise null</returns>
        //*************************************************************************
        private IDiffChange[] ComputeRecursionPoint(int originalStart, int originalEnd,
                                                    int modifiedStart, int modifiedEnd,
                                                    out int midOriginal, out int midModified,
                                                    out bool quitEarly)
        {
            int originalIndex, modifiedIndex;
            int diagonalForwardStart = 0, diagonalForwardEnd = 0;
            int diagonalReverseStart = 0, diagonalReverseEnd = 0;
            int numDifferences;

            // To traverse the edit graph and produce the proper LCS, our actual
            // start position is just outside the given boundary
            originalStart--;
            modifiedStart--;

            // We set these up to make the compiler happy, but they will
            // be replaced before we return with the actual recursion point
            midOriginal = 0;
            midModified = 0;

            // Clear out the history
            m_forwardHistory.Clear();
            m_reverseHistory.Clear();

            // Each cell in the two arrays corresponds to a diagonal in the edit graph.
            // The integer value in the cell represents the originalIndex of the furthest
            // reaching point found so far that ends in that diagonal.
            // The modifiedIndex can be computed mathematically from the originalIndex and the diagonal number.
            int maxDifferences = (originalEnd - originalStart) + (modifiedEnd - modifiedStart);
            int numDiagonals   = maxDifferences + 1;

            int[] forwardPoints = new int[numDiagonals];
            int[] reversePoints = new int[numDiagonals];
            // diagonalForwardBase: Index into forwardPoints of the diagonal which passes through (originalStart, modifiedStart)
            // diagonalReverseBase: Index into reversePoints of the diagonal which passes through (originalEnd, modifiedEnd)
            int diagonalForwardBase = (modifiedEnd - modifiedStart);
            int diagonalReverseBase = (originalEnd - originalStart);
            // diagonalForwardOffset: Geometric offset which allows modifiedIndex to be computed from originalIndex and the
            //    diagonal number (relative to diagonalForwardBase)
            // diagonalReverseOffset: Geometric offset which allows modifiedIndex to be computed from originalIndex and the
            //    diagonal number (relative to diagonalReverseBase)
            int diagonalForwardOffset = (originalStart - modifiedStart);
            int diagonalReverseOffset = (originalEnd - modifiedEnd);

            // delta: The difference between the end diagonal and the start diagonal. This is used to relate diagonal numbers
            //   relative to the start diagonal with diagonal numbers relative to the end diagonal.
            // The Even/Oddn-ness of this delta is important for determining when we should check for overlap
            int  delta       = diagonalReverseBase - diagonalForwardBase;
            bool deltaIsEven = (delta % 2 == 0);

            // Here we set up the start and end points as the furthest points found so far
            // in both the forward and reverse directions, respectively
            forwardPoints[diagonalForwardBase] = originalStart;
            reversePoints[diagonalReverseBase] = originalEnd;

            // Remember if we quit early, and thus need to do a best-effort result instead of a real result.
            quitEarly = false;

            // A couple of points:
            // --With this method, we iterate on the number of differences between the two sequences.
            //   The more differences there actually are, the longer this will take.
            // --Also, as the number of differences increases, we have to search on diagonals further
            //   away from the reference diagonal (which is diagonalForwardBase for forward, diagonalReverseBase for reverse).
            // --We extend on even diagonals (relative to the reference diagonal) only when numDifferences
            //   is even and odd diagonals only when numDifferences is odd.
            for (numDifferences = 1; numDifferences <= (maxDifferences / 2) + 1; numDifferences++)
            {
                int furthestOriginalIndex = 0;
                int furthestModifiedIndex = 0;

                // Run the algorithm in the forward direction
                diagonalForwardStart = ClipDiagonalBound(diagonalForwardBase - numDifferences,
                                                         numDifferences,
                                                         diagonalForwardBase,
                                                         numDiagonals);
                diagonalForwardEnd = ClipDiagonalBound(diagonalForwardBase + numDifferences,
                                                       numDifferences,
                                                       diagonalForwardBase,
                                                       numDiagonals);
                for (int diagonal = diagonalForwardStart; diagonal <= diagonalForwardEnd; diagonal += 2)
                {
                    // STEP 1: We extend the furthest reaching point in the present diagonal
                    // by looking at the diagonals above and below and picking the one whose point
                    // is further away from the start point (originalStart, modifiedStart)
                    if (diagonal == diagonalForwardStart || (diagonal < diagonalForwardEnd &&
                                                             forwardPoints[diagonal - 1] < forwardPoints[diagonal + 1]))
                    {
                        originalIndex = forwardPoints[diagonal + 1];
                    }
                    else
                    {
                        originalIndex = forwardPoints[diagonal - 1] + 1;
                    }
                    modifiedIndex = originalIndex - (diagonal - diagonalForwardBase) - diagonalForwardOffset;

                    // Save the current originalIndex so we can test for false overlap in step 3
                    int tempOriginalIndex = originalIndex;

                    // STEP 2: We can continue to extend the furthest reaching point in the present diagonal
                    // so long as the elements are equal.
                    while (originalIndex < originalEnd && modifiedIndex < modifiedEnd &&
                           ElementsAreEqual(originalIndex + 1, modifiedIndex + 1))
                    {
                        originalIndex++;
                        modifiedIndex++;
                    }
                    forwardPoints[diagonal] = originalIndex;

                    if (originalIndex + modifiedIndex > furthestOriginalIndex + furthestModifiedIndex)
                    {
                        furthestOriginalIndex = originalIndex;
                        furthestModifiedIndex = modifiedIndex;
                    }

                    // STEP 3: If delta is odd (overlap first happens on forward when delta is odd)
                    // and diagonal is in the range of reverse diagonals computed for numDifferences-1
                    // (the previous iteration; we havent computed reverse diagonals for numDifferences yet)
                    // then check for overlap.
                    if (!deltaIsEven && Math.Abs(diagonal - diagonalReverseBase) <= (numDifferences - 1))
                    {
                        if (originalIndex >= reversePoints[diagonal])
                        {
                            midOriginal = originalIndex;
                            midModified = modifiedIndex;

                            if (tempOriginalIndex <= reversePoints[diagonal] &&
                                MaxDifferencesHistory > 0 && numDifferences <= (MaxDifferencesHistory + 1))
                            {
                                // BINGO! We overlapped, and we have the full trace in memory!
                                goto WALKTRACE;
                            }
                            else
                            {
                                // Either false overlap, or we didn't have enough memory for the full trace
                                // Just return the recursion point
                                return(null);
                            }
                        }
                    }
                }

                // Check to see if we should be quitting early, before moving on to the next iteration.
                int matchLengthOfLongest =
                    ((furthestOriginalIndex - originalStart) + (furthestModifiedIndex - modifiedStart) - numDifferences) / 2;

                if (ContinueDifferencePredicate != null &&
                    !ContinueDifferencePredicate(furthestOriginalIndex, OriginalSequence, matchLengthOfLongest))
                {
                    // We can't finish, so skip ahead to generating a result from what we have.
                    quitEarly = true;

                    // Use the furthest distance we got in the forward direction.
                    midOriginal = furthestOriginalIndex;
                    midModified = furthestModifiedIndex;

                    if (matchLengthOfLongest > 0 && MaxDifferencesHistory > 0 && numDifferences <= (MaxDifferencesHistory + 1))
                    {
                        // Enough of the history is in memory to walk it backwards
                        goto WALKTRACE;
                    }
                    else
                    {
                        // We didn't actually remember enough of the history.

                        //Since we are quiting the diff early, we need to shift back the originalStart and modified start
                        //back into the boundary limits since we decremented their value above beyond the boundary limit.
                        originalStart++;
                        modifiedStart++;

                        return(new DiffChange[]
                        {
                            new DiffChange(originalStart, originalEnd - originalStart + 1,
                                           modifiedStart, modifiedEnd - modifiedStart + 1)
                        });
                    }
                }

                // Run the algorithm in the reverse direction
                diagonalReverseStart = ClipDiagonalBound(diagonalReverseBase - numDifferences,
                                                         numDifferences,
                                                         diagonalReverseBase,
                                                         numDiagonals);
                diagonalReverseEnd = ClipDiagonalBound(diagonalReverseBase + numDifferences,
                                                       numDifferences,
                                                       diagonalReverseBase,
                                                       numDiagonals);
                for (int diagonal = diagonalReverseStart; diagonal <= diagonalReverseEnd; diagonal += 2)
                {
                    // STEP 1: We extend the furthest reaching point in the present diagonal
                    // by looking at the diagonals above and below and picking the one whose point
                    // is further away from the start point (originalEnd, modifiedEnd)
                    if (diagonal == diagonalReverseStart || (diagonal < diagonalReverseEnd &&
                                                             reversePoints[diagonal - 1] >= reversePoints[diagonal + 1]))
                    {
                        originalIndex = reversePoints[diagonal + 1] - 1;
                    }
                    else
                    {
                        originalIndex = reversePoints[diagonal - 1];
                    }
                    modifiedIndex = originalIndex - (diagonal - diagonalReverseBase) - diagonalReverseOffset;

                    // Save the current originalIndex so we can test for false overlap
                    int tempOriginalIndex = originalIndex;

                    // STEP 2: We can continue to extend the furthest reaching point in the present diagonal
                    // as long as the elements are equal.
                    while (originalIndex > originalStart && modifiedIndex > modifiedStart &&
                           ElementsAreEqual(originalIndex, modifiedIndex))
                    {
                        originalIndex--;
                        modifiedIndex--;
                    }
                    reversePoints[diagonal] = originalIndex;

                    // STEP 4: If delta is even (overlap first happens on reverse when delta is even)
                    // and diagonal is in the range of forward diagonals computed for numDifferences
                    // then check for overlap.
                    if (deltaIsEven && Math.Abs(diagonal - diagonalForwardBase) <= numDifferences)
                    {
                        if (originalIndex <= forwardPoints[diagonal])
                        {
                            midOriginal = originalIndex;
                            midModified = modifiedIndex;

                            if (tempOriginalIndex >= forwardPoints[diagonal] &&
                                MaxDifferencesHistory > 0 && numDifferences <= (MaxDifferencesHistory + 1))
                            {
                                // BINGO! We overlapped, and we have the full trace in memory!
                                goto WALKTRACE;
                            }
                            else
                            {
                                // Either false overlap, or we didn't have enough memory for the full trace
                                // Just return the recursion point
                                return(null);
                            }
                        }
                    }
                }

                // Save current vectors to history before the next iteration
                if (numDifferences <= MaxDifferencesHistory)
                {
                    // We are allocating space for one extra int, which we fill with
                    // the index of the diagonal base index
                    int[] temp = new int[diagonalForwardEnd - diagonalForwardStart + 2];
                    temp[0] = diagonalForwardBase - diagonalForwardStart + 1;
                    Array.Copy(forwardPoints, diagonalForwardStart, temp, 1, diagonalForwardEnd - diagonalForwardStart + 1);
                    m_forwardHistory.Add(temp);

                    temp    = new int[diagonalReverseEnd - diagonalReverseStart + 2];
                    temp[0] = diagonalReverseBase - diagonalReverseStart + 1;
                    Array.Copy(reversePoints, diagonalReverseStart, temp, 1, diagonalReverseEnd - diagonalReverseStart + 1);
                    m_reverseHistory.Add(temp);
                }
            }

            // If we got here, then we have the full trace in history. We just have to convert it to a change list
            // NOTE: This part is a bit messy
            WALKTRACE : IDiffChange[] forwardChanges, reverseChanges;

            // First, walk backward through the forward diagonals history
            using (DiffChangeHelper changeHelper = new DiffChangeHelper())
            {
                int diagonalMin       = diagonalForwardStart;
                int diagonalMax       = diagonalForwardEnd;
                int diagonalRelative  = (midOriginal - midModified) - diagonalForwardOffset;
                int lastOriginalIndex = Int32.MinValue;
                int historyIndex      = m_forwardHistory.Count - 1;

                do
                {
                    // Get the diagonal index from the relative diagonal number
                    int diagonal = diagonalRelative + diagonalForwardBase;

                    // Figure out where we came from
                    if (diagonal == diagonalMin || (diagonal < diagonalMax &&
                                                    forwardPoints[diagonal - 1] < forwardPoints[diagonal + 1]))
                    {
                        // Vertical line (the element is an insert)
                        originalIndex = forwardPoints[diagonal + 1];
                        modifiedIndex = originalIndex - diagonalRelative - diagonalForwardOffset;
                        if (originalIndex < lastOriginalIndex)
                        {
                            changeHelper.MarkNextChange();
                        }
                        lastOriginalIndex = originalIndex;
                        changeHelper.AddModifiedElement(originalIndex + 1, modifiedIndex);
                        diagonalRelative = (diagonal + 1) - diagonalForwardBase; //Setup for the next iteration
                    }
                    else
                    {
                        // Horizontal line (the element is a deletion)
                        originalIndex = forwardPoints[diagonal - 1] + 1;
                        modifiedIndex = originalIndex - diagonalRelative - diagonalForwardOffset;
                        if (originalIndex < lastOriginalIndex)
                        {
                            changeHelper.MarkNextChange();
                        }
                        lastOriginalIndex = originalIndex - 1;
                        changeHelper.AddOriginalElement(originalIndex, modifiedIndex + 1);
                        diagonalRelative = (diagonal - 1) - diagonalForwardBase; //Setup for the next iteration
                    }

                    if (historyIndex >= 0)
                    {
                        forwardPoints       = m_forwardHistory[historyIndex];
                        diagonalForwardBase = forwardPoints[0]; //We stored this in the first spot
                        diagonalMin         = 1;
                        diagonalMax         = forwardPoints.Length - 1;
                    }
                } while (--historyIndex >= -1);

                // Ironically, we get the forward changes as the reverse of the
                // order we added them since we technically added them backwards
                forwardChanges = changeHelper.ReverseChanges;
            }

            if (quitEarly)
            {
                // Since we did quit early, assume everything after the midOriginal/midModified point is a diff

                int originalStartPoint = midOriginal + 1;
                int modifiedStartPoint = midModified + 1;

                if (forwardChanges != null && forwardChanges.Length > 0)
                {
                    IDiffChange lastForwardChange = forwardChanges[forwardChanges.Length - 1];
                    originalStartPoint = Math.Max(originalStartPoint, lastForwardChange.OriginalEnd);
                    modifiedStartPoint = Math.Max(modifiedStartPoint, lastForwardChange.ModifiedEnd);
                }

                reverseChanges = new DiffChange[]
                {
                    new DiffChange(originalStartPoint, originalEnd - originalStartPoint + 1,
                                   modifiedStartPoint, modifiedEnd - modifiedStartPoint + 1)
                };
            }
            else
            {
                // Now walk backward through the reverse diagonals history
                using (DiffChangeHelper changeHelper = new DiffChangeHelper())
                {
                    int diagonalMin       = diagonalReverseStart;
                    int diagonalMax       = diagonalReverseEnd;
                    int diagonalRelative  = (midOriginal - midModified) - diagonalReverseOffset;
                    int lastOriginalIndex = Int32.MaxValue;
                    int historyIndex      = (deltaIsEven) ? m_reverseHistory.Count - 1
                                                     : m_reverseHistory.Count - 2;

                    do
                    {
                        // Get the diagonal index from the relative diagonal number
                        int diagonal = diagonalRelative + diagonalReverseBase;

                        // Figure out where we came from
                        if (diagonal == diagonalMin || (diagonal < diagonalMax &&
                                                        reversePoints[diagonal - 1] >= reversePoints[diagonal + 1]))
                        {
                            // Horizontal line (the element is a deletion))
                            originalIndex = reversePoints[diagonal + 1] - 1;
                            modifiedIndex = originalIndex - diagonalRelative - diagonalReverseOffset;
                            if (originalIndex > lastOriginalIndex)
                            {
                                changeHelper.MarkNextChange();
                            }
                            lastOriginalIndex = originalIndex + 1;
                            changeHelper.AddOriginalElement(originalIndex + 1, modifiedIndex + 1);
                            diagonalRelative = (diagonal + 1) - diagonalReverseBase; //Setup for the next iteration
                        }
                        else
                        {
                            // Vertical line (the element is an insertion)
                            originalIndex = reversePoints[diagonal - 1];
                            modifiedIndex = originalIndex - diagonalRelative - diagonalReverseOffset;
                            if (originalIndex > lastOriginalIndex)
                            {
                                changeHelper.MarkNextChange();
                            }
                            lastOriginalIndex = originalIndex;
                            changeHelper.AddModifiedElement(originalIndex + 1, modifiedIndex + 1);
                            diagonalRelative = (diagonal - 1) - diagonalReverseBase; //Setup for the next iteration
                        }

                        if (historyIndex >= 0)
                        {
                            reversePoints       = m_reverseHistory[historyIndex];
                            diagonalReverseBase = reversePoints[0]; //We stored this in the first spot
                            diagonalMin         = 1;
                            diagonalMax         = reversePoints.Length - 1;
                        }
                    } while (--historyIndex >= -1);

                    // There are cases where the reverse history will find diffs that
                    // are correct, but not intuitive, so we need shift them.
                    reverseChanges = changeHelper.Changes;
                }
            }

            return(ConcatenateChanges(forwardChanges, reverseChanges));
        }
        /// <summary>
        /// Rollback all modified text in the document except specified <see cref="IDiffChange"/>.
        /// </summary>
        /// <param name="diffChange">Information about a specific difference between two sequences.</param>
        private void RollbackAllButThisChange(IDiffChange diffChange)
        {
            ITextEdit edit = _textView.TextBuffer.CreateEdit();
            Span viewSpan;

            try
            {
                string modifiedRegionText = _marginCore.GetModifiedText(diffChange, false);
                string originalText = _marginCore.GetOriginalText();

                edit.Delete(0, edit.Snapshot.Length);
                edit.Insert(0, originalText);
                ApplyEdit(edit, "Undo Modified Text");

                edit = _textView.TextBuffer.CreateEdit();

                ITextSnapshotLine startLine = edit.Snapshot.GetLineFromLineNumber(diffChange.OriginalStart);
                ITextSnapshotLine endLine = edit.Snapshot.GetLineFromLineNumber(diffChange.OriginalEnd);
                int start = startLine.Start.Position;
                int length = endLine.EndIncludingLineBreak.Position - start;

                switch (diffChange.ChangeType)
                {
                    case DiffChangeType.Insert:
                        edit.Insert(start, modifiedRegionText);
                        viewSpan = new Span(start, modifiedRegionText.Length);
                        break;

                    case DiffChangeType.Delete:
                        edit.Delete(start, length);
                        viewSpan = new Span(start, 0);
                        break;

                    case DiffChangeType.Change:
                        edit.Replace(start, length, modifiedRegionText);
                        viewSpan = new Span(start, modifiedRegionText.Length);
                        break;

                    default:
                        throw new InvalidEnumArgumentException();
                }

                ApplyEdit(edit, "Restore Modified Region");
            }
            catch (Exception)
            {
                edit.Cancel();
                throw;
            }

            var viewSnapshotSpan = new SnapshotSpan(_textView.TextSnapshot, viewSpan);
            _textView.ViewScroller.EnsureSpanVisible(viewSnapshotSpan, EnsureSpanVisibleOptions.AlwaysCenter);
        }
        /// <summary>
        /// Visual compare <see cref="IDiffChange"/> with the Visual Studio Diff Tool.
        /// </summary>
        /// <param name="diffChange">Information about a specific difference between two sequences.</param>
        private void CompareChangeRegion(IDiffChange diffChange)
        {
            const string SourceFileTag = "Server";
            const string TargetFileTag = "Local";

            const bool IsSourceReadOnly = true;
            const bool IsTargetReadOnly = true;

            const bool DeleteSourceOnExit = true;
            const bool DeleteTargetOnExit = true;

            const string FileLabelTemplateSingleLine = "{0};{1}[line {2}]";
            const string FileLabelTemplateBetweenLines = "{0};{1}[between lines {3}-{2}]";
            const string FileLabelTemplateLinesRange = "{0};{1}[lines {2}-{3}]";

            string sourceFileLabelTemplate;
            if (diffChange.OriginalStart == diffChange.OriginalEnd)
                sourceFileLabelTemplate = FileLabelTemplateSingleLine;
            else if (diffChange.ChangeType == DiffChangeType.Insert)
                sourceFileLabelTemplate = FileLabelTemplateBetweenLines;
            else
                sourceFileLabelTemplate = FileLabelTemplateLinesRange;

            string sourceFileLabel = string.Format(
                    sourceFileLabelTemplate,
                    _marginCore.VersionControlItem.ServerItem,
                    "T;" /* Latest version token */,
                    diffChange.OriginalStart + 1,
                    diffChange.OriginalEnd + 1);

            string targetFileLabelTemplate;
            if (diffChange.ModifiedStart == diffChange.ModifiedEnd)
                targetFileLabelTemplate = FileLabelTemplateSingleLine;
            else if (diffChange.ChangeType == DiffChangeType.Delete)
                targetFileLabelTemplate = FileLabelTemplateBetweenLines;
            else
                targetFileLabelTemplate = FileLabelTemplateLinesRange;

            string targetFileLabel = string.Format(
                    targetFileLabelTemplate,
                    _marginCore.TextDocument.FilePath,
                    string.Empty /* No additional parameter */,
                    diffChange.ModifiedStart + 1,
                    diffChange.ModifiedEnd + 1);

            const bool RemoveLastLineTerminator = true;

            string sourceText = _marginCore.GetOriginalText(diffChange, RemoveLastLineTerminator);
            string sourceFilePath = System.IO.Path.GetTempFileName();
            if (!string.IsNullOrEmpty(sourceText))
                File.WriteAllText(sourceFilePath, sourceText);

            string targetText = _marginCore.GetModifiedText(diffChange, RemoveLastLineTerminator);
            string targetFilePath = System.IO.Path.GetTempFileName();
            if (!string.IsNullOrEmpty(targetText))
                File.WriteAllText(targetFilePath, targetText);

            Difference.VisualDiffFiles(
                sourceFilePath,
                targetFilePath,
                SourceFileTag,
                TargetFileTag,
                sourceFileLabel,
                targetFileLabel,
                IsSourceReadOnly,
                IsTargetReadOnly,
                DeleteSourceOnExit,
                DeleteTargetOnExit);
        }
        /// <summary>
        /// Adds a line of text from an <see cref="ITextSnapshot"/> to <see cref="DiffLinesCollection"/>.
        /// </summary>
        /// <param name="collection">Collection of differences between the current document and the version in TFS.</param>
        /// <param name="textSnapshot">The text snapshot.</param>
        /// <param name="lineNumber">The line number.</param>
        /// <param name="diffChangeInfo">Represents information about a specific difference between two sequences.</param>
        private static void AddLineToDiffLinesCollection(DiffLinesCollection collection, ITextSnapshot textSnapshot, int lineNumber, IDiffChange diffChangeInfo)
        {
            ITextSnapshotLine line;

            try
            {
                line = textSnapshot.GetLineFromLineNumber(lineNumber);
            }
            catch (ArgumentOutOfRangeException ex)
            {
                string msg = string.Format("Line number {0} is out of range [0..{1}].", lineNumber, textSnapshot.LineCount);
                throw new ArgumentOutOfRangeException(msg, ex);
            }

            collection.Add(line, diffChangeInfo);
        }
Пример #25
0
        /// <summary>
        /// Get a local text from the modified sequence of the <see cref="IDiffChange"/>.
        /// </summary>
        /// <param name="diffChange">Information about a specific difference between two sequences.</param>
        /// <param name="removeLastLineTerminator">Remove line terminator for the last line of the returned text, if line terminator is present.</param>
        /// <returns>Modified text from the <see cref="IDiffChange"/>.</returns>
        public string GetModifiedText(IDiffChange diffChange, bool removeLastLineTerminator)
        {
            if (diffChange.ChangeType == DiffChangeType.Delete)
                return string.Empty;

            return GetModifiedText(diffChange.ModifiedStart, diffChange.ModifiedEnd, removeLastLineTerminator);
        }
Пример #26
0
        /// <summary>
        /// Visual compare <see cref="IDiffChange"/> with the Visual Studio Diff Tool.
        /// </summary>
        /// <param name="diffChange">Information about a specific difference between two sequences.</param>
        private void CompareChangeRegion(IDiffChange diffChange)
        {
            const string SourceFileTag = "Server";
            const string TargetFileTag = "Local";

            const bool IsSourceReadOnly = true;
            const bool IsTargetReadOnly = true;

            const bool DeleteSourceOnExit = true;
            const bool DeleteTargetOnExit = true;

            const string FileLabelTemplateSingleLine   = "{0};{1}[line {2}]";
            const string FileLabelTemplateBetweenLines = "{0};{1}[between lines {3}-{2}]";
            const string FileLabelTemplateLinesRange   = "{0};{1}[lines {2}-{3}]";

            string sourceFileLabelTemplate;

            if (diffChange.OriginalStart == diffChange.OriginalEnd)
            {
                sourceFileLabelTemplate = FileLabelTemplateSingleLine;
            }
            else if (diffChange.ChangeType == DiffChangeType.Insert)
            {
                sourceFileLabelTemplate = FileLabelTemplateBetweenLines;
            }
            else
            {
                sourceFileLabelTemplate = FileLabelTemplateLinesRange;
            }

            string sourceFileLabel = string.Format(
                sourceFileLabelTemplate,
                _marginCore.VersionControlItem.ServerItem,
                "T;" /* Latest version token */,
                diffChange.OriginalStart + 1,
                diffChange.OriginalEnd + 1);

            string targetFileLabelTemplate;

            if (diffChange.ModifiedStart == diffChange.ModifiedEnd)
            {
                targetFileLabelTemplate = FileLabelTemplateSingleLine;
            }
            else if (diffChange.ChangeType == DiffChangeType.Delete)
            {
                targetFileLabelTemplate = FileLabelTemplateBetweenLines;
            }
            else
            {
                targetFileLabelTemplate = FileLabelTemplateLinesRange;
            }

            string targetFileLabel = string.Format(
                targetFileLabelTemplate,
                _marginCore.TextDocument.FilePath,
                string.Empty /* No additional parameter */,
                diffChange.ModifiedStart + 1,
                diffChange.ModifiedEnd + 1);

            const bool RemoveLastLineTerminator = true;

            string sourceText     = _marginCore.GetOriginalText(diffChange, RemoveLastLineTerminator);
            string sourceFilePath = System.IO.Path.GetTempFileName();

            if (!string.IsNullOrEmpty(sourceText))
            {
                File.WriteAllText(sourceFilePath, sourceText);
            }

            string targetText     = _marginCore.GetModifiedText(diffChange, RemoveLastLineTerminator);
            string targetFilePath = System.IO.Path.GetTempFileName();

            if (!string.IsNullOrEmpty(targetText))
            {
                File.WriteAllText(targetFilePath, targetText);
            }

            Difference.VisualDiffFiles(
                sourceFilePath,
                targetFilePath,
                SourceFileTag,
                TargetFileTag,
                sourceFileLabel,
                targetFileLabel,
                IsSourceReadOnly,
                IsTargetReadOnly,
                DeleteSourceOnExit,
                DeleteTargetOnExit);
        }
Пример #27
0
        /// <summary>
        /// Get a committed text from the original sequence of the <see cref="IDiffChange"/>.
        /// </summary>
        /// <param name="diffChange">Information about a specific difference between two sequences.</param>
        /// <param name="removeLastLineTerminator">Remove line terminator for the last line of the returned text, if line terminator is present.</param>
        /// <returns>Original text from the <see cref="IDiffChange"/>.</returns>
        public string GetOriginalText(IDiffChange diffChange, bool removeLastLineTerminator)
        {
            if (diffChange.ChangeType == DiffChangeType.Insert)
                return string.Empty;

            return GetOriginalText(diffChange.OriginalStart, diffChange.OriginalEnd, removeLastLineTerminator);
        }
Пример #28
0
        /// <summary>
        /// Adds a line of text from an <see cref="ITextSnapshot"/> to <see cref="DiffLinesCollection"/>.
        /// </summary>
        /// <param name="collection">Collection of differences between the current document and the version in TFS.</param>
        /// <param name="textSnapshot">The text snapshot.</param>
        /// <param name="lineNumber">The line number.</param>
        /// <param name="diffChangeInfo">Represents information about a specific difference between two sequences.</param>
        private static void AddLineToDiffLinesCollection(DiffLinesCollection collection, ITextSnapshot textSnapshot, int lineNumber, IDiffChange diffChangeInfo)
        {
            ITextSnapshotLine line;
            try
            {
                line = textSnapshot.GetLineFromLineNumber(lineNumber);
            }
            catch (ArgumentOutOfRangeException ex)
            {
                string msg = string.Format("Line number {0} is out of range [0..{1}].", lineNumber, textSnapshot.LineCount);
                throw new ArgumentOutOfRangeException(msg, ex);
            }

            collection.Add(line, diffChangeInfo);
        }
Пример #29
0
        /// <summary>
        /// Draw margins for each diff line.
        /// </summary>
        /// <param name="diffLines">Differences between the current document and the version in TFS.</param>
        private void DrawMargins(DiffLinesCollection diffLines)
        {
            if (!Dispatcher.CheckAccess())
            {
                Dispatcher.Invoke(() => DrawMargins(diffLines));
                return;
            }

            Children.Clear();

            foreach (ITextViewLine viewLine in _textView.TextViewLines)
            {
                DiffChangeType    diffType;
                ITextSnapshotLine line;

                try
                {
                    if (ContainsChanges(viewLine, diffLines[DiffChangeType.Change], out line))
                    {
                        diffType = DiffChangeType.Change;
                    }
                    else if (ContainsChanges(viewLine, diffLines[DiffChangeType.Insert], out line))
                    {
                        diffType = DiffChangeType.Insert;

                        ITextSnapshotLine tmp;
                        if (ContainsChanges(viewLine, diffLines[DiffChangeType.Delete], out tmp))
                        {
                            diffType = DiffChangeType.Change;
                            line     = tmp;
                        }
                    }
                    else if (ContainsChanges(viewLine, diffLines[DiffChangeType.Delete], out line))
                    {
                        diffType = DiffChangeType.Delete;
                    }
                    else
                    {
                        continue;
                    }
                }
                catch (ArgumentException)
                {
                    // The supplied line is on an incorrect snapshot (old version).
                    return;
                }

                IDiffChange diffChangeInfo = diffLines[line];

                var rect = new Rectangle
                {
                    Height      = viewLine.Height,
                    Width       = MarginElementWidth,
                    Cursor      = Cursors.Hand,
                    ContextMenu = _contextMenu,
                    Tag         = new { DiffChangeInfo = diffChangeInfo, Line = line }
                };
                SetLeft(rect, MarginElementLeft);
                SetTop(rect, viewLine.Top - _textView.ViewportTop);

                switch (diffType)
                {
                case DiffChangeType.Insert:
                    rect.Fill = _marginCore.MarginSettings.AddedLineMarginBrush;
                    break;

                case DiffChangeType.Change:
                    rect.Fill = _marginCore.MarginSettings.ModifiedLineMarginBrush;
                    break;

                case DiffChangeType.Delete:
                    rect.Fill = _marginCore.MarginSettings.RemovedLineMarginBrush;
                    break;

                default:
                    throw new ArgumentOutOfRangeException();
                }

                if (diffType != DiffChangeType.Insert)
                {
                    rect.MouseEnter += OnMarginElementMouseEnter;
                }

                rect.MouseLeftButtonDown += OnMarginElementMouseLeftButtonDown;
                rect.MouseLeftButtonUp   += OnMarginElementMouseLeftButtonUp;

                Children.Add(rect);
            }
        }