Browse Source

Code snippets: implemented input handling and background renderer.

Added support for "stacked" input handlers.

git-svn-id: svn://svn.sharpdevelop.net/sharpdevelop/trunk@5065 1ccf3a8d-04fe-1044-b7c0-cef0b8235c61
shortcuts
Daniel Grunwald 17 years ago
parent
commit
aad8c0de16
  1. 1
      src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/TextMarkerService.cs
  2. 10
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/CodeCompletion/CompletionWindow.cs
  3. 2
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/LineNumberMargin.cs
  4. 68
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/TextArea.cs
  5. 14
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/TextAreaInputHandler.cs
  6. 2
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Folding/FoldingManager.cs
  7. 2
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Highlighting/HighlightingColorizer.cs
  8. 2
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/ICSharpCode.AvalonEdit.csproj
  9. 2
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/BackgroundGeometryBuilder.cs
  10. 12
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Snippets/IActiveElement.cs
  11. 134
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Snippets/InsertionContext.cs
  12. 3
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Snippets/Snippet.cs
  13. 34
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Snippets/SnippetBoundElement.cs
  14. 9
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Snippets/SnippetCaretElement.cs
  15. 9
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Snippets/SnippetContainerElement.cs
  16. 42
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Snippets/SnippetElement.cs
  17. 83
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Snippets/SnippetInputHandler.cs
  18. 98
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Snippets/SnippetReplaceableTextElement.cs
  19. 5
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Snippets/SnippetTextElement.cs
  20. 2
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/TextEditor.cs
  21. 72
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Utils/IFreezable.cs

1
src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/TextMarkerService.cs

