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

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

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

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

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

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

@ -6,6 +6,7 @@ @@ -6,6 +6,7 @@
// </file>
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
@ -83,24 +84,39 @@ namespace ICSharpCode.AvalonEdit.Editing @@ -83,24 +84,39 @@ namespace ICSharpCode.AvalonEdit.Editing
/// <summary>
/// Gets the default input handler.
/// </summary>
/// <remarks><inheritdoc cref="ITextAreaInputHandler"/></remarks>
public TextAreaDefaultInputHandler DefaultInputHandler { get; private set; }
ITextAreaInputHandler activeInputHandler;
bool isChangingInputHandler;
/// <summary>
/// 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>
/// <remarks><inheritdoc cref="ITextAreaInputHandler"/></remarks>
public ITextAreaInputHandler ActiveInputHandler {
get { return activeInputHandler; }
set {
if (value != null && value.TextArea != this)
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 != null)
activeInputHandler.Detach();
activeInputHandler = value;
if (value != null)
value.Attach();
isChangingInputHandler = true;
try {
// pop the whole stack
PopStackedInputHandler(stackedInputHandlers.LastOrDefault());
Debug.Assert(stackedInputHandlers.IsEmpty);
if (activeInputHandler != null)
activeInputHandler.Detach();
activeInputHandler = value;
if (value != null)
value.Attach();
} finally {
isChangingInputHandler = false;
}
if (ActiveInputHandlerChanged != null)
ActiveInputHandlerChanged(this, EventArgs.Empty);
}
@ -111,6 +127,46 @@ namespace ICSharpCode.AvalonEdit.Editing @@ -111,6 +127,46 @@ namespace ICSharpCode.AvalonEdit.Editing
/// Occurs when the ActiveInputHandler property changes.
/// </summary>
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
#region Document property
@ -202,7 +258,7 @@ namespace ICSharpCode.AvalonEdit.Editing @@ -202,7 +258,7 @@ namespace ICSharpCode.AvalonEdit.Editing
#endregion
#region ReceiveWeakEvent
/// <inheritdoc/>
/// <inheritdoc cref="IWeakEventListener.ReceiveWeakEvent"/>
protected virtual bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
{
if (managerType == typeof(TextDocumentWeakEventManager.Changing)) {

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

@ -16,6 +16,19 @@ namespace ICSharpCode.AvalonEdit.Editing @@ -16,6 +16,19 @@ namespace ICSharpCode.AvalonEdit.Editing
/// <summary>
/// A set of input bindings and event handlers for the text area.
/// </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
{
/// <summary>
@ -39,6 +52,7 @@ namespace ICSharpCode.AvalonEdit.Editing @@ -39,6 +52,7 @@ namespace ICSharpCode.AvalonEdit.Editing
/// <summary>
/// Default-implementation of <see cref="ITextAreaInputHandler"/>.
/// </summary>
/// <remarks><inheritdoc cref="ITextAreaInputHandler"/></remarks>
public class TextAreaInputHandler : ITextAreaInputHandler
{
readonly ObserveAddRemoveCollection<CommandBinding> commandBindings;

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

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

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

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

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

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

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

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

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

@ -6,6 +6,8 @@ @@ -6,6 +6,8 @@
// </file>
using System;
using System.Windows.Media;
using ICSharpCode.AvalonEdit.Document;
namespace ICSharpCode.AvalonEdit.Snippets
{
@ -23,5 +25,15 @@ namespace ICSharpCode.AvalonEdit.Snippets @@ -23,5 +25,15 @@ namespace ICSharpCode.AvalonEdit.Snippets
/// Called when the interactive mode is deactivated.
/// </summary>
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 @@ @@ -7,6 +7,7 @@
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Input;
using ICSharpCode.AvalonEdit.Document;
@ -18,8 +19,19 @@ namespace ICSharpCode.AvalonEdit.Snippets @@ -18,8 +19,19 @@ namespace ICSharpCode.AvalonEdit.Snippets
/// <summary>
/// Represents the context of a snippet insertion.
/// </summary>
public class InsertionContext
public class InsertionContext : IWeakEventListener
{
enum Status
{
Insertion,
RaisingInsertionCompleted,
Interactive,
RaisingDeactivated,
Deactivated
}
Status currentStatus = Status.Insertion;
/// <summary>
/// Creates a new InsertionContext instance.
/// </summary>
@ -30,10 +42,12 @@ namespace ICSharpCode.AvalonEdit.Snippets @@ -30,10 +42,12 @@ namespace ICSharpCode.AvalonEdit.Snippets
this.TextArea = textArea;
this.Document = textArea.Document;
this.InsertionPosition = insertionPosition;
this.startPosition = 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.Tab = textArea.Options.IndentationString;
this.LineTerminator = NewLineFinder.GetNewLineFromDocument(this.Document, startLine.LineNumber);
}
@ -53,6 +67,11 @@ namespace ICSharpCode.AvalonEdit.Snippets @@ -53,6 +67,11 @@ namespace ICSharpCode.AvalonEdit.Snippets
/// </summary>
public string Indentation { get; private set; }
/// <summary>
/// Gets the indentation string for a single indentation level.
/// </summary>
public string Tab { get; private set; }
/// <summary>
/// Gets the line terminator at the insertion position.
/// </summary>
@ -63,6 +82,9 @@ namespace ICSharpCode.AvalonEdit.Snippets @@ -63,6 +82,9 @@ namespace ICSharpCode.AvalonEdit.Snippets
/// </summary>
public int InsertionPosition { get; set; }
readonly int startPosition;
AnchorSegment wholeSnippetAnchor;
/// <summary>
/// Inserts text at the insertion position and advances the insertion position.
/// </summary>
@ -70,18 +92,25 @@ namespace ICSharpCode.AvalonEdit.Snippets @@ -70,18 +92,25 @@ namespace ICSharpCode.AvalonEdit.Snippets
{
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;
if (currentStatus != Status.Insertion)
throw new InvalidOperationException();
text = text.Replace("\t", this.Tab);
using (this.Document.RunUpdate()) {
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;
}
string remainingInsertString = text.Substring(textOffset);
this.Document.Insert(InsertionPosition, remainingInsertString);
this.InsertionPosition += remainingInsertString.Length;
}
Dictionary<SnippetElement, IActiveElement> elementMap = new Dictionary<SnippetElement, IActiveElement>();
@ -99,6 +128,8 @@ namespace ICSharpCode.AvalonEdit.Snippets @@ -99,6 +128,8 @@ namespace ICSharpCode.AvalonEdit.Snippets
throw new ArgumentNullException("owner");
if (element == null)
throw new ArgumentNullException("element");
if (currentStatus != Status.Insertion)
throw new InvalidOperationException();
elementMap.Add(owner, element);
registeredElements.Add(element);
}
@ -117,39 +148,53 @@ namespace ICSharpCode.AvalonEdit.Snippets @@ -117,39 +148,53 @@ namespace ICSharpCode.AvalonEdit.Snippets
return null;
}
bool insertionCompleted;
/// <summary>
/// Gets the list of active elements.
/// </summary>
public IEnumerable<IActiveElement> ActiveElements {
get { return registeredElements; }
}
/// <summary>
/// Calls the <see cref="IActiveElement.OnInsertionCompleted"/> method on all registered active elements
/// and raises the <see cref="InsertionCompleted"/> event.
/// </summary>
/// <param name="e">The EventArgs to use</param>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1030:UseEventsWhereAppropriate",
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) {
element.OnInsertionCompleted();
}
if (InsertionCompleted != null)
InsertionCompleted(this, EventArgs.Empty);
insertionCompleted = true;
InsertionCompleted(this, e);
currentStatus = Status.Interactive;
if (registeredElements.Count == 0) {
// deactivate immediately if there are no interactive elements
Deactivate();
Deactivate(EventArgs.Empty);
} 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;
myInputHandler = new SnippetInputHandler(this);
foreach (ITextAreaInputHandler h in TextArea.StackedInputHandlers) {
if (h is SnippetInputHandler)
TextArea.PopStackedInputHandler(h);
}
TextArea.PushStackedInputHandler(myInputHandler);
}
}
SnippetInputHandler myInputHandler;
/// <summary>
/// Occurs when the all snippet elements have been inserted.
/// </summary>
@ -158,21 +203,48 @@ namespace ICSharpCode.AvalonEdit.Snippets @@ -158,21 +203,48 @@ namespace ICSharpCode.AvalonEdit.Snippets
/// <summary>
/// Calls the <see cref="IActiveElement.Deactivate"/> method on all registered active elements.
/// </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.");
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) {
element.Deactivate();
}
if (Deactivated != null)
Deactivated(this, EventArgs.Empty);
Deactivated(this, e);
currentStatus = Status.Deactivated;
}
/// <summary>
/// Occurs when the interactive mode is deactivated.
/// </summary>
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 @@ -25,11 +25,10 @@ namespace ICSharpCode.AvalonEdit.Snippets
{
if (textArea == null)
throw new ArgumentNullException("textArea");
Freeze();
InsertionContext context = new InsertionContext(textArea, textArea.Caret.Offset);
using (context.Document.RunUpdate()) {
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 @@ -23,10 +23,7 @@ namespace ICSharpCode.AvalonEdit.Snippets
/// </summary>
public SnippetReplaceableTextElement TargetElement {
get { return targetElement; }
set {
CheckBeforeMutation();
targetElement = value;
}
set { targetElement = value; }
}
/// <summary>
@ -58,7 +55,7 @@ namespace ICSharpCode.AvalonEdit.Snippets @@ -58,7 +55,7 @@ namespace ICSharpCode.AvalonEdit.Snippets
InsertionContext context;
SnippetReplaceableTextElement targetSnippetElement;
SnippetBoundElement boundElement;
IReplaceableActiveElement targetElement;
internal IReplaceableActiveElement targetElement;
AnchorSegment segment;
public BoundActiveElement(InsertionContext context, SnippetReplaceableTextElement targetSnippetElement, SnippetBoundElement boundElement, AnchorSegment segment)
@ -79,18 +76,31 @@ namespace ICSharpCode.AvalonEdit.Snippets @@ -79,18 +76,31 @@ namespace ICSharpCode.AvalonEdit.Snippets
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);
// Don't copy text if the segments overlap (we would get an endless loop).
// This can happen if the user deletes the text between the replaceable element and the bound element.
if (segment.GetOverlap(targetElement.Segment) == SimpleSegment.Invalid) {
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()
{
}
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 @@ @@ -6,6 +6,7 @@
// </file>
using System;
using System.Windows.Input;
using ICSharpCode.AvalonEdit.Document;
namespace ICSharpCode.AvalonEdit.Snippets
@ -13,6 +14,7 @@ namespace ICSharpCode.AvalonEdit.Snippets @@ -13,6 +14,7 @@ namespace ICSharpCode.AvalonEdit.Snippets
/// <summary>
/// Sets the caret position after interactive mode has finished.
/// </summary>
[Serializable]
public class SnippetCaretElement : SnippetElement
{
/// <inheritdoc/>
@ -20,8 +22,11 @@ namespace ICSharpCode.AvalonEdit.Snippets @@ -20,8 +22,11 @@ namespace ICSharpCode.AvalonEdit.Snippets
{
TextAnchor pos = context.Document.CreateAnchor(context.InsertionPosition);
pos.SurviveDeletion = true;
context.Deactivated += delegate {
context.TextArea.Caret.Offset = pos.Offset;
context.Deactivated += (sender, e) => {
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 @@ -18,7 +18,7 @@ namespace ICSharpCode.AvalonEdit.Snippets
[Serializable]
public class SnippetContainerElement : SnippetElement
{
FreezableNullSafeCollection<SnippetElement> elements = new FreezableNullSafeCollection<SnippetElement>();
NullSafeCollection<SnippetElement> elements = new NullSafeCollection<SnippetElement>();
/// <summary>
/// Gets the list of child elements.
@ -27,13 +27,6 @@ namespace ICSharpCode.AvalonEdit.Snippets @@ -27,13 +27,6 @@ namespace ICSharpCode.AvalonEdit.Snippets
get { return elements; }
}
/// <inheritdoc/>
protected override void FreezeInternal()
{
elements.Freeze();
base.FreezeInternal();
}
/// <inheritdoc/>
public override void Insert(InsertionContext context)
{

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

@ -16,48 +16,8 @@ namespace ICSharpCode.AvalonEdit.Snippets @@ -16,48 +16,8 @@ namespace ICSharpCode.AvalonEdit.Snippets
/// An element inside a snippet.
/// </summary>
[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>
/// Performs insertion of the snippet.
/// </summary>

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

@ -0,0 +1,83 @@ @@ -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 @@ @@ -7,9 +7,12 @@
using System;
using System.Windows;
using System.Windows.Media;
using System.Windows.Threading;
using System.Linq;
using ICSharpCode.AvalonEdit.Document;
using ICSharpCode.AvalonEdit.Rendering;
namespace ICSharpCode.AvalonEdit.Snippets
{
@ -61,7 +64,7 @@ namespace ICSharpCode.AvalonEdit.Snippets @@ -61,7 +64,7 @@ namespace ICSharpCode.AvalonEdit.Snippets
void AnchorDeleted(object sender, EventArgs e)
{
context.Deactivate();
context.Deactivate(EventArgs.Empty);
}
public void OnInsertionCompleted()
@ -76,18 +79,44 @@ namespace ICSharpCode.AvalonEdit.Snippets @@ -76,18 +79,44 @@ namespace ICSharpCode.AvalonEdit.Snippets
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.
// 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()
{
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()
@ -113,5 +142,68 @@ namespace ICSharpCode.AvalonEdit.Snippets @@ -113,5 +142,68 @@ namespace ICSharpCode.AvalonEdit.Snippets
}
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 @@ -23,10 +23,7 @@ namespace ICSharpCode.AvalonEdit.Snippets
/// </summary>
public string Text {
get { return text; }
set {
CheckBeforeMutation();
text = value;
}
set { text = value; }
}
/// <inheritdoc/>

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

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

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

@ -1,72 +0,0 @@ @@ -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