~ubuntu-branches/ubuntu/trusty/monodevelop/trusty-proposed

« back to all changes in this revision

Viewing changes to src/addins/CSharpBinding/MonoDevelop.CSharp.Formatting/CSharpTextEditorIndentation.cs

  • Committer: Package Import Robot
  • Author(s): Jo Shields
  • Date: 2013-05-12 09:46:03 UTC
  • mto: This revision was merged to the branch mainline in revision 29.
  • Revision ID: package-import@ubuntu.com-20130512094603-mad323bzcxvmcam0
Tags: upstream-4.0.5+dfsg
ImportĀ upstreamĀ versionĀ 4.0.5+dfsg

Show diffs side-by-side

added added

removed removed

Lines of Context:
27
27
//
28
28
using System;
29
29
using System.Collections.Generic;
30
 
using System.Diagnostics;
31
 
using System.Text;
32
 
using System.Xml;
33
30
using MonoDevelop.Core;
34
 
using MonoDevelop.Ide.Gui;
35
31
using MonoDevelop.Ide.Gui.Content;
36
32
 
37
33
using MonoDevelop.Projects;
38
34
using MonoDevelop.Ide.CodeCompletion;
39
35
 
40
36
using MonoDevelop.CSharp.Formatting;
41
 
using MonoDevelop.CSharp.Parser;
42
37
using Mono.TextEditor;
43
38
using MonoDevelop.Ide.CodeTemplates;
44
 
using MonoDevelop.CSharp.Resolver;
45
 
using ICSharpCode.NRefactory.TypeSystem;
46
 
using ICSharpCode.NRefactory.CSharp;
47
 
using MonoDevelop.Ide.TypeSystem;
48
 
using ICSharpCode.NRefactory;
49
39
using MonoDevelop.SourceEditor;
50
40
using ICSharpCode.NRefactory.CSharp.Completion;
 
41
using ICSharpCode.NRefactory.Editor;
 
42
using System.Linq;
 
43
using System.Text;
51
44
 