@ -155,6 +155,7 @@ namespace ICSharpCode.AvalonEdit.AddIn
foreach (TextMarker marker in markers.FindOverlappingSegments(viewStart, viewEnd - viewStart)) { foreach (TextMarker marker in markers.FindOverlappingSegments(viewStart, viewEnd - viewStart)) {
if (marker.BackgroundColor != null) { if (marker.BackgroundColor != null) {
BackgroundGeometryBuilder geoBuilder = new BackgroundGeometryBuilder(); BackgroundGeometryBuilder geoBuilder = new BackgroundGeometryBuilder();
geoBuilder.CornerRadius = 3;
geoBuilder.AddSegment(textView, marker); geoBuilder.AddSegment(textView, marker);
Geometry geometry = geoBuilder.CreateGeometry(); Geometry geometry = geoBuilder.CreateGeometry();
if (geometry != null) { if (geometry != null) {

10
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/CodeCompletion/CompletionWindow.cs

@ -106,7 +106,7 @@ namespace ICSharpCode.AvalonEdit.CodeCompletion
this.TextArea.MouseWheel += textArea_MouseWheel; this.TextArea.MouseWheel += textArea_MouseWheel;
this.TextArea.PreviewTextInput += textArea_PreviewTextInput; this.TextArea.PreviewTextInput += textArea_PreviewTextInput;
myInputHandler = new InputHandler(this); myInputHandler = new InputHandler(this);
this.TextArea.ActiveInputHandler = myInputHandler; this.TextArea.PushStackedInputHandler(myInputHandler);
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -116,8 +116,7 @@ namespace ICSharpCode.AvalonEdit.CodeCompletion
this.TextArea.MouseWheel -= textArea_MouseWheel; this.TextArea.MouseWheel -= textArea_MouseWheel;
this.TextArea.PreviewTextInput -= textArea_PreviewTextInput; this.TextArea.PreviewTextInput -= textArea_PreviewTextInput;
base.DetachEvents(); base.DetachEvents();
if (this.TextArea.ActiveInputHandler == myInputHandler) this.TextArea.PopStackedInputHandler(myInputHandler);
this.TextArea.ActiveInputHandler = this.TextArea.DefaultInputHandler;
} }
#region InputHandler #region InputHandler
@ -142,14 +141,11 @@ namespace ICSharpCode.AvalonEdit.CodeCompletion
public void Attach() public void Attach()
{ {
this.TextArea.DefaultInputHandler.Attach();
} }
public void Detach() public void Detach()
{ {
this.TextArea.DefaultInputHandler.Detach(); window.Close();
// close with dispatcher so we don't get reentrance problems in input handler Detach/Attach
window.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(window.Close));
} }
} }
#endregion #endregion

2
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/LineNumberMargin.cs

@ -104,7 +104,7 @@ namespace ICSharpCode.AvalonEdit.Editing
OnDocumentLineCountChanged(); OnDocumentLineCountChanged();
} }
/// <inheritdoc/> /// <inheritdoc cref="IWeakEventListener.ReceiveWeakEvent"/>
protected virtual bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e) protected virtual bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
{ {
if (managerType == typeof(TextDocumentWeakEventManager.LineCountChanged)) { if (managerType == typeof(TextDocumentWeakEventManager.LineCountChanged)) {

68
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/TextArea.cs

@ -6,6 +6,7 @@
// </file> // </file>
using System; using System;
using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Collections.Specialized; using System.Collections.Specialized;
using System.ComponentModel; using System.ComponentModel;
@ -83,24 +84,39 @@ namespace ICSharpCode.AvalonEdit.Editing
/// <summary> /// <summary>
/// Gets the default input handler. /// Gets the default input handler.
/// </summary> /// </summary>
/// <remarks><inheritdoc cref="ITextAreaInputHandler"/></remarks>
public TextAreaDefaultInputHandler DefaultInputHandler { get; private set; } public TextAreaDefaultInputHandler DefaultInputHandler { get; private set; }
ITextAreaInputHandler activeInputHandler; ITextAreaInputHandler activeInputHandler;
bool isChangingInputHandler;
/// <summary> /// <summary>
/// Gets/Sets the active input handler. /// Gets/Sets the active input handler.
/// This property does not return currently active stacked input handlers. Setting this property detached all stacked input handlers.
/// </summary> /// </summary>
/// <remarks><inheritdoc cref="ITextAreaInputHandler"/></remarks>
public ITextAreaInputHandler ActiveInputHandler { public ITextAreaInputHandler ActiveInputHandler {
get { return activeInputHandler; } get { return activeInputHandler; }
set { set {
if (value != null && value.TextArea != this) if (value != null && value.TextArea != this)
throw new ArgumentException("The input handler was created for a different text area than this one."); throw new ArgumentException("The input handler was created for a different text area than this one.");
if (isChangingInputHandler)
throw new InvalidOperationException("Cannot set ActiveInputHandler recursively");
if (activeInputHandler != value) { if (activeInputHandler != value) {
if (activeInputHandler != null) isChangingInputHandler = true;
activeInputHandler.Detach(); try {
activeInputHandler = value; // pop the whole stack
if (value != null) PopStackedInputHandler(stackedInputHandlers.LastOrDefault());
value.Attach(); Debug.Assert(stackedInputHandlers.IsEmpty);
if (activeInputHandler != null)
activeInputHandler.Detach();
activeInputHandler = value;
if (value != null)
value.Attach();
} finally {
isChangingInputHandler = false;
}
if (ActiveInputHandlerChanged != null) if (ActiveInputHandlerChanged != null)
ActiveInputHandlerChanged(this, EventArgs.Empty); ActiveInputHandlerChanged(this, EventArgs.Empty);
} }
@ -111,6 +127,46 @@ namespace ICSharpCode.AvalonEdit.Editing
/// Occurs when the ActiveInputHandler property changes. /// Occurs when the ActiveInputHandler property changes.
/// </summary> /// </summary>
public event EventHandler ActiveInputHandlerChanged; public event EventHandler ActiveInputHandlerChanged;
ImmutableStack<ITextAreaInputHandler> stackedInputHandlers = ImmutableStack<ITextAreaInputHandler>.Empty;
/// <summary>
/// Gets the list of currently active stacked input handlers.
/// </summary>
/// <remarks><inheritdoc cref="ITextAreaInputHandler"/></remarks>
public ImmutableStack<ITextAreaInputHandler> StackedInputHandlers {
get { return stackedInputHandlers; }
}
/// <summary>
/// Pushes an input handler onto the list of stacked input handlers.
/// </summary>
/// <remarks><inheritdoc cref="ITextAreaInputHandler"/></remarks>
public void PushStackedInputHandler(ITextAreaInputHandler inputHandler)
{
if (inputHandler == null)
throw new ArgumentNullException("inputHandler");
stackedInputHandlers = stackedInputHandlers.Push(inputHandler);
inputHandler.Attach();
}
/// <summary>
/// Pops the stacked input handler (and all input handlers above it).
/// If <paramref name="inputHandler"/> is not found in the currently stacked input handlers, or is null, this method
/// does nothing.
/// </summary>
/// <remarks><inheritdoc cref="ITextAreaInputHandler"/></remarks>
public void PopStackedInputHandler(ITextAreaInputHandler inputHandler)
{
if (stackedInputHandlers.Any(i => i == inputHandler)) {
ITextAreaInputHandler oldHandler;
do {
oldHandler = stackedInputHandlers.Peek();
stackedInputHandlers = stackedInputHandlers.Pop();
oldHandler.Detach();
} while (oldHandler != inputHandler);
}
}
#endregion #endregion
#region Document property #region Document property
@ -202,7 +258,7 @@ namespace ICSharpCode.AvalonEdit.Editing
#endregion #endregion
#region ReceiveWeakEvent #region ReceiveWeakEvent
/// <inheritdoc/> /// <inheritdoc cref="IWeakEventListener.ReceiveWeakEvent"/>
protected virtual bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e) protected virtual bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
{ {
if (managerType == typeof(TextDocumentWeakEventManager.Changing)) { if (managerType == typeof(TextDocumentWeakEventManager.Changing)) {

14
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/TextAreaInputHandler.cs

@ -16,6 +16,19 @@ namespace ICSharpCode.AvalonEdit.Editing
/// <summary> /// <summary>
/// A set of input bindings and event handlers for the text area. /// A set of input bindings and event handlers for the text area.
/// </summary> /// </summary>
/// <remarks>
/// <para>
/// There is one active input handler per text area (<see cref="Editing.TextArea.ActiveInputHandler"/>), plus
/// a number of active stacked input handlers.
/// </para>
/// <para>
/// The text area also stores a reference to a default input handler, but that is not necessarily active.
/// </para>
/// <para>
/// Stacked input handlers work in addition to the set of currently active handlers (without detaching them).
/// They are detached in the reverse order of being attached.
/// </para>
/// </remarks>
public interface ITextAreaInputHandler public interface ITextAreaInputHandler
{ {
/// <summary> /// <summary>
@ -39,6 +52,7 @@ namespace ICSharpCode.AvalonEdit.Editing
/// <summary> /// <summary>
/// Default-implementation of <see cref="ITextAreaInputHandler"/>. /// Default-implementation of <see cref="ITextAreaInputHandler"/>.
/// </summary> /// </summary>
/// <remarks><inheritdoc cref="ITextAreaInputHandler"/></remarks>
public class TextAreaInputHandler : ITextAreaInputHandler public class TextAreaInputHandler : ITextAreaInputHandler
{ {
readonly ObserveAddRemoveCollection<CommandBinding> commandBindings; readonly ObserveAddRemoveCollection<CommandBinding> commandBindings;

2
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Folding/FoldingManager.cs

@ -48,7 +48,7 @@ namespace ICSharpCode.AvalonEdit.Folding
#endregion #endregion
#region ReceiveWeakEvent #region ReceiveWeakEvent
/// <inheritdoc/> /// <inheritdoc cref="IWeakEventListener.ReceiveWeakEvent"/>
protected virtual bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e) protected virtual bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
{ {
if (managerType == typeof(TextDocumentWeakEventManager.Changed)) { if (managerType == typeof(TextDocumentWeakEventManager.Changed)) {

2
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Highlighting/HighlightingColorizer.cs

@ -43,7 +43,7 @@ namespace ICSharpCode.AvalonEdit.Highlighting
OnDocumentChanged(); OnDocumentChanged();
} }
/// <inheritdoc/> /// <inheritdoc cref="IWeakEventListener.ReceiveWeakEvent"/>
protected virtual bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e) protected virtual bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
{ {
if (managerType == typeof(TextViewWeakEventManager.DocumentChanged)) { if (managerType == typeof(TextViewWeakEventManager.DocumentChanged)) {

2
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/ICSharpCode.AvalonEdit.csproj

@ -278,6 +278,7 @@
<DependentUpon>VisualLine.cs</DependentUpon> <DependentUpon>VisualLine.cs</DependentUpon>
</Compile> </Compile>
<Compile Include="Snippets\IActiveElement.cs" /> <Compile Include="Snippets\IActiveElement.cs" />
<Compile Include="Snippets\SnippetInputHandler.cs" />
<Compile Include="Snippets\Snippet.cs" /> <Compile Include="Snippets\Snippet.cs" />
<Compile Include="Snippets\SnippetBoundElement.cs" /> <Compile Include="Snippets\SnippetBoundElement.cs" />
<Compile Include="Snippets\SnippetCaretElement.cs" /> <Compile Include="Snippets\SnippetCaretElement.cs" />
@ -318,7 +319,6 @@
<Compile Include="Utils\Empty.cs" /> <Compile Include="Utils\Empty.cs" />
<Compile Include="Utils\ExtensionMethods.cs" /> <Compile Include="Utils\ExtensionMethods.cs" />
<Compile Include="Utils\FileReader.cs" /> <Compile Include="Utils\FileReader.cs" />
<Compile Include="Utils\IFreezable.cs" />
<Compile Include="Utils\ImmutableStack.cs" /> <Compile Include="Utils\ImmutableStack.cs" />
<Compile Include="Utils\NullSafeCollection.cs" /> <Compile Include="Utils\NullSafeCollection.cs" />
<Compile Include="Utils\ObserveAddRemoveCollection.cs" /> <Compile Include="Utils\ObserveAddRemoveCollection.cs" />

2
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/BackgroundGeometryBuilder.cs

@ -22,7 +22,7 @@ namespace ICSharpCode.AvalonEdit.Rendering
/// </summary> /// </summary>
public sealed class BackgroundGeometryBuilder public sealed class BackgroundGeometryBuilder
{ {
double cornerRadius = 3; double cornerRadius;
/// <summary> /// <summary>
/// Gets/sets the radius of the rounded corners. /// Gets/sets the radius of the rounded corners.

12
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Snippets/IActiveElement.cs

@ -6,6 +6,8 @@
// </file> // </file>
using System; using System;
using System.Windows.Media;
using ICSharpCode.AvalonEdit.Document;
namespace ICSharpCode.AvalonEdit.Snippets namespace ICSharpCode.AvalonEdit.Snippets
{ {
@ -23,5 +25,15 @@ namespace ICSharpCode.AvalonEdit.Snippets
/// Called when the interactive mode is deactivated. /// Called when the interactive mode is deactivated.
/// </summary> /// </summary>
void Deactivate(); void Deactivate();
/// <summary>
/// Gets whether this element is editable (the user will be able to select it with Tab).
/// </summary>
bool IsEditable { get; }
/// <summary>
/// Gets the segment associated with this element. May be null.
/// </summary>
ISegment Segment { get; }
} }
} }

134
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Snippets/InsertionContext.cs

@ -7,6 +7,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Windows;
using System.Windows.Input; using System.Windows.Input;
using ICSharpCode.AvalonEdit.Document; using ICSharpCode.AvalonEdit.Document;
@ -18,8 +19,19 @@ namespace ICSharpCode.AvalonEdit.Snippets
/// <summary> /// <summary>
/// Represents the context of a snippet insertion. /// Represents the context of a snippet insertion.
/// </summary> /// </summary>
public class InsertionContext public class InsertionContext : IWeakEventListener
{ {
enum Status
{
Insertion,
RaisingInsertionCompleted,
Interactive,
RaisingDeactivated,
Deactivated
}
Status currentStatus = Status.Insertion;
/// <summary> /// <summary>
/// Creates a new InsertionContext instance. /// Creates a new InsertionContext instance.
/// </summary> /// </summary>
@ -30,10 +42,12 @@ namespace ICSharpCode.AvalonEdit.Snippets
this.TextArea = textArea; this.TextArea = textArea;
this.Document = textArea.Document; this.Document = textArea.Document;
this.InsertionPosition = insertionPosition; this.InsertionPosition = insertionPosition;
this.startPosition = insertionPosition;
DocumentLine startLine = this.Document.GetLineByOffset(insertionPosition); DocumentLine startLine = this.Document.GetLineByOffset(insertionPosition);
ISegment indentation = TextUtilities.GetWhitespaceAfter(this.Document, startLine.Offset); ISegment indentation = TextUtilities.GetWhitespaceAfter(this.Document, startLine.Offset);
this.Indentation = Document.GetText(indentation.Offset, Math.Min(indentation.EndOffset, insertionPosition) - indentation.Offset); this.Indentation = Document.GetText(indentation.Offset, Math.Min(indentation.EndOffset, insertionPosition) - indentation.Offset);
this.Tab = textArea.Options.IndentationString;
this.LineTerminator = NewLineFinder.GetNewLineFromDocument(this.Document, startLine.LineNumber); this.LineTerminator = NewLineFinder.GetNewLineFromDocument(this.Document, startLine.LineNumber);
} }
@ -53,6 +67,11 @@ namespace ICSharpCode.AvalonEdit.Snippets
/// </summary> /// </summary>
public string Indentation { get; private set; } public string Indentation { get; private set; }
/// <summary>
/// Gets the indentation string for a single indentation level.
/// </summary>
public string Tab { get; private set; }
/// <summary> /// <summary>
/// Gets the line terminator at the insertion position. /// Gets the line terminator at the insertion position.
/// </summary> /// </summary>
@ -63,6 +82,9 @@ namespace ICSharpCode.AvalonEdit.Snippets
/// </summary> /// </summary>
public int InsertionPosition { get; set; } public int InsertionPosition { get; set; }
readonly int startPosition;
AnchorSegment wholeSnippetAnchor;
/// <summary> /// <summary>
/// Inserts text at the insertion position and advances the insertion position. /// Inserts text at the insertion position and advances the insertion position.
/// </summary> /// </summary>
@ -70,18 +92,25 @@ namespace ICSharpCode.AvalonEdit.Snippets
{ {
if (text == null) if (text == null)
throw new ArgumentNullException("text"); throw new ArgumentNullException("text");
int textOffset = 0; if (currentStatus != Status.Insertion)
SimpleSegment segment; throw new InvalidOperationException();
while ((segment = NewLineFinder.NextNewLine(text, textOffset)) != SimpleSegment.Invalid) {
string insertString = text.Substring(textOffset, segment.Offset - textOffset) text = text.Replace("\t", this.Tab);
+ this.LineTerminator + this.Indentation;
this.Document.Insert(InsertionPosition, insertString); using (this.Document.RunUpdate()) {
this.InsertionPosition += insertString.Length; int textOffset = 0;
textOffset = segment.EndOffset; 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;
} }
string remainingInsertString = text.Substring(textOffset);
this.Document.Insert(InsertionPosition, remainingInsertString);
this.InsertionPosition += remainingInsertString.Length;
} }
Dictionary<SnippetElement, IActiveElement> elementMap = new Dictionary<SnippetElement, IActiveElement>(); Dictionary<SnippetElement, IActiveElement> elementMap = new Dictionary<SnippetElement, IActiveElement>();
@ -99,6 +128,8 @@ namespace ICSharpCode.AvalonEdit.Snippets
throw new ArgumentNullException("owner"); throw new ArgumentNullException("owner");
if (element == null) if (element == null)
throw new ArgumentNullException("element"); throw new ArgumentNullException("element");
if (currentStatus != Status.Insertion)
throw new InvalidOperationException();
elementMap.Add(owner, element); elementMap.Add(owner, element);
registeredElements.Add(element); registeredElements.Add(element);
} }
@ -117,39 +148,53 @@ namespace ICSharpCode.AvalonEdit.Snippets
return null; return null;
} }
bool insertionCompleted; /// <summary>
/// Gets the list of active elements.
/// </summary>
public IEnumerable<IActiveElement> ActiveElements {
get { return registeredElements; }
}
/// <summary> /// <summary>
/// Calls the <see cref="IActiveElement.OnInsertionCompleted"/> method on all registered active elements /// Calls the <see cref="IActiveElement.OnInsertionCompleted"/> method on all registered active elements
/// and raises the <see cref="InsertionCompleted"/> event. /// and raises the <see cref="InsertionCompleted"/> event.
/// </summary> /// </summary>
/// <param name="e">The EventArgs to use</param>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1030:UseEventsWhereAppropriate", [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1030:UseEventsWhereAppropriate",
Justification="There is an event and this method is raising it.")] Justification="There is an event and this method is raising it.")]
public void RaiseInsertionCompleted() public void RaiseInsertionCompleted(EventArgs e)
{ {
if (currentStatus != Status.Insertion)
throw new InvalidOperationException();
if (e == null)
e = EventArgs.Empty;
currentStatus = Status.RaisingInsertionCompleted;
int endPosition = this.InsertionPosition;
this.wholeSnippetAnchor = new AnchorSegment(Document, startPosition, endPosition - startPosition);
TextDocumentWeakEventManager.UpdateFinished.AddListener(Document, this);
foreach (IActiveElement element in registeredElements) { foreach (IActiveElement element in registeredElements) {
element.OnInsertionCompleted(); element.OnInsertionCompleted();
} }
if (InsertionCompleted != null) if (InsertionCompleted != null)
InsertionCompleted(this, EventArgs.Empty); InsertionCompleted(this, e);
insertionCompleted = true; currentStatus = Status.Interactive;
if (registeredElements.Count == 0) { if (registeredElements.Count == 0) {
// deactivate immediately if there are no interactive elements // deactivate immediately if there are no interactive elements
Deactivate(); Deactivate(EventArgs.Empty);
} else { } else {
// register Escape key handler myInputHandler = new SnippetInputHandler(this);
TextArea.KeyDown += TextArea_KeyDown; foreach (ITextAreaInputHandler h in TextArea.StackedInputHandlers) {
} if (h is SnippetInputHandler)
} TextArea.PopStackedInputHandler(h);
}
void TextArea_KeyDown(object sender, KeyEventArgs e) TextArea.PushStackedInputHandler(myInputHandler);
{
if (e.Key == Key.Escape) {
Deactivate();
e.Handled = true;
} }
} }
SnippetInputHandler myInputHandler;
/// <summary> /// <summary>
/// Occurs when the all snippet elements have been inserted. /// Occurs when the all snippet elements have been inserted.
/// </summary> /// </summary>
@ -158,21 +203,48 @@ namespace ICSharpCode.AvalonEdit.Snippets
/// <summary> /// <summary>
/// Calls the <see cref="IActiveElement.Deactivate"/> method on all registered active elements. /// Calls the <see cref="IActiveElement.Deactivate"/> method on all registered active elements.
/// </summary> /// </summary>
public void Deactivate() /// <param name="e">The EventArgs to use</param>
public void Deactivate(EventArgs e)
{ {
if (!insertionCompleted) if (currentStatus == Status.Deactivated || currentStatus == Status.RaisingDeactivated)
return;
if (currentStatus != Status.Interactive)
throw new InvalidOperationException("Cannot call Deactivate() until RaiseInsertionCompleted() has finished."); throw new InvalidOperationException("Cannot call Deactivate() until RaiseInsertionCompleted() has finished.");
TextArea.KeyDown -= TextArea_KeyDown; if (e == null)
e = EventArgs.Empty;
TextDocumentWeakEventManager.UpdateFinished.RemoveListener(Document, this);
currentStatus = Status.RaisingDeactivated;
TextArea.PopStackedInputHandler(myInputHandler);
foreach (IActiveElement element in registeredElements) { foreach (IActiveElement element in registeredElements) {
element.Deactivate(); element.Deactivate();
} }
if (Deactivated != null) if (Deactivated != null)
Deactivated(this, EventArgs.Empty); Deactivated(this, e);
currentStatus = Status.Deactivated;
} }
/// <summary> /// <summary>
/// Occurs when the interactive mode is deactivated. /// Occurs when the interactive mode is deactivated.
/// </summary> /// </summary>
public event EventHandler Deactivated; public event EventHandler Deactivated;
bool IWeakEventListener.ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
{
return ReceiveWeakEvent(managerType, sender, e);
}
/// <inheritdoc cref="IWeakEventListener.ReceiveWeakEvent"/>
protected virtual bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
{
if (managerType == typeof(TextDocumentWeakEventManager.UpdateFinished)) {
// Deactivate if snippet is deleted. This is necessary for correctly leaving interactive
// mode if Undo is pressed after a snippet insertion.
if (wholeSnippetAnchor.Length == 0)
Deactivate(e);
return true;
}
return false;
}
} }
} }

3
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Snippets/Snippet.cs

@ -25,11 +25,10 @@ namespace ICSharpCode.AvalonEdit.Snippets
{ {
if (textArea == null) if (textArea == null)
throw new ArgumentNullException("textArea"); throw new ArgumentNullException("textArea");
Freeze();
InsertionContext context = new InsertionContext(textArea, textArea.Caret.Offset); InsertionContext context = new InsertionContext(textArea, textArea.Caret.Offset);
using (context.Document.RunUpdate()) { using (context.Document.RunUpdate()) {
Insert(context); Insert(context);
context.RaiseInsertionCompleted(); context.RaiseInsertionCompleted(EventArgs.Empty);
} }
} }
} }

34
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Snippets/SnippetBoundElement.cs

@ -23,10 +23,7 @@ namespace ICSharpCode.AvalonEdit.Snippets
/// </summary> /// </summary>
public SnippetReplaceableTextElement TargetElement { public SnippetReplaceableTextElement TargetElement {
get { return targetElement; } get { return targetElement; }
set { set { targetElement = value; }
CheckBeforeMutation();
targetElement = value;
}
} }
/// <summary> /// <summary>
@ -58,7 +55,7 @@ namespace ICSharpCode.AvalonEdit.Snippets
InsertionContext context; InsertionContext context;
SnippetReplaceableTextElement targetSnippetElement; SnippetReplaceableTextElement targetSnippetElement;
SnippetBoundElement boundElement; SnippetBoundElement boundElement;
IReplaceableActiveElement targetElement; internal IReplaceableActiveElement targetElement;
AnchorSegment segment; AnchorSegment segment;
public BoundActiveElement(InsertionContext context, SnippetReplaceableTextElement targetSnippetElement, SnippetBoundElement boundElement, AnchorSegment segment) public BoundActiveElement(InsertionContext context, SnippetReplaceableTextElement targetSnippetElement, SnippetBoundElement boundElement, AnchorSegment segment)
@ -79,18 +76,31 @@ namespace ICSharpCode.AvalonEdit.Snippets
void targetElement_TextChanged(object sender, EventArgs e) void targetElement_TextChanged(object sender, EventArgs e)
{ {
int offset = segment.Offset; // Don't copy text if the segments overlap (we would get an endless loop).
int length = segment.Length; // This can happen if the user deletes the text between the replaceable element and the bound element.
string text = boundElement.ConvertText(targetElement.Text); if (segment.GetOverlap(targetElement.Segment) == SimpleSegment.Invalid) {
context.Document.Replace(offset, length, text); int offset = segment.Offset;
if (length == 0) { int length = segment.Length;
// replacing an empty anchor segment with text won't enlarge it, so we have to recreate it string text = boundElement.ConvertText(targetElement.Text);
segment = new AnchorSegment(context.Document, offset, text.Length); 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() public void Deactivate()
{ {
} }
public bool IsEditable {
get { return false; }
}
public ISegment Segment {
get { return segment; }
}
} }
} }

9
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Snippets/SnippetCaretElement.cs

@ -6,6 +6,7 @@
// </file> // </file>
using System; using System;
using System.Windows.Input;
using ICSharpCode.AvalonEdit.Document; using ICSharpCode.AvalonEdit.Document;
namespace ICSharpCode.AvalonEdit.Snippets namespace ICSharpCode.AvalonEdit.Snippets
@ -13,6 +14,7 @@ namespace ICSharpCode.AvalonEdit.Snippets
/// <summary> /// <summary>
/// Sets the caret position after interactive mode has finished. /// Sets the caret position after interactive mode has finished.
/// </summary> /// </summary>
[Serializable]
public class SnippetCaretElement : SnippetElement public class SnippetCaretElement : SnippetElement
{ {
/// <inheritdoc/> /// <inheritdoc/>
@ -20,8 +22,11 @@ namespace ICSharpCode.AvalonEdit.Snippets
{ {
TextAnchor pos = context.Document.CreateAnchor(context.InsertionPosition); TextAnchor pos = context.Document.CreateAnchor(context.InsertionPosition);
pos.SurviveDeletion = true; pos.SurviveDeletion = true;
context.Deactivated += delegate { context.Deactivated += (sender, e) => {
context.TextArea.Caret.Offset = pos.Offset; KeyEventArgs ke = e as KeyEventArgs;
if (ke != null && ke.Key == Key.Return) {
context.TextArea.Caret.Offset = pos.Offset;
}
}; };
} }
} }

9
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Snippets/SnippetContainerElement.cs

@ -18,7 +18,7 @@ namespace ICSharpCode.AvalonEdit.Snippets
[Serializable] [Serializable]
public class SnippetContainerElement : SnippetElement public class SnippetContainerElement : SnippetElement
{ {
FreezableNullSafeCollection<SnippetElement> elements = new FreezableNullSafeCollection<SnippetElement>(); NullSafeCollection<SnippetElement> elements = new NullSafeCollection<SnippetElement>();
/// <summary> /// <summary>
/// Gets the list of child elements. /// Gets the list of child elements.
@ -27,13 +27,6 @@ namespace ICSharpCode.AvalonEdit.Snippets
get { return elements; } get { return elements; }
} }
/// <inheritdoc/>
protected override void FreezeInternal()
{
elements.Freeze();
base.FreezeInternal();
}
/// <inheritdoc/> /// <inheritdoc/>
public override void Insert(InsertionContext context) public override void Insert(InsertionContext context)
{ {

42
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Snippets/SnippetElement.cs

@ -16,48 +16,8 @@ namespace ICSharpCode.AvalonEdit.Snippets
/// An element inside a snippet. /// An element inside a snippet.
/// </summary> /// </summary>
[Serializable] [Serializable]
public abstract class SnippetElement : IFreezable public abstract class SnippetElement
{ {
// 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> /// <summary>
/// Performs insertion of the snippet. /// Performs insertion of the snippet.
/// </summary> /// </summary>

83
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Snippets/SnippetInputHandler.cs

@ -0,0 +1,83 @@
// <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.Linq;
using System.Collections.Generic;
using System.Diagnostics;
using System.Windows.Input;
using ICSharpCode.AvalonEdit.Document;
using ICSharpCode.AvalonEdit.Editing;
namespace ICSharpCode.AvalonEdit.Snippets
{
sealed class SnippetInputHandler : ITextAreaInputHandler
{
readonly InsertionContext context;
public SnippetInputHandler(InsertionContext context)
{
this.context = context;
}
public TextArea TextArea {
get { return context.TextArea; }
}
public void Attach()
{
TextArea.PreviewKeyDown += TextArea_PreviewKeyDown;
SelectElement(FindNextEditableElement(-1, false));
}
void SelectElement(IActiveElement element)
{
if (element != null) {
TextArea.Selection = new SimpleSelection(element.Segment);
TextArea.Caret.Offset = element.Segment.EndOffset;
}
}
public void Detach()
{
TextArea.PreviewKeyDown -= TextArea_PreviewKeyDown;
context.Deactivate(EventArgs.Empty);
}
void TextArea_PreviewKeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Escape || e.Key == Key.Return) {
context.Deactivate(e);
e.Handled = true;
} else if (e.Key == Key.Tab) {
bool backwards = e.KeyboardDevice.Modifiers == ModifierKeys.Shift;
SelectElement(FindNextEditableElement(TextArea.Caret.Offset, backwards));
e.Handled = true;
}
}
IActiveElement FindNextEditableElement(int offset, bool backwards)
{
IEnumerable<IActiveElement> elements = context.ActiveElements.Where(e => e.IsEditable && e.Segment != null);
if (backwards) {
elements = elements.Reverse();
foreach (IActiveElement element in elements) {
if (offset > element.Segment.EndOffset)
return element;
}
} else {
foreach (IActiveElement element in elements) {
if (offset < element.Segment.Offset)
return element;
}
}
return elements.FirstOrDefault();
}
}
}

98
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Snippets/SnippetReplaceableTextElement.cs

@ -7,9 +7,12 @@
using System; using System;
using System.Windows; using System.Windows;
using System.Windows.Media;
using System.Windows.Threading; using System.Windows.Threading;
using System.Linq;
using ICSharpCode.AvalonEdit.Document; using ICSharpCode.AvalonEdit.Document;
using ICSharpCode.AvalonEdit.Rendering;
namespace ICSharpCode.AvalonEdit.Snippets namespace ICSharpCode.AvalonEdit.Snippets
{ {
@ -61,7 +64,7 @@ namespace ICSharpCode.AvalonEdit.Snippets
void AnchorDeleted(object sender, EventArgs e) void AnchorDeleted(object sender, EventArgs e)
{ {
context.Deactivate(); context.Deactivate(EventArgs.Empty);
} }
public void OnInsertionCompleted() public void OnInsertionCompleted()
@ -76,18 +79,44 @@ namespace ICSharpCode.AvalonEdit.Snippets
end.Deleted += AnchorDeleted; end.Deleted += AnchorDeleted;
// Be careful with references from the document to the editing/snippet layer - use weak events // 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. // 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. // The InsertionContext will keep us alive as long as the snippet is in interactive mode.
TextDocumentWeakEventManager.TextChanged.AddListener(context.Document, this); 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(); this.Text = GetText();
} }
public void Deactivate() public void Deactivate()
{ {
TextDocumentWeakEventManager.TextChanged.RemoveListener(context.Document, this); 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; } public string Text { get; private set; }
string GetText() string GetText()
@ -113,5 +142,68 @@ namespace ICSharpCode.AvalonEdit.Snippets
} }
return false; 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();
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());
}
}
}
}
}
} }
} }

5
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Snippets/SnippetTextElement.cs

@ -23,10 +23,7 @@ namespace ICSharpCode.AvalonEdit.Snippets
/// </summary> /// </summary>
public string Text { public string Text {
get { return text; } get { return text; }
set { set { text = value; }
CheckBeforeMutation();
text = value;
}
} }
/// <inheritdoc/> /// <inheritdoc/>

2
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/TextEditor.cs

@ -178,7 +178,7 @@ namespace ICSharpCode.AvalonEdit
OnOptionChanged(new PropertyChangedEventArgs(null)); OnOptionChanged(new PropertyChangedEventArgs(null));
} }
/// <inheritdoc/> /// <inheritdoc cref="IWeakEventListener.ReceiveWeakEvent"/>
protected virtual bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e) protected virtual bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
{ {
if (managerType == typeof(PropertyChangedWeakEventManager)) { if (managerType == typeof(PropertyChangedWeakEventManager)) {

72
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Utils/IFreezable.cs

@ -1,72 +0,0 @@
// <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…
Cancel
Save