Browse Source
git-svn-id: svn://svn.sharpdevelop.net/sharpdevelop/trunk@5064 1ccf3a8d-04fe-1044-b7c0-cef0b8235c61shortcuts
12 changed files with 717 additions and 0 deletions
@ -0,0 +1,27 @@ |
|||||||
|
// <file>
|
||||||
|
// <copyright see="prj:///doc/copyright.txt"/>
|
||||||
|
// <license see="prj:///doc/license.txt"/>
|
||||||
|
// <owner name="Daniel Grunwald"/>
|
||||||
|
// <version>$Revision$</version>
|
||||||
|
// </file>
|
||||||
|
|
||||||
|
using System; |
||||||
|
|
||||||
|
namespace ICSharpCode.AvalonEdit.Snippets |
||||||
|
{ |
||||||
|
/// <summary>
|
||||||
|
/// Represents an active element that allows the snippet to stay interactive after insertion.
|
||||||
|
/// </summary>
|
||||||
|
public interface IActiveElement |
||||||
|
{ |
||||||
|
/// <summary>
|
||||||
|
/// Called when the all snippet elements have been inserted.
|
||||||
|
/// </summary>
|
||||||
|
void OnInsertionCompleted(); |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called when the interactive mode is deactivated.
|
||||||
|
/// </summary>
|
||||||
|
void Deactivate(); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,178 @@ |
|||||||
|
// <file>
|
||||||
|
// <copyright see="prj:///doc/copyright.txt"/>
|
||||||
|
// <license see="prj:///doc/license.txt"/>
|
||||||
|
// <owner name="Daniel Grunwald"/>
|
||||||
|
// <version>$Revision$</version>
|
||||||
|
// </file>
|
||||||
|
|
||||||
|
using System; |
||||||
|
using System.Collections.Generic; |
||||||
|
using System.Windows.Input; |
||||||
|
|
||||||
|
using ICSharpCode.AvalonEdit.Document; |
||||||
|
using ICSharpCode.AvalonEdit.Editing; |
||||||
|
using ICSharpCode.AvalonEdit.Utils; |
||||||
|
|
||||||
|
namespace ICSharpCode.AvalonEdit.Snippets |
||||||
|
{ |
||||||
|
/// <summary>
|
||||||
|
/// Represents the context of a snippet insertion.
|
||||||
|
/// </summary>
|
||||||
|
public class InsertionContext |
||||||
|
{ |
||||||
|
/// <summary>
|
||||||
|
/// Creates a new InsertionContext instance.
|
||||||
|
/// </summary>
|
||||||
|
public InsertionContext(TextArea textArea, int insertionPosition) |
||||||
|
{ |
||||||
|
if (textArea == null) |
||||||
|
throw new ArgumentNullException("textArea"); |
||||||
|
this.TextArea = textArea; |
||||||
|
this.Document = textArea.Document; |
||||||
|
this.InsertionPosition = insertionPosition; |
||||||
|
|
||||||
|
DocumentLine startLine = this.Document.GetLineByOffset(insertionPosition); |
||||||
|
ISegment indentation = TextUtilities.GetWhitespaceAfter(this.Document, startLine.Offset); |
||||||
|
this.Indentation = Document.GetText(indentation.Offset, Math.Min(indentation.EndOffset, insertionPosition) - indentation.Offset); |
||||||
|
|
||||||
|
this.LineTerminator = NewLineFinder.GetNewLineFromDocument(this.Document, startLine.LineNumber); |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the text area.
|
||||||
|
/// </summary>
|
||||||
|
public TextArea TextArea { get; private set; } |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the text document.
|
||||||
|
/// </summary>
|
||||||
|
public TextDocument Document { get; private set; } |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the indentation at the insertion position.
|
||||||
|
/// </summary>
|
||||||
|
public string Indentation { get; private set; } |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the line terminator at the insertion position.
|
||||||
|
/// </summary>
|
||||||
|
public string LineTerminator { get; private set; } |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets/Sets the insertion position.
|
||||||
|
/// </summary>
|
||||||
|
public int InsertionPosition { get; set; } |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Inserts text at the insertion position and advances the insertion position.
|
||||||
|
/// </summary>
|
||||||
|
public void InsertText(string text) |
||||||
|
{ |
||||||
|
if (text == null) |
||||||
|
throw new ArgumentNullException("text"); |
||||||
|
int textOffset = 0; |
||||||
|
SimpleSegment segment; |
||||||
|
while ((segment = NewLineFinder.NextNewLine(text, textOffset)) != SimpleSegment.Invalid) { |
||||||
|
string insertString = text.Substring(textOffset, segment.Offset - textOffset) |
||||||
|
+ this.LineTerminator + this.Indentation; |
||||||
|
this.Document.Insert(InsertionPosition, insertString); |
||||||
|
this.InsertionPosition += insertString.Length; |
||||||
|
textOffset = segment.EndOffset; |
||||||
|
} |
||||||
|
string remainingInsertString = text.Substring(textOffset); |
||||||
|
this.Document.Insert(InsertionPosition, remainingInsertString); |
||||||
|
this.InsertionPosition += remainingInsertString.Length; |
||||||
|
} |
||||||
|
|
||||||
|
Dictionary<SnippetElement, IActiveElement> elementMap = new Dictionary<SnippetElement, IActiveElement>(); |
||||||
|
List<IActiveElement> registeredElements = new List<IActiveElement>(); |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Registers an active element. Elements should be registered during insertion and will be called back
|
||||||
|
/// when insertion has completed.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="owner">The snippet element that created the active element.</param>
|
||||||
|
/// <param name="element">The active element.</param>
|
||||||
|
public void RegisterActiveElement(SnippetElement owner, IActiveElement element) |
||||||
|
{ |
||||||
|
if (owner == null) |
||||||
|
throw new ArgumentNullException("owner"); |
||||||
|
if (element == null) |
||||||
|
throw new ArgumentNullException("element"); |
||||||
|
elementMap.Add(owner, element); |
||||||
|
registeredElements.Add(element); |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the active element belonging to the specified snippet element, or null if no such active element is found.
|
||||||
|
/// </summary>
|
||||||
|
public IActiveElement GetActiveElement(SnippetElement owner) |
||||||
|
{ |
||||||
|
if (owner == null) |
||||||
|
throw new ArgumentNullException("owner"); |
||||||
|
IActiveElement element; |
||||||
|
if (elementMap.TryGetValue(owner, out element)) |
||||||
|
return element; |
||||||
|
else |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
bool insertionCompleted; |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Calls the <see cref="IActiveElement.OnInsertionCompleted"/> method on all registered active elements
|
||||||
|
/// and raises the <see cref="InsertionCompleted"/> event.
|
||||||
|
/// </summary>
|
||||||
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1030:UseEventsWhereAppropriate", |
||||||
|
Justification="There is an event and this method is raising it.")] |
||||||
|
public void RaiseInsertionCompleted() |
||||||
|
{ |
||||||
|
foreach (IActiveElement element in registeredElements) { |
||||||
|
element.OnInsertionCompleted(); |
||||||
|
} |
||||||
|
if (InsertionCompleted != null) |
||||||
|
InsertionCompleted(this, EventArgs.Empty); |
||||||
|
insertionCompleted = true; |
||||||
|
if (registeredElements.Count == 0) { |
||||||
|
// deactivate immediately if there are no interactive elements
|
||||||
|
Deactivate(); |
||||||
|
} else { |
||||||
|
// register Escape key handler
|
||||||
|
TextArea.KeyDown += TextArea_KeyDown; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void TextArea_KeyDown(object sender, KeyEventArgs e) |
||||||
|
{ |
||||||
|
if (e.Key == Key.Escape) { |
||||||
|
Deactivate(); |
||||||
|
e.Handled = true; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Occurs when the all snippet elements have been inserted.
|
||||||
|
/// </summary>
|
||||||
|
public event EventHandler InsertionCompleted; |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Calls the <see cref="IActiveElement.Deactivate"/> method on all registered active elements.
|
||||||
|
/// </summary>
|
||||||
|
public void Deactivate() |
||||||
|
{ |
||||||
|
if (!insertionCompleted) |
||||||
|
throw new InvalidOperationException("Cannot call Deactivate() until RaiseInsertionCompleted() has finished."); |
||||||
|
TextArea.KeyDown -= TextArea_KeyDown; |
||||||
|
foreach (IActiveElement element in registeredElements) { |
||||||
|
element.Deactivate(); |
||||||
|
} |
||||||
|
if (Deactivated != null) |
||||||
|
Deactivated(this, EventArgs.Empty); |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Occurs when the interactive mode is deactivated.
|
||||||
|
/// </summary>
|
||||||
|
public event EventHandler Deactivated; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,36 @@ |
|||||||
|
// <file>
|
||||||
|
// <copyright see="prj:///doc/copyright.txt"/>
|
||||||
|
// <license see="prj:///doc/license.txt"/>
|
||||||
|
// <owner name="Daniel Grunwald"/>
|
||||||
|
// <version>$Revision$</version>
|
||||||
|
// </file>
|
||||||
|
|
||||||
|
using System; |
||||||
|
using System.Collections.Generic; |
||||||
|
using ICSharpCode.AvalonEdit.Editing; |
||||||
|
using ICSharpCode.AvalonEdit.Utils; |
||||||
|
|
||||||
|
namespace ICSharpCode.AvalonEdit.Snippets |
||||||
|
{ |
||||||
|
/// <summary>
|
||||||
|
/// A code snippet that can be inserted into the text editor.
|
||||||
|
/// </summary>
|
||||||
|
[Serializable] |
||||||
|
public class Snippet : SnippetContainerElement |
||||||
|
{ |
||||||
|
/// <summary>
|
||||||
|
/// Inserts the snippet into the text area.
|
||||||
|
/// </summary>
|
||||||
|
public void Insert(TextArea textArea) |
||||||
|
{ |
||||||
|
if (textArea == null) |
||||||
|
throw new ArgumentNullException("textArea"); |
||||||
|
Freeze(); |
||||||
|
InsertionContext context = new InsertionContext(textArea, textArea.Caret.Offset); |
||||||
|
using (context.Document.RunUpdate()) { |
||||||
|
Insert(context); |
||||||
|
context.RaiseInsertionCompleted(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,96 @@ |
|||||||
|
// <file>
|
||||||
|
// <copyright see="prj:///doc/copyright.txt"/>
|
||||||
|
// <license see="prj:///doc/license.txt"/>
|
||||||
|
// <owner name="Daniel Grunwald"/>
|
||||||
|
// <version>$Revision$</version>
|
||||||
|
// </file>
|
||||||
|
|
||||||
|
using System; |
||||||
|
using ICSharpCode.AvalonEdit.Document; |
||||||
|
|
||||||
|
namespace ICSharpCode.AvalonEdit.Snippets |
||||||
|
{ |
||||||
|
/// <summary>
|
||||||
|
/// An element that binds to a <see cref="SnippetReplaceableTextElement"/> and displays the same text.
|
||||||
|
/// </summary>
|
||||||
|
[Serializable] |
||||||
|
public class SnippetBoundElement : SnippetElement |
||||||
|
{ |
||||||
|
SnippetReplaceableTextElement targetElement; |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets/Sets the target element.
|
||||||
|
/// </summary>
|
||||||
|
public SnippetReplaceableTextElement TargetElement { |
||||||
|
get { return targetElement; } |
||||||
|
set { |
||||||
|
CheckBeforeMutation(); |
||||||
|
targetElement = value; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts the text before copying it.
|
||||||
|
/// </summary>
|
||||||
|
public virtual string ConvertText(string input) |
||||||
|
{ |
||||||
|
return input; |
||||||
|
} |
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override void Insert(InsertionContext context) |
||||||
|
{ |
||||||
|
if (targetElement != null) { |
||||||
|
int start = context.InsertionPosition; |
||||||
|
string inputText = targetElement.Text; |
||||||
|
if (inputText != null) { |
||||||
|
context.InsertText(ConvertText(inputText)); |
||||||
|
} |
||||||
|
int end = context.InsertionPosition; |
||||||
|
AnchorSegment segment = new AnchorSegment(context.Document, start, end - start); |
||||||
|
context.RegisterActiveElement(this, new BoundActiveElement(context, targetElement, this, segment)); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
sealed class BoundActiveElement : IActiveElement |
||||||
|
{ |
||||||
|
InsertionContext context; |
||||||
|
SnippetReplaceableTextElement targetSnippetElement; |
||||||
|
SnippetBoundElement boundElement; |
||||||
|
IReplaceableActiveElement targetElement; |
||||||
|
AnchorSegment segment; |
||||||
|
|
||||||
|
public BoundActiveElement(InsertionContext context, SnippetReplaceableTextElement targetSnippetElement, SnippetBoundElement boundElement, AnchorSegment segment) |
||||||
|
{ |
||||||
|
this.context = context; |
||||||
|
this.targetSnippetElement = targetSnippetElement; |
||||||
|
this.boundElement = boundElement; |
||||||
|
this.segment = segment; |
||||||
|
} |
||||||
|
|
||||||
|
public void OnInsertionCompleted() |
||||||
|
{ |
||||||
|
targetElement = context.GetActiveElement(targetSnippetElement) as IReplaceableActiveElement; |
||||||
|
if (targetElement != null) { |
||||||
|
targetElement.TextChanged += targetElement_TextChanged; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void targetElement_TextChanged(object sender, EventArgs e) |
||||||
|
{ |
||||||
|
int offset = segment.Offset; |
||||||
|
int length = segment.Length; |
||||||
|
string text = boundElement.ConvertText(targetElement.Text); |
||||||
|
context.Document.Replace(offset, length, text); |
||||||
|
if (length == 0) { |
||||||
|
// replacing an empty anchor segment with text won't enlarge it, so we have to recreate it
|
||||||
|
segment = new AnchorSegment(context.Document, offset, text.Length); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public void Deactivate() |
||||||
|
{ |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,28 @@ |
|||||||
|
// <file>
|
||||||
|
// <copyright see="prj:///doc/copyright.txt"/>
|
||||||
|
// <license see="prj:///doc/license.txt"/>
|
||||||
|
// <owner name="Daniel Grunwald"/>
|
||||||
|
// <version>$Revision$</version>
|
||||||
|
// </file>
|
||||||
|
|
||||||
|
using System; |
||||||
|
using ICSharpCode.AvalonEdit.Document; |
||||||
|
|
||||||
|
namespace ICSharpCode.AvalonEdit.Snippets |
||||||
|
{ |
||||||
|
/// <summary>
|
||||||
|
/// Sets the caret position after interactive mode has finished.
|
||||||
|
/// </summary>
|
||||||
|
public class SnippetCaretElement : SnippetElement |
||||||
|
{ |
||||||
|
/// <inheritdoc/>
|
||||||
|
public override void Insert(InsertionContext context) |
||||||
|
{ |
||||||
|
TextAnchor pos = context.Document.CreateAnchor(context.InsertionPosition); |
||||||
|
pos.SurviveDeletion = true; |
||||||
|
context.Deactivated += delegate { |
||||||
|
context.TextArea.Caret.Offset = pos.Offset; |
||||||
|
}; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,45 @@ |
|||||||
|
// <file>
|
||||||
|
// <copyright see="prj:///doc/copyright.txt"/>
|
||||||
|
// <license see="prj:///doc/license.txt"/>
|
||||||
|
// <owner name="Daniel Grunwald"/>
|
||||||
|
// <version>$Revision$</version>
|
||||||
|
// </file>
|
||||||
|
|
||||||
|
using System; |
||||||
|
using System.Collections.Generic; |
||||||
|
using ICSharpCode.AvalonEdit.Editing; |
||||||
|
using ICSharpCode.AvalonEdit.Utils; |
||||||
|
|
||||||
|
namespace ICSharpCode.AvalonEdit.Snippets |
||||||
|
{ |
||||||
|
/// <summary>
|
||||||
|
/// A snippet element that has sub-elements.
|
||||||
|
/// </summary>
|
||||||
|
[Serializable] |
||||||
|
public class SnippetContainerElement : SnippetElement |
||||||
|
{ |
||||||
|
FreezableNullSafeCollection<SnippetElement> elements = new FreezableNullSafeCollection<SnippetElement>(); |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the list of child elements.
|
||||||
|
/// </summary>
|
||||||
|
public IList<SnippetElement> Elements { |
||||||
|
get { return elements; } |
||||||
|
} |
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
protected override void FreezeInternal() |
||||||
|
{ |
||||||
|
elements.Freeze(); |
||||||
|
base.FreezeInternal(); |
||||||
|
} |
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override void Insert(InsertionContext context) |
||||||
|
{ |
||||||
|
foreach (SnippetElement e in this.Elements) { |
||||||
|
e.Insert(context); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,66 @@ |
|||||||
|
// <file>
|
||||||
|
// <copyright see="prj:///doc/copyright.txt"/>
|
||||||
|
// <license see="prj:///doc/license.txt"/>
|
||||||
|
// <owner name="Daniel Grunwald"/>
|
||||||
|
// <version>$Revision$</version>
|
||||||
|
// </file>
|
||||||
|
|
||||||
|
using System; |
||||||
|
using System.Collections.Generic; |
||||||
|
using ICSharpCode.AvalonEdit.Editing; |
||||||
|
using ICSharpCode.AvalonEdit.Utils; |
||||||
|
|
||||||
|
namespace ICSharpCode.AvalonEdit.Snippets |
||||||
|
{ |
||||||
|
/// <summary>
|
||||||
|
/// An element inside a snippet.
|
||||||
|
/// </summary>
|
||||||
|
[Serializable] |
||||||
|
public abstract class SnippetElement : IFreezable |
||||||
|
{ |
||||||
|
// TODO: think about removing IFreezable, it may not be required after all
|
||||||
|
|
||||||
|
#region IFreezable infrastructure
|
||||||
|
bool isFrozen; |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets if this instance is frozen. Frozen instances are immutable and thus thread-safe.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsFrozen { |
||||||
|
get { return isFrozen; } |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Freezes this instance.
|
||||||
|
/// </summary>
|
||||||
|
public void Freeze() |
||||||
|
{ |
||||||
|
if (!isFrozen) { |
||||||
|
FreezeInternal(); |
||||||
|
isFrozen = true; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Override this method to freeze child elements.
|
||||||
|
/// </summary>
|
||||||
|
protected virtual void FreezeInternal() |
||||||
|
{ |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Throws an exception if this instance is frozen.
|
||||||
|
/// </summary>
|
||||||
|
protected void CheckBeforeMutation() |
||||||
|
{ |
||||||
|
if (isFrozen) |
||||||
|
throw new InvalidOperationException("Cannot mutate frozen " + GetType().Name); |
||||||
|
} |
||||||
|
#endregion
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Performs insertion of the snippet.
|
||||||
|
/// </summary>
|
||||||
|
public abstract void Insert(InsertionContext context); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,117 @@ |
|||||||
|
// <file>
|
||||||
|
// <copyright see="prj:///doc/copyright.txt"/>
|
||||||
|
// <license see="prj:///doc/license.txt"/>
|
||||||
|
// <owner name="Daniel Grunwald"/>
|
||||||
|
// <version>$Revision$</version>
|
||||||
|
// </file>
|
||||||
|
|
||||||
|
using System; |
||||||
|
using System.Windows; |
||||||
|
using System.Windows.Threading; |
||||||
|
|
||||||
|
using ICSharpCode.AvalonEdit.Document; |
||||||
|
|
||||||
|
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)); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// <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(); |
||||||
|
} |
||||||
|
|
||||||
|
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 text areas get 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); |
||||||
|
|
||||||
|
this.Text = GetText(); |
||||||
|
} |
||||||
|
|
||||||
|
public void Deactivate() |
||||||
|
{ |
||||||
|
TextDocumentWeakEventManager.TextChanged.RemoveListener(context.Document, this); |
||||||
|
} |
||||||
|
|
||||||
|
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; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,39 @@ |
|||||||
|
// <file>
|
||||||
|
// <copyright see="prj:///doc/copyright.txt"/>
|
||||||
|
// <license see="prj:///doc/license.txt"/>
|
||||||
|
// <owner name="Daniel Grunwald"/>
|
||||||
|
// <version>$Revision$</version>
|
||||||
|
// </file>
|
||||||
|
|
||||||
|
using System; |
||||||
|
using ICSharpCode.AvalonEdit.Document; |
||||||
|
|
||||||
|
namespace ICSharpCode.AvalonEdit.Snippets |
||||||
|
{ |
||||||
|
/// <summary>
|
||||||
|
/// Represents a text element in a snippet.
|
||||||
|
/// </summary>
|
||||||
|
[Serializable] |
||||||
|
public class SnippetTextElement : SnippetElement |
||||||
|
{ |
||||||
|
string text; |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The text to be inserted.
|
||||||
|
/// </summary>
|
||||||
|
public string Text { |
||||||
|
get { return text; } |
||||||
|
set { |
||||||
|
CheckBeforeMutation(); |
||||||
|
text = value; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override void Insert(InsertionContext context) |
||||||
|
{ |
||||||
|
if (text != null) |
||||||
|
context.InsertText(text); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,72 @@ |
|||||||
|
// <file>
|
||||||
|
// <copyright see="prj:///doc/copyright.txt"/>
|
||||||
|
// <license see="prj:///doc/license.txt"/>
|
||||||
|
// <owner name="Daniel Grunwald"/>
|
||||||
|
// <version>$Revision$</version>
|
||||||
|
// </file>
|
||||||
|
|
||||||
|
using System; |
||||||
|
|
||||||
|
namespace ICSharpCode.AvalonEdit.Utils |
||||||
|
{ |
||||||
|
interface IFreezable |
||||||
|
{ |
||||||
|
/// <summary>
|
||||||
|
/// Gets if this instance is frozen. Frozen instances are immutable and thus thread-safe.
|
||||||
|
/// </summary>
|
||||||
|
bool IsFrozen { get; } |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Freezes this instance.
|
||||||
|
/// </summary>
|
||||||
|
void Freeze(); |
||||||
|
} |
||||||
|
|
||||||
|
[Serializable] |
||||||
|
sealed class FreezableNullSafeCollection<T> : NullSafeCollection<T>, IFreezable where T : class, IFreezable |
||||||
|
{ |
||||||
|
bool isFrozen; |
||||||
|
|
||||||
|
public bool IsFrozen { get { return isFrozen; } } |
||||||
|
|
||||||
|
public void Freeze() |
||||||
|
{ |
||||||
|
if (!isFrozen) { |
||||||
|
foreach (T item in this) { |
||||||
|
item.Freeze(); |
||||||
|
} |
||||||
|
isFrozen = true; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void CheckBeforeMutation() |
||||||
|
{ |
||||||
|
if (isFrozen) |
||||||
|
throw new InvalidOperationException("Cannot mutate frozen " + GetType().Name); |
||||||
|
} |
||||||
|
|
||||||
|
protected override void ClearItems() |
||||||
|
{ |
||||||
|
this.CheckBeforeMutation(); |
||||||
|
base.ClearItems(); |
||||||
|
} |
||||||
|
|
||||||
|
protected override void InsertItem(int index, T item) |
||||||
|
{ |
||||||
|
this.CheckBeforeMutation(); |
||||||
|
base.InsertItem(index, item); |
||||||
|
} |
||||||
|
|
||||||
|
protected override void RemoveItem(int index) |
||||||
|
{ |
||||||
|
this.CheckBeforeMutation(); |
||||||
|
base.RemoveItem(index); |
||||||
|
} |
||||||
|
|
||||||
|
protected override void SetItem(int index, T item) |
||||||
|
{ |
||||||
|
this.CheckBeforeMutation(); |
||||||
|
base.SetItem(index, item); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue