2
// CodeGenerationService.cs
5
// mkrueger <mkrueger@novell.com>
7
// Copyright (c) 2011 Novell, Inc (http://www.novell.com)
9
// Permission is hereby granted, free of charge, to any person obtaining a copy
10
// of this software and associated documentation files (the "Software"), to deal
11
// in the Software without restriction, including without limitation the rights
12
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13
// copies of the Software, and to permit persons to whom the Software is
14
// furnished to do so, subject to the following conditions:
16
// The above copyright notice and this permission notice shall be included in
17
// all copies or substantial portions of the Software.
19
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
27
using System.Collections.Generic;
31
using Mono.TextEditor;
32
using MonoDevelop.Core;
33
using MonoDevelop.Projects.Dom;
34
using MonoDevelop.Projects.Dom.Parser;
36
using MonoDevelop.Projects;
37
using System.CodeDom.Compiler;
39
namespace MonoDevelop.Ide
41
public class CodeGenerationService
43
public static IMember AddCodeDomMember (IType type, CodeTypeMember newMember)
46
var data = TextFileProvider.Instance.GetTextEditorData (type.CompilationUnit.FileName, out isOpen);
47
var parsedDocument = ProjectDomService.GetParsedDocument (type.SourceProjectDom, type.CompilationUnit.FileName);
49
var insertionPoints = GetInsertionPoints (data, parsedDocument, type);
51
var suitableInsertionPoint = GetSuitableInsertionPoint (insertionPoints, type, newMember);
53
var dotNetProject = type.SourceProject as DotNetProject;
54
if (dotNetProject == null) {
55
LoggingService.LogError ("Only .NET projects are supported.");
59
var generator = dotNetProject.LanguageBinding.GetCodeDomProvider ();
60
StringWriter sw = new StringWriter ();
61
var options = new CodeGeneratorOptions ();
62
options.IndentString = data.GetLineIndent (type.Location.Line) + "\t";
63
if (newMember is CodeMemberMethod)
64
options.BracingStyle = "C";
65
generator.GenerateCodeFromMember (newMember, sw, options);
67
suitableInsertionPoint.Insert (data, sw.ToString ());
70
File.WriteAllText (type.CompilationUnit.FileName, data.Text);
71
} catch (Exception e) {
72
LoggingService.LogError (string.Format ("Failed to write file '{0}'.", type.CompilationUnit.FileName), e);
73
MessageService.ShowError (GettextCatalog.GetString ("Failed to write file '{0}'.", type.CompilationUnit.FileName));
76
var newDocument = ProjectDomService.Parse (type.SourceProject as Project, type.CompilationUnit.FileName, data.Text);
77
return newDocument.CompilationUnit.GetMemberAt (suitableInsertionPoint.Location.Line, int.MaxValue);
80
public static void AddNewMember (IType type, IMember newMember, bool implementExplicit = false)
83
var data = TextFileProvider.Instance.GetTextEditorData (type.CompilationUnit.FileName, out isOpen);
84
var parsedDocument = ProjectDomService.GetParsedDocument (type.SourceProjectDom, type.CompilationUnit.FileName);
86
var insertionPoints = GetInsertionPoints (data, parsedDocument, type);
88
var suitableInsertionPoint = GetSuitableInsertionPoint (insertionPoints, type, newMember);
90
var generator = CreateCodeGenerator (data);
92
generator.IndentLevel = CalculateBodyIndentLevel (parsedDocument.CompilationUnit.GetTypeAt (type.Location));
93
var generatedCode = generator.CreateMemberImplementation (type, newMember, implementExplicit);
94
suitableInsertionPoint.Insert (data, generatedCode.Code);
97
File.WriteAllText (type.CompilationUnit.FileName, data.Text);
98
} catch (Exception e) {
99
LoggingService.LogError (GettextCatalog.GetString ("Failed to write file '{0}'.", type.CompilationUnit.FileName), e);
100
MessageService.ShowError (GettextCatalog.GetString ("Failed to write file '{0}'.", type.CompilationUnit.FileName));
105
public static int CalculateBodyIndentLevel (IType declaringType)
108
IType t = declaringType;
113
DomLocation lastLoc = DomLocation.Empty;
114
foreach (IUsing us in declaringType.CompilationUnit.Usings.Where (u => u.IsFromNamespace && u.ValidRegion.Contains (declaringType.Location))) {
115
if (lastLoc == us.Region.Start)
117
lastLoc = us.Region.Start;
123
public static void AddNewMembers (IType type, IEnumerable<IMember> newMembers, string regionName = null, Func<IMember, bool> implementExplicit = null)
125
IMember firstNewMember = newMembers.FirstOrDefault ();
126
if (firstNewMember == null)
129
var data = TextFileProvider.Instance.GetTextEditorData (type.CompilationUnit.FileName, out isOpen);
130
var parsedDocument = ProjectDomService.GetParsedDocument (type.SourceProjectDom, type.CompilationUnit.FileName);
132
var insertionPoints = GetInsertionPoints (data, parsedDocument, type);
135
var suitableInsertionPoint = GetSuitableInsertionPoint (insertionPoints, type, firstNewMember);
137
var generator = CreateCodeGenerator (data);
138
generator.IndentLevel = CalculateBodyIndentLevel (parsedDocument.CompilationUnit.GetTypeAt (type.Location));
139
StringBuilder sb = new StringBuilder ();
140
foreach (IMember newMember in newMembers) {
145
sb.Append (generator.CreateMemberImplementation (type, newMember, implementExplicit != null ? implementExplicit (newMember) : false).Code);
147
suitableInsertionPoint.Insert (data, string.IsNullOrEmpty (regionName) ? sb.ToString () : generator.WrapInRegions (regionName, sb.ToString ()));
150
File.WriteAllText (type.CompilationUnit.FileName, data.Text);
151
} catch (Exception e) {
152
LoggingService.LogError (GettextCatalog.GetString ("Failed to write file '{0}'.", type.CompilationUnit.FileName), e);
153
MessageService.ShowError (GettextCatalog.GetString ("Failed to write file '{0}'.", type.CompilationUnit.FileName));
158
static MonoDevelop.Projects.CodeGeneration.CodeGenerator CreateCodeGenerator (TextEditorData data)
160
return MonoDevelop.Projects.CodeGeneration.CodeGenerator.CreateGenerator (data.Document.MimeType,
161
data.Options.TabsToSpaces, data.Options.TabSize, data.EolMarker);
164
protected static IType GetMainPart (IType t)
166
return t.HasParts ? t.Parts.First () : t;
169
#region Insertion Points
170
public static List<InsertionPoint> GetInsertionPoints (MonoDevelop.Ide.Gui.Document document, IType type)
172
if (document == null)
173
throw new ArgumentNullException ("document");
174
return GetInsertionPoints (document.Editor, document.ParsedDocument, type);
177
public static List<InsertionPoint> GetInsertionPoints (TextEditorData data, ParsedDocument parsedDocument, IType type)
180
throw new ArgumentNullException ("data");
181
if (parsedDocument == null)
182
throw new ArgumentNullException ("parsedDocument");
184
throw new ArgumentNullException ("type");
186
// update type from parsed document, since this is always newer.
187
type = parsedDocument.CompilationUnit.GetTypeAt (type.Location) ?? type;
189
List<InsertionPoint> result = new List<InsertionPoint> ();
190
int offset = data.LocationToOffset (type.BodyRegion.Start.Line, type.BodyRegion.Start.Column);
193
while (offset < data.Length && data.GetCharAt (offset) != '{') {
197
var realStartLocation = data.OffsetToLocation (offset);
198
result.Add (GetInsertionPosition (data.Document, realStartLocation.Line, realStartLocation.Column));
199
result[0].LineBefore = NewLineInsertion.None;
200
foreach (IMember member in type.Members) {
201
DomLocation domLocation = member.BodyRegion.End;
202
if (domLocation.Line <= 0) {
203
LineSegment lineSegment = data.GetLine (member.Location.Line);
204
if (lineSegment == null)
206
domLocation = new DomLocation (member.Location.Line, lineSegment.EditableLength + 1);
208
result.Add (GetInsertionPosition (data.Document, domLocation.Line, domLocation.Column));
210
result[result.Count - 1].LineAfter = NewLineInsertion.None;
211
CheckStartPoint (data.Document, result[0], result.Count == 1);
212
if (result.Count > 1) {
213
result.RemoveAt (result.Count - 1);
214
NewLineInsertion insertLine;
215
var lineBefore = data.GetLine (type.BodyRegion.End.Line - 1);
216
if (lineBefore != null && lineBefore.EditableLength == lineBefore.GetIndentation (data.Document).Length) {
217
insertLine = NewLineInsertion.None;
219
insertLine = NewLineInsertion.Eol;
221
// search for line start
222
var line = data.GetLine (type.BodyRegion.End.Line);
223
int col = type.BodyRegion.End.Column - 1;
224
while (col > 1 && char.IsWhiteSpace (data.GetCharAt (line.Offset + col - 2)))
226
result.Add (new InsertionPoint (new DocumentLocation (type.BodyRegion.End.Line, col), insertLine, NewLineInsertion.Eol));
227
CheckEndPoint (data.Document, result[result.Count - 1], result.Count == 1);
230
foreach (var region in parsedDocument.UserRegions.Where (r => type.BodyRegion.Contains (r.Region))) {
231
result.Add (new InsertionPoint (new DocumentLocation (region.Region.Start.Line + 1, 1), NewLineInsertion.Eol, NewLineInsertion.Eol));
232
result.Add (new InsertionPoint (new DocumentLocation (region.Region.End.Line, 1), NewLineInsertion.Eol, NewLineInsertion.Eol));
233
result.Add (new InsertionPoint (new DocumentLocation (region.Region.End.Line + 1, 1), NewLineInsertion.Eol, NewLineInsertion.Eol));
235
result.Sort ((left, right) => left.Location.CompareTo (right.Location));
239
static void CheckEndPoint (Document doc, InsertionPoint point, bool isStartPoint)
241
LineSegment line = doc.GetLine (point.Location.Line);
245
if (doc.GetLineIndent (line).Length + 1 < point.Location.Column)
246
point.LineBefore = NewLineInsertion.BlankLine;
247
if (point.Location.Column < line.EditableLength + 1)
248
point.LineAfter = NewLineInsertion.Eol;
251
static void CheckStartPoint (Document doc, InsertionPoint point, bool isEndPoint)
253
LineSegment line = doc.GetLine (point.Location.Line);
256
if (doc.GetLineIndent (line).Length + 1 == point.Location.Column) {
257
int lineNr = point.Location.Line;
258
while (lineNr > 1 && doc.GetLineIndent (lineNr - 1).Length == doc.GetLine (lineNr - 1).EditableLength) {
261
line = doc.GetLine (lineNr);
262
point.Location = new DocumentLocation (lineNr, doc.GetLineIndent (line).Length + 1);
265
if (doc.GetLineIndent (line).Length + 1 < point.Location.Column)
266
point.LineBefore = NewLineInsertion.Eol;
267
if (point.Location.Column < line.EditableLength + 1)
268
point.LineAfter = isEndPoint ? NewLineInsertion.Eol : NewLineInsertion.BlankLine;
271
static InsertionPoint GetInsertionPosition (Document doc, int line, int column)
273
int bodyEndOffset = doc.LocationToOffset (line, column) + 1;
274
LineSegment curLine = doc.GetLine (line);
275
if (curLine != null) {
276
if (bodyEndOffset < curLine.Offset + curLine.EditableLength) {
277
// case1: positition is somewhere inside the start line
278
return new InsertionPoint (new DocumentLocation (line, column + 1), NewLineInsertion.Eol, NewLineInsertion.BlankLine);
282
// -> if position is at line end check next line
283
LineSegment nextLine = doc.GetLine (line + 1);
284
if (nextLine == null) // check for 1 line case.
285
return new InsertionPoint (new DocumentLocation (line, column + 1), NewLineInsertion.BlankLine, NewLineInsertion.BlankLine);
287
for (int i = nextLine.Offset; i < nextLine.Offset + nextLine.EditableLength; i++) {
288
char ch = doc.GetCharAt (i);
289
if (!char.IsWhiteSpace (ch)) {
290
// case2: next line contains non ws chars.
291
return new InsertionPoint (new DocumentLocation (line + 1, 1), NewLineInsertion.Eol, NewLineInsertion.BlankLine);
294
// case3: whitespace line
295
return new InsertionPoint (new DocumentLocation (line + 1, 1), NewLineInsertion.Eol, NewLineInsertion.None);
298
static InsertionPoint GetSuitableInsertionPoint (IEnumerable<InsertionPoint> points, IType cls, IMember member)
300
var mainPart = GetMainPart (cls);
301
switch (member.MemberType) {
302
case MemberType.Field:
303
return GetNewFieldPosition (points, mainPart);
304
case MemberType.Method:
305
return GetNewMethodPosition (points, mainPart);
306
case MemberType.Event:
307
return GetNewEventPosition (points, mainPart);
308
case MemberType.Property:
309
return GetNewPropertyPosition (points, mainPart);
311
throw new InvalidOperationException ("Invalid member type: " + member.MemberType);
314
static InsertionPoint GetSuitableInsertionPoint (IEnumerable<InsertionPoint> points, IType cls, CodeTypeMember mem)
316
var mainPart = GetMainPart (cls);
317
if (mem is System.CodeDom.CodeMemberEvent)
318
return GetNewEventPosition (points, mainPart);
319
if (mem is System.CodeDom.CodeMemberProperty)
320
return GetNewPropertyPosition (points, mainPart);
321
if (mem is System.CodeDom.CodeMemberField)
322
return GetNewFieldPosition (points, mainPart);
323
if (mem is System.CodeDom.CodeMemberMethod)
324
return GetNewMethodPosition (points, mainPart);
325
return GetNewFieldPosition (points, mainPart);
328
static InsertionPoint GetNewFieldPosition (IEnumerable<InsertionPoint> points, IType cls)
330
if (cls.FieldCount == 0)
331
return points.FirstOrDefault ();
332
var lastField = cls.Fields.Last ();
333
return points.FirstOrDefault (p => p.Location.Convert () > lastField.Location);
336
static InsertionPoint GetNewMethodPosition (IEnumerable<InsertionPoint> points, IType cls)
338
if (cls.MethodCount + cls.ConstructorCount == 0)
339
return GetNewPropertyPosition (points, cls);
340
var lastMember = cls.Members.Last ();
341
return points.FirstOrDefault (p => p.Location.Convert () > lastMember.Location);
344
static InsertionPoint GetNewPropertyPosition (IEnumerable<InsertionPoint> points, IType cls)
346
if (cls.PropertyCount == 0)
347
return GetNewFieldPosition (points, cls);
348
IProperty lastProperty = cls.Properties.Last ();
349
return points.FirstOrDefault (p => p.Location.Convert () > lastProperty.Location);
352
static InsertionPoint GetNewEventPosition (IEnumerable<InsertionPoint> points, IType cls)
354
if (cls.EventCount == 0)
355
return GetNewMethodPosition (points, cls);
356
IEvent lastEvent = cls.Events.Last ();
357
return points.FirstOrDefault (p => p.Location.Convert () > lastEvent.Location);
362
public static class HelperMethods
364
public static DomLocation Convert (this DocumentLocation location)
366
return new DomLocation (location.Line, location.Column);