43
42
using MonoDevelop.Projects.Text;
44
43
using MonoDevelop.Projects.Policies;
45
44
using MonoDevelop.Ide;
45
using MonoDevelop.Refactoring;
47
using MonoDevelop.Ide.CodeFormatting;
47
49
namespace MonoDevelop.CSharp.Formatting
49
public class CSharpFormatter : AbstractPrettyPrinter
51
public class CSharpFormatter : AbstractAdvancedFormatter
51
53
static internal readonly string MimeType = "text/x-csharp";
53
public CSharpFormatter ()
56
static int GetTrailingWhitespaces (StringBuilder sb)
58
string str = sb.ToString ();
59
int i = str.Length - 1;
60
while (i >= 0 && Char.IsWhiteSpace (str[i])) {
63
// Console.WriteLine (str.Length - 1 - i);
65
return str.Length - 1 - i;
67
static string CreateWrapperClassForMember (IMember member, bool generateOuterClass, TextEditorData data, out int end)
73
StringBuilder result = new StringBuilder ();
74
int offset = data.Document.LocationToOffset (member.Location.Line - 1, 0);
76
while (offset < data.Document.Length && data.Document.GetCharAt (offset) != '{') {
79
if (data.Caret.Offset < offset) {
83
end = data.Document.GetMatchingBracketOffset (offset);
87
if (generateOuterClass)
88
result.Append ("class " + (member.DeclaringType != null ? member.DeclaringType.Name : "GenericClass") + " {");
89
for (int i = start; i <= end; i++) {
90
char ch = data.Document.GetCharAt (i);
93
if (i + 1 <= end && data.Document.GetCharAt (i + 1) == '/') {
95
ch = data.Document.GetCharAt (i);
97
if (i == '\n' || i == '\r')
105
result.Length -= GetTrailingWhitespaces (result);
112
if (generateOuterClass)
114
return result.ToString ();
117
public static int GetColumn (string wrapper, int i, int tabSize)
121
for (; j < wrapper.Length && (wrapper[j] == ' ' || wrapper[j] == '\t'); j++) {
122
if (wrapper[j] == ' ') {
125
col = GetNextTabstop (col, tabSize);
131
public override bool SupportsOnTheFlyFormatting {
137
public override void CorrectIndenting (object textEditorData, int line)
139
TextEditorData data = (TextEditorData)textEditorData;
55
public override bool SupportsOnTheFlyFormatting { get { return true; } }
57
public override bool SupportsCorrectingIndent { get { return true; } }
59
public override void CorrectIndenting (PolicyContainer policyParent, IEnumerable<string> mimeTypeChain,
60
TextEditorData data, int line)
140
62
LineSegment lineSegment = data.Document.GetLine (line);
141
63
if (lineSegment == null)
143
IEnumerable<string> types = MonoDevelop.Ide.DesktopService.GetMimeTypeInheritanceChain (CSharpFormatter.MimeType);
144
var policy = MonoDevelop.Projects.Policies.PolicyService.GetDefaultPolicy<CSharpFormattingPolicy> (types);
145
DocumentStateTracker<CSharpIndentEngine> tracker = new DocumentStateTracker<CSharpIndentEngine> (new CSharpIndentEngine (policy), data);
66
var policy = policyParent.Get<CSharpFormattingPolicy> (mimeTypeChain);
67
var tracker = new DocumentStateTracker<CSharpIndentEngine> (new CSharpIndentEngine (policy), data);
146
68
tracker.UpdateEngine (lineSegment.Offset);
147
69
for (int i = lineSegment.Offset; i < lineSegment.Offset + lineSegment.EditableLength; i++) {
148
70
tracker.Engine.Push (data.Document.GetCharAt (i));
151
73
string curIndent = lineSegment.GetIndentation (data.Document);
153
75
int nlwsp = curIndent.Length;
154
// int cursor = data.Caret.Offset;
155
// int pos = lineSegment.Offset;
156
// int offset = cursor > pos + nlwsp ? cursor - (pos + nlwsp) : 0;
157
76
if (!tracker.Engine.LineBeganInsideMultiLineComment || (nlwsp < lineSegment.Length && data.Document.GetCharAt (lineSegment.Offset + nlwsp) == '*')) {
158
77
// Possibly replace the indent
159
78
string newIndent = tracker.Engine.ThisLineIndent;
160
// int newIndentLength = newIndent.Length;
161
79
if (newIndent != curIndent)
162
80
data.Replace (lineSegment.Offset, nlwsp, newIndent);
166
string endIndent = tracker.Engine.ThisLineIndent;
167
string indent = endIndent.Length < beginIndent.Length ? endIndent : beginIndent;
168
if (indent != curIndent)
169
data.Replace (lineSegment.Offset, curIndent.Length, indent);
171
82
tracker.Dispose ();
174
public override void OnTheFlyFormat (object textEditorData, IType type, IMember member, ProjectDom dom, ICompilationUnit unit, DomLocation caretLocation)
176
Format ((TextEditorData)textEditorData, type, member, dom, unit, caretLocation);
179
public static void Format (TextEditorData data, ProjectDom dom, ICompilationUnit unit, DomLocation caretLocation)
181
IType type = NRefactoryResolver.GetTypeAtCursor (unit, unit.FileName, caretLocation);
184
Format (data, type, NRefactoryResolver.GetMemberAt (type, unit.FileName, caretLocation), dom, unit, caretLocation);
187
static string GetIndent (string text)
189
StringBuilder result = new StringBuilder ();
190
foreach (char ch in text) {
191
if (!char.IsWhiteSpace (ch))
195
return result.ToString ();
198
static string RemoveIndent (string text, string indent)
200
Document doc = new Document ();
202
StringBuilder result = new StringBuilder ();
203
foreach (LineSegment line in doc.Lines) {
204
string curLineIndent = line.GetIndentation (doc);
205
int offset = Math.Min (curLineIndent.Length, indent.Length);
206
result.Append (doc.GetTextBetween (line.Offset + offset, line.EndOffset));
208
return result.ToString ();
211
static string AddIndent (string text, string indent)
213
Document doc = new Document ();
215
StringBuilder result = new StringBuilder ();
216
foreach (LineSegment line in doc.Lines) {
217
if (result.Length > 0)
218
result.Append (indent);
219
result.Append (doc.GetTextAt (line));
221
return result.ToString ();
224
static string GetIndent (TextEditorData data, int lineNumber)
226
return data.Document.GetLine (lineNumber).GetIndentation (data.Document);
229
public static void Format (TextEditorData data, IType type, IMember member, ProjectDom dom, ICompilationUnit unit, DomLocation caretLocation)
233
if (member == null) {
239
wrapper = CreateWrapperClassForMember (member, member != type, data, out endPos);
240
if (string.IsNullOrEmpty (wrapper) || endPos < 0)
242
// Console.WriteLine (wrapper);
244
int bracketIndex = wrapper.IndexOf ('{') + 1;
245
int col = GetColumn (wrapper, bracketIndex, data.Options.TabSize);
247
CSharpFormatter formatter = new CSharpFormatter ();
248
formatter.startIndentLevel = System.Math.Max (0, col / data.Options.TabSize - 1);
250
string formattedText = formatter.InternalFormat (dom != null && dom.Project != null ? dom.Project.Policies : null, MimeType, wrapper, 0, wrapper.Length);
252
if (formatter.hasErrors)
255
int startPos = data.Document.LocationToOffset (member.Location.Line - 1, 0) - 1;
257
if (member != type) {
258
int len1 = formattedText.IndexOf ('{') + 1;
259
int last = formattedText.LastIndexOf ('}');
260
formattedText = formattedText.Substring (len1, last - len1 - 1).TrimStart ('\n', '\r');
265
//Console.WriteLine ("formattedText0:" + formattedText.Replace ("\t", "->").Replace (" ", "Ā°"));
266
if (member != type) {
267
string indentToRemove = GetIndent (formattedText);
268
// Console.WriteLine ("Remove:" + indentToRemove.Replace ("\t", "->").Replace (" ", "Ā°"));
269
formattedText = RemoveIndent (formattedText, indentToRemove);
271
formattedText = formattedText.TrimStart ();
273
//Console.WriteLine ("Indent:" + GetIndent (data, member.Location.Line - 1).Replace ("\t", "->").Replace (" ", "Ā°"));
274
//Console.WriteLine ("formattedText1:" + formattedText.Replace ("\t", "->").Replace (" ", "Ā°"));
275
formattedText = AddIndent (formattedText, GetIndent (data, member.Location.Line - 1));
277
Document doc = new Document ();
278
doc.Text = formattedText;
279
for (int i = doc.LineCount - 1; i >= 0; i--) {
280
LineSegment lineSegment = doc.GetLine (i);
281
if (doc.IsEmptyLine (lineSegment))
282
((IBuffer)doc).Remove (lineSegment.Offset, lineSegment.Length);
284
formattedText = doc.Text;
286
//Console.WriteLine ("formattedText3:" + formattedText.Replace ("\t", "->").Replace (" ", "Ā°").Replace ("\n", "\\n").Replace ("\r", "\\r"));
288
int textLength = CanInsertFormattedText (data, startPos, data.Document.LocationToOffset (caretLocation.Line, caretLocation.Column), formattedText);
289
if (textLength > 0) {
290
// Console.WriteLine (formattedText.Substring (0, textLength));
291
InsertFormattedText (data, startPos, formattedText.Substring (0, textLength).TrimEnd ());
293
Console.WriteLine ("Can't insert !!!");
298
static int CanInsertFormattedText (TextEditorData data, int offset, int endOffset, string formattedText)
301
endOffset = System.Math.Min (data.Document.Length, endOffset);
303
while (textOffset < formattedText.Length && offset < endOffset) {
309
char ch1 = data.Document.GetCharAt (offset);
310
char ch2 = formattedText[textOffset];
311
bool ch1Ws = Char.IsWhiteSpace (ch1);
312
bool ch2Ws = Char.IsWhiteSpace (ch2);
313
//Console.WriteLine ("ch1={0}, ch2={1}", ch1, ch2);
318
} else if (ch1 == '\n' || ch1 == '\r') {
320
int firstWhitespace = -1;
321
while (textOffset < formattedText.Length && IsPlainWhitespace (formattedText[textOffset])) {
322
if (firstWhitespace < 0)
323
firstWhitespace = textOffset;
327
if (firstWhitespace >= 0 && firstWhitespace != textOffset && (formattedText[textOffset] == '\n' || formattedText[textOffset] == '\r')) {
328
int length = textOffset - firstWhitespace;
329
offset += length - 1;
334
while (offset < data.Caret.Offset && IsPlainWhitespace (data.Document.GetCharAt (offset))) {
340
if (ch2Ws && !ch1Ws) {
344
if ((!ch2Ws || ch2 == '\n') && ch1Ws) {
349
if (ch1Ws && ch2Ws) {
357
return textOffset - 1;
360
static void InsertFormattedText (TextEditorData data, int offset, string formattedText)
362
data.Document.BeginAtomicUndo ();
363
// DocumentLocation caretLocation = data.Caret.Location;
365
int selAnchor = data.IsSomethingSelected ? data.Document.LocationToOffset (data.MainSelection.Anchor) : -1;
366
int selLead = data.IsSomethingSelected ? data.Document.LocationToOffset (data.MainSelection.Lead) : -1;
368
int caretOffset = data.Caret.Offset;
370
// Console.WriteLine ("formattedText3:" + formattedText.Replace ("\t", "->").Replace (" ", "Ā°").Replace ("\n", "\\n").Replace ("\r", "\\r"));
371
while (textOffset < formattedText.Length /*&& offset < caretOffset*/) {
377
char ch1 = data.Document.GetCharAt (offset);
378
char ch2 = formattedText[textOffset];
379
bool isCh1Eol = ch1 == '\n'|| ch1 == '\r';
381
if (ch1 == '\r' && offset + 1 < data.Document.Length && data.Document.GetCharAt (offset + 1) == '\n') {
386
if (ch1 == ch2 || (ch2 == '\n' && isCh1Eol)) {
390
} else if (isCh1Eol) {
392
// int firstWhitespace = 0;
394
// skip all white spaces in formatted text - we had a line break
395
int firstWhitespace = -1;
396
while (textOffset < formattedText.Length && IsPlainWhitespace (formattedText[textOffset])) {
397
if (firstWhitespace < 0)
398
firstWhitespace = textOffset;
401
if (firstWhitespace >= 0 && firstWhitespace != textOffset && formattedText[textOffset] == '\n') {
402
int length = textOffset - firstWhitespace - 1;
403
data.Insert (offset, formattedText.Substring (firstWhitespace, length) + data.EolMarker);
404
data.Document.CommitLineUpdate (data.Document.OffsetToLineNumber (offset));
405
length += data.EolMarker.Length;
406
if (offset < caretOffset)
407
caretOffset += length;
408
if (offset < selAnchor)
410
if (offset < selLead)
413
offset += length - 1;
418
while (offset < data.Caret.Offset && IsPlainWhitespace (data.Document.GetCharAt (offset))) {
423
bool ch1Ws = Char.IsWhiteSpace (ch1);
424
bool ch2Ws = Char.IsWhiteSpace (ch2);
426
if (ch2Ws && !ch1Ws) {
428
data.Insert (offset, data.EolMarker);
429
data.Document.CommitLineUpdate (data.Document.OffsetToLineNumber (offset));
430
if (offset < caretOffset)
431
caretOffset += data.EolMarker.Length;
432
if (offset < selAnchor)
433
selAnchor += data.EolMarker.Length;
434
if (offset < selLead)
435
selLead += data.EolMarker.Length;
437
offset += data.EolMarker.Length;
439
data.Insert (offset, ch2.ToString ());
440
data.Document.CommitLineUpdate (data.Document.OffsetToLineNumber (offset));
441
if (offset < caretOffset)
443
if (offset < selAnchor)
445
if (offset < selLead)
453
if ((!ch2Ws || ch2 == '\n') && ch1Ws) {
454
if (offset < caretOffset)
456
if (offset < selAnchor)
458
if (offset < selLead)
460
data.Remove (offset, 1);
461
data.Document.CommitLineUpdate (data.Document.OffsetToLineNumber (offset));
464
if (ch1Ws && ch2Ws) {
465
data.Replace (offset, 1, ch2.ToString ());
466
data.Document.CommitLineUpdate (data.Document.OffsetToLineNumber (offset));
471
Console.WriteLine ("BAIL OUT");
474
data.Caret.Offset = caretOffset;
477
data.MainSelection = new Selection (data.Document.OffsetToLocation (selAnchor), data.Document.OffsetToLocation (selLead));
478
data.Document.EndAtomicUndo ();
481
static bool IsPlainWhitespace (char ch)
483
return ch == ' ' || ch == '\t';
486
public static bool InFormat = false;
488
public override bool CanFormat (string mimeType)
490
return mimeType == MimeType;
493
static int GetNextTabstop (int currentColumn, int tabSize)
495
int result = currentColumn + tabSize;
496
return (result / tabSize) * tabSize;
499
public static void SetFormatOptions (CSharpOutputVisitor outputVisitor, PolicyContainer policyParent)
501
IEnumerable<string> types = DesktopService.GetMimeTypeInheritanceChain (MimeType);
502
TextStylePolicy currentPolicy = policyParent != null ? policyParent.Get<TextStylePolicy> (types) : MonoDevelop.Projects.Policies.PolicyService.GetDefaultPolicy<TextStylePolicy> (types);
503
CSharpFormattingPolicy codePolicy = policyParent != null ? policyParent.Get<CSharpFormattingPolicy> (types) : MonoDevelop.Projects.Policies.PolicyService.GetDefaultPolicy<CSharpFormattingPolicy> (types);
505
outputVisitor.Options.IndentationChar = currentPolicy.TabsToSpaces ? ' ' : '\t';
506
outputVisitor.Options.TabSize = currentPolicy.TabWidth;
507
outputVisitor.Options.IndentSize = currentPolicy.TabWidth;
508
outputVisitor.Options.EolMarker = TextStylePolicy.GetEolMarker(currentPolicy.EolMarker);
510
CodeFormatDescription descr = CSharpFormattingPolicyPanel.CodeFormatDescription;
511
Type optionType = outputVisitor.Options.GetType ();
513
foreach (CodeFormatOption option in descr.AllOptions) {
514
KeyValuePair<string, string> val = descr.GetValue (codePolicy, option);
515
PropertyInfo info = optionType.GetProperty (option.Name);
517
System.Console.WriteLine ("option : " + option.Name + " not found.");
521
if (info.PropertyType.IsEnum) {
522
cval = Enum.Parse (info.PropertyType, val.Key);
523
} else if (info.PropertyType == typeof(bool)) {
524
cval = Convert.ToBoolean (val.Key);
526
cval = Convert.ChangeType (val.Key, info.PropertyType);
528
//System.Console.WriteLine("set " + option.Name + " to " + cval);
529
info.SetValue (outputVisitor.Options, cval, null);
534
bool hasErrors = false;
535
int startIndentLevel = 0;
536
protected override string InternalFormat (PolicyContainer policyParent, string mimeType, string input, int startOffset, int endOffset)
538
string text = GetFormattedText (policyParent, input);
539
if (startOffset == 0 && endOffset >= input.Length - 1)
541
int newStartOffset = TranslateOffset (input, text, startOffset);
542
int newEndOffset = TranslateOffset (input, text, endOffset);
543
if (newStartOffset < 0 || newEndOffset < 0)
544
return input.Substring (startOffset, System.Math.Max (0, System.Math.Min (endOffset - startOffset, input.Length - startOffset)));
546
return text.Substring (newStartOffset, newEndOffset - newStartOffset);
549
static int TranslateOffset (string baseInput, string formattedInput, int offset)
553
while (i < baseInput.Length && j < formattedInput.Length && i < offset) {
554
char ch1 = baseInput[i];
555
char ch2 = formattedInput[j];
556
bool ch1IsWs = Char.IsWhiteSpace (ch1);
557
bool ch2IsWs = Char.IsWhiteSpace (ch2);
558
// Console.WriteLine ("ch1={0}, ch2={1}, ch1IsWs={2}, ch2IsWs={3}", ch1, ch2, ch1IsWs, ch2IsWs);
559
if (ch1 == ch2 || ch1IsWs && ch2IsWs) {
562
} else if (!ch1IsWs && ch2IsWs) {
564
} else if (ch1IsWs && !ch2IsWs) {
573
string GetFormattedText (PolicyContainer policyParent, string input)
576
if (string.IsNullOrEmpty (input))
579
CSharpOutputVisitor outputVisitor = new CSharpOutputVisitor ();
580
SetFormatOptions (outputVisitor, policyParent);
582
outputVisitor.OutputFormatter.IndentationLevel = startIndentLevel;
583
using (ICSharpCode.NRefactory.IParser parser = ParserFactory.CreateParser (SupportedLanguage.CSharp, new StringReader (input))) {
585
hasErrors = parser.Errors.Count != 0;
587
// Console.WriteLine (parser.Errors.ErrorOutput);
588
IList<ISpecial> specials = parser.Lexer.SpecialTracker.RetrieveSpecials ();
589
if (parser.Errors.Count == 0) {
590
using (SpecialNodesInserter.Install (specials, outputVisitor)) {
591
parser.CompilationUnit.AcceptVisitor (outputVisitor, null);
593
return outputVisitor.Text;
596
// Console.WriteLine ("trying to parse block.");
597
using (ICSharpCode.NRefactory.IParser parser = ParserFactory.CreateParser (SupportedLanguage.CSharp, new StringReader (input))) {
598
BlockStatement blockStatement = parser.ParseBlock ();
599
hasErrors = parser.Errors.Count != 0;
601
// Console.WriteLine (parser.Errors.ErrorOutput);
602
IList<ISpecial> specials = parser.Lexer.SpecialTracker.RetrieveSpecials ();
603
if (parser.Errors.Count == 0) {
604
StringBuilder result = new StringBuilder ();
605
using (var inserter = SpecialNodesInserter.Install (specials, outputVisitor)) {
606
foreach (ICSharpCode.NRefactory.Ast.INode node in blockStatement.Children) {
607
node.AcceptVisitor (outputVisitor, null);
608
// result.AppendLine (outputVisitor.Text);
610
if (!outputVisitor.OutputFormatter.LastCharacterIsNewLine)
611
outputVisitor.OutputFormatter.NewLine ();
613
result.AppendLine (outputVisitor.Text);
615
return result.ToString ();
618
// Console.WriteLine ("trying to parse expression.");
619
using (ICSharpCode.NRefactory.IParser parser = ParserFactory.CreateParser (SupportedLanguage.CSharp, new StringReader (input))) {
620
Expression expression = parser.ParseExpression ();
621
hasErrors = parser.Errors.Count != 0;
623
// Console.WriteLine (parser.Errors.ErrorOutput);
624
IList<ISpecial> specials = parser.Lexer.SpecialTracker.RetrieveSpecials ();
625
if (parser.Errors.Count == 0) {
626
using (SpecialNodesInserter.Install (specials, outputVisitor)) {
627
expression.AcceptVisitor (outputVisitor, null);
629
return outputVisitor.Text;
85
public override void OnTheFlyFormat (PolicyContainer policyParent, IEnumerable<string> mimeTypeChain,
86
TextEditorData data, IType type, IMember member, ProjectDom dom, ICompilationUnit unit, DomLocation caretLocation)
88
// OnTheFlyFormatter.Format (policyParent, mimeTypeChain, data, dom, caretLocation, true);
91
public override void OnTheFlyFormat (PolicyContainer policyParent, IEnumerable<string> mimeTypeChain,
92
TextEditorData data, int startOffset, int endOffset)
94
var parser = new MonoDevelop.CSharp.Parser.CSharpParser ();
95
var compilationUnit = parser.Parse (data);
96
bool hadErrors = parser.HasErrors;
97
var policy = policyParent.Get<CSharpFormattingPolicy> (mimeTypeChain);
98
var formattingVisitor = new AstFormattingVisitor (policy, data) {
99
AutoAcceptChanges = false,
100
HadErrors = hadErrors
102
compilationUnit.AcceptVisitor (formattingVisitor, null);
105
var changes = new List<Change> ();
107
changes.AddRange (formattingVisitor.Changes.
108
Where (c => c is TextReplaceChange && (startOffset <= ((TextReplaceChange)c).Offset && ((TextReplaceChange)c).Offset < endOffset)));
110
RefactoringService.AcceptChanges (null, null, changes);
113
public override string FormatText (PolicyContainer policyParent, IEnumerable<string> mimeTypeChain, string input, int startOffset, int endOffset)
115
var data = new TextEditorData ();
116
data.Document.SuppressHighlightUpdate = true;
117
data.Document.MimeType = mimeTypeChain.First ();
118
data.Document.FileName = "toformat.cs";
119
var textPolicy = policyParent.Get<TextStylePolicy> (mimeTypeChain);
120
data.Options.TabsToSpaces = textPolicy.TabsToSpaces;
121
data.Options.TabSize = textPolicy.TabWidth;
122
data.Options.OverrideDocumentEolMarker = true;
123
data.Options.DefaultEolMarker = textPolicy.GetEolMarker ();
126
// System.Console.WriteLine ("-----");
127
// System.Console.WriteLine (data.Text.Replace (" ", ".").Replace ("\t", "->"));
128
// System.Console.WriteLine ("-----");
130
MonoDevelop.CSharp.Parser.CSharpParser parser = new MonoDevelop.CSharp.Parser.CSharpParser ();
131
var compilationUnit = parser.Parse (data);
132
bool hadErrors = parser.HasErrors;
134
// foreach (var e in parser.ErrorReportPrinter.Errors)
135
// Console.WriteLine (e.Message);
136
return input.Substring (startOffset, Math.Max (0, Math.Min (endOffset, input.Length) - startOffset));
138
var policy = policyParent.Get<CSharpFormattingPolicy> (mimeTypeChain);
140
var formattingVisitor = new AstFormattingVisitor (policy, data) {
141
AutoAcceptChanges = false,
142
HadErrors = hadErrors
144
compilationUnit.AcceptVisitor (formattingVisitor, null);
146
var changes = new List<Change> ();
148
changes.AddRange (formattingVisitor.Changes.
149
Where (c => c is TextReplaceChange && (startOffset <= ((TextReplaceChange)c).Offset && ((TextReplaceChange)c).Offset < endOffset)));
151
RefactoringService.AcceptChanges (null, null, changes);
153
foreach (TextReplaceChange c in changes) {
154
end -= c.RemovedChars;
155
if (c.InsertedText != null)
156
end += c.InsertedText.Length;
158
/* System.Console.WriteLine ("-----");
159
System.Console.WriteLine (data.Text.Replace (" ", "^").Replace ("\t", "->"));
160
System.Console.WriteLine ("-----");*/
161
string result = data.GetTextBetween (startOffset, Math.Min (data.Length, end));