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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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