52
45
namespace MonoDevelop.CSharp.Formatting
53
46
{
54
 
        public class CSharpTextEditorIndentation : TextEditorExtension
 
47
        public class CSharpTextEditorIndentation : TextEditorExtension, ITextPasteHandler
55
48
        {
56
49
                DocumentStateTracker<CSharpIndentEngine> stateTracker;
57
50
                int cursorPositionBeforeKeyPress;
58
 
                TextEditorData textEditorData;
59
 
                CSharpFormattingPolicy policy;
60
 
                TextStylePolicy textStylePolicy;
 
51
                TextEditorData textEditorData {
 
52
                        get {
 
53
                                return document.Editor;
 
54
                        }
 
55
                }
 
56
 
 
57
                IEnumerable<string> types = MonoDevelop.Ide.DesktopService.GetMimeTypeInheritanceChain (CSharpFormatter.MimeType);
 
58
 
 
59
                CSharpFormattingPolicy Policy {
 
60
                        get {
 
61
                                if (Document != null && Document.Project != null && Document.Project.Policies != null) {
 
62
                                        return base.Document.Project.Policies.Get<CSharpFormattingPolicy> (types);
 
63
                                }
 
64
                                return MonoDevelop.Projects.Policies.PolicyService.GetDefaultPolicy<CSharpFormattingPolicy> (types);
 
65
                        }
 
66
                }
 
67
 
 
68
                TextStylePolicy TextStylePolicy {
 
69
                        get {
 
70
                                if (Document != null && Document.Project != null && Document.Project.Policies != null) {
 
71
                                        return base.Document.Project.Policies.Get<TextStylePolicy> (types);
 
72
                                }
 
73
                                return MonoDevelop.Projects.Policies.PolicyService.GetDefaultPolicy<TextStylePolicy> (types);
 
74
                        }
 
75
                }
61
76
 
62
77
                char lastCharInserted;
63
78
 
64
79
                static CSharpTextEditorIndentation ()
65
80
                {
66
81
                        CompletionWindowManager.WordCompleted += delegate(object sender,CodeCompletionContextEventArgs e) {
67
 
                                IExtensibleTextEditor editor = e.Widget as IExtensibleTextEditor;
 
82
                                var editor = e.Widget as IExtensibleTextEditor;
68
83
                                if (editor == null)
69
84
                                        return;
70
 
                                ITextEditorExtension textEditorExtension = editor.Extension;
 
85
                                var textEditorExtension = editor.Extension;
71
86
                                while (textEditorExtension != null && !(textEditorExtension is CSharpTextEditorIndentation)) {
72
87
                                        textEditorExtension = textEditorExtension.Next;
73
88
                                }
74
 
                                CSharpTextEditorIndentation extension = textEditorExtension as CSharpTextEditorIndentation;
 
89
                                var extension = textEditorExtension as CSharpTextEditorIndentation;
75
90
                                if (extension == null)
76
91
                                        return;
77
92
                                extension.stateTracker.UpdateEngine ();
80
95
                        };
81
96
                }
82
97
 
 
98
                bool IsPreprocessorDirective (DocumentLine documentLine)
 
99
                {
 
100
                        int o = documentLine.Offset;
 
101
                        for (int i = 0; i < documentLine.Length; i++) {
 
102
                                char ch = Editor.GetCharAt (o++);
 
103
                                if (ch == '#')
 
104
                                        return true;
 
105
                                if (!char.IsWhiteSpace (ch)) {
 
106
                                        return false;
 
107
                                }
 
108
                        }
 
109
                        return false;
 
110
                }
83
111
 
84
112
                void HandleTextPaste (int insertionOffset, string text, int insertedChars)
85
113
                {
86
 
                        // Trim blank spaces on text paste, see: Bug 511 - Trim blank spaces when copy-pasting
87
 
                        if (OnTheFlyFormatting) {
88
 
                                int i = insertionOffset + insertedChars;
89
 
                                bool foundNonWsFollowUp = false;
90
 
 
91
 
                                var line = Document.Editor.GetLineByOffset (i);
92
 
                                if (line != null) {
93
 
                                        for (int j = 0; j < line.Offset + line.Length; j++) {
94
 
                                                var ch = Document.Editor.GetCharAt (j);
95
 
                                                if (ch != ' ' && ch != '\t') {
96
 
                                                        foundNonWsFollowUp = true;
97
 
                                                        break;
 
114
                        if (document.Editor.Options.IndentStyle == IndentStyle.None ||
 
115
                            document.Editor.Options.IndentStyle == IndentStyle.Auto)
 
116
                                return;
 
117
 
 
118
                        // Just correct the start line of the paste operation - the text is already indented.
 
119
                        var curLine = Editor.GetLineByOffset (insertionOffset);
 
120
                        var curLineOffset = curLine.Offset;
 
121
                        stateTracker.UpdateEngine (curLineOffset);
 
122
                        if (!stateTracker.Engine.IsInsideOrdinaryCommentOrString) {
 
123
                                // The Indent engine doesn't really handle pre processor directives very well.
 
124
                                if (IsPreprocessorDirective (curLine)) {
 
125
                                        Editor.Replace (curLineOffset, curLine.Length, stateTracker.Engine.NewLineIndent + Editor.GetTextAt (curLine).TrimStart ());
 
126
                                } else {
 
127
                                        int pos = curLineOffset;
 
128
                                        string curIndent = curLine.GetIndentation (textEditorData.Document);
 
129
                                        int nlwsp = curIndent.Length;
 
130
 
 
131
                                        if (!stateTracker.Engine.LineBeganInsideMultiLineComment || (nlwsp < curLine.LengthIncludingDelimiter && textEditorData.Document.GetCharAt (curLineOffset + nlwsp) == '*')) {
 
132
                                                // Possibly replace the indent
 
133
                                                stateTracker.UpdateEngine (curLineOffset + curLine.Length);
 
134
                                                string newIndent = stateTracker.Engine.ThisLineIndent;
 
135
                                                if (newIndent != curIndent) {
 
136
                                                        if (CompletionWindowManager.IsVisible) {
 
137
                                                                if (pos < CompletionWindowManager.CodeCompletionContext.TriggerOffset)
 
138
                                                                        CompletionWindowManager.CodeCompletionContext.TriggerOffset -= nlwsp;
 
139
                                                        }
 
140
                                                        textEditorData.Replace (pos, nlwsp, newIndent);
 
141
                                                        textEditorData.Document.CommitLineUpdate (textEditorData.Caret.Line);
98
142
                                                }
99
143
                                        }
100
144
                                }
101
 
 
102
 
                                if (!foundNonWsFollowUp) {
103
 
                                        while (i > insertionOffset) {
104
 
                                                char ch = Document.Editor.GetCharAt (i - 1);
105
 
                                                if (ch != ' ' && ch != '\t') 
106
 
                                                        break;
107
 
                                                i--;
108
 
                                        }
109
 
                                        int delta = insertionOffset + insertedChars - i;
110
 
                                        if (delta > 0) {
111
 
                                                Editor.Caret.Offset -= delta;
112
 
                                                Editor.Remove (insertionOffset + insertedChars - delta, delta);
113
 
                                        }
114
 
                                }
115
 
                        }
116
 
                        var documentLine = Editor.GetLineByOffset (insertionOffset + insertedChars);
117
 
                        while (documentLine != null && insertionOffset < documentLine.EndOffset) {
118
 
                                DoReSmartIndent (documentLine.Offset);
119
 
                                documentLine = documentLine.PreviousLine;
120
 
                        }
 
145
                        }
 
146
                        textEditorData.FixVirtualIndentation ();
121
147
                } 
122
148
 
123
149
                public static bool OnTheFlyFormatting {
132
158
                void RunFormatter (DocumentLocation location)
133
159
                {
134
160
                        if (OnTheFlyFormatting && textEditorData != null && !(textEditorData.CurrentMode is TextLinkEditMode) && !(textEditorData.CurrentMode is InsertionCursorEditMode)) {
135
 
 
136
161
                                OnTheFlyFormatter.Format (Document, location);
137
162
                        }
138
163
                }
139
164
 
140
 
                public CSharpTextEditorIndentation ()
141
 
                {
142
 
                        IEnumerable<string> types = MonoDevelop.Ide.DesktopService.GetMimeTypeInheritanceChain (CSharpFormatter.MimeType);
143
 
                        policy = MonoDevelop.Projects.Policies.PolicyService.GetDefaultPolicy<CSharpFormattingPolicy> (types);
144
 
                        textStylePolicy = MonoDevelop.Projects.Policies.PolicyService.GetDefaultPolicy<TextStylePolicy> (types);
145
 
                }
146
 
 
147
165
                public override void Initialize ()
148
166
                {
149
167
                        base.Initialize ();
150
168
 
151
 
                        IEnumerable<string> types = MonoDevelop.Ide.DesktopService.GetMimeTypeInheritanceChain (CSharpFormatter.MimeType);
152
 
                        if (base.Document.Project != null && base.Document.Project.Policies != null) {
153
 
                                policy = base.Document.Project.Policies.Get<CSharpFormattingPolicy> (types);
154
 
                                textStylePolicy = base.Document.Project.Policies.Get<TextStylePolicy> (types);
155
 
                        }
156
169
 
157
 
                        textEditorData = Document.Editor;
158
170
                        if (textEditorData != null) {
159
 
                                textEditorData.Options.Changed += delegate {
160
 
                                        var project = base.Document.Project;
161
 
                                        if (project != null) {
162
 
                                                policy = project.Policies.Get<CSharpFormattingPolicy> (types);
163
 
                                                textStylePolicy = project.Policies.Get<TextStylePolicy> (types);
164
 
                                        }
165
 
                                        textEditorData.IndentationTracker = new IndentVirtualSpaceManager (
166
 
                                                textEditorData,
167
 
                                                new DocumentStateTracker<CSharpIndentEngine> (new CSharpIndentEngine (policy, textStylePolicy), textEditorData)
168
 
                                        );
169
 
                                };
 
171
                                textEditorData.Options.Changed += HandleTextOptionsChanged;
170
172
                                textEditorData.IndentationTracker = new IndentVirtualSpaceManager (
171
173
                                        textEditorData,
172
 
                                        new DocumentStateTracker<CSharpIndentEngine> (new CSharpIndentEngine (policy, textStylePolicy), textEditorData)
 
174
                                        new DocumentStateTracker<CSharpIndentEngine> (new CSharpIndentEngine (Policy, TextStylePolicy), textEditorData)
173
175
                                );
 
176
                                textEditorData.Document.TextReplacing += HandleTextReplacing;
 
177
                                textEditorData.Document.TextReplaced += HandleTextReplaced;;
 
178
                                textEditorData.TextPasteHandler = this;
 
179
                                textEditorData.Paste += HandleTextPaste;
174
180
                        }
175
181
 
176
182
                        InitTracker ();
177
 
//                      Document.Editor.Paste += HandleTextPaste;
178
 
                }
179
 
 
180
 
                /*              void TextCut (object sender, ReplaceEventArgs e)
181
 
                {
182
 
                        if (!string.IsNullOrEmpty (e.Value) || e.Count == 0)
183
 
                                return;
184
 
                        RunFormatterAt (e.Offset);
185
 
                }*/
186
 
 
 
183
                }
 
184
 
 
185
                void HandleTextOptionsChanged (object sender, EventArgs e)
 
186
                {
 
187
                        textEditorData.IndentationTracker = new IndentVirtualSpaceManager (
 
188
                                textEditorData,
 
189
                                new DocumentStateTracker<CSharpIndentEngine> (new CSharpIndentEngine (Policy, TextStylePolicy), textEditorData)
 
190
                                );
 
191
 
 
192
                }
 
193
 
 
194
                public override void Dispose ()
 
195
                {
 
196
                        if (textEditorData != null) {
 
197
                                textEditorData.TextPasteHandler = null;
 
198
                                textEditorData.Paste -= HandleTextPaste;
 
199
                                textEditorData.Options.Changed -= HandleTextOptionsChanged;
 
200
                                textEditorData.IndentationTracker = null;
 
201
                                textEditorData.Document.TextReplacing -= HandleTextReplacing;
 
202
                                textEditorData.Document.TextReplaced -= HandleTextReplaced;;
 
203
                        }
 
204
                        base.Dispose ();
 
205
                }
 
206
 
 
207
                bool wasInVerbatimString;
 
208
 
 
209
                void HandleTextReplaced (object sender, DocumentChangeEventArgs e)
 
210
                {
 
211
                        if (e.RemovalLength != 1)
 
212
                                return;
 
213
                        stateTracker.UpdateEngine (e.Offset + e.InsertionLength + 1);
 
214
                        if (wasInVerbatimString && !stateTracker.Engine.IsInsideVerbatimString) {
 
215
                                textEditorData.Document.TextReplacing -= HandleTextReplacing;
 
216
                                textEditorData.Document.TextReplaced -= HandleTextReplaced;;
 
217
                                ConvertVerbatimStringToNormal (textEditorData, e.Offset + e.InsertionLength + 1);
 
218
                                textEditorData.Document.TextReplacing += HandleTextReplacing;
 
219
                                textEditorData.Document.TextReplaced += HandleTextReplaced;;
 
220
                        }
 
221
                }
 
222
 
 
223
                void HandleTextReplacing (object sender, DocumentChangeEventArgs e)
 
224
                {
 
225
                        var o = e.Offset + e.RemovalLength;
 
226
                        if (o < 0 || o + 1 > textEditorData.Length || e.RemovalLength != 1 || textEditorData.Document.IsInUndo) {
 
227
                                wasInVerbatimString = false;
 
228
                                return;
 
229
                        }
 
230
                        if (textEditorData.GetCharAt (o) != '"')
 
231
                                return;
 
232
                        stateTracker.UpdateEngine (o + 1);
 
233
                        wasInVerbatimString = stateTracker.Engine.IsInsideVerbatimString;
 
234
                }
 
235
 
 
236
                #region ITextPasteHandler implementation
 
237
                enum CopySource : byte
 
238
                {
 
239
                        Text = 0,
 
240
                        StringLiteral = 1,
 
241
                        VerbatimString = 2
 
242
                }
 
243
 
 
244
                byte[] ITextPasteHandler.GetCopyData (TextSegment segment)
 
245
                {
 
246
                        stateTracker.UpdateEngine (segment.Offset);
 
247
                        if (stateTracker.Engine.IsInsideStringLiteral)
 
248
                                return new [] { (byte)CopySource.StringLiteral };
 
249
                        if (stateTracker.Engine.IsInsideVerbatimString)
 
250
                                return new [] { (byte)CopySource.VerbatimString };
 
251
                        return null;
 
252
                }
 
253
                
 
254
                static string ConvertFromString (string nonVerbatimStringContent)
 
255
                {
 
256
                        var result = new StringBuilder ();
 
257
                        for (int i = 0; i < nonVerbatimStringContent.Length; i++) {
 
258
                                var ch = nonVerbatimStringContent [i];
 
259
                                switch (ch) {
 
260
                                case '\\':
 
261
                                        i++;
 
262
                                        switch (nonVerbatimStringContent [i]) {
 
263
                                        case '\\':
 
264
                                                result.Append ('\\');
 
265
                                                break;
 
266
                                        case 'r':
 
267
                                                result.Append ('\r');
 
268
                                                break;
 
269
                                        case 'n':
 
270
                                                result.Append ('\n');
 
271
                                                break;
 
272
                                        case 't':
 
273
                                                result.Append ('\t');
 
274
                                                break;
 
275
                                        case '"':
 
276
                                                result.Append ('"');
 
277
                                                break;
 
278
                                        }
 
279
                                        break;
 
280
                                default:
 
281
                                        result.Append (ch);
 
282
                                        break;
 
283
                                }
 
284
                        }
 
285
                        return result.ToString ();
 
286
                }
 
287
                
 
288
                static string ConvertFromVerbatimString (string verbatimStringContent)
 
289
                {
 
290
                        var result = new StringBuilder ();
 
291
                        for (int i = 0; i < verbatimStringContent.Length; i++) {
 
292
                                var ch = verbatimStringContent [i];
 
293
 
 
294
                                switch (ch) {
 
295
                                case '"':
 
296
                                        if (i + 1 < verbatimStringContent.Length && verbatimStringContent [i + 1] == '"') {
 
297
                                                result.Append ("\"");
 
298
                                                i++;
 
299
                                        }
 
300
                                        break;
 
301
                                default:
 
302
                                        result.Append (ch);
 
303
                                        break;
 
304
                                }
 
305
                        }
 
306
                        return result.ToString ();
 
307
                }
 
308
 
 
309
                static string ConvertToStringLiteral (string text)
 
310
                {
 
311
                        var result = new StringBuilder ();
 
312
                        foreach (var ch in text) {
 
313
                                switch (ch) {
 
314
                                case '\t':
 
315
                                        result.Append ("\\t");
 
316
                                        break;
 
317
                                case '"':
 
318
                                        result.Append ("\\\"");
 
319
                                        break;
 
320
                                case '\n':
 
321
                                        result.Append ("\\n");
 
322
                                        break;
 
323
                                case '\r':
 
324
                                        result.Append ("\\r");
 
325
                                        break;
 
326
                                case '\\':
 
327
                                        result.Append ("\\\\");
 
328
                                        break;
 
329
                                default:
 
330
                                        result.Append (ch);
 
331
                                        break;
 
332
                                }
 
333
                        }
 
334
                        return result.ToString ();
 
335
                }
 
336
 
 
337
                static string ConvertToVerbatimLiteral (string text)
 
338
                {
 
339
                        var result = new StringBuilder ();
 
340
                        foreach (var ch in text) {
 
341
                                switch (ch) {
 
342
                                        case '"':
 
343
                                        result.Append ("\"\"");
 
344
                                        break;
 
345
                                        default:
 
346
                                        result.Append (ch);
 
347
                                        break;
 
348
                                }
 
349
                        }
 
350
                        return result.ToString ();
 
351
                }
 
352
 
 
353
                string ITextPasteHandler.FormatPlainText (int insertionOffset, string text, byte[] copyData)
 
354
                {
 
355
                        if (document.Editor.Options.IndentStyle == IndentStyle.None ||
 
356
                            document.Editor.Options.IndentStyle == IndentStyle.Auto)
 
357
                                return text;
 
358
 
 
359
                        if (copyData != null && copyData.Length == 1) {
 
360
                                CopySource src = (CopySource)copyData [0];
 
361
                                switch (src) {
 
362
                                case CopySource.VerbatimString:
 
363
                                        text = ConvertFromVerbatimString (text);
 
364
                                        break;
 
365
                                case CopySource.StringLiteral:
 
366
                                        text = ConvertFromString (text);
 
367
                                        break;
 
368
                                }
 
369
                        }
 
370
 
 
371
                        stateTracker.UpdateEngine (insertionOffset);
 
372
                        var engine = stateTracker.Engine.Clone () as CSharpIndentEngine;
 
373
 
 
374
                        var result = new StringBuilder ();
 
375
 
 
376
                        if (engine.IsInsideStringLiteral)
 
377
                                return ConvertToStringLiteral (text);
 
378
 
 
379
                        if (engine.IsInsideVerbatimString)
 
380
                                return ConvertToVerbatimLiteral (text);
 
381
 
 
382
                        bool inNewLine = false;
 
383
                        foreach (var ch in text) {
 
384
                                if (!engine.IsInsideOrdinaryCommentOrString) {
 
385
                                        if (inNewLine && (ch == ' ' || ch == '\t')) {
 
386
                                                engine.Push (ch);
 
387
                                                continue;
 
388
                                        }
 
389
                                }
 
390
 
 
391
                                if (inNewLine && ch != '\n' && ch != '\r') {
 
392
                                        if (!engine.IsInsideOrdinaryCommentOrString) {
 
393
                                                if (ch != '#')
 
394
                                                        engine.Push (ch);
 
395
                                                result.Append (engine.ThisLineIndent);
 
396
                                                if (ch == '#')
 
397
                                                        engine.Push (ch);
 
398
                                        }
 
399
                                        inNewLine = false;
 
400
                                } else {
 
401
                                        engine.Push (ch);
 
402
                                }
 
403
                                result.Append (ch);
 
404
                                if (ch == '\n' || ch == '\r')
 
405
                                        inNewLine = true;
 
406
                        }
 
407
                        return result.ToString ();
 
408
                }
 
409
                #endregion
 
410
 
 
411
                void ConvertNormalToVerbatimString (TextEditorData textEditorData, int offset)
 
412
                {
 
413
                        var endOffset = offset;
 
414
                        while (endOffset < textEditorData.Length) {
 
415
                                char ch = textEditorData.GetCharAt (endOffset);
 
416
                                if (ch == '\\' && (endOffset + 1 < textEditorData.Length && textEditorData.GetCharAt (endOffset + 1) == '"'))  {
 
417
                                        endOffset += 2;
 
418
                                        continue;
 
419
                                }
 
420
                                if (ch == '"')
 
421
                                        break;
 
422
                                endOffset++;
 
423
                        }
 
424
                        textEditorData.Replace (offset, endOffset - offset, ConvertToVerbatimLiteral (ConvertFromString (textEditorData.GetTextAt (offset, endOffset - offset))));
 
425
                }
 
426
 
 
427
                void ConvertVerbatimStringToNormal (TextEditorData textEditorData, int offset)
 
428
                {
 
429
                        var endOffset = offset;
 
430
                        while (endOffset < textEditorData.Length) {
 
431
                                char ch = textEditorData.GetCharAt (endOffset);
 
432
                                if (ch == '"' && (endOffset + 1 < textEditorData.Length && textEditorData.GetCharAt (endOffset + 1) == '"'))  {
 
433
                                        endOffset += 2;
 
434
                                        continue;
 
435
                                }
 
436
                                if (ch == '"')
 
437
                                        break;
 
438
                                endOffset++;
 
439
                        }
 
440
                        textEditorData.Replace (offset, endOffset - offset, ConvertToStringLiteral (ConvertFromVerbatimString (textEditorData.GetTextAt (offset, endOffset - offset))));
 
441
                }
187
442
 
188
443
                #region Sharing the tracker
189
444
 
190
445
                void InitTracker ()
191
446
                {
192
 
                        stateTracker = new DocumentStateTracker<CSharpIndentEngine> (new CSharpIndentEngine (policy, textStylePolicy), textEditorData);
 
447
                        stateTracker = new DocumentStateTracker<CSharpIndentEngine> (new CSharpIndentEngine (Policy, TextStylePolicy), textEditorData);
193
448
                }
194
449
 
195
450
                internal DocumentStateTracker<CSharpIndentEngine> StateTracker { get { return stateTracker; } }
208
463
 
209
464
                int lastInsertedSemicolon = -1;
210
465
 
 
466
                void CheckXmlCommentCloseTag (char keyChar)
 
467
                {
 
468
                        if (keyChar == '>' && stateTracker.Engine.IsInsideDocLineComment) {
 
469
                                var location = Editor.Caret.Location;
 
470
                                string lineText = Editor.GetLineText (Editor.Caret.Line);
 
471
                                int startIndex = Math.Min (location.Column - 1, lineText.Length - 1);
 
472
                                while (startIndex >= 0 && lineText [startIndex] != '<') {
 
473
                                        --startIndex;
 
474
                                        if (lineText [startIndex] == '/') {
 
475
                                                // already closed.
 
476
                                                startIndex = -1;
 
477
                                                break;
 
478
                                        }
 
479
                                }
 
480
                                if (startIndex >= 0) {
 
481
                                        int endIndex = startIndex;
 
482
                                        while (endIndex <= location.Column && endIndex < lineText.Length && !Char.IsWhiteSpace (lineText [endIndex])) {
 
483
                                                endIndex++;
 
484
                                        }
 
485
                                        string tag = endIndex - startIndex - 1 > 0 ? lineText.Substring (startIndex + 1, endIndex - startIndex - 2) : null;
 
486
                                        if (!string.IsNullOrEmpty (tag) && CSharpCompletionEngine.CommentTags.Any (t => t == tag)) {
 
487
                                                Editor.Document.Insert (Editor.Caret.Offset, "</" + tag + ">", AnchorMovementType.BeforeInsertion);
 
488
                                        }
 
489
                                }
 
490
                        }
 
491
                }
 
492
 
211
493
                public override bool KeyPress (Gdk.Key key, char keyChar, Gdk.ModifierType modifier)
212
494
                {
213
495
                        bool skipFormatting = StateTracker.Engine.IsInsideOrdinaryCommentOrString ||
228
510
                                bool retval = base.KeyPress (key, keyChar, modifier);
229
511
                                DocumentLine curLine = textEditorData.Document.GetLine (textEditorData.Caret.Line);
230
512
                                string text = textEditorData.Document.GetTextAt (curLine);
231
 
                                if (text.EndsWith (";") || text.Trim ().StartsWith ("for"))
232
 
                                        return retval;
 
513
                                if (!(text.EndsWith (";", StringComparison.Ordinal) || text.Trim ().StartsWith ("for", StringComparison.Ordinal))) {
 
514
                                        int guessedOffset;
233
515
 
234
 
                                int guessedOffset = GuessSemicolonInsertionOffset (textEditorData, curLine);
235
 
                                if (guessedOffset != textEditorData.Caret.Offset) {
236
 
                                        using (var undo = textEditorData.OpenUndoGroup ()) {
237
 
                                                textEditorData.Remove (textEditorData.Caret.Offset - 1, 1);
238
 
                                                textEditorData.Caret.Offset = guessedOffset;
239
 
                                                lastInsertedSemicolon = textEditorData.Caret.Offset + 1;
240
 
                                                retval = base.KeyPress (key, keyChar, modifier);
 
516
                                        if (GuessSemicolonInsertionOffset (textEditorData, curLine, textEditorData.Caret.Offset, out guessedOffset)) {
 
517
                                                using (var undo = textEditorData.OpenUndoGroup ()) {
 
518
                                                        textEditorData.Remove (textEditorData.Caret.Offset - 1, 1);
 
519
                                                        textEditorData.Caret.Offset = guessedOffset;
 
520
                                                        lastInsertedSemicolon = textEditorData.Caret.Offset + 1;
 
521
                                                        retval = base.KeyPress (key, keyChar, modifier);
 
522
                                                }
 
523
                                        }
 
524
                                }
 
525
                                using (var undo = textEditorData.OpenUndoGroup ()) {
 
526
                                        if (OnTheFlyFormatting && textEditorData != null && !(textEditorData.CurrentMode is TextLinkEditMode) && !(textEditorData.CurrentMode is InsertionCursorEditMode)) {
 
527
                                                OnTheFlyFormatter.FormatStatmentAt (Document, textEditorData.Caret.Location);
241
528
                                        }
242
529
                                }
243
530
                                return retval;
245
532
                        
246
533
                        if (key == Gdk.Key.Tab) {
247
534
                                stateTracker.UpdateEngine ();
248
 
                                if (stateTracker.Engine.IsInsideStringLiteral) {
 
535
                                if (stateTracker.Engine.IsInsideStringLiteral && !textEditorData.IsSomethingSelected) {
249
536
                                        var lexer = new CSharpCompletionEngineBase.MiniLexer (textEditorData.Document.GetTextAt (0, textEditorData.Caret.Offset));
250
537
                                        lexer.Parse ();
251
538
                                        if (lexer.IsInString) {
271
558
                                        textEditorData.Document.CommitLineUpdate (textEditorData.Caret.Line);
272
559
                                } else if (cursor >= 1) {
273
560
                                        if (textEditorData.Caret.Column > 1) {
274
 
                                                int delta = cursor - this.cursorPositionBeforeKeyPress;
 
561
                                                int delta = cursor - cursorPositionBeforeKeyPress;
275
562
                                                if (delta < 2 && delta > 0) {
276
563
                                                        textEditorData.Remove (cursor - delta, delta);
277
564
                                                        textEditorData.Caret.Offset = cursor - delta;
284
571
                                return false;
285
572
                        }
286
573
 
 
574
                        stateTracker.UpdateEngine ();
 
575
                        if (!stateTracker.Engine.IsInsideOrdinaryCommentOrString) {
 
576
                                if (keyChar == '@') {
 
577
                                        var retval = base.KeyPress (key, keyChar, modifier);
 
578
                                        int cursor = textEditorData.Caret.Offset;
 
579
                                        if (cursor < textEditorData.Length && textEditorData.GetCharAt (cursor) == '"')
 
580
                                                ConvertNormalToVerbatimString (textEditorData, cursor + 1);
 
581
                                        return retval;
 
582
                                }
 
583
                        }
 
584
 
 
585
 
287
586
                        //do the smart indent
288
587
                        if (textEditorData.Options.IndentStyle == IndentStyle.Smart || textEditorData.Options.IndentStyle == IndentStyle.Virtual) {
289
588
                                bool retval;
298
597
                                using (var undo = textEditorData.OpenUndoGroup ()) {
299
598
                                        DoPreInsertionSmartIndent (key);
300
599
                                }
 
600
 
 
601
                                bool automaticReindent;
 
602
                                // need to be outside of an undo group - otherwise it interferes with other text editor extension
 
603
                                // esp. the documentation insertion undo steps.
301
604
                                retval = base.KeyPress (key, keyChar, modifier);
 
605
                                //handle inserted characters
 
606
                                if (textEditorData.Caret.Offset <= 0 || textEditorData.IsSomethingSelected)
 
607
                                        return retval;
 
608
                                
 
609
                                lastCharInserted = TranslateKeyCharForIndenter (key, keyChar, textEditorData.GetCharAt (textEditorData.Caret.Offset - 1));
 
610
                                if (lastCharInserted == '\0')
 
611
                                        return retval;
302
612
 
303
613
                                using (var undo = textEditorData.OpenUndoGroup ()) {
304
 
                                        //handle inserted characters
305
 
                                        if (textEditorData.Caret.Offset <= 0 || textEditorData.IsSomethingSelected)
306
 
                                                return retval;
307
 
 
308
 
                                        lastCharInserted = TranslateKeyCharForIndenter (key, keyChar, textEditorData.GetCharAt (textEditorData.Caret.Offset - 1));
309
 
                                        if (lastCharInserted == '\0')
310
 
                                                return retval;
311
 
 
312
614
                                        stateTracker.UpdateEngine ();
313
615
 
314
616
                                        if (key == Gdk.Key.Return && modifier == Gdk.ModifierType.ControlMask) {
322
624
                                        //inserted rather than just updating the stack due to moving around
323
625
 
324
626
                                        stateTracker.UpdateEngine ();
325
 
                                        bool automaticReindent = (stateTracker.Engine.NeedsReindent && lastCharInserted != '\0');
326
 
                                        if (reIndent || automaticReindent)
327
 
                                                DoReSmartIndent ();
328
 
                                        if (!skipFormatting && keyChar == '}')
329
 
                                                RunFormatter (new DocumentLocation (textEditorData.Caret.Location.Line, textEditorData.Caret.Location.Column));
 
627
                                        automaticReindent = (stateTracker.Engine.NeedsReindent && lastCharInserted != '\0');
 
628
                                        if (key == Gdk.Key.Return && (reIndent || automaticReindent))
 
629
                                                DoReSmartIndent ();
 
630
                                }
 
631
 
 
632
                                if (key != Gdk.Key.Return && (reIndent || automaticReindent)) {
 
633
                                        using (var undo = textEditorData.OpenUndoGroup ()) {
 
634
                                                DoReSmartIndent ();
 
635
                                        }
 
636
                                }
 
637
                                if (!skipFormatting) {
 
638
                                        if (keyChar == ';' || keyChar == '}') {
 
639
                                                using (var undo = textEditorData.OpenUndoGroup ()) {
 
640
                                                        if (OnTheFlyFormatting && textEditorData != null && !(textEditorData.CurrentMode is TextLinkEditMode) && !(textEditorData.CurrentMode is InsertionCursorEditMode)) {
 
641
                                                                OnTheFlyFormatter.FormatStatmentAt (Document, textEditorData.Caret.Location);
 
642
                                                        }
 
643
                                                }
 
644
                                        }
330
645
                                }
331
646
 
332
647
                                stateTracker.UpdateEngine ();
333
648
                                lastCharInserted = '\0';
 
649
                                CheckXmlCommentCloseTag (keyChar);
334
650
                                return retval;
335
651
                        }
336
652
 
337
653
                        if (textEditorData.Options.IndentStyle == IndentStyle.Auto && DefaultSourceEditorOptions.Instance.TabIsReindent && key == Gdk.Key.Tab) {
338
654
                                bool retval = base.KeyPress (key, keyChar, modifier);
339
655
                                DoReSmartIndent ();
 
656
                                CheckXmlCommentCloseTag (keyChar);
340
657
                                return retval;
341
658
                        }
342
659
 
344
661
                        //and calls HandleCodeCompletion etc to handles completion
345
662
                        var result = base.KeyPress (key, keyChar, modifier);
346
663
 
 
664
                        CheckXmlCommentCloseTag (keyChar);
 
665
 
347
666
                        if (!skipFormatting && keyChar == '}')
348
667
                                RunFormatter (new DocumentLocation (textEditorData.Caret.Location.Line, textEditorData.Caret.Location.Column));
349
668
                        return result;
350
669
                }
351
670
 
352
 
                static int GuessSemicolonInsertionOffset (TextEditorData data, DocumentLine curLine)
353
 
                {
354
 
                        int offset = data.Caret.Offset;
355
 
                        int lastNonWsOffset = offset;
356
 
 
357
 
                        int max = curLine.Offset + curLine.Length;
358
 
 
359
 
                        // if the line ends with ';' the line end is not the correct place for a new semicolon.
360
 
                        if (curLine.Length > 0 && data.Document.GetCharAt (max - 1) == ';')
361
 
                                return offset;
 
671
                static bool IsSemicolonalreadyPlaced (TextEditorData data, int caretOffset)
 
672
                {
 
673
                        for (int pos2 = caretOffset - 1; pos2 --> 0;) {
 
674
                                var ch2 = data.Document.GetCharAt (pos2);
 
675
                                if (ch2 == ';') {
 
676
                                        return true;
 
677
                                }
 
678
                                if (!char.IsWhiteSpace (ch2))
 
679
                                        return false;
 
680
                        }
 
681
                        return false;
 
682
                }
 
683
 
 
684
                public static bool GuessSemicolonInsertionOffset (TextEditorData data, IDocumentLine curLine, int caretOffset, out int outOffset)
 
685
                {
 
686
                        int lastNonWsOffset = caretOffset;
 
687
                        char lastNonWsChar = '\0';
 
688
                        outOffset = caretOffset;
 
689
                        int max = curLine.EndOffset;
 
690
        //              if (caretOffset - 2 >= curLine.Offset && data.Document.GetCharAt (caretOffset - 2) == ')' && !IsSemicolonalreadyPlaced (data, caretOffset))
 
691
        //                      return false;
 
692
 
 
693
                        int end = caretOffset;
 
694
                        while (end > 1 && char.IsWhiteSpace (data.GetCharAt (end)))
 
695
                                end--;
 
696
                        int end2 = end;
 
697
                        while (end2 > 1 && char.IsLetter(data.GetCharAt (end2 - 1)))
 
698
                                end2--;
 
699
                        if (end != end2) {
 
700
                                string token = data.GetTextBetween (end2, end + 1);
 
701
                                // guess property context
 
702
                                if (token == "get" || token == "set")
 
703
                                        return false;
 
704
                        }
362
705
 
363
706
                        bool isInString = false , isInChar= false , isVerbatimString= false;
364
707
                        bool isInLineComment = false , isInBlockComment= false;
365
 
                        for (int pos = offset; pos < max; pos++) {
 
708
                        bool firstChar = true;
 
709
                        for (int pos = caretOffset; pos < max; pos++) {
 
710
                                if (pos == caretOffset) {
 
711
                                        if (isInString || isInChar || isVerbatimString || isInLineComment || isInBlockComment) {
 
712
                                                outOffset = pos;
 
713
                                                return true;
 
714
                                        }
 
715
                                }
366
716
                                char ch = data.Document.GetCharAt (pos);
367
717
                                switch (ch) {
 
718
                                case '}':
 
719
                                        if (firstChar && !IsSemicolonalreadyPlaced (data, caretOffset))
 
720
                                                return false;
 
721
                                        break;
368
722
                                case '/':
369
723
                                        if (isInBlockComment) {
370
724
                                                if (pos > 0 && data.Document.GetCharAt (pos - 1) == '*') 
372
726
                                        } else if (!isInString && !isInChar && pos + 1 < max) {
373
727
                                                char nextChar = data.Document.GetCharAt (pos + 1);
374
728
                                                if (nextChar == '/') {
375
 
                                                        isInLineComment = true;
376
 
                                                        return lastNonWsOffset;
 
729
                                                        outOffset = lastNonWsOffset;
 
730
                                                        return true;
377
731
                                                }
378
732
                                                if (!isInLineComment && nextChar == '*') {
379
 
                                                        isInBlockComment = true;
380
 
                                                        return lastNonWsOffset;
 
733
                                                        outOffset = lastNonWsOffset;
 
734
                                                        return true;
381
735
                                                }
382
736
                                        }
383
737
                                        break;
407
761
                                                isInChar = !isInChar;
408
762
                                        break;
409
763
                                }
410
 
                                if (!char.IsWhiteSpace (ch))
 
764
                                if (!char.IsWhiteSpace (ch)) {
 
765
                                        firstChar = false;
411
766
                                        lastNonWsOffset = pos;
 
767
                                        lastNonWsChar = ch;
 
768
                                }
412
769
                        }
413
 
 
414
 
                        return lastNonWsOffset;
415
 
 
 
770
                        // if the line ends with ';' the line end is not the correct place for a new semicolon.
 
771
                        if (lastNonWsChar == ';')
 
772
                                return false;
 
773
                        outOffset = lastNonWsOffset;
 
774
                        return true;
416
775
                }
417
776
 
418
777
                static char TranslateKeyCharForIndenter (Gdk.Key key, char keyChar, char docChar)
435
794
                // removes "\s*\+\s*" patterns (used for special behaviour inside strings)
436
795
                void HandleStringConcatinationDeletion (int start, int end)
437
796
                {
438
 
                        if (start < 0 || end >= textEditorData.Length)
 
797
                        if (start < 0 || end >= textEditorData.Length || textEditorData.IsSomethingSelected)
439
798
                                return;
440
799
                        char ch = textEditorData.GetCharAt (start);
441
800
                        if (ch == '"') {
515
874
                                //xml doc comments
516
875
                                //check previous line was a doc comment
517
876
                                //check there's a following line?
518
 
                                if (trimmedPreviousLine.StartsWith ("///")) {
519
 
                                        if (textEditorData.GetTextAt (line.Offset, line.Length).TrimStart ().StartsWith ("///"))
 
877
                                if (trimmedPreviousLine.StartsWith ("///", StringComparison.Ordinal)) {
 
878
                                        if (textEditorData.GetTextAt (line.Offset, line.Length).TrimStart ().StartsWith ("///", StringComparison.Ordinal))
520
879
                                                return false;
521
880
                                        //check that the newline command actually inserted a newline
522
881
                                        textEditorData.EnsureCaretIsNotVirtual ();
523
 
                                        int insertionPoint = line.Offset + line.GetIndentation (textEditorData.Document).Length;
524
882
                                        string nextLine = textEditorData.Document.GetTextAt (textEditorData.Document.GetLine (lineNumber + 1)).TrimStart ();
525
883
 
526
 
                                        if (trimmedPreviousLine.Length > "///".Length || nextLine.StartsWith ("///")) {
 
884
                                        if (trimmedPreviousLine.Length > "///".Length || nextLine.StartsWith ("///", StringComparison.Ordinal)) {
 
885
                                                var insertionPoint = line.Offset + line.GetIndentation (textEditorData.Document).Length;
527
886
                                                textEditorData.Insert (insertionPoint, "/// ");
528
887
                                                return true;
529
888
                                        }
530
889
                                        //multi-line comments
531
890
                                } else if (stateTracker.Engine.IsInsideMultiLineComment) {
532
 
                                        if (textEditorData.GetTextAt (line.Offset, line.Length).TrimStart ().StartsWith ("*"))
 
891
                                        if (textEditorData.GetTextAt (line.Offset, line.Length).TrimStart ().StartsWith ("*", StringComparison.Ordinal))
533
892
                                                return false;
534
893
                                        textEditorData.EnsureCaretIsNotVirtual ();
535
894
                                        string commentPrefix = string.Empty;
536
 
                                        if (trimmedPreviousLine.StartsWith ("* ")) {
 
895
                                        if (trimmedPreviousLine.StartsWith ("* ", StringComparison.Ordinal)) {
537
896
                                                commentPrefix = "* ";
538
 
                                        } else if (trimmedPreviousLine.StartsWith ("/**") || trimmedPreviousLine.StartsWith ("/*")) {
 
897
                                        } else if (trimmedPreviousLine.StartsWith ("/**", StringComparison.Ordinal) || trimmedPreviousLine.StartsWith ("/*", StringComparison.Ordinal)) {
539
898
                                                commentPrefix = " * ";
540
 
                                        } else if (trimmedPreviousLine.StartsWith ("*")) {
 
899
                                        } else if (trimmedPreviousLine.StartsWith ("*", StringComparison.Ordinal)) {
541
900
                                                commentPrefix = "*";
542
901
                                        }
543
902
 
555
914
                                        textEditorData.Insert (prevLine.Offset + prevLine.Length, "\" +");
556
915
 
557
916
                                        int indentSize = line.GetIndentation (textEditorData.Document).Length;
558
 
                                        var insertedText = prevLine.GetIndentation (textEditorData.Document) + (trimmedPreviousLine.StartsWith ("\"") ? "" : "\t") + "\"";
 
917
                                        var insertedText = prevLine.GetIndentation (textEditorData.Document) + (trimmedPreviousLine.StartsWith ("\"", StringComparison.Ordinal) ? "" : "\t") + "\"";
559
918
                                        textEditorData.Replace (line.Offset, indentSize, insertedText);
560
919
                                        return true;
561
920
                                }
571
930
 
572
931
                void DoReSmartIndent (int cursor)
573
932
                {
574
 
                        string newIndent = string.Empty;
 
933
                        if (stateTracker.Engine.LineBeganInsideVerbatimString || stateTracker.Engine.LineBeganInsideMultiLineComment)
 
934
                                return;
575
935
                        DocumentLine line = textEditorData.Document.GetLineByOffset (cursor);
576
936
//                      stateTracker.UpdateEngine (line.Offset);
577
937
                        // Get context to the end of the line w/o changing the main engine's state
578
 
                        CSharpIndentEngine ctx = (CSharpIndentEngine)stateTracker.Engine.Clone ();
 
938
                        var ctx = (CSharpIndentEngine)stateTracker.Engine.Clone ();
579
939
                        for (int max = cursor; max < line.EndOffset; max++) {
580
940
                                ctx.Push (textEditorData.Document.GetCharAt (max));
581
941
                        }
582
 
                        
583
942
                        int pos = line.Offset;
584
943
                        string curIndent = line.GetIndentation (textEditorData.Document);
585
944
                        int nlwsp = curIndent.Length;
586
945
                        int offset = cursor > pos + nlwsp ? cursor - (pos + nlwsp) : 0;
587
946
                        if (!stateTracker.Engine.LineBeganInsideMultiLineComment || (nlwsp < line.LengthIncludingDelimiter && textEditorData.Document.GetCharAt (line.Offset + nlwsp) == '*')) {
588
947
                                // Possibly replace the indent
589
 
                                newIndent = ctx.ThisLineIndent;
 
948
                                string newIndent = ctx.ThisLineIndent;
590
949
                                int newIndentLength = newIndent.Length;
591
950
                                if (newIndent != curIndent) {
592
951
                                        if (CompletionWindowManager.IsVisible) {
598
957
                                        textEditorData.Document.CommitLineUpdate (textEditorData.Caret.Line);
599
958
                                        // Engine state is now invalid
600
959
                                        stateTracker.ResetEngineToPosition (pos);
 
960
                                        CompletionWindowManager.HideWindow ();
601
961
                                }
602
962
                                pos += newIndentLength;
603
963
                        } else {