~halega/+junk/sharpdevelop

« back to all changes in this revision

Viewing changes to src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Snippets/InsertionContext.cs

  • Committer: sk
  • Date: 2011-09-10 05:17:57 UTC
  • Revision ID: halega@halega.com-20110910051757-qfouz1llya9m6boy
4.1.0.7915 Release Candidate 1

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
 
2
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
 
3
 
 
4
using System;
 
5
using System.Collections.Generic;
 
6
using System.Windows;
 
7
using System.Windows.Input;
 
8
 
 
9
using ICSharpCode.AvalonEdit.Document;
 
10
using ICSharpCode.AvalonEdit.Editing;
 
11
using ICSharpCode.AvalonEdit.Utils;
 
12
 
 
13
namespace ICSharpCode.AvalonEdit.Snippets
 
14
{
 
15
        /// <summary>
 
16
        /// Represents the context of a snippet insertion.
 
17
        /// </summary>
 
18
        public class InsertionContext : IWeakEventListener
 
19
        {
 
20
                enum Status
 
21
                {
 
22
                        Insertion,
 
23
                        RaisingInsertionCompleted,
 
24
                        Interactive,
 
25
                        RaisingDeactivated,
 
26
                        Deactivated
 
27
                }
 
28
                
 
29
                Status currentStatus = Status.Insertion;
 
30
                
 
31
                /// <summary>
 
32
                /// Creates a new InsertionContext instance.
 
33
                /// </summary>
 
34
                public InsertionContext(TextArea textArea, int insertionPosition)
 
35
                {
 
36
                        if (textArea == null)
 
37
                                throw new ArgumentNullException("textArea");
 
38
                        this.TextArea = textArea;
 
39
                        this.Document = textArea.Document;
 
40
                        this.SelectedText = textArea.Selection.GetText(textArea.Document);
 
41
                        this.InsertionPosition = insertionPosition;
 
42
                        this.startPosition = insertionPosition;
 
43
                        
 
44
                        DocumentLine startLine = this.Document.GetLineByOffset(insertionPosition);
 
45
                        ISegment indentation = TextUtilities.GetWhitespaceAfter(this.Document, startLine.Offset);
 
46
                        this.Indentation = Document.GetText(indentation.Offset, Math.Min(indentation.EndOffset, insertionPosition) - indentation.Offset);
 
47
                        this.Tab = textArea.Options.IndentationString;
 
48
                        
 
49
                        this.LineTerminator = TextUtilities.GetNewLineFromDocument(this.Document, startLine.LineNumber);
 
50
                }
 
51
                
 
52
                /// <summary>
 
53
                /// Gets the text area.
 
54
                /// </summary>
 
55
                public TextArea TextArea { get; private set; }
 
56
                
 
57
                /// <summary>
 
58
                /// Gets the text document.
 
59
                /// </summary>
 
60
                public TextDocument Document { get; private set; }
 
61
                
 
62
                /// <summary>
 
63
                /// Gets the text that was selected before the insertion of the snippet.
 
64
                /// </summary>
 
65
                public string SelectedText { get; private set; }
 
66
                
 
67
                /// <summary>
 
68
                /// Gets the indentation at the insertion position.
 
69
                /// </summary>
 
70
                public string Indentation { get; private set; }
 
71
                
 
72
                /// <summary>
 
73
                /// Gets the indentation string for a single indentation level.
 
74
                /// </summary>
 
75
                public string Tab { get; private set; }
 
76
                
 
77
                /// <summary>
 
78
                /// Gets the line terminator at the insertion position.
 
79
                /// </summary>
 
80
                public string LineTerminator { get; private set; }
 
81
                
 
82
                /// <summary>
 
83
                /// Gets/Sets the insertion position.
 
84
                /// </summary>
 
85
                public int InsertionPosition { get; set; }
 
86
                
 
87
                readonly int startPosition;
 
88
                AnchorSegment wholeSnippetAnchor;
 
89
                bool deactivateIfSnippetEmpty;
 
90
                
 
91
                /// <summary>
 
92
                /// Gets the start position of the snippet insertion.
 
93
                /// </summary>
 
94
                public int StartPosition {
 
95
                        get {
 
96
                                if (wholeSnippetAnchor != null)
 
97
                                        return wholeSnippetAnchor.Offset;
 
98
                                else
 
99
                                        return startPosition;
 
100
                        }
 
101
                }
 
102
                
 
103
                /// <summary>
 
104
                /// Inserts text at the insertion position and advances the insertion position.
 
105
                /// This method will add the current indentation to every line in <paramref name="text"/> and will
 
106
                /// replace newlines with the expected newline for the document.
 
107
                /// </summary>
 
108
                public void InsertText(string text)
 
109
                {
 
110
                        if (text == null)
 
111
                                throw new ArgumentNullException("text");
 
112
                        if (currentStatus != Status.Insertion)
 
113
                                throw new InvalidOperationException();
 
114
                        
 
115
                        text = text.Replace("\t", this.Tab);
 
116
                        
 
117
                        using (this.Document.RunUpdate()) {
 
118
                                int textOffset = 0;
 
119
                                SimpleSegment segment;
 
120
                                while ((segment = NewLineFinder.NextNewLine(text, textOffset)) != SimpleSegment.Invalid) {
 
121
                                        string insertString = text.Substring(textOffset, segment.Offset - textOffset)
 
122
                                                + this.LineTerminator + this.Indentation;
 
123
                                        this.Document.Insert(InsertionPosition, insertString);
 
124
                                        this.InsertionPosition += insertString.Length;
 
125
                                        textOffset = segment.EndOffset;
 
126
                                }
 
127
                                string remainingInsertString = text.Substring(textOffset);
 
128
                                this.Document.Insert(InsertionPosition, remainingInsertString);
 
129
                                this.InsertionPosition += remainingInsertString.Length;
 
130
                        }
 
131
                }
 
132
                
 
133
                Dictionary<SnippetElement, IActiveElement> elementMap = new Dictionary<SnippetElement, IActiveElement>();
 
134
                List<IActiveElement> registeredElements = new List<IActiveElement>();
 
135
                
 
136
                /// <summary>
 
137
                /// Registers an active element. Elements should be registered during insertion and will be called back
 
138
                /// when insertion has completed.
 
139
                /// </summary>
 
140
                /// <param name="owner">The snippet element that created the active element.</param>
 
141
                /// <param name="element">The active element.</param>
 
142
                public void RegisterActiveElement(SnippetElement owner, IActiveElement element)
 
143
                {
 
144
                        if (owner == null)
 
145
                                throw new ArgumentNullException("owner");
 
146
                        if (element == null)
 
147
                                throw new ArgumentNullException("element");
 
148
                        if (currentStatus != Status.Insertion)
 
149
                                throw new InvalidOperationException();
 
150
                        elementMap.Add(owner, element);
 
151
                        registeredElements.Add(element);
 
152
                }
 
153
                
 
154
                /// <summary>
 
155
                /// Returns the active element belonging to the specified snippet element, or null if no such active element is found.
 
156
                /// </summary>
 
157
                public IActiveElement GetActiveElement(SnippetElement owner)
 
158
                {
 
159
                        if (owner == null)
 
160
                                throw new ArgumentNullException("owner");
 
161
                        IActiveElement element;
 
162
                        if (elementMap.TryGetValue(owner, out element))
 
163
                                return element;
 
164
                        else
 
165
                                return null;
 
166
                }
 
167
                
 
168
                /// <summary>
 
169
                /// Gets the list of active elements.
 
170
                /// </summary>
 
171
                public IEnumerable<IActiveElement> ActiveElements {
 
172
                        get { return registeredElements; }
 
173
                }
 
174
                
 
175
                /// <summary>
 
176
                /// Calls the <see cref="IActiveElement.OnInsertionCompleted"/> method on all registered active elements
 
177
                /// and raises the <see cref="InsertionCompleted"/> event.
 
178
                /// </summary>
 
179
                /// <param name="e">The EventArgs to use</param>
 
180
                [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1030:UseEventsWhereAppropriate",
 
181
                                                                 Justification="There is an event and this method is raising it.")]
 
182
                public void RaiseInsertionCompleted(EventArgs e)
 
183
                {
 
184
                        if (currentStatus != Status.Insertion)
 
185
                                throw new InvalidOperationException();
 
186
                        if (e == null)
 
187
                                e = EventArgs.Empty;
 
188
                        
 
189
                        currentStatus = Status.RaisingInsertionCompleted;
 
190
                        int endPosition = this.InsertionPosition;
 
191
                        this.wholeSnippetAnchor = new AnchorSegment(Document, startPosition, endPosition - startPosition);
 
192
                        TextDocumentWeakEventManager.UpdateFinished.AddListener(Document, this);
 
193
                        deactivateIfSnippetEmpty = (endPosition != startPosition);
 
194
                        
 
195
                        foreach (IActiveElement element in registeredElements) {
 
196
                                element.OnInsertionCompleted();
 
197
                        }
 
198
                        if (InsertionCompleted != null)
 
199
                                InsertionCompleted(this, e);
 
200
                        currentStatus = Status.Interactive;
 
201
                        if (registeredElements.Count == 0) {
 
202
                                // deactivate immediately if there are no interactive elements
 
203
                                Deactivate(new SnippetEventArgs(DeactivateReason.NoActiveElements));
 
204
                        } else {
 
205
                                myInputHandler = new SnippetInputHandler(this);
 
206
                                // disable existing snippet input handlers - there can be only 1 active snippet
 
207
                                foreach (TextAreaStackedInputHandler h in TextArea.StackedInputHandlers) {
 
208
                                        if (h is SnippetInputHandler)
 
209
                                                TextArea.PopStackedInputHandler(h);
 
210
                                }
 
211
                                TextArea.PushStackedInputHandler(myInputHandler);
 
212
                        }
 
213
                }
 
214
                
 
215
                SnippetInputHandler myInputHandler;
 
216
                
 
217
                /// <summary>
 
218
                /// Occurs when the all snippet elements have been inserted.
 
219
                /// </summary>
 
220
                public event EventHandler InsertionCompleted;
 
221
                
 
222
                /// <summary>
 
223
                /// Calls the <see cref="IActiveElement.Deactivate"/> method on all registered active elements.
 
224
                /// </summary>
 
225
                /// <param name="e">The EventArgs to use</param>
 
226
                public void Deactivate(SnippetEventArgs e)
 
227
                {
 
228
                        if (currentStatus == Status.Deactivated || currentStatus == Status.RaisingDeactivated)
 
229
                                return;
 
230
                        if (currentStatus != Status.Interactive)
 
231
                                throw new InvalidOperationException("Cannot call Deactivate() until RaiseInsertionCompleted() has finished.");
 
232
                        if (e == null)
 
233
                                e = new SnippetEventArgs(DeactivateReason.Unknown);
 
234
                        
 
235
                        TextDocumentWeakEventManager.UpdateFinished.RemoveListener(Document, this);
 
236
                        currentStatus = Status.RaisingDeactivated;
 
237
                        TextArea.PopStackedInputHandler(myInputHandler);
 
238
                        foreach (IActiveElement element in registeredElements) {
 
239
                                element.Deactivate(e);
 
240
                        }
 
241
                        if (Deactivated != null)
 
242
                                Deactivated(this, e);
 
243
                        currentStatus = Status.Deactivated;
 
244
                }
 
245
                
 
246
                /// <summary>
 
247
                /// Occurs when the interactive mode is deactivated.
 
248
                /// </summary>
 
249
                public event EventHandler<SnippetEventArgs> Deactivated;
 
250
                
 
251
                bool IWeakEventListener.ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
 
252
                {
 
253
                        return ReceiveWeakEvent(managerType, sender, e);
 
254
                }
 
255
                
 
256
                /// <inheritdoc cref="IWeakEventListener.ReceiveWeakEvent"/>
 
257
                protected virtual bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
 
258
                {
 
259
                        if (managerType == typeof(TextDocumentWeakEventManager.UpdateFinished)) {
 
260
                                // Deactivate if snippet is deleted. This is necessary for correctly leaving interactive
 
261
                                // mode if Undo is pressed after a snippet insertion.
 
262
                                if (wholeSnippetAnchor.Length == 0 && deactivateIfSnippetEmpty)
 
263
                                        Deactivate(new SnippetEventArgs(DeactivateReason.Deleted));
 
264
                                return true;
 
265
                        }
 
266
                        return false;
 
267
                }
 
268
        }
 
269
}