Free cookie consent management tool by TermsFeed Policy Generator

source: stable/HeuristicLab.ExtLibs/HeuristicLab.AvalonEdit/5.0.1/AvalonEdit-5.0.1/Snippets/InsertionContext.cs @ 14706

Last change on this file since 14706 was 11700, checked in by jkarder, 10 years ago

#2077: created branch and added first version

File size: 11.0 KB
Line 
1// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
2//
3// Permission is hereby granted, free of charge, to any person obtaining a copy of this
4// software and associated documentation files (the "Software"), to deal in the Software
5// without restriction, including without limitation the rights to use, copy, modify, merge,
6// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
7// to whom the Software is furnished to do so, subject to the following conditions:
8//
9// The above copyright notice and this permission notice shall be included in all copies or
10// substantial portions of the Software.
11//
12// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
13// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
14// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
15// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
16// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
17// DEALINGS IN THE SOFTWARE.
18
19using System;
20using System.Collections.Generic;
21using System.Windows;
22using ICSharpCode.NRefactory.Editor;
23using ICSharpCode.AvalonEdit.Document;
24using ICSharpCode.AvalonEdit.Editing;
25
26namespace ICSharpCode.AvalonEdit.Snippets
27{
28  /// <summary>
29  /// Represents the context of a snippet insertion.
30  /// </summary>
31  public class InsertionContext : IWeakEventListener
32  {
33    enum Status
34    {
35      Insertion,
36      RaisingInsertionCompleted,
37      Interactive,
38      RaisingDeactivated,
39      Deactivated
40    }
41   
42    Status currentStatus = Status.Insertion;
43   
44    /// <summary>
45    /// Creates a new InsertionContext instance.
46    /// </summary>
47    public InsertionContext(TextArea textArea, int insertionPosition)
48    {
49      if (textArea == null)
50        throw new ArgumentNullException("textArea");
51      this.TextArea = textArea;
52      this.Document = textArea.Document;
53      this.SelectedText = textArea.Selection.GetText();
54      this.InsertionPosition = insertionPosition;
55      this.startPosition = insertionPosition;
56     
57      DocumentLine startLine = this.Document.GetLineByOffset(insertionPosition);
58      ISegment indentation = TextUtilities.GetWhitespaceAfter(this.Document, startLine.Offset);
59      this.Indentation = Document.GetText(indentation.Offset, Math.Min(indentation.EndOffset, insertionPosition) - indentation.Offset);
60      this.Tab = textArea.Options.IndentationString;
61     
62      this.LineTerminator = TextUtilities.GetNewLineFromDocument(this.Document, startLine.LineNumber);
63    }
64   
65    /// <summary>
66    /// Gets the text area.
67    /// </summary>
68    public TextArea TextArea { get; private set; }
69   
70    /// <summary>
71    /// Gets the text document.
72    /// </summary>
73    public ICSharpCode.AvalonEdit.Document.TextDocument Document { get; private set; }
74   
75    /// <summary>
76    /// Gets the text that was selected before the insertion of the snippet.
77    /// </summary>
78    public string SelectedText { get; private set; }
79   
80    /// <summary>
81    /// Gets the indentation at the insertion position.
82    /// </summary>
83    public string Indentation { get; private set; }
84   
85    /// <summary>
86    /// Gets the indentation string for a single indentation level.
87    /// </summary>
88    public string Tab { get; private set; }
89   
90    /// <summary>
91    /// Gets the line terminator at the insertion position.
92    /// </summary>
93    public string LineTerminator { get; private set; }
94   
95    /// <summary>
96    /// Gets/Sets the insertion position.
97    /// </summary>
98    public int InsertionPosition { get; set; }
99   
100    readonly int startPosition;
101    ICSharpCode.AvalonEdit.Document.AnchorSegment wholeSnippetAnchor;
102    bool deactivateIfSnippetEmpty;
103   
104    /// <summary>
105    /// Gets the start position of the snippet insertion.
106    /// </summary>
107    public int StartPosition {
108      get {
109        if (wholeSnippetAnchor != null)
110          return wholeSnippetAnchor.Offset;
111        else
112          return startPosition;
113      }
114    }
115   
116    /// <summary>
117    /// Inserts text at the insertion position and advances the insertion position.
118    /// This method will add the current indentation to every line in <paramref name="text"/> and will
119    /// replace newlines with the expected newline for the document.
120    /// </summary>
121    public void InsertText(string text)
122    {
123      if (text == null)
124        throw new ArgumentNullException("text");
125      if (currentStatus != Status.Insertion)
126        throw new InvalidOperationException();
127     
128      text = text.Replace("\t", this.Tab);
129     
130      using (this.Document.RunUpdate()) {
131        int textOffset = 0;
132        SimpleSegment segment;
133        while ((segment = NewLineFinder.NextNewLine(text, textOffset)) != SimpleSegment.Invalid) {
134          string insertString = text.Substring(textOffset, segment.Offset - textOffset)
135            + this.LineTerminator + this.Indentation;
136          this.Document.Insert(InsertionPosition, insertString);
137          this.InsertionPosition += insertString.Length;
138          textOffset = segment.EndOffset;
139        }
140        string remainingInsertString = text.Substring(textOffset);
141        this.Document.Insert(InsertionPosition, remainingInsertString);
142        this.InsertionPosition += remainingInsertString.Length;
143      }
144    }
145   
146    Dictionary<SnippetElement, IActiveElement> elementMap = new Dictionary<SnippetElement, IActiveElement>();
147    List<IActiveElement> registeredElements = new List<IActiveElement>();
148   
149    /// <summary>
150    /// Registers an active element. Elements should be registered during insertion and will be called back
151    /// when insertion has completed.
152    /// </summary>
153    /// <param name="owner">The snippet element that created the active element.</param>
154    /// <param name="element">The active element.</param>
155    public void RegisterActiveElement(SnippetElement owner, IActiveElement element)
156    {
157      if (owner == null)
158        throw new ArgumentNullException("owner");
159      if (element == null)
160        throw new ArgumentNullException("element");
161      if (currentStatus != Status.Insertion)
162        throw new InvalidOperationException();
163      elementMap.Add(owner, element);
164      registeredElements.Add(element);
165    }
166   
167    /// <summary>
168    /// Returns the active element belonging to the specified snippet element, or null if no such active element is found.
169    /// </summary>
170    public IActiveElement GetActiveElement(SnippetElement owner)
171    {
172      if (owner == null)
173        throw new ArgumentNullException("owner");
174      IActiveElement element;
175      if (elementMap.TryGetValue(owner, out element))
176        return element;
177      else
178        return null;
179    }
180   
181    /// <summary>
182    /// Gets the list of active elements.
183    /// </summary>
184    public IEnumerable<IActiveElement> ActiveElements {
185      get { return registeredElements; }
186    }
187   
188    /// <summary>
189    /// Calls the <see cref="IActiveElement.OnInsertionCompleted"/> method on all registered active elements
190    /// and raises the <see cref="InsertionCompleted"/> event.
191    /// </summary>
192    /// <param name="e">The EventArgs to use</param>
193    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1030:UseEventsWhereAppropriate",
194                                                     Justification="There is an event and this method is raising it.")]
195    public void RaiseInsertionCompleted(EventArgs e)
196    {
197      if (currentStatus != Status.Insertion)
198        throw new InvalidOperationException();
199      if (e == null)
200        e = EventArgs.Empty;
201     
202      currentStatus = Status.RaisingInsertionCompleted;
203      int endPosition = this.InsertionPosition;
204      this.wholeSnippetAnchor = new AnchorSegment(Document, startPosition, endPosition - startPosition);
205      TextDocumentWeakEventManager.UpdateFinished.AddListener(Document, this);
206      deactivateIfSnippetEmpty = (endPosition != startPosition);
207     
208      foreach (IActiveElement element in registeredElements) {
209        element.OnInsertionCompleted();
210      }
211      if (InsertionCompleted != null)
212        InsertionCompleted(this, e);
213      currentStatus = Status.Interactive;
214      if (registeredElements.Count == 0) {
215        // deactivate immediately if there are no interactive elements
216        Deactivate(new SnippetEventArgs(DeactivateReason.NoActiveElements));
217      } else {
218        myInputHandler = new SnippetInputHandler(this);
219        // disable existing snippet input handlers - there can be only 1 active snippet
220        foreach (TextAreaStackedInputHandler h in TextArea.StackedInputHandlers) {
221          if (h is SnippetInputHandler)
222            TextArea.PopStackedInputHandler(h);
223        }
224        TextArea.PushStackedInputHandler(myInputHandler);
225      }
226    }
227   
228    SnippetInputHandler myInputHandler;
229   
230    /// <summary>
231    /// Occurs when the all snippet elements have been inserted.
232    /// </summary>
233    public event EventHandler InsertionCompleted;
234   
235    /// <summary>
236    /// Calls the <see cref="IActiveElement.Deactivate"/> method on all registered active elements.
237    /// </summary>
238    /// <param name="e">The EventArgs to use</param>
239    public void Deactivate(SnippetEventArgs e)
240    {
241      if (currentStatus == Status.Deactivated || currentStatus == Status.RaisingDeactivated)
242        return;
243      if (currentStatus != Status.Interactive)
244        throw new InvalidOperationException("Cannot call Deactivate() until RaiseInsertionCompleted() has finished.");
245      if (e == null)
246        e = new SnippetEventArgs(DeactivateReason.Unknown);
247     
248      TextDocumentWeakEventManager.UpdateFinished.RemoveListener(Document, this);
249      currentStatus = Status.RaisingDeactivated;
250      TextArea.PopStackedInputHandler(myInputHandler);
251      foreach (IActiveElement element in registeredElements) {
252        element.Deactivate(e);
253      }
254      if (Deactivated != null)
255        Deactivated(this, e);
256      currentStatus = Status.Deactivated;
257    }
258   
259    /// <summary>
260    /// Occurs when the interactive mode is deactivated.
261    /// </summary>
262    public event EventHandler<SnippetEventArgs> Deactivated;
263   
264    bool IWeakEventListener.ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
265    {
266      return ReceiveWeakEvent(managerType, sender, e);
267    }
268   
269    /// <inheritdoc cref="IWeakEventListener.ReceiveWeakEvent"/>
270    protected virtual bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
271    {
272      if (managerType == typeof(TextDocumentWeakEventManager.UpdateFinished)) {
273        // Deactivate if snippet is deleted. This is necessary for correctly leaving interactive
274        // mode if Undo is pressed after a snippet insertion.
275        if (wholeSnippetAnchor.Length == 0 && deactivateIfSnippetEmpty)
276          Deactivate(new SnippetEventArgs(DeactivateReason.Deleted));
277        return true;
278      }
279      return false;
280    }
281   
282    /// <summary>
283    /// Adds existing segments as snippet elements.
284    /// </summary>
285    public void Link(ISegment mainElement, ISegment[] boundElements)
286    {
287      var main = new SnippetReplaceableTextElement { Text = Document.GetText(mainElement) };
288      RegisterActiveElement(main, new ReplaceableActiveElement(this, mainElement.Offset, mainElement.EndOffset));
289      foreach (var boundElement in boundElements) {
290        var bound = new SnippetBoundElement { TargetElement = main };
291        var start = Document.CreateAnchor(boundElement.Offset);
292        start.MovementType = AnchorMovementType.BeforeInsertion;
293        start.SurviveDeletion = true;
294        var end = Document.CreateAnchor(boundElement.EndOffset);
295        end.MovementType = AnchorMovementType.BeforeInsertion;
296        end.SurviveDeletion = true;
297       
298        RegisterActiveElement(bound, new BoundActiveElement(this, main, bound, new AnchorSegment(start, end)));
299      }
300    }
301  }
302}
Note: See TracBrowser for help on using the repository browser.