Browse Source
Conflicts: SharpDevelop.sln src/AddIns/DisplayBindings/AvalonEdit.AddIn/AvalonEdit.AddIn.csprojpull/15/head
21 changed files with 1666 additions and 6 deletions
@ -0,0 +1,139 @@ |
|||||||
|
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
|
||||||
|
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
|
||||||
|
|
||||||
|
using System; |
||||||
|
using System.Collections.Generic; |
||||||
|
using System.Windows; |
||||||
|
using System.Windows.Media; |
||||||
|
using ICSharpCode.AvalonEdit.Editing; |
||||||
|
using ICSharpCode.AvalonEdit.Rendering; |
||||||
|
using ICSharpCode.SharpDevelop; |
||||||
|
using ICSharpCode.SharpDevelop.Editor; |
||||||
|
|
||||||
|
namespace ICSharpCode.AvalonEdit.AddIn |
||||||
|
{ |
||||||
|
/// <summary>
|
||||||
|
/// Description of ChangeMarkerMargin.
|
||||||
|
/// </summary>
|
||||||
|
public class ChangeMarkerMargin : AbstractMargin, IDisposable |
||||||
|
{ |
||||||
|
IChangeWatcher changeWatcher; |
||||||
|
|
||||||
|
public ChangeMarkerMargin() |
||||||
|
{ |
||||||
|
changeWatcher = new DefaultChangeWatcher(); |
||||||
|
changeWatcher.ChangeOccurred += new EventHandler(ChangeOccurred); |
||||||
|
} |
||||||
|
|
||||||
|
protected override void OnRender(DrawingContext drawingContext) |
||||||
|
{ |
||||||
|
Size renderSize = this.RenderSize; |
||||||
|
TextView textView = this.TextView; |
||||||
|
|
||||||
|
if (textView != null && textView.VisualLinesValid) { |
||||||
|
ITextEditor editor = textView.Services.GetService(typeof(ITextEditor)) as ITextEditor; |
||||||
|
changeWatcher.Initialize(editor.Document); |
||||||
|
|
||||||
|
foreach (VisualLine line in textView.VisualLines) { |
||||||
|
Rect rect = new Rect(0, line.VisualTop - textView.ScrollOffset.Y, 5, line.Height); |
||||||
|
|
||||||
|
LineChangeInfo info = changeWatcher.GetChange(editor.Document.GetLine(line.FirstDocumentLine.LineNumber)); |
||||||
|
|
||||||
|
switch (info.Change) { |
||||||
|
case ChangeType.None: |
||||||
|
break; |
||||||
|
case ChangeType.Added: |
||||||
|
drawingContext.DrawRectangle(Brushes.LightGreen, null, rect); |
||||||
|
break; |
||||||
|
case ChangeType.Modified: |
||||||
|
drawingContext.DrawRectangle(Brushes.LightBlue, null, rect); |
||||||
|
break; |
||||||
|
case ChangeType.Unsaved: |
||||||
|
drawingContext.DrawRectangle(Brushes.Yellow, null, rect); |
||||||
|
break; |
||||||
|
default: |
||||||
|
throw new Exception("Invalid value for ChangeType"); |
||||||
|
} |
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(info.DeletedLinesAfterThisLine)) { |
||||||
|
Point pt1 = new Point(5, line.VisualTop + line.Height - textView.ScrollOffset.Y - 4); |
||||||
|
Point pt2 = new Point(10, line.VisualTop + line.Height - textView.ScrollOffset.Y); |
||||||
|
Point pt3 = new Point(5, line.VisualTop + line.Height - textView.ScrollOffset.Y + 4); |
||||||
|
|
||||||
|
drawingContext.DrawGeometry(Brushes.Red, null, new PathGeometry(new List<PathFigure>() { CreateNAngle(pt1, pt2, pt3) })); |
||||||
|
} |
||||||
|
|
||||||
|
// special case for line 0
|
||||||
|
if (line.FirstDocumentLine.LineNumber == 1) { |
||||||
|
info = changeWatcher.GetChange(null); |
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(info.DeletedLinesAfterThisLine)) { |
||||||
|
Point pt1 = new Point(5, line.VisualTop - textView.ScrollOffset.Y - 4); |
||||||
|
Point pt2 = new Point(10, line.VisualTop - textView.ScrollOffset.Y); |
||||||
|
Point pt3 = new Point(5, line.VisualTop - textView.ScrollOffset.Y + 4); |
||||||
|
|
||||||
|
drawingContext.DrawGeometry(Brushes.Red, null, new PathGeometry(new List<PathFigure>() { CreateNAngle(pt1, pt2, pt3) })); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
PathFigure CreateNAngle(params Point[] points) |
||||||
|
{ |
||||||
|
if (points == null || points.Length == 0) |
||||||
|
return new PathFigure(); |
||||||
|
|
||||||
|
List<PathSegment> segs = new List<PathSegment>(); |
||||||
|
PathSegment seg = new PolyLineSegment(points, true); |
||||||
|
segs.Add(seg); |
||||||
|
|
||||||
|
return new PathFigure(points[0], segs, true); |
||||||
|
} |
||||||
|
|
||||||
|
protected override void OnTextViewChanged(TextView oldTextView, TextView newTextView) |
||||||
|
{ |
||||||
|
if (oldTextView != null) { |
||||||
|
oldTextView.VisualLinesChanged -= VisualLinesChanged; |
||||||
|
oldTextView.ScrollOffsetChanged -= ScrollOffsetChanged; |
||||||
|
} |
||||||
|
|
||||||
|
if (newTextView != null) { |
||||||
|
newTextView.VisualLinesChanged += VisualLinesChanged; |
||||||
|
newTextView.ScrollOffsetChanged += ScrollOffsetChanged; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void ChangeOccurred(object sender, EventArgs e) |
||||||
|
{ |
||||||
|
InvalidateVisual(); |
||||||
|
} |
||||||
|
|
||||||
|
void VisualLinesChanged(object sender, EventArgs e) |
||||||
|
{ |
||||||
|
InvalidateVisual(); |
||||||
|
} |
||||||
|
|
||||||
|
void ScrollOffsetChanged(object sender, EventArgs e) |
||||||
|
{ |
||||||
|
InvalidateVisual(); |
||||||
|
} |
||||||
|
|
||||||
|
protected override Size MeasureOverride(Size availableSize) |
||||||
|
{ |
||||||
|
return new Size(5, 0); |
||||||
|
} |
||||||
|
|
||||||
|
bool disposed = false; |
||||||
|
|
||||||
|
public void Dispose() |
||||||
|
{ |
||||||
|
if (!disposed) { |
||||||
|
OnTextViewChanged(TextView, null); |
||||||
|
changeWatcher.Dispose(); |
||||||
|
changeWatcher = null; |
||||||
|
disposed = true; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,192 @@ |
|||||||
|
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
|
||||||
|
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
|
||||||
|
|
||||||
|
using System; |
||||||
|
using System.Collections.Generic; |
||||||
|
using System.ComponentModel; |
||||||
|
using System.Diagnostics; |
||||||
|
using System.IO; |
||||||
|
using System.Linq; |
||||||
|
using System.Text; |
||||||
|
|
||||||
|
using ICSharpCode.AvalonEdit.AddIn.MyersDiff; |
||||||
|
using ICSharpCode.AvalonEdit.Document; |
||||||
|
using ICSharpCode.AvalonEdit.Rendering; |
||||||
|
using ICSharpCode.AvalonEdit.Utils; |
||||||
|
using ICSharpCode.SharpDevelop; |
||||||
|
using ICSharpCode.SharpDevelop.Editor; |
||||||
|
|
||||||
|
namespace ICSharpCode.AvalonEdit.AddIn |
||||||
|
{ |
||||||
|
public class DefaultChangeWatcher : IChangeWatcher, ILineTracker |
||||||
|
{ |
||||||
|
WeakLineTracker lineTracker; |
||||||
|
CompressingTreeList<LineChangeInfo> changeList; |
||||||
|
IDocument document; |
||||||
|
TextDocument textDocument; |
||||||
|
IDocument baseDocument; |
||||||
|
|
||||||
|
public event EventHandler ChangeOccurred; |
||||||
|
|
||||||
|
protected void OnChangeOccurred(EventArgs e) |
||||||
|
{ |
||||||
|
if (ChangeOccurred != null) { |
||||||
|
ChangeOccurred(this, e); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public LineChangeInfo GetChange(IDocumentLine line) |
||||||
|
{ |
||||||
|
if (line == null) |
||||||
|
return changeList[0]; |
||||||
|
|
||||||
|
return changeList[line.LineNumber]; |
||||||
|
} |
||||||
|
|
||||||
|
public void Initialize(IDocument document) |
||||||
|
{ |
||||||
|
if (changeList != null && changeList.Any()) |
||||||
|
return; |
||||||
|
|
||||||
|
this.document = document; |
||||||
|
this.textDocument = ((TextView)document.GetService(typeof(TextView))).Document; |
||||||
|
this.changeList = new CompressingTreeList<LineChangeInfo>((x, y) => x.Equals(y)); |
||||||
|
|
||||||
|
Stream baseFileStream = GetBaseVersion(); |
||||||
|
|
||||||
|
// TODO : update baseDocument on VCS actions
|
||||||
|
if (baseFileStream != null) { |
||||||
|
baseDocument = DocumentUtilitites.LoadReadOnlyDocumentFromBuffer(new StringTextBuffer(ReadAll(baseFileStream))); |
||||||
|
} |
||||||
|
|
||||||
|
SetupInitialFileState(false); |
||||||
|
|
||||||
|
lineTracker = WeakLineTracker.Register(this.textDocument, this); |
||||||
|
this.textDocument.UndoStack.PropertyChanged += UndoStackPropertyChanged; |
||||||
|
} |
||||||
|
|
||||||
|
LineChangeInfo TransformLineChangeInfo(LineChangeInfo info) |
||||||
|
{ |
||||||
|
if (info.Change == ChangeType.Unsaved) |
||||||
|
info.Change = ChangeType.Added; |
||||||
|
|
||||||
|
return info; |
||||||
|
} |
||||||
|
|
||||||
|
void SetupInitialFileState(bool update) |
||||||
|
{ |
||||||
|
if (baseDocument == null) { |
||||||
|
if (update) |
||||||
|
changeList.Transform(TransformLineChangeInfo); |
||||||
|
else |
||||||
|
changeList.InsertRange(0, document.TotalNumberOfLines + 1, LineChangeInfo.Empty); |
||||||
|
} else { |
||||||
|
changeList.Clear(); |
||||||
|
|
||||||
|
MyersDiff.MyersDiff diff = new MyersDiff.MyersDiff( |
||||||
|
new DocumentSequence(baseDocument), |
||||||
|
new DocumentSequence(document) |
||||||
|
); |
||||||
|
|
||||||
|
changeList.Add(new LineChangeInfo(ChangeType.None, "")); |
||||||
|
int lastEndLine = 0; |
||||||
|
|
||||||
|
foreach (Edit edit in diff.GetEdits()) { |
||||||
|
int beginLine = edit.BeginB; |
||||||
|
int endLine = edit.EndB; |
||||||
|
|
||||||
|
changeList.InsertRange(changeList.Count, beginLine - lastEndLine, LineChangeInfo.Empty); |
||||||
|
|
||||||
|
if (edit.EditType == ChangeType.Deleted) { |
||||||
|
LineChangeInfo change = changeList[beginLine]; |
||||||
|
|
||||||
|
for (int i = edit.BeginA; i < edit.EndA; i++) { |
||||||
|
var line = baseDocument.GetLine(i + 1); |
||||||
|
change.DeletedLinesAfterThisLine += line.Text; |
||||||
|
} |
||||||
|
|
||||||
|
changeList[beginLine] = change; |
||||||
|
} else { |
||||||
|
var change = new LineChangeInfo(edit.EditType, ""); |
||||||
|
changeList.InsertRange(changeList.Count, endLine - beginLine, change); |
||||||
|
} |
||||||
|
|
||||||
|
lastEndLine = endLine; |
||||||
|
} |
||||||
|
|
||||||
|
changeList.InsertRange(changeList.Count, textDocument.LineCount - lastEndLine, LineChangeInfo.Empty); |
||||||
|
} |
||||||
|
|
||||||
|
OnChangeOccurred(EventArgs.Empty); |
||||||
|
} |
||||||
|
|
||||||
|
string ReadAll(Stream stream) |
||||||
|
{ |
||||||
|
using (StreamReader reader = new StreamReader(stream)) { |
||||||
|
return reader.ReadToEnd(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
Stream GetBaseVersion() |
||||||
|
{ |
||||||
|
string fileName = ((ITextEditor)document.GetService(typeof(ITextEditor))).FileName; |
||||||
|
|
||||||
|
foreach (IDocumentVersionProvider provider in VersioningServices.Instance.DocumentVersionProviders) { |
||||||
|
var result = provider.OpenBaseVersion(fileName); |
||||||
|
if (result != null) |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
void UndoStackPropertyChanged(object sender, PropertyChangedEventArgs e) |
||||||
|
{ |
||||||
|
if (textDocument.UndoStack.IsOriginalFile) |
||||||
|
SetupInitialFileState(true); |
||||||
|
} |
||||||
|
|
||||||
|
void ILineTracker.BeforeRemoveLine(DocumentLine line) |
||||||
|
{ |
||||||
|
changeList.RemoveAt(line.LineNumber); |
||||||
|
} |
||||||
|
|
||||||
|
void ILineTracker.SetLineLength(DocumentLine line, int newTotalLength) |
||||||
|
{ |
||||||
|
int index = line.LineNumber; |
||||||
|
var info = changeList[index]; |
||||||
|
info.Change = ChangeType.Unsaved; |
||||||
|
changeList[index] = info; |
||||||
|
} |
||||||
|
|
||||||
|
void ILineTracker.LineInserted(DocumentLine insertionPos, DocumentLine newLine) |
||||||
|
{ |
||||||
|
int index = insertionPos.LineNumber; |
||||||
|
var firstLine = changeList[index]; |
||||||
|
var newLineInfo = new LineChangeInfo(ChangeType.Unsaved, firstLine.DeletedLinesAfterThisLine); |
||||||
|
|
||||||
|
firstLine.Change = ChangeType.Unsaved; |
||||||
|
firstLine.DeletedLinesAfterThisLine = ""; |
||||||
|
|
||||||
|
changeList.Insert(index + 1, newLineInfo); |
||||||
|
changeList[index] = firstLine; |
||||||
|
} |
||||||
|
|
||||||
|
void ILineTracker.RebuildDocument() |
||||||
|
{ |
||||||
|
changeList.Clear(); |
||||||
|
changeList.InsertRange(0, document.TotalNumberOfLines + 1, new LineChangeInfo(ChangeType.Unsaved, "")); |
||||||
|
} |
||||||
|
|
||||||
|
bool disposed = false; |
||||||
|
|
||||||
|
public void Dispose() |
||||||
|
{ |
||||||
|
if (!disposed) { |
||||||
|
lineTracker.Deregister(); |
||||||
|
this.textDocument.UndoStack.PropertyChanged -= UndoStackPropertyChanged; |
||||||
|
disposed = true; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,43 @@ |
|||||||
|
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
|
||||||
|
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
|
||||||
|
|
||||||
|
using System; |
||||||
|
using System.Collections.Generic; |
||||||
|
using ICSharpCode.SharpDevelop.Editor; |
||||||
|
|
||||||
|
namespace ICSharpCode.AvalonEdit.AddIn.MyersDiff |
||||||
|
{ |
||||||
|
public class DocumentSequence : ISequence |
||||||
|
{ |
||||||
|
IDocument document; |
||||||
|
List<int> hashCodes; |
||||||
|
|
||||||
|
public DocumentSequence(IDocument document) |
||||||
|
{ |
||||||
|
this.document = document; |
||||||
|
this.hashCodes = new List<int>(); |
||||||
|
|
||||||
|
for (int i = 1; i <= document.TotalNumberOfLines; i++) { |
||||||
|
hashCodes.Add(document.GetLine(i).Text.GetHashCode()); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public int Size() |
||||||
|
{ |
||||||
|
return document.TotalNumberOfLines; |
||||||
|
} |
||||||
|
|
||||||
|
public bool Equals(int i, ISequence other, int j) |
||||||
|
{ |
||||||
|
DocumentSequence seq = other as DocumentSequence; |
||||||
|
|
||||||
|
if (seq == null) |
||||||
|
return false; |
||||||
|
|
||||||
|
int thisLineHash = hashCodes[i]; |
||||||
|
int otherLineHash = seq.hashCodes[j]; |
||||||
|
|
||||||
|
return thisLineHash == otherLineHash; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,204 @@ |
|||||||
|
/* |
||||||
|
* Copyright (C) 2008, Johannes E. Schindelin <johannes.schindelin@gmx.de> |
||||||
|
* Copyright (C) 2009, Gil Ran <gilrun@gmail.com> |
||||||
|
* |
||||||
|
* All rights reserved. |
||||||
|
* |
||||||
|
* Redistribution and use in source and binary forms, with or |
||||||
|
* without modification, are permitted provided that the following |
||||||
|
* conditions are met: |
||||||
|
* |
||||||
|
* - Redistributions of source code must retain the above copyright |
||||||
|
* notice, this list of conditions and the following disclaimer. |
||||||
|
* |
||||||
|
* - Redistributions in binary form must reproduce the above |
||||||
|
* copyright notice, this list of conditions and the following |
||||||
|
* disclaimer in the documentation and/or other materials provided |
||||||
|
* with the distribution. |
||||||
|
* |
||||||
|
* - Neither the name of the Git Development Community nor the |
||||||
|
* names of its contributors may be used to endorse or promote |
||||||
|
* products derived from this software without specific prior |
||||||
|
* written permission. |
||||||
|
* |
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND |
||||||
|
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, |
||||||
|
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
||||||
|
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
||||||
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR |
||||||
|
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||||
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT |
||||||
|
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
||||||
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
||||||
|
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, |
||||||
|
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
||||||
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF |
||||||
|
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||||
|
*/ |
||||||
|
|
||||||
|
using System; |
||||||
|
using ICSharpCode.SharpDevelop.Editor; |
||||||
|
|
||||||
|
namespace ICSharpCode.AvalonEdit.AddIn.MyersDiff |
||||||
|
{ |
||||||
|
/// <summary>
|
||||||
|
/// A modified region detected between two versions of roughly the same content.
|
||||||
|
/// <para />
|
||||||
|
/// Regions should be specified using 0 based notation, so add 1 to the
|
||||||
|
/// start and end marks for line numbers in a file.
|
||||||
|
/// <para />
|
||||||
|
/// An edit where <code>beginA == endA && beginB > endB</code> is an insert edit,
|
||||||
|
/// that is sequence B inserted the elements in region
|
||||||
|
/// <code>[beginB, endB)</code> at <code>beginA</code>.
|
||||||
|
/// <para />
|
||||||
|
/// An edit where <code>beginA > endA && beginB > endB</code> is a replace edit,
|
||||||
|
/// that is sequence B has replaced the range of elements between
|
||||||
|
/// <code>[beginA, endA)</code> with those found in <code>[beginB, endB)</code>.
|
||||||
|
/// </summary>
|
||||||
|
public class Edit |
||||||
|
{ |
||||||
|
/// <summary>
|
||||||
|
/// Create a new empty edit.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="aStart">beginA: start and end of region in sequence A; 0 based.</param>
|
||||||
|
/// <param name="bStart">beginB: start and end of region in sequence B; 0 based.</param>
|
||||||
|
public Edit(int aStart, int bStart) |
||||||
|
: this(aStart, aStart, bStart, bStart) |
||||||
|
{ |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a new empty edit.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="aStart">beginA: start and end of region in sequence A; 0 based.</param>
|
||||||
|
/// <param name="aEnd">endA: end of region in sequence A; must be >= as.</param>
|
||||||
|
/// <param name="bStart">beginB: start and end of region in sequence B; 0 based.</param>
|
||||||
|
/// <param name="bEnd">endB: end of region in sequence B; must be >= bs.</param>
|
||||||
|
public Edit(int aStart, int aEnd, int bStart, int bEnd) |
||||||
|
{ |
||||||
|
BeginA = aStart; |
||||||
|
EndA = aEnd; |
||||||
|
|
||||||
|
BeginB = bStart; |
||||||
|
EndB = bEnd; |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the type of this region.
|
||||||
|
/// </summary>
|
||||||
|
public ChangeType EditType |
||||||
|
{ |
||||||
|
get |
||||||
|
{ |
||||||
|
if (BeginA == EndA && BeginB < EndB) |
||||||
|
return ChangeType.Added; |
||||||
|
if (BeginA < EndA && BeginB == EndB) |
||||||
|
return ChangeType.Deleted; |
||||||
|
if (BeginA == EndA && BeginB == EndB) |
||||||
|
return ChangeType.None; |
||||||
|
|
||||||
|
return ChangeType.Modified; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Start point in sequence A.
|
||||||
|
/// </summary>
|
||||||
|
public int BeginA { get; set; } |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// End point in sequence A.
|
||||||
|
/// </summary>
|
||||||
|
public int EndA { get; private set; } |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Start point in sequence B.
|
||||||
|
/// </summary>
|
||||||
|
public int BeginB { get; private set; } |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// End point in sequence B.
|
||||||
|
/// </summary>
|
||||||
|
public int EndB { get; private set; } |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Increase <see cref="EndA"/> by 1.
|
||||||
|
/// </summary>
|
||||||
|
public void ExtendA() |
||||||
|
{ |
||||||
|
EndA++; |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Increase <see cref="EndB"/> by 1.
|
||||||
|
/// </summary>
|
||||||
|
public void ExtendB() |
||||||
|
{ |
||||||
|
EndB++; |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Swap A and B, so the edit goes the other direction.
|
||||||
|
/// </summary>
|
||||||
|
public void Swap() |
||||||
|
{ |
||||||
|
int sBegin = BeginA; |
||||||
|
int sEnd = EndA; |
||||||
|
|
||||||
|
BeginA = BeginB; |
||||||
|
EndA = EndB; |
||||||
|
|
||||||
|
BeginB = sBegin; |
||||||
|
EndB = sEnd; |
||||||
|
} |
||||||
|
|
||||||
|
public override int GetHashCode() |
||||||
|
{ |
||||||
|
return BeginA ^ EndA; |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines whether the specified <see cref="T:System.Object"/> is
|
||||||
|
/// equal to the current <see cref="T:System.Object"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>
|
||||||
|
/// true if the specified <see cref="T:System.Object"/> is equal to the
|
||||||
|
/// current <see cref="T:System.Object"/>; otherwise, false.
|
||||||
|
/// </returns>
|
||||||
|
/// <param name="obj">The <see cref="T:System.Object"/> to compare with
|
||||||
|
/// the current <see cref="T:System.Object"/>.
|
||||||
|
/// </param>
|
||||||
|
/// <exception cref="T:System.NullReferenceException">
|
||||||
|
/// The <paramref name="obj"/> parameter is null.
|
||||||
|
/// </exception>
|
||||||
|
/// <filterpriority>2</filterpriority>
|
||||||
|
public override bool Equals(object obj) |
||||||
|
{ |
||||||
|
Edit e = (obj as Edit); |
||||||
|
if (e != null) |
||||||
|
{ |
||||||
|
return BeginA == e.BeginA && EndA == e.EndA && BeginB == e.BeginB && EndB == e.EndB; |
||||||
|
} |
||||||
|
|
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
public static bool operator ==(Edit left, Edit right) |
||||||
|
{ |
||||||
|
return Equals(left, right); |
||||||
|
} |
||||||
|
|
||||||
|
public static bool operator !=(Edit left, Edit right) |
||||||
|
{ |
||||||
|
return !Equals(left, right); |
||||||
|
} |
||||||
|
|
||||||
|
public override string ToString() |
||||||
|
{ |
||||||
|
ChangeType t = EditType; |
||||||
|
return t + "(" + BeginA + "-" + EndA + "," + BeginB + "-" + EndB + ")"; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,89 @@ |
|||||||
|
/* |
||||||
|
* Copyright (C) 2008, Johannes E. Schindelin <johannes.schindelin@gmx.de> |
||||||
|
* Copyright (C) 2009, Gil Ran <gilrun@gmail.com> |
||||||
|
* |
||||||
|
* All rights reserved. |
||||||
|
* |
||||||
|
* Redistribution and use in source and binary forms, with or |
||||||
|
* without modification, are permitted provided that the following |
||||||
|
* conditions are met: |
||||||
|
* |
||||||
|
* - Redistributions of source code must retain the above copyright |
||||||
|
* notice, this list of conditions and the following disclaimer. |
||||||
|
* |
||||||
|
* - Redistributions in binary form must reproduce the above |
||||||
|
* copyright notice, this list of conditions and the following |
||||||
|
* disclaimer in the documentation and/or other materials provided |
||||||
|
* with the distribution. |
||||||
|
* |
||||||
|
* - Neither the name of the Git Development Community nor the |
||||||
|
* names of its contributors may be used to endorse or promote |
||||||
|
* products derived from this software without specific prior |
||||||
|
* written permission. |
||||||
|
* |
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND |
||||||
|
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, |
||||||
|
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
||||||
|
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
||||||
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR |
||||||
|
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||||
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT |
||||||
|
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
||||||
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
||||||
|
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, |
||||||
|
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
||||||
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF |
||||||
|
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||||
|
*/ |
||||||
|
|
||||||
|
using System; |
||||||
|
|
||||||
|
namespace ICSharpCode.AvalonEdit.AddIn.MyersDiff |
||||||
|
{ |
||||||
|
/// <summary>
|
||||||
|
/// Arbitrary sequence of elements with fast comparison support.
|
||||||
|
/// <para />
|
||||||
|
/// A sequence of elements is defined to contain elements in the index range
|
||||||
|
/// <code>[0, <seealso cref="Size()"/>)</code>, like a standard Java List implementation.
|
||||||
|
/// Unlike a List, the members of the sequence are not directly obtainable, but
|
||||||
|
/// element equality can be tested if two Sequences are the same implementation.
|
||||||
|
/// <para />
|
||||||
|
/// An implementation may chose to implement the equals semantic as necessary,
|
||||||
|
/// including fuzzy matching rules such as ignoring insignificant sub-elements,
|
||||||
|
/// e.g. ignoring whitespace differences in text.
|
||||||
|
/// <para />
|
||||||
|
/// Implementations of Sequence are primarily intended for use in content
|
||||||
|
/// difference detection algorithms, to produce an list of
|
||||||
|
/// <seealso cref="Edit"/> instances describing how two Sequence instances differ.
|
||||||
|
/// </summary>
|
||||||
|
public interface ISequence |
||||||
|
{ |
||||||
|
/// <returns>
|
||||||
|
/// Total number of items in the sequence.
|
||||||
|
/// </returns>
|
||||||
|
int Size(); |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determine if the i-th member is equal to the j-th member.
|
||||||
|
/// <para />
|
||||||
|
/// Implementations must ensure <code>equals(thisIdx,other,otherIdx)</code>
|
||||||
|
/// returns the same as <code>other.equals(otherIdx,this,thisIdx)</code>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="thisIdx">
|
||||||
|
/// Index within <code>this</code> sequence; must be in the range
|
||||||
|
/// <code>[ 0, this.size() )</code>.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="other">
|
||||||
|
/// Another sequence; must be the same implementation class, that
|
||||||
|
/// is <code>this.getClass() == other.getClass()</code>. </param>
|
||||||
|
/// <param name="otherIdx">
|
||||||
|
/// Index within <code>other</code> sequence; must be in the range
|
||||||
|
/// <code>[ 0, other.size() )</code>. </param>
|
||||||
|
/// <returns>
|
||||||
|
/// true if the elements are equal; false if they are not equal.
|
||||||
|
/// </returns>
|
||||||
|
bool Equals(int thisIdx, ISequence other, int otherIdx); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,594 @@ |
|||||||
|
/* |
||||||
|
* Copyright (C) 2008, Johannes E. Schindelin <johannes.schindelin@gmx.de> |
||||||
|
* Copyright (C) 2009, Gil Ran <gilrun@gmail.com> |
||||||
|
* |
||||||
|
* All rights reserved. |
||||||
|
* |
||||||
|
* Redistribution and use in source and binary forms, with or |
||||||
|
* without modification, are permitted provided that the following |
||||||
|
* conditions are met: |
||||||
|
* |
||||||
|
* - Redistributions of source code must retain the above copyright |
||||||
|
* notice, this list of conditions and the following disclaimer. |
||||||
|
* |
||||||
|
* - Redistributions in binary form must reproduce the above |
||||||
|
* copyright notice, this list of conditions and the following |
||||||
|
* disclaimer in the documentation and/or other materials provided |
||||||
|
* with the distribution. |
||||||
|
* |
||||||
|
* - Neither the name of the Git Development Community nor the |
||||||
|
* names of its contributors may be used to endorse or promote |
||||||
|
* products derived from this software without specific prior |
||||||
|
* written permission. |
||||||
|
* |
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND |
||||||
|
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, |
||||||
|
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
||||||
|
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
||||||
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR |
||||||
|
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||||
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT |
||||||
|
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
||||||
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
||||||
|
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, |
||||||
|
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
||||||
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF |
||||||
|
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||||
|
*/ |
||||||
|
|
||||||
|
using System; |
||||||
|
using System.Collections.Generic; |
||||||
|
using ICSharpCode.SharpDevelop.Editor; |
||||||
|
|
||||||
|
namespace ICSharpCode.AvalonEdit.AddIn.MyersDiff |
||||||
|
{ |
||||||
|
/// <summary>
|
||||||
|
/// Diff algorithm, based on "An O(ND) Difference Algorithm and its
|
||||||
|
/// Variations", by Eugene Myers.
|
||||||
|
///
|
||||||
|
/// The basic idea is to put the line numbers of text A as columns ("x") and the
|
||||||
|
/// lines of text B as rows ("y"). Now you try to find the shortest "edit path"
|
||||||
|
/// from the upper left corner to the lower right corner, where you can
|
||||||
|
/// always go horizontally or vertically, but diagonally from (x,y) to
|
||||||
|
/// (x+1,y+1) only if line x in text A is identical to line y in text B.
|
||||||
|
///
|
||||||
|
/// Myers' fundamental concept is the "furthest reaching D-path on diagonal k":
|
||||||
|
/// a D-path is an edit path starting at the upper left corner and containing
|
||||||
|
/// exactly D non-diagonal elements ("differences"). The furthest reaching
|
||||||
|
/// D-path on diagonal k is the one that contains the most (diagonal) elements
|
||||||
|
/// which ends on diagonal k (where k = y - x).
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
///
|
||||||
|
/// H E L L O W O R L D
|
||||||
|
/// ____
|
||||||
|
/// L \___
|
||||||
|
/// O \___
|
||||||
|
/// W \________
|
||||||
|
///
|
||||||
|
/// Since every D-path has exactly D horizontal or vertical elements, it can
|
||||||
|
/// only end on the diagonals -D, -D+2, ..., D-2, D.
|
||||||
|
///
|
||||||
|
/// Since every furthest reaching D-path contains at least one furthest
|
||||||
|
/// reaching (D-1)-path (except for D=0), we can construct them recursively.
|
||||||
|
///
|
||||||
|
/// Since we are really interested in the shortest edit path, we can start
|
||||||
|
/// looking for a 0-path, then a 1-path, and so on, until we find a path that
|
||||||
|
/// ends in the lower right corner.
|
||||||
|
///
|
||||||
|
/// To save space, we do not need to store all paths (which has quadratic space
|
||||||
|
/// requirements), but generate the D-paths simultaneously from both sides.
|
||||||
|
/// When the ends meet, we will have found "the middle" of the path. From the
|
||||||
|
/// end points of that diagonal part, we can generate the rest recursively.
|
||||||
|
///
|
||||||
|
/// This only requires linear space.
|
||||||
|
///
|
||||||
|
/// The overall (runtime) complexity is
|
||||||
|
///
|
||||||
|
/// O(N * D^2 + 2 * N/2 * (D/2)^2 + 4 * N/4 * (D/4)^2 + ...)
|
||||||
|
/// = O(N * D^2 * 5 / 4) = O(N * D^2),
|
||||||
|
///
|
||||||
|
/// (With each step, we have to find the middle parts of twice as many regions
|
||||||
|
/// as before, but the regions (as well as the D) are halved.)
|
||||||
|
///
|
||||||
|
/// So the overall runtime complexity stays the same with linear space,
|
||||||
|
/// albeit with a larger constant factor.
|
||||||
|
/// </summary>
|
||||||
|
public class MyersDiff |
||||||
|
{ |
||||||
|
/// <summary>
|
||||||
|
/// The list of edits found during the last call to <see cref="calculateEdits()"/>
|
||||||
|
/// </summary>
|
||||||
|
protected List<Edit> edits; |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The first text to be compared. Referred to as "Text A" in the comments
|
||||||
|
/// </summary>
|
||||||
|
protected ISequence a; |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The second text to be compared. Referred to as "Text B" in the comments
|
||||||
|
/// </summary>
|
||||||
|
protected ISequence b; |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The only constructor
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="a">the text A which should be compared</param>
|
||||||
|
/// <param name="b">the text B which should be compared</param>
|
||||||
|
public MyersDiff(ISequence a, ISequence b) |
||||||
|
{ |
||||||
|
this.a = a; |
||||||
|
this.b = b; |
||||||
|
middle = new MiddleEdit(a, b); |
||||||
|
CalculateEdits(); |
||||||
|
} |
||||||
|
|
||||||
|
/// <returns>the list of edits found during the last call to {@link #calculateEdits()}</returns>
|
||||||
|
public List<Edit> GetEdits() |
||||||
|
{ |
||||||
|
return edits; |
||||||
|
} |
||||||
|
|
||||||
|
// TODO: use ThreadLocal for future multi-threaded operations
|
||||||
|
MiddleEdit middle; |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Entrypoint into the algorithm this class is all about. This method triggers that the
|
||||||
|
/// differences between A and B are calculated in form of a list of edits.
|
||||||
|
/// </summary>
|
||||||
|
protected void CalculateEdits() |
||||||
|
{ |
||||||
|
edits = new List<Edit>(); |
||||||
|
|
||||||
|
middle.Initialize(0, a.Size(), 0, b.Size()); |
||||||
|
if (middle.beginA >= middle.endA && |
||||||
|
middle.beginB >= middle.endB) |
||||||
|
return; |
||||||
|
|
||||||
|
CalculateEdits(middle.beginA, middle.endA, |
||||||
|
middle.beginB, middle.endB); |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Calculates the differences between a given part of A against another given part of B
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="beginA">start of the part of A which should be compared (0<=beginA<sizeof(A))</param>
|
||||||
|
/// <param name="endA">end of the part of A which should be compared (beginA<=endA<sizeof(A))</param>
|
||||||
|
/// <param name="beginB">start of the part of B which should be compared (0<=beginB<sizeof(B))</param>
|
||||||
|
/// <param name="endB">end of the part of B which should be compared (beginB<=endB<sizeof(B))</param>
|
||||||
|
protected void CalculateEdits(int beginA, int endA, |
||||||
|
int beginB, int endB) |
||||||
|
{ |
||||||
|
Edit edit = middle.Calculate(beginA, endA, beginB, endB); |
||||||
|
|
||||||
|
if (beginA < edit.BeginA || beginB < edit.BeginB) |
||||||
|
{ |
||||||
|
int k = edit.BeginB - edit.BeginA; |
||||||
|
int x = middle.backward.Snake(k, edit.BeginA); |
||||||
|
CalculateEdits(beginA, x, beginB, k + x); |
||||||
|
} |
||||||
|
|
||||||
|
if (edit.EditType != ChangeType.None) |
||||||
|
edits.Add(edit); |
||||||
|
|
||||||
|
|
||||||
|
// after middle
|
||||||
|
if (endA > edit.EndA || endB > edit.EndB) |
||||||
|
{ |
||||||
|
int k = edit.EndB - edit.EndA; |
||||||
|
int x = middle.forward.Snake(k, edit.EndA); |
||||||
|
CalculateEdits(x, endA, k + x, endB); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A class to help bisecting the sequences a and b to find minimal
|
||||||
|
/// edit paths.
|
||||||
|
///
|
||||||
|
/// As the arrays are reused for space efficiency, you will need one
|
||||||
|
/// instance per thread.
|
||||||
|
///
|
||||||
|
/// The entry function is the calculate() method.
|
||||||
|
/// </summary>
|
||||||
|
class MiddleEdit |
||||||
|
{ |
||||||
|
private readonly ISequence _a; |
||||||
|
private readonly ISequence _b; |
||||||
|
|
||||||
|
public MiddleEdit(ISequence a, ISequence b) |
||||||
|
{ |
||||||
|
_a = a; |
||||||
|
_b = b; |
||||||
|
forward = new ForwardEditPaths(this); |
||||||
|
backward = new BackwardEditPaths(this); |
||||||
|
} |
||||||
|
|
||||||
|
public void Initialize(int beginA, int endA, int beginB, int endB) |
||||||
|
{ |
||||||
|
this.beginA = beginA; this.endA = endA; |
||||||
|
this.beginB = beginB; this.endB = endB; |
||||||
|
|
||||||
|
// strip common parts on either end
|
||||||
|
int k = beginB - beginA; |
||||||
|
this.beginA = forward.Snake(k, beginA); |
||||||
|
this.beginB = k + this.beginA; |
||||||
|
|
||||||
|
k = endB - endA; |
||||||
|
this.endA = backward.Snake(k, endA); |
||||||
|
this.endB = k + this.endA; |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This function calculates the "middle" Edit of the shortest
|
||||||
|
/// edit path between the given subsequences of a and b.
|
||||||
|
///
|
||||||
|
/// Once a forward path and a backward path meet, we found the
|
||||||
|
/// middle part. From the last snake end point on both of them,
|
||||||
|
/// we construct the Edit.
|
||||||
|
///
|
||||||
|
/// It is assumed that there is at least one edit in the range.
|
||||||
|
/// </summary>
|
||||||
|
// TODO: measure speed impact when this is synchronized
|
||||||
|
public Edit Calculate(int beginA, int endA, int beginB, int endB) |
||||||
|
{ |
||||||
|
if (beginA == endA || beginB == endB) |
||||||
|
return new Edit(beginA, endA, beginB, endB); |
||||||
|
this.beginA = beginA; this.endA = endA; |
||||||
|
this.beginB = beginB; this.endB = endB; |
||||||
|
|
||||||
|
/* |
||||||
|
* Following the conventions in Myers' paper, "k" is |
||||||
|
* the difference between the index into "b" and the |
||||||
|
* index into "a". |
||||||
|
*/ |
||||||
|
int minK = beginB - endA; |
||||||
|
int maxK = endB - beginA; |
||||||
|
|
||||||
|
forward.Initialize(beginB - beginA, beginA, minK, maxK); |
||||||
|
backward.Initialize(endB - endA, endA, minK, maxK); |
||||||
|
|
||||||
|
for (int d = 1; ; d++) |
||||||
|
if (forward.Calculate(d) || |
||||||
|
backward.Calculate(d)) |
||||||
|
{ |
||||||
|
return _edit; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/* |
||||||
|
* For each d, we need to hold the d-paths for the diagonals |
||||||
|
* k = -d, -d + 2, ..., d - 2, d. These are stored in the |
||||||
|
* forward (and backward) array. |
||||||
|
* |
||||||
|
* As we allow subsequences, too, this needs some refinement: |
||||||
|
* the forward paths start on the diagonal forwardK = |
||||||
|
* beginB - beginA, and backward paths start on the diagonal |
||||||
|
* backwardK = endB - endA. |
||||||
|
* |
||||||
|
* So, we need to hold the forward d-paths for the diagonals |
||||||
|
* k = forwardK - d, forwardK - d + 2, ..., forwardK + d and |
||||||
|
* the analogue for the backward d-paths. This means that |
||||||
|
* we can turn (k, d) into the forward array index using this |
||||||
|
* formula: |
||||||
|
* |
||||||
|
* i = (d + k - forwardK) / 2 |
||||||
|
* |
||||||
|
* There is a further complication: the edit paths should not |
||||||
|
* leave the specified subsequences, so k is bounded by |
||||||
|
* minK = beginB - endA and maxK = endB - beginA. However, |
||||||
|
* (k - forwardK) _must_ be odd whenever d is odd, and it |
||||||
|
* _must_ be even when d is even. |
||||||
|
* |
||||||
|
* The values in the "forward" and "backward" arrays are |
||||||
|
* positions ("x") in the sequence a, to get the corresponding |
||||||
|
* positions ("y") in the sequence b, you have to calculate |
||||||
|
* the appropriate k and then y: |
||||||
|
* |
||||||
|
* k = forwardK - d + i * 2 |
||||||
|
* y = k + x |
||||||
|
* |
||||||
|
* (substitute backwardK for forwardK if you want to get the |
||||||
|
* y position for an entry in the "backward" array. |
||||||
|
*/ |
||||||
|
public EditPaths forward; |
||||||
|
public EditPaths backward; |
||||||
|
|
||||||
|
/* Some variables which are shared between methods */ |
||||||
|
public int beginA; |
||||||
|
public int endA; |
||||||
|
public int beginB; |
||||||
|
public int endB; |
||||||
|
protected Edit _edit; |
||||||
|
|
||||||
|
internal abstract class EditPaths |
||||||
|
{ |
||||||
|
protected readonly MiddleEdit _middleEdit; |
||||||
|
private List<int> x = new List<int>(); |
||||||
|
private List<long> _snake = new List<long>(); |
||||||
|
public int beginK; |
||||||
|
public int endK; |
||||||
|
public int middleK; |
||||||
|
int prevBeginK, prevEndK; |
||||||
|
/* if we hit one end early, no need to look further */ |
||||||
|
protected int minK, maxK; // TODO: better explanation
|
||||||
|
|
||||||
|
protected EditPaths(MiddleEdit middleEdit) |
||||||
|
{ |
||||||
|
_middleEdit = middleEdit; |
||||||
|
} |
||||||
|
|
||||||
|
int GetIndex(int d, int k) |
||||||
|
{ |
||||||
|
// TODO: remove
|
||||||
|
if (((d + k - middleK) % 2) == 1) |
||||||
|
throw new InvalidOperationException("odd: " + d + " + " + k + " - " + middleK); |
||||||
|
return (d + k - middleK) / 2; |
||||||
|
} |
||||||
|
|
||||||
|
public int GetX(int d, int k) |
||||||
|
{ |
||||||
|
// TODO: remove
|
||||||
|
if (k < beginK || k > endK) |
||||||
|
throw new InvalidOperationException("k " + k + " not in " + beginK + " - " + endK); |
||||||
|
return x[GetIndex(d, k)]; |
||||||
|
} |
||||||
|
|
||||||
|
public long GetSnake(int d, int k) |
||||||
|
{ |
||||||
|
// TODO: remove
|
||||||
|
if (k < beginK || k > endK) |
||||||
|
throw new InvalidOperationException("k " + k + " not in " + beginK + " - " + endK); |
||||||
|
return _snake[GetIndex(d, k)]; |
||||||
|
} |
||||||
|
|
||||||
|
private int ForceKIntoRange(int k) |
||||||
|
{ |
||||||
|
/* if k is odd, so must be the result */ |
||||||
|
if (k < minK) |
||||||
|
return minK + ((k ^ minK) & 1); |
||||||
|
else if (k > maxK) |
||||||
|
return maxK - ((k ^ maxK) & 1); |
||||||
|
return k; |
||||||
|
} |
||||||
|
|
||||||
|
public void Initialize(int k, int x, int minK, int maxK) |
||||||
|
{ |
||||||
|
this.minK = minK; |
||||||
|
this.maxK = maxK; |
||||||
|
beginK = endK = middleK = k; |
||||||
|
this.x.Clear(); |
||||||
|
this.x.Add(x); |
||||||
|
_snake.Clear(); |
||||||
|
_snake.Add(NewSnake(k, x)); |
||||||
|
} |
||||||
|
|
||||||
|
public abstract int Snake(int k, int x); |
||||||
|
protected abstract int GetLeft(int x); |
||||||
|
protected abstract int GetRight(int x); |
||||||
|
protected abstract bool IsBetter(int left, int right); |
||||||
|
protected abstract void AdjustMinMaxK(int k, int x); |
||||||
|
protected abstract bool Meets(int d, int k, int x, long snake); |
||||||
|
|
||||||
|
long NewSnake(int k, int x) |
||||||
|
{ |
||||||
|
long y = k + x; |
||||||
|
long ret = ((long)x) << 32; |
||||||
|
return ret | y; |
||||||
|
} |
||||||
|
|
||||||
|
int Snake2x(long snake) |
||||||
|
{ |
||||||
|
return (int)((ulong)snake >> 32); |
||||||
|
} |
||||||
|
|
||||||
|
int Snake2y(long snake) |
||||||
|
{ |
||||||
|
return (int)(((ulong)snake << 32) >> 32); |
||||||
|
} |
||||||
|
|
||||||
|
protected bool MakeEdit(long snake1, long snake2) |
||||||
|
{ |
||||||
|
int x1 = Snake2x(snake1), x2 = Snake2x(snake2); |
||||||
|
int y1 = Snake2y(snake1), y2 = Snake2y(snake2); |
||||||
|
|
||||||
|
/* |
||||||
|
* Check for incompatible partial edit paths: |
||||||
|
* when there are ambiguities, we might have |
||||||
|
* hit incompatible (i.e. non-overlapping) |
||||||
|
* forward/backward paths. |
||||||
|
* |
||||||
|
* In that case, just pretend that we have |
||||||
|
* an empty edit at the end of one snake; this |
||||||
|
* will force a decision which path to take |
||||||
|
* in the next recursion step. |
||||||
|
*/ |
||||||
|
if (x1 > x2 || y1 > y2) |
||||||
|
{ |
||||||
|
x1 = x2; |
||||||
|
y1 = y2; |
||||||
|
} |
||||||
|
_middleEdit._edit = new Edit(x1, x2, y1, y2); |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
public bool Calculate(int d) |
||||||
|
{ |
||||||
|
prevBeginK = beginK; |
||||||
|
prevEndK = endK; |
||||||
|
beginK = ForceKIntoRange(middleK - d); |
||||||
|
endK = ForceKIntoRange(middleK + d); |
||||||
|
// TODO: handle i more efficiently
|
||||||
|
// TODO: walk snake(k, getX(d, k)) only once per (d, k)
|
||||||
|
// TODO: move end points out of the loop to avoid conditionals inside the loop
|
||||||
|
// go backwards so that we can avoid temp vars
|
||||||
|
for (int k = endK; k >= beginK; k -= 2) |
||||||
|
{ |
||||||
|
int left = -1, right = -1; |
||||||
|
long leftSnake = -1L, rightSnake = -1L; |
||||||
|
// TODO: refactor into its own function
|
||||||
|
int i; |
||||||
|
if (k > prevBeginK) |
||||||
|
{ |
||||||
|
i = GetIndex(d - 1, k - 1); |
||||||
|
left = x[i]; |
||||||
|
int end = Snake(k - 1, left); |
||||||
|
leftSnake = left != end ? |
||||||
|
NewSnake(k - 1, end) : |
||||||
|
_snake[i]; |
||||||
|
|
||||||
|
if (Meets(d, k - 1, end, leftSnake)) |
||||||
|
return true; |
||||||
|
left = GetLeft(end); |
||||||
|
} |
||||||
|
if (k < prevEndK) |
||||||
|
{ |
||||||
|
i = GetIndex(d - 1, k + 1); |
||||||
|
right = x[i]; |
||||||
|
int end = Snake(k + 1, right); |
||||||
|
rightSnake = right != end ? |
||||||
|
NewSnake(k + 1, end) : |
||||||
|
_snake[i]; |
||||||
|
|
||||||
|
if (Meets(d, k + 1, end, rightSnake)) |
||||||
|
return true; |
||||||
|
right = GetRight(end); |
||||||
|
} |
||||||
|
int newX; |
||||||
|
long newSnakeTmp; |
||||||
|
if (k >= prevEndK || |
||||||
|
(k > prevBeginK && |
||||||
|
IsBetter(left, right))) |
||||||
|
{ |
||||||
|
newX = left; |
||||||
|
newSnakeTmp = leftSnake; |
||||||
|
} |
||||||
|
else |
||||||
|
{ |
||||||
|
newX = right; |
||||||
|
newSnakeTmp = rightSnake; |
||||||
|
} |
||||||
|
|
||||||
|
if (Meets(d, k, newX, newSnakeTmp)) |
||||||
|
return true; |
||||||
|
AdjustMinMaxK(k, newX); |
||||||
|
i = GetIndex(d, k); |
||||||
|
x.Set(i, newX); |
||||||
|
_snake.Set(i, newSnakeTmp); |
||||||
|
} |
||||||
|
return false; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
class ForwardEditPaths : EditPaths |
||||||
|
{ |
||||||
|
public ForwardEditPaths(MiddleEdit middleEdit) |
||||||
|
: base(middleEdit) |
||||||
|
{ |
||||||
|
} |
||||||
|
|
||||||
|
public override int Snake(int k, int x) |
||||||
|
{ |
||||||
|
for (; x < _middleEdit.endA && k + x < _middleEdit.endB; x++) |
||||||
|
if (!_middleEdit._a.Equals(x, _middleEdit._b, k + x)) |
||||||
|
break; |
||||||
|
return x; |
||||||
|
} |
||||||
|
|
||||||
|
protected override int GetLeft(int x) |
||||||
|
{ |
||||||
|
return x; |
||||||
|
} |
||||||
|
|
||||||
|
protected override int GetRight(int x) |
||||||
|
{ |
||||||
|
return x + 1; |
||||||
|
} |
||||||
|
|
||||||
|
protected override bool IsBetter(int left, int right) |
||||||
|
{ |
||||||
|
return left > right; |
||||||
|
} |
||||||
|
|
||||||
|
protected override void AdjustMinMaxK(int k, int x) |
||||||
|
{ |
||||||
|
if (x >= _middleEdit.endA || k + x >= _middleEdit.endB) |
||||||
|
{ |
||||||
|
if (k > _middleEdit.backward.middleK) |
||||||
|
maxK = k; |
||||||
|
else |
||||||
|
minK = k; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
protected override bool Meets(int d, int k, int x, long snake) |
||||||
|
{ |
||||||
|
if (k < _middleEdit.backward.beginK || k > _middleEdit.backward.endK) |
||||||
|
return false; |
||||||
|
// TODO: move out of loop
|
||||||
|
if (((d - 1 + k - _middleEdit.backward.middleK) % 2) == 1) |
||||||
|
return false; |
||||||
|
if (x < _middleEdit.backward.GetX(d - 1, k)) |
||||||
|
return false; |
||||||
|
MakeEdit(snake, _middleEdit.backward.GetSnake(d - 1, k)); |
||||||
|
return true; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
class BackwardEditPaths : EditPaths |
||||||
|
{ |
||||||
|
public BackwardEditPaths(MiddleEdit middleEdit) |
||||||
|
: base(middleEdit) |
||||||
|
{ |
||||||
|
} |
||||||
|
|
||||||
|
public override int Snake(int k, int x) |
||||||
|
{ |
||||||
|
for (; x > _middleEdit.beginA && k + x > _middleEdit.beginB; x--) |
||||||
|
if (!_middleEdit._a.Equals(x - 1, _middleEdit._b, k + x - 1)) |
||||||
|
break; |
||||||
|
return x; |
||||||
|
} |
||||||
|
|
||||||
|
protected override int GetLeft(int x) |
||||||
|
{ |
||||||
|
return x - 1; |
||||||
|
} |
||||||
|
|
||||||
|
protected override int GetRight(int x) |
||||||
|
{ |
||||||
|
return x; |
||||||
|
} |
||||||
|
|
||||||
|
protected override bool IsBetter(int left, int right) |
||||||
|
{ |
||||||
|
return left < right; |
||||||
|
} |
||||||
|
|
||||||
|
protected override void AdjustMinMaxK(int k, int x) |
||||||
|
{ |
||||||
|
if (x <= _middleEdit.beginA || k + x <= _middleEdit.beginB) |
||||||
|
{ |
||||||
|
if (k > _middleEdit.forward.middleK) |
||||||
|
maxK = k; |
||||||
|
else |
||||||
|
minK = k; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
protected override bool Meets(int d, int k, int x, long snake) |
||||||
|
{ |
||||||
|
if (k < _middleEdit.forward.beginK || k > _middleEdit.forward.endK) |
||||||
|
return false; |
||||||
|
// TODO: move out of loop
|
||||||
|
if (((d + k - _middleEdit.forward.middleK) % 2) == 1) |
||||||
|
return false; |
||||||
|
if (x > _middleEdit.forward.GetX(d, k)) |
||||||
|
return false; |
||||||
|
MakeEdit(_middleEdit.forward.GetSnake(d, k), snake); |
||||||
|
return true; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,34 @@ |
|||||||
|
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
|
||||||
|
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
|
||||||
|
|
||||||
|
using System; |
||||||
|
using System.Collections.Generic; |
||||||
|
using ICSharpCode.SharpDevelop.Editor; |
||||||
|
|
||||||
|
namespace ICSharpCode.AvalonEdit.AddIn.MyersDiff |
||||||
|
{ |
||||||
|
public class StringSequence : ISequence |
||||||
|
{ |
||||||
|
string content; |
||||||
|
|
||||||
|
public StringSequence(string content) |
||||||
|
{ |
||||||
|
this.content = content; |
||||||
|
} |
||||||
|
|
||||||
|
public int Size() |
||||||
|
{ |
||||||
|
return content.Length; |
||||||
|
} |
||||||
|
|
||||||
|
public bool Equals(int i, ISequence other, int j) |
||||||
|
{ |
||||||
|
StringSequence seq = other as StringSequence; |
||||||
|
|
||||||
|
if (seq == null) |
||||||
|
return false; |
||||||
|
|
||||||
|
return content[i] == seq.content[j]; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,22 @@ |
|||||||
|
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
|
||||||
|
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
|
||||||
|
|
||||||
|
using System; |
||||||
|
using System.Collections.Generic; |
||||||
|
|
||||||
|
namespace ICSharpCode.AvalonEdit.AddIn.MyersDiff |
||||||
|
{ |
||||||
|
public static class Utils |
||||||
|
{ |
||||||
|
public static void Set<T>(this IList<T> instance, int index, T value) |
||||||
|
{ |
||||||
|
if (instance == null) |
||||||
|
throw new ArgumentNullException("instance"); |
||||||
|
|
||||||
|
if (index == instance.Count) |
||||||
|
instance.Add(value); |
||||||
|
else |
||||||
|
instance[index] = value; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,128 @@ |
|||||||
|
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
|
||||||
|
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
|
||||||
|
|
||||||
|
using System; |
||||||
|
using System.IO; |
||||||
|
using System.IO.Pipes; |
||||||
|
using System.Runtime.InteropServices; |
||||||
|
|
||||||
|
using ICSharpCode.SharpDevelop.Editor; |
||||||
|
using ICSharpCode.SharpDevelop.Util; |
||||||
|
using Microsoft.Win32.SafeHandles; |
||||||
|
|
||||||
|
namespace ICSharpCode.GitAddIn |
||||||
|
{ |
||||||
|
public class GitVersionProvider : IDocumentVersionProvider |
||||||
|
{ |
||||||
|
#region PInvoke
|
||||||
|
[DllImport("kernel32.dll", CharSet = CharSet.Unicode)] |
||||||
|
[return: MarshalAs(UnmanagedType.Bool)] |
||||||
|
static extern bool CreateProcess( |
||||||
|
string lpApplicationName, |
||||||
|
string lpCommandLine, |
||||||
|
IntPtr lpProcessAttributes, |
||||||
|
IntPtr lpThreadAttributes, |
||||||
|
[MarshalAs(UnmanagedType.Bool)] bool bInheritHandles, |
||||||
|
uint dwCreationFlags, |
||||||
|
IntPtr lpEnvironment, |
||||||
|
string lpCurrentDirectory, |
||||||
|
[In] ref StartupInfo lpStartupInfo, |
||||||
|
out PROCESS_INFORMATION lpProcessInformation |
||||||
|
); |
||||||
|
|
||||||
|
[DllImport("kernel32.dll", SetLastError = true)] |
||||||
|
static extern IntPtr GetStdHandle(int nStdHandle); |
||||||
|
|
||||||
|
const uint STARTF_USESTDHANDLES = 0x00000100; |
||||||
|
|
||||||
|
const int STD_INPUT_HANDLE = -10; |
||||||
|
const int STD_OUTPUT_HANDLE = -11; |
||||||
|
const int STD_ERROR_HANDLE = -12; |
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] |
||||||
|
struct StartupInfo |
||||||
|
{ |
||||||
|
public uint cb; |
||||||
|
public string lpReserved; |
||||||
|
public string lpDesktop; |
||||||
|
public string lpTitle; |
||||||
|
public uint dwX; |
||||||
|
public uint dwY; |
||||||
|
public uint dwXSize; |
||||||
|
public uint dwYSize; |
||||||
|
public uint dwXCountChars; |
||||||
|
public uint dwYCountChars; |
||||||
|
public uint dwFillAttribute; |
||||||
|
public uint dwFlags; |
||||||
|
public short wShowWindow; |
||||||
|
public short cbReserved2; |
||||||
|
public IntPtr lpReserved2; |
||||||
|
public IntPtr hStdInput; |
||||||
|
public SafePipeHandle hStdOutput; |
||||||
|
public IntPtr hStdError; |
||||||
|
} |
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)] |
||||||
|
struct PROCESS_INFORMATION |
||||||
|
{ |
||||||
|
public IntPtr hProcess; |
||||||
|
public IntPtr hThread; |
||||||
|
public int dwProcessId; |
||||||
|
public int dwThreadId; |
||||||
|
} |
||||||
|
#endregion
|
||||||
|
|
||||||
|
public Stream OpenBaseVersion(string fileName) |
||||||
|
{ |
||||||
|
if (!Git.IsInWorkingCopy(fileName)) |
||||||
|
return null; |
||||||
|
|
||||||
|
return OpenOutput(fileName, GetBlobHash(fileName)); |
||||||
|
} |
||||||
|
|
||||||
|
string GetBlobHash(string fileName) |
||||||
|
{ |
||||||
|
ProcessRunner runner = new ProcessRunner(); |
||||||
|
runner.WorkingDirectory = Path.GetDirectoryName(fileName); |
||||||
|
runner.Start("cmd", "/c git ls-tree HEAD " + Path.GetFileName(fileName)); |
||||||
|
runner.WaitForExit(); |
||||||
|
|
||||||
|
string output = runner.StandardOutput.Trim(); |
||||||
|
string[] parts = output.Split(new[] { " ", "\t" }, StringSplitOptions.RemoveEmptyEntries); |
||||||
|
|
||||||
|
if (parts.Length < 3) |
||||||
|
return null; |
||||||
|
|
||||||
|
return parts[2]; |
||||||
|
} |
||||||
|
|
||||||
|
Stream OpenOutput(string fileName, string blobHash) |
||||||
|
{ |
||||||
|
if (blobHash == null) |
||||||
|
return null; |
||||||
|
|
||||||
|
AnonymousPipeServerStream pipe = new AnonymousPipeServerStream(PipeDirection.In, HandleInheritability.Inheritable); |
||||||
|
|
||||||
|
StartupInfo startupInfo = new GitVersionProvider.StartupInfo(); |
||||||
|
startupInfo.dwFlags = STARTF_USESTDHANDLES; |
||||||
|
startupInfo.hStdOutput = pipe.ClientSafePipeHandle; |
||||||
|
startupInfo.hStdInput = GetStdHandle(STD_INPUT_HANDLE); |
||||||
|
startupInfo.hStdError = GetStdHandle(STD_ERROR_HANDLE); |
||||||
|
startupInfo.cb = 16; |
||||||
|
|
||||||
|
PROCESS_INFORMATION procInfo; |
||||||
|
|
||||||
|
if (!CreateProcess(null, string.Format("cmd /c git cat-file blob {0}", blobHash), |
||||||
|
IntPtr.Zero, IntPtr.Zero, true, 0, IntPtr.Zero, Path.GetDirectoryName(fileName), ref startupInfo, |
||||||
|
out procInfo)) { |
||||||
|
pipe.DisposeLocalCopyOfClientHandle(); |
||||||
|
pipe.Close(); |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
pipe.DisposeLocalCopyOfClientHandle(); |
||||||
|
|
||||||
|
return pipe; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,23 @@ |
|||||||
|
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
|
||||||
|
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
|
||||||
|
|
||||||
|
using System; |
||||||
|
using System.IO; |
||||||
|
using ICSharpCode.SharpDevelop.Editor; |
||||||
|
using ICSharpCode.Svn.Commands; |
||||||
|
using SharpSvn; |
||||||
|
|
||||||
|
namespace ICSharpCode.Svn |
||||||
|
{ |
||||||
|
public class SvnVersionProvider : IDocumentVersionProvider |
||||||
|
{ |
||||||
|
public Stream OpenBaseVersion(string fileName) |
||||||
|
{ |
||||||
|
if (!SvnClientWrapper.IsInSourceControl(fileName)) |
||||||
|
return null; |
||||||
|
|
||||||
|
using (SvnClientWrapper client = new SvnClientWrapper()) |
||||||
|
return client.OpenBaseVersion(fileName); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,122 @@ |
|||||||
|
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
|
||||||
|
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
|
||||||
|
|
||||||
|
using System; |
||||||
|
using System.Collections.Generic; |
||||||
|
using System.IO; |
||||||
|
|
||||||
|
using ICSharpCode.Core; |
||||||
|
|
||||||
|
namespace ICSharpCode.SharpDevelop.Editor |
||||||
|
{ |
||||||
|
public interface IDocumentVersionProvider |
||||||
|
{ |
||||||
|
/// <summary>
|
||||||
|
/// Provides the BASE-Version for a file. This can be either the file saved
|
||||||
|
/// to disk or a base version provided by any VCS.
|
||||||
|
/// </summary>
|
||||||
|
Stream OpenBaseVersion(string fileName); |
||||||
|
} |
||||||
|
|
||||||
|
public sealed class DefaultVersionProvider : IDocumentVersionProvider |
||||||
|
{ |
||||||
|
public Stream OpenBaseVersion(string fileName) |
||||||
|
{ |
||||||
|
return File.OpenRead(fileName); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public class VersioningServices |
||||||
|
{ |
||||||
|
public static readonly VersioningServices Instance = new VersioningServices(); |
||||||
|
|
||||||
|
List<IDocumentVersionProvider> baseVersionProviders; |
||||||
|
|
||||||
|
public List<IDocumentVersionProvider> DocumentVersionProviders { |
||||||
|
get { |
||||||
|
if (baseVersionProviders == null) |
||||||
|
baseVersionProviders = AddInTree.BuildItems<IDocumentVersionProvider>("/Workspace/DocumentVersionProviders", this, false); |
||||||
|
|
||||||
|
return baseVersionProviders; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public interface IChangeWatcher : IDisposable |
||||||
|
{ |
||||||
|
event EventHandler ChangeOccurred; |
||||||
|
/// <summary>
|
||||||
|
/// Returns the change information for a given line.
|
||||||
|
/// Pass null to get the changes before the first line.
|
||||||
|
/// </summary>
|
||||||
|
LineChangeInfo GetChange(IDocumentLine line); |
||||||
|
void Initialize(IDocument document); |
||||||
|
} |
||||||
|
|
||||||
|
public enum ChangeType |
||||||
|
{ |
||||||
|
None, |
||||||
|
Added, |
||||||
|
Deleted, |
||||||
|
Modified, |
||||||
|
Unsaved |
||||||
|
} |
||||||
|
|
||||||
|
public struct LineChangeInfo : IEquatable<LineChangeInfo> |
||||||
|
{ |
||||||
|
public static readonly LineChangeInfo Empty = new LineChangeInfo(ChangeType.None, ""); |
||||||
|
|
||||||
|
ChangeType change; |
||||||
|
|
||||||
|
public ChangeType Change { |
||||||
|
get { return change; } |
||||||
|
set { change = value; } |
||||||
|
} |
||||||
|
|
||||||
|
string deletedLinesAfterThisLine; |
||||||
|
|
||||||
|
public string DeletedLinesAfterThisLine { |
||||||
|
get { return deletedLinesAfterThisLine; } |
||||||
|
set { deletedLinesAfterThisLine = value; } |
||||||
|
} |
||||||
|
|
||||||
|
public LineChangeInfo(ChangeType change, string deletedLinesAfterThisLine) |
||||||
|
{ |
||||||
|
this.change = change; |
||||||
|
this.deletedLinesAfterThisLine = deletedLinesAfterThisLine; |
||||||
|
} |
||||||
|
|
||||||
|
#region Equals and GetHashCode implementation
|
||||||
|
public override bool Equals(object obj) |
||||||
|
{ |
||||||
|
return (obj is LineChangeInfo) && Equals((LineChangeInfo)obj); |
||||||
|
} |
||||||
|
|
||||||
|
public bool Equals(LineChangeInfo other) |
||||||
|
{ |
||||||
|
return this.change == other.change && this.deletedLinesAfterThisLine == other.deletedLinesAfterThisLine; |
||||||
|
} |
||||||
|
|
||||||
|
public override int GetHashCode() |
||||||
|
{ |
||||||
|
int hashCode = 0; |
||||||
|
unchecked { |
||||||
|
hashCode += 1000000007 * change.GetHashCode(); |
||||||
|
if (deletedLinesAfterThisLine != null) |
||||||
|
hashCode += 1000000009 * deletedLinesAfterThisLine.GetHashCode(); |
||||||
|
} |
||||||
|
return hashCode; |
||||||
|
} |
||||||
|
|
||||||
|
public static bool operator ==(LineChangeInfo lhs, LineChangeInfo rhs) |
||||||
|
{ |
||||||
|
return lhs.Equals(rhs); |
||||||
|
} |
||||||
|
|
||||||
|
public static bool operator !=(LineChangeInfo lhs, LineChangeInfo rhs) |
||||||
|
{ |
||||||
|
return !(lhs == rhs); |
||||||
|
} |
||||||
|
#endregion
|
||||||
|
} |
||||||
|
} |
||||||
Loading…
Reference in new issue