29
29
using System.Collections.Generic;
30
using System.Diagnostics;
33
30
using MonoDevelop.Core;
34
using MonoDevelop.Ide.Gui;
35
31
using MonoDevelop.Ide.Gui.Content;
37
33
using MonoDevelop.Projects;
38
34
using MonoDevelop.Ide.CodeCompletion;
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;
52
45
namespace MonoDevelop.CSharp.Formatting
54
public class CSharpTextEditorIndentation : TextEditorExtension
47
public class CSharpTextEditorIndentation : TextEditorExtension, ITextPasteHandler
56
49
DocumentStateTracker<CSharpIndentEngine> stateTracker;
57
50
int cursorPositionBeforeKeyPress;
58
TextEditorData textEditorData;
59
CSharpFormattingPolicy policy;
60
TextStylePolicy textStylePolicy;
51
TextEditorData textEditorData {
53
return document.Editor;
57
IEnumerable<string> types = MonoDevelop.Ide.DesktopService.GetMimeTypeInheritanceChain (CSharpFormatter.MimeType);
59
CSharpFormattingPolicy Policy {
61
if (Document != null && Document.Project != null && Document.Project.Policies != null) {
62
return base.Document.Project.Policies.Get<CSharpFormattingPolicy> (types);
64
return MonoDevelop.Projects.Policies.PolicyService.GetDefaultPolicy<CSharpFormattingPolicy> (types);
68
TextStylePolicy TextStylePolicy {
70
if (Document != null && Document.Project != null && Document.Project.Policies != null) {
71
return base.Document.Project.Policies.Get<TextStylePolicy> (types);
73
return MonoDevelop.Projects.Policies.PolicyService.GetDefaultPolicy<TextStylePolicy> (types);
62
77
char lastCharInserted;
64
79
static CSharpTextEditorIndentation ()
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)
70
ITextEditorExtension textEditorExtension = editor.Extension;
85
var textEditorExtension = editor.Extension;
71
86
while (textEditorExtension != null && !(textEditorExtension is CSharpTextEditorIndentation)) {
72
87
textEditorExtension = textEditorExtension.Next;
74
CSharpTextEditorIndentation extension = textEditorExtension as CSharpTextEditorIndentation;
89
var extension = textEditorExtension as CSharpTextEditorIndentation;
75
90
if (extension == null)
77
92
extension.stateTracker.UpdateEngine ();
98
bool IsPreprocessorDirective (DocumentLine documentLine)
100
int o = documentLine.Offset;
101
for (int i = 0; i < documentLine.Length; i++) {
102
char ch = Editor.GetCharAt (o++);
105
if (!char.IsWhiteSpace (ch)) {
84
112
void HandleTextPaste (int insertionOffset, string text, int insertedChars)
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;
91
var line = Document.Editor.GetLineByOffset (i);
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;
114
if (document.Editor.Options.IndentStyle == IndentStyle.None ||
115
document.Editor.Options.IndentStyle == IndentStyle.Auto)
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 ());
127
int pos = curLineOffset;
128
string curIndent = curLine.GetIndentation (textEditorData.Document);
129
int nlwsp = curIndent.Length;
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;
140
textEditorData.Replace (pos, nlwsp, newIndent);
141
textEditorData.Document.CommitLineUpdate (textEditorData.Caret.Line);
102
if (!foundNonWsFollowUp) {
103
while (i > insertionOffset) {
104
char ch = Document.Editor.GetCharAt (i - 1);
105
if (ch != ' ' && ch != '\t')
109
int delta = insertionOffset + insertedChars - i;
111
Editor.Caret.Offset -= delta;
112
Editor.Remove (insertionOffset + insertedChars - delta, delta);
116
var documentLine = Editor.GetLineByOffset (insertionOffset + insertedChars);
117
while (documentLine != null && insertionOffset < documentLine.EndOffset) {
118
DoReSmartIndent (documentLine.Offset);
119
documentLine = documentLine.PreviousLine;
146
textEditorData.FixVirtualIndentation ();
123
149
public static bool OnTheFlyFormatting {
132
158
void RunFormatter (DocumentLocation location)
134
160
if (OnTheFlyFormatting && textEditorData != null && !(textEditorData.CurrentMode is TextLinkEditMode) && !(textEditorData.CurrentMode is InsertionCursorEditMode)) {
136
161
OnTheFlyFormatter.Format (Document, location);
140
public CSharpTextEditorIndentation ()
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);
147
165
public override void Initialize ()
149
167
base.Initialize ();
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);
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);
165
textEditorData.IndentationTracker = new IndentVirtualSpaceManager (
167
new DocumentStateTracker<CSharpIndentEngine> (new CSharpIndentEngine (policy, textStylePolicy), textEditorData)
171
textEditorData.Options.Changed += HandleTextOptionsChanged;
170
172
textEditorData.IndentationTracker = new IndentVirtualSpaceManager (
172
new DocumentStateTracker<CSharpIndentEngine> (new CSharpIndentEngine (policy, textStylePolicy), textEditorData)
174
new DocumentStateTracker<CSharpIndentEngine> (new CSharpIndentEngine (Policy, TextStylePolicy), textEditorData)
176
textEditorData.Document.TextReplacing += HandleTextReplacing;
177
textEditorData.Document.TextReplaced += HandleTextReplaced;;
178
textEditorData.TextPasteHandler = this;
179
textEditorData.Paste += HandleTextPaste;
177
// Document.Editor.Paste += HandleTextPaste;
180
/* void TextCut (object sender, ReplaceEventArgs e)
182
if (!string.IsNullOrEmpty (e.Value) || e.Count == 0)
184
RunFormatterAt (e.Offset);
185
void HandleTextOptionsChanged (object sender, EventArgs e)
187
textEditorData.IndentationTracker = new IndentVirtualSpaceManager (
189
new DocumentStateTracker<CSharpIndentEngine> (new CSharpIndentEngine (Policy, TextStylePolicy), textEditorData)
194
public override void Dispose ()
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;;
207
bool wasInVerbatimString;
209
void HandleTextReplaced (object sender, DocumentChangeEventArgs e)
211
if (e.RemovalLength != 1)
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;;
223
void HandleTextReplacing (object sender, DocumentChangeEventArgs e)
225
var o = e.Offset + e.RemovalLength;
226
if (o < 0 || o + 1 > textEditorData.Length || e.RemovalLength != 1 || textEditorData.Document.IsInUndo) {
227
wasInVerbatimString = false;
230
if (textEditorData.GetCharAt (o) != '"')
232
stateTracker.UpdateEngine (o + 1);
233
wasInVerbatimString = stateTracker.Engine.IsInsideVerbatimString;
236
#region ITextPasteHandler implementation
237
enum CopySource : byte
244
byte[] ITextPasteHandler.GetCopyData (TextSegment segment)
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 };
254
static string ConvertFromString (string nonVerbatimStringContent)
256
var result = new StringBuilder ();
257
for (int i = 0; i < nonVerbatimStringContent.Length; i++) {
258
var ch = nonVerbatimStringContent [i];
262
switch (nonVerbatimStringContent [i]) {
264
result.Append ('\\');
267
result.Append ('\r');
270
result.Append ('\n');
273
result.Append ('\t');
285
return result.ToString ();
288
static string ConvertFromVerbatimString (string verbatimStringContent)
290
var result = new StringBuilder ();
291
for (int i = 0; i < verbatimStringContent.Length; i++) {
292
var ch = verbatimStringContent [i];
296
if (i + 1 < verbatimStringContent.Length && verbatimStringContent [i + 1] == '"') {
297
result.Append ("\"");
306
return result.ToString ();
309
static string ConvertToStringLiteral (string text)
311
var result = new StringBuilder ();
312
foreach (var ch in text) {
315
result.Append ("\\t");
318
result.Append ("\\\"");
321
result.Append ("\\n");
324
result.Append ("\\r");
327
result.Append ("\\\\");
334
return result.ToString ();
337
static string ConvertToVerbatimLiteral (string text)
339
var result = new StringBuilder ();
340
foreach (var ch in text) {
343
result.Append ("\"\"");
350
return result.ToString ();
353
string ITextPasteHandler.FormatPlainText (int insertionOffset, string text, byte[] copyData)
355
if (document.Editor.Options.IndentStyle == IndentStyle.None ||
356
document.Editor.Options.IndentStyle == IndentStyle.Auto)
359
if (copyData != null && copyData.Length == 1) {
360
CopySource src = (CopySource)copyData [0];
362
case CopySource.VerbatimString:
363
text = ConvertFromVerbatimString (text);
365
case CopySource.StringLiteral:
366
text = ConvertFromString (text);
371
stateTracker.UpdateEngine (insertionOffset);
372
var engine = stateTracker.Engine.Clone () as CSharpIndentEngine;
374
var result = new StringBuilder ();
376
if (engine.IsInsideStringLiteral)
377
return ConvertToStringLiteral (text);
379
if (engine.IsInsideVerbatimString)
380
return ConvertToVerbatimLiteral (text);
382
bool inNewLine = false;
383
foreach (var ch in text) {
384
if (!engine.IsInsideOrdinaryCommentOrString) {
385
if (inNewLine && (ch == ' ' || ch == '\t')) {
391
if (inNewLine && ch != '\n' && ch != '\r') {
392
if (!engine.IsInsideOrdinaryCommentOrString) {
395
result.Append (engine.ThisLineIndent);
404
if (ch == '\n' || ch == '\r')
407
return result.ToString ();
411
void ConvertNormalToVerbatimString (TextEditorData textEditorData, int offset)
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) == '"')) {
424
textEditorData.Replace (offset, endOffset - offset, ConvertToVerbatimLiteral (ConvertFromString (textEditorData.GetTextAt (offset, endOffset - offset))));
427
void ConvertVerbatimStringToNormal (TextEditorData textEditorData, int offset)
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) == '"')) {
440
textEditorData.Replace (offset, endOffset - offset, ConvertToStringLiteral (ConvertFromVerbatimString (textEditorData.GetTextAt (offset, endOffset - offset))));
188
443
#region Sharing the tracker
190
445
void InitTracker ()
192
stateTracker = new DocumentStateTracker<CSharpIndentEngine> (new CSharpIndentEngine (policy, textStylePolicy), textEditorData);
447
stateTracker = new DocumentStateTracker<CSharpIndentEngine> (new CSharpIndentEngine (Policy, TextStylePolicy), textEditorData);
195
450
internal DocumentStateTracker<CSharpIndentEngine> StateTracker { get { return stateTracker; } }
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"))
513
if (!(text.EndsWith (";", StringComparison.Ordinal) || text.Trim ().StartsWith ("for", StringComparison.Ordinal))) {
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);
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);
344
661
//and calls HandleCodeCompletion etc to handles completion
345
662
var result = base.KeyPress (key, keyChar, modifier);
664
CheckXmlCommentCloseTag (keyChar);
347
666
if (!skipFormatting && keyChar == '}')
348
667
RunFormatter (new DocumentLocation (textEditorData.Caret.Location.Line, textEditorData.Caret.Location.Column));
352
static int GuessSemicolonInsertionOffset (TextEditorData data, DocumentLine curLine)
354
int offset = data.Caret.Offset;
355
int lastNonWsOffset = offset;
357
int max = curLine.Offset + curLine.Length;
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) == ';')
671
static bool IsSemicolonalreadyPlaced (TextEditorData data, int caretOffset)
673
for (int pos2 = caretOffset - 1; pos2 --> 0;) {
674
var ch2 = data.Document.GetCharAt (pos2);
678
if (!char.IsWhiteSpace (ch2))
684
public static bool GuessSemicolonInsertionOffset (TextEditorData data, IDocumentLine curLine, int caretOffset, out int outOffset)
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))
693
int end = caretOffset;
694
while (end > 1 && char.IsWhiteSpace (data.GetCharAt (end)))
697
while (end2 > 1 && char.IsLetter(data.GetCharAt (end2 - 1)))
700
string token = data.GetTextBetween (end2, end + 1);
701
// guess property context
702
if (token == "get" || token == "set")
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) {
366
716
char ch = data.Document.GetCharAt (pos);
719
if (firstChar && !IsSemicolonalreadyPlaced (data, caretOffset))
369
723
if (isInBlockComment) {
370
724
if (pos > 0 && data.Document.GetCharAt (pos - 1) == '*')
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))
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 ();
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, "/// ");
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))
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 = "*";