You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
291 lines
9.3 KiB
291 lines
9.3 KiB
// 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.Diagnostics; |
|
using System.Windows; |
|
using System.Windows.Controls; |
|
using System.Windows.Controls.Primitives; |
|
using System.Windows.Input; |
|
using System.Windows.Media; |
|
|
|
using ICSharpCode.AvalonEdit.Document; |
|
using ICSharpCode.AvalonEdit.Editing; |
|
using ICSharpCode.AvalonEdit.Highlighting; |
|
using ICSharpCode.AvalonEdit.Rendering; |
|
using ICSharpCode.SharpDevelop.Editor; |
|
using ICSharpCode.SharpDevelop.Widgets.MyersDiff; |
|
|
|
namespace ICSharpCode.AvalonEdit.AddIn |
|
{ |
|
/// <summary> |
|
/// Description of ChangeMarkerMargin. |
|
/// </summary> |
|
public class ChangeMarkerMargin : AbstractMargin, IDisposable |
|
{ |
|
IChangeWatcher changeWatcher; |
|
|
|
public ChangeMarkerMargin(IChangeWatcher changeWatcher) |
|
{ |
|
this.changeWatcher = changeWatcher; |
|
this.hoverLogic = new MouseHoverLogic(this); |
|
this.hoverLogic.MouseHover += delegate(object sender, MouseEventArgs e) { DisplayTooltip(e); }; |
|
changeWatcher.ChangeOccurred += ChangeOccurred; |
|
} |
|
|
|
bool disposed = false; |
|
|
|
public void Dispose() |
|
{ |
|
if (!disposed) { |
|
changeWatcher.ChangeOccurred -= ChangeOccurred; |
|
disposed = true; |
|
} |
|
} |
|
|
|
#region Brushes |
|
public static readonly DependencyProperty AddedLineBrushProperty = |
|
DependencyProperty.Register("AddedLineBrush", typeof(Brush), typeof(ChangeMarkerMargin), |
|
new FrameworkPropertyMetadata(Brushes.LightGreen)); |
|
|
|
public Brush AddedLineBrush { |
|
get { return (Brush)GetValue(AddedLineBrushProperty); } |
|
set { SetValue(AddedLineBrushProperty, value); } |
|
} |
|
|
|
public static readonly DependencyProperty ChangedLineBrushProperty = |
|
DependencyProperty.Register("ChangedLineBrush", typeof(Brush), typeof(ChangeMarkerMargin), |
|
new FrameworkPropertyMetadata(Brushes.LightBlue)); |
|
|
|
public Brush ChangedLineBrush { |
|
get { return (Brush)GetValue(ChangedLineBrushProperty); } |
|
set { SetValue(ChangedLineBrushProperty, value); } |
|
} |
|
|
|
public static readonly DependencyProperty UnsavedLineBrushProperty = |
|
DependencyProperty.Register("UnsavedLineBrush", typeof(Brush), typeof(ChangeMarkerMargin), |
|
new FrameworkPropertyMetadata(Brushes.Yellow)); |
|
|
|
public Brush UnsavedLineBrush { |
|
get { return (Brush)GetValue(UnsavedLineBrushProperty); } |
|
set { SetValue(UnsavedLineBrushProperty, value); } |
|
} |
|
#endregion |
|
|
|
protected override void OnRender(DrawingContext drawingContext) |
|
{ |
|
Size renderSize = this.RenderSize; |
|
TextView textView = this.TextView; |
|
|
|
if (textView != null && textView.VisualLinesValid) { |
|
var zeroLineInfo = changeWatcher.GetChange(0); |
|
|
|
foreach (VisualLine line in textView.VisualLines) { |
|
Rect rect = new Rect(0, line.VisualTop - textView.ScrollOffset.Y - 1, 5, line.Height + 2); |
|
|
|
LineChangeInfo info = changeWatcher.GetChange(line.FirstDocumentLine.LineNumber); |
|
|
|
if (zeroLineInfo.Change == ChangeType.Deleted && line.FirstDocumentLine.LineNumber == 1 && info.Change != ChangeType.Unsaved) { |
|
info.Change = ChangeType.Modified; |
|
} |
|
|
|
switch (info.Change) { |
|
case ChangeType.None: |
|
break; |
|
case ChangeType.Added: |
|
drawingContext.DrawRectangle(AddedLineBrush, null, rect); |
|
break; |
|
case ChangeType.Deleted: |
|
case ChangeType.Modified: |
|
drawingContext.DrawRectangle(ChangedLineBrush, null, rect); |
|
break; |
|
case ChangeType.Unsaved: |
|
drawingContext.DrawRectangle(UnsavedLineBrush, null, rect); |
|
break; |
|
default: |
|
throw new Exception("Invalid value for ChangeType"); |
|
} |
|
} |
|
} |
|
} |
|
|
|
protected override void OnTextViewChanged(TextView oldTextView, TextView newTextView) |
|
{ |
|
if (oldTextView != null) { |
|
oldTextView.VisualLinesChanged -= VisualLinesChanged; |
|
oldTextView.ScrollOffsetChanged -= ScrollOffsetChanged; |
|
((TextArea)oldTextView.Services.GetService(typeof(TextArea))).KeyDown -= TextViewKeyDown; |
|
} |
|
base.OnTextViewChanged(oldTextView, newTextView); |
|
if (newTextView != null) { |
|
newTextView.VisualLinesChanged += VisualLinesChanged; |
|
newTextView.ScrollOffsetChanged += ScrollOffsetChanged; |
|
((TextArea)newTextView.Services.GetService(typeof(TextArea))).KeyDown += TextViewKeyDown; |
|
} |
|
} |
|
|
|
void TextViewKeyDown(object sender, KeyEventArgs e) |
|
{ |
|
// close tooltip on pressing Esc |
|
if (e.Key == Key.Escape) |
|
tooltip.IsOpen = false; |
|
} |
|
|
|
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); |
|
} |
|
|
|
#region Diffs tooltip |
|
|
|
Popup tooltip = new Popup() { StaysOpen = false }; |
|
ITextMarker marker; |
|
ITextMarkerService markerService; |
|
MouseHoverLogic hoverLogic; |
|
|
|
void DisplayTooltip(MouseEventArgs e) |
|
{ |
|
int line = GetLineFromMousePosition(e); |
|
|
|
if (line == 0) |
|
return; |
|
|
|
int startLine; |
|
bool added; |
|
string oldText = changeWatcher.GetOldVersionFromLine(line, out startLine, out added); |
|
|
|
TextEditor editor = this.TextView.Services.GetService(typeof(TextEditor)) as TextEditor; |
|
markerService = this.TextView.Services.GetService(typeof(ITextMarkerService)) as ITextMarkerService; |
|
|
|
LineChangeInfo zeroLineInfo = changeWatcher.GetChange(0); |
|
|
|
int offset, length; |
|
bool hasNewVersion = changeWatcher.GetNewVersionFromLine(line, out offset, out length); |
|
|
|
if (line == 1 && zeroLineInfo.Change == ChangeType.Deleted) { |
|
int zeroStartLine; bool zeroAdded; |
|
startLine = 1; |
|
string deletedText = changeWatcher.GetOldVersionFromLine(0, out zeroStartLine, out zeroAdded); |
|
var docLine = editor.Document.GetLineByNumber(line); |
|
string newLine = DocumentUtilitites.GetLineTerminator(changeWatcher.CurrentDocument, 1); |
|
deletedText += newLine; |
|
deletedText += editor.Document.GetText(docLine.Offset, docLine.Length); |
|
if (oldText != null) |
|
oldText = deletedText + newLine + oldText; |
|
else |
|
oldText = deletedText; |
|
|
|
if (!hasNewVersion) { |
|
offset = 0; |
|
length = docLine.Length; |
|
hasNewVersion = true; |
|
} |
|
} |
|
|
|
if (hasNewVersion) { |
|
if (marker != null) |
|
markerService.Remove(marker); |
|
if (length <= 0) { |
|
marker = null; |
|
length = 0; |
|
} else { |
|
marker = markerService.Create(offset, length); |
|
marker.BackgroundColor = Colors.LightGreen; |
|
} |
|
} |
|
|
|
if (oldText != null) { |
|
LineChangeInfo currLineInfo = changeWatcher.GetChange(startLine); |
|
|
|
if (currLineInfo.Change == ChangeType.Deleted && !(line == 1 && zeroLineInfo.Change == ChangeType.Deleted)) { |
|
var docLine = editor.Document.GetLineByNumber(startLine); |
|
if (docLine.DelimiterLength == 0) |
|
oldText = DocumentUtilitites.GetLineTerminator(changeWatcher.CurrentDocument, startLine) + oldText; |
|
oldText = editor.Document.GetText(docLine.Offset, docLine.TotalLength) + oldText; |
|
} |
|
|
|
DiffControl differ = new DiffControl(); |
|
differ.editor.SyntaxHighlighting = editor.SyntaxHighlighting; |
|
differ.editor.HorizontalScrollBarVisibility = ScrollBarVisibility.Hidden; |
|
differ.editor.VerticalScrollBarVisibility = ScrollBarVisibility.Hidden; |
|
differ.editor.Document.Text = oldText; |
|
differ.Background = Brushes.White; |
|
|
|
if (oldText == string.Empty) { |
|
differ.editor.Visibility = Visibility.Collapsed; |
|
differ.copyButton.Visibility = Visibility.Collapsed; |
|
} else { |
|
var baseDocument = new TextDocument(DocumentUtilitites.GetTextSource(changeWatcher.BaseDocument)); |
|
if (differ.editor.SyntaxHighlighting != null) { |
|
var mainHighlighter = new DocumentHighlighter(baseDocument, differ.editor.SyntaxHighlighting.MainRuleSet); |
|
var popupHighlighter = differ.editor.TextArea.GetService(typeof(IHighlighter)) as DocumentHighlighter; |
|
|
|
popupHighlighter.InitialSpanStack = mainHighlighter.GetSpanStack(currLineInfo.OldStartLineNumber); |
|
} |
|
} |
|
|
|
differ.revertButton.Click += delegate { |
|
if (hasNewVersion) { |
|
Document.Replace(offset, length, oldText); |
|
tooltip.IsOpen = false; |
|
} |
|
}; |
|
|
|
tooltip.Child = new Border() { |
|
Child = differ, |
|
BorderBrush = Brushes.Black, |
|
BorderThickness = new Thickness(1) |
|
}; |
|
|
|
if (tooltip.IsOpen) |
|
tooltip.IsOpen = false; |
|
|
|
tooltip.IsOpen = true; |
|
|
|
tooltip.Closed += delegate { |
|
if (marker != null) markerService.Remove(marker); |
|
}; |
|
tooltip.HorizontalOffset = -10; |
|
tooltip.VerticalOffset = |
|
TextView.GetVisualTopByDocumentLine(startLine) - TextView.ScrollOffset.Y; |
|
tooltip.Placement = PlacementMode.Top; |
|
tooltip.PlacementTarget = this.TextView; |
|
} |
|
} |
|
|
|
protected override void OnMouseLeave(MouseEventArgs e) |
|
{ |
|
if (marker != null && !tooltip.IsOpen) |
|
markerService.Remove(marker); |
|
base.OnMouseLeave(e); |
|
} |
|
|
|
int GetLineFromMousePosition(MouseEventArgs e) |
|
{ |
|
TextView textView = this.TextView; |
|
if (textView == null) |
|
return 0; |
|
VisualLine vl = textView.GetVisualLineFromVisualTop(e.GetPosition(textView).Y + textView.ScrollOffset.Y); |
|
if (vl == null) |
|
return 0; |
|
return vl.FirstDocumentLine.LineNumber; |
|
} |
|
|
|
#endregion |
|
} |
|
} |