mirror of https://github.com/icsharpcode/ILSpy.git
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.
213 lines
6.5 KiB
213 lines
6.5 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.Linq; |
|
using System.Windows; |
|
using System.Windows.Documents; |
|
using System.Windows.Media; |
|
using System.Windows.Threading; |
|
|
|
using ICSharpCode.AvalonEdit.Document; |
|
using ICSharpCode.AvalonEdit.Rendering; |
|
|
|
namespace ICSharpCode.AvalonEdit.Snippets |
|
{ |
|
/// <summary> |
|
/// Text element that is supposed to be replaced by the user. |
|
/// Will register an <see cref="IReplaceableActiveElement"/>. |
|
/// </summary> |
|
[Serializable] |
|
public class SnippetReplaceableTextElement : SnippetTextElement |
|
{ |
|
/// <inheritdoc/> |
|
public override void Insert(InsertionContext context) |
|
{ |
|
int start = context.InsertionPosition; |
|
base.Insert(context); |
|
int end = context.InsertionPosition; |
|
context.RegisterActiveElement(this, new ReplaceableActiveElement(context, start, end)); |
|
} |
|
|
|
/// <inheritdoc/> |
|
public override Inline ToTextRun() |
|
{ |
|
return new Italic(base.ToTextRun()); |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Interface for active element registered by <see cref="SnippetReplaceableTextElement"/>. |
|
/// </summary> |
|
public interface IReplaceableActiveElement : IActiveElement |
|
{ |
|
/// <summary> |
|
/// Gets the current text inside the element. |
|
/// </summary> |
|
string Text { get; } |
|
|
|
/// <summary> |
|
/// Occurs when the text inside the element changes. |
|
/// </summary> |
|
event EventHandler TextChanged; |
|
} |
|
|
|
sealed class ReplaceableActiveElement : IReplaceableActiveElement, IWeakEventListener |
|
{ |
|
readonly InsertionContext context; |
|
readonly int startOffset, endOffset; |
|
TextAnchor start, end; |
|
|
|
public ReplaceableActiveElement(InsertionContext context, int startOffset, int endOffset) |
|
{ |
|
this.context = context; |
|
this.startOffset = startOffset; |
|
this.endOffset = endOffset; |
|
} |
|
|
|
void AnchorDeleted(object sender, EventArgs e) |
|
{ |
|
context.Deactivate(new SnippetEventArgs(DeactivateReason.Deleted)); |
|
} |
|
|
|
public void OnInsertionCompleted() |
|
{ |
|
// anchors must be created in OnInsertionCompleted because they should move only |
|
// due to user insertions, not due to insertions of other snippet parts |
|
start = context.Document.CreateAnchor(startOffset); |
|
start.MovementType = AnchorMovementType.BeforeInsertion; |
|
end = context.Document.CreateAnchor(endOffset); |
|
end.MovementType = AnchorMovementType.AfterInsertion; |
|
start.Deleted += AnchorDeleted; |
|
end.Deleted += AnchorDeleted; |
|
|
|
// Be careful with references from the document to the editing/snippet layer - use weak events |
|
// to prevent memory leaks when the text area control gets dropped from the UI while the snippet is active. |
|
// The InsertionContext will keep us alive as long as the snippet is in interactive mode. |
|
TextDocumentWeakEventManager.TextChanged.AddListener(context.Document, this); |
|
|
|
background = new Renderer { Layer = KnownLayer.Background, element = this }; |
|
foreground = new Renderer { Layer = KnownLayer.Text, element = this }; |
|
context.TextArea.TextView.BackgroundRenderers.Add(background); |
|
context.TextArea.TextView.BackgroundRenderers.Add(foreground); |
|
context.TextArea.Caret.PositionChanged += Caret_PositionChanged; |
|
Caret_PositionChanged(null, null); |
|
|
|
this.Text = GetText(); |
|
} |
|
|
|
public void Deactivate(SnippetEventArgs e) |
|
{ |
|
TextDocumentWeakEventManager.TextChanged.RemoveListener(context.Document, this); |
|
context.TextArea.TextView.BackgroundRenderers.Remove(background); |
|
context.TextArea.TextView.BackgroundRenderers.Remove(foreground); |
|
context.TextArea.Caret.PositionChanged -= Caret_PositionChanged; |
|
} |
|
|
|
bool isCaretInside; |
|
|
|
void Caret_PositionChanged(object sender, EventArgs e) |
|
{ |
|
ISegment s = this.Segment; |
|
if (s != null) { |
|
bool newIsCaretInside = s.Contains(context.TextArea.Caret.Offset); |
|
if (newIsCaretInside != isCaretInside) { |
|
isCaretInside = newIsCaretInside; |
|
context.TextArea.TextView.InvalidateLayer(foreground.Layer); |
|
} |
|
} |
|
} |
|
|
|
Renderer background, foreground; |
|
|
|
public string Text { get; private set; } |
|
|
|
string GetText() |
|
{ |
|
if (start.IsDeleted || end.IsDeleted) |
|
return string.Empty; |
|
else |
|
return context.Document.GetText(start.Offset, Math.Max(0, end.Offset - start.Offset)); |
|
} |
|
|
|
public event EventHandler TextChanged; |
|
|
|
bool IWeakEventListener.ReceiveWeakEvent(Type managerType, object sender, EventArgs e) |
|
{ |
|
if (managerType == typeof(TextDocumentWeakEventManager.TextChanged)) { |
|
string newText = GetText(); |
|
if (this.Text != newText) { |
|
this.Text = newText; |
|
if (TextChanged != null) |
|
TextChanged(this, e); |
|
} |
|
return true; |
|
} |
|
return false; |
|
} |
|
|
|
public bool IsEditable { |
|
get { return true; } |
|
} |
|
|
|
public ISegment Segment { |
|
get { |
|
if (start.IsDeleted || end.IsDeleted) |
|
return null; |
|
else |
|
return new SimpleSegment(start.Offset, Math.Max(0, end.Offset - start.Offset)); |
|
} |
|
} |
|
|
|
sealed class Renderer : IBackgroundRenderer |
|
{ |
|
static readonly Brush backgroundBrush = CreateBackgroundBrush(); |
|
static readonly Pen activeBorderPen = CreateBorderPen(); |
|
|
|
static Brush CreateBackgroundBrush() |
|
{ |
|
SolidColorBrush b = new SolidColorBrush(Colors.LimeGreen); |
|
b.Opacity = 0.4; |
|
b.Freeze(); |
|
return b; |
|
} |
|
|
|
static Pen CreateBorderPen() |
|
{ |
|
Pen p = new Pen(Brushes.Black, 1); |
|
p.DashStyle = DashStyles.Dot; |
|
p.Freeze(); |
|
return p; |
|
} |
|
|
|
internal ReplaceableActiveElement element; |
|
|
|
public KnownLayer Layer { get; set; } |
|
|
|
public void Draw(TextView textView, System.Windows.Media.DrawingContext drawingContext) |
|
{ |
|
ISegment s = element.Segment; |
|
if (s != null) { |
|
BackgroundGeometryBuilder geoBuilder = new BackgroundGeometryBuilder(); |
|
geoBuilder.AlignToMiddleOfPixels = true; |
|
if (Layer == KnownLayer.Background) { |
|
geoBuilder.AddSegment(textView, s); |
|
drawingContext.DrawGeometry(backgroundBrush, null, geoBuilder.CreateGeometry()); |
|
} else { |
|
// draw foreground only if active |
|
if (element.isCaretInside) { |
|
geoBuilder.AddSegment(textView, s); |
|
foreach (BoundActiveElement boundElement in element.context.ActiveElements.OfType<BoundActiveElement>()) { |
|
if (boundElement.targetElement == element) { |
|
geoBuilder.AddSegment(textView, boundElement.Segment); |
|
geoBuilder.CloseFigure(); |
|
} |
|
} |
|
drawingContext.DrawGeometry(null, activeBorderPen, geoBuilder.CreateGeometry()); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
} |
|
}
|
|
|