~ubuntu-branches/ubuntu/oneiric/monodevelop/oneiric

« back to all changes in this revision

Viewing changes to src/core/MonoDevelop.Ide/MonoDevelop.Ide/CodeGenerationService.cs

  • Committer: Bazaar Package Importer
  • Author(s): Jo Shields
  • Date: 2011-06-27 17:03:13 UTC
  • mto: (1.8.1 upstream)
  • mto: This revision was merged to the branch mainline in revision 54.
  • Revision ID: james.westby@ubuntu.com-20110627170313-6cvz3s19x6e9hqe9
ImportĀ upstreamĀ versionĀ 2.5.92+dfsg

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
// 
 
2
// CodeGenerationService.cs
 
3
//  
 
4
// Author:
 
5
//       mkrueger <mkrueger@novell.com>
 
6
// 
 
7
// Copyright (c) 2011 Novell, Inc (http://www.novell.com)
 
8
// 
 
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:
 
15
// 
 
16
// The above copyright notice and this permission notice shall be included in
 
17
// all copies or substantial portions of the Software.
 
18
// 
 
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
 
25
// THE SOFTWARE.
 
26
using System;
 
27
using System.Collections.Generic;
 
28
using System.IO;
 
29
using System.Linq;
 
30
using System.Text;
 
31
using Mono.TextEditor;
 
32
using MonoDevelop.Core;
 
33
using MonoDevelop.Projects.Dom;
 
34
using MonoDevelop.Projects.Dom.Parser;
 
35
using System.CodeDom;
 
36
using MonoDevelop.Projects;
 
37
using System.CodeDom.Compiler;
 
38
 
 
39
namespace MonoDevelop.Ide
 
40
{
 
41
        public class CodeGenerationService
 
42
        {
 
43
                public static IMember AddCodeDomMember (IType type, CodeTypeMember newMember)
 
44
                {
 
45
                        bool isOpen;
 
46
                        var data = TextFileProvider.Instance.GetTextEditorData (type.CompilationUnit.FileName, out isOpen);
 
47
                        var parsedDocument = ProjectDomService.GetParsedDocument (type.SourceProjectDom, type.CompilationUnit.FileName);
 
48
                        
 
49
                        var insertionPoints = GetInsertionPoints (data, parsedDocument, type);
 
50
                        
 
51
                        var suitableInsertionPoint = GetSuitableInsertionPoint (insertionPoints, type, newMember);
 
52
                        
 
53
                        var dotNetProject = type.SourceProject as DotNetProject;
 
54
                        if (dotNetProject == null) {
 
55
                                LoggingService.LogError ("Only .NET projects are supported.");
 
56
                                return null;
 
57
                        }
 
58
                        
 
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);
 
66
                        
 
67
                        suitableInsertionPoint.Insert (data, sw.ToString ());
 
68
                        if (!isOpen) {
 
69
                                try {
 
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));
 
74
                                }
 
75
                        }
 
76
                        var newDocument = ProjectDomService.Parse (type.SourceProject as Project, type.CompilationUnit.FileName, data.Text);
 
77
                        return newDocument.CompilationUnit.GetMemberAt (suitableInsertionPoint.Location.Line, int.MaxValue);
 
78
                }
 
79
                
 
80
                public static void AddNewMember (IType type, IMember newMember, bool implementExplicit = false)
 
81
                {
 
82
                        bool isOpen;
 
83
                        var data = TextFileProvider.Instance.GetTextEditorData (type.CompilationUnit.FileName, out isOpen);
 
84
                        var parsedDocument = ProjectDomService.GetParsedDocument (type.SourceProjectDom, type.CompilationUnit.FileName);
 
85
                        
 
86
                        var insertionPoints = GetInsertionPoints (data, parsedDocument, type);
 
87
                        
 
88
                        var suitableInsertionPoint = GetSuitableInsertionPoint (insertionPoints, type, newMember);
 
89
                        
 
90
                        var generator = CreateCodeGenerator (data);
 
91
 
 
92
                        generator.IndentLevel = CalculateBodyIndentLevel (parsedDocument.CompilationUnit.GetTypeAt (type.Location));
 
93
                        var generatedCode = generator.CreateMemberImplementation (type, newMember, implementExplicit);
 
94
                        suitableInsertionPoint.Insert (data, generatedCode.Code);
 
95
                        if (!isOpen) {
 
96
                                try {
 
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));
 
101
                                }
 
102
                        }
 
103
                }
 
104
                
 
105
                public static int CalculateBodyIndentLevel (IType declaringType)
 
106
                {
 
107
                        int indentLevel = 0;
 
108
                        IType t = declaringType;
 
109
                        do {
 
110
                                indentLevel++;
 
111
                                t = t.DeclaringType;
 
112
                        } while (t != null);
 
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)
 
116
                                        continue;
 
117
                                lastLoc = us.Region.Start;
 
118
                                indentLevel++;
 
119
                        }
 
120
                        return indentLevel;
 
121
                }
 
122
                
 
123
                public static void AddNewMembers (IType type, IEnumerable<IMember> newMembers, string regionName = null, Func<IMember, bool> implementExplicit = null)
 
124
                {
 
125
                        IMember firstNewMember = newMembers.FirstOrDefault ();
 
126
                        if (firstNewMember == null)
 
127
                                return;
 
128
                        bool isOpen;
 
129
                        var data = TextFileProvider.Instance.GetTextEditorData (type.CompilationUnit.FileName, out isOpen);
 
130
                        var parsedDocument = ProjectDomService.GetParsedDocument (type.SourceProjectDom, type.CompilationUnit.FileName);
 
131
                        
 
132
                        var insertionPoints = GetInsertionPoints (data, parsedDocument, type);
 
133
                        
 
134
                        
 
135
                        var suitableInsertionPoint = GetSuitableInsertionPoint (insertionPoints, type, firstNewMember);
 
136
                        
 
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) {
 
141
                                if (sb.Length > 0) {
 
142
                                        sb.AppendLine ();
 
143
                                        sb.AppendLine ();
 
144
                                }
 
145
                                sb.Append (generator.CreateMemberImplementation (type, newMember, implementExplicit != null ? implementExplicit (newMember) : false).Code);
 
146
                        }
 
147
                        suitableInsertionPoint.Insert (data, string.IsNullOrEmpty (regionName) ? sb.ToString () : generator.WrapInRegions (regionName, sb.ToString ()));
 
148
                        if (!isOpen) {
 
149
                                try {
 
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));
 
154
                                }
 
155
                        }
 
156
                }
 
157
                
 
158
                static MonoDevelop.Projects.CodeGeneration.CodeGenerator CreateCodeGenerator (TextEditorData data)
 
159
                {
 
160
                        return MonoDevelop.Projects.CodeGeneration.CodeGenerator.CreateGenerator (data.Document.MimeType, 
 
161
                                data.Options.TabsToSpaces, data.Options.TabSize, data.EolMarker);
 
162
                }
 
163
                
 
164
                protected static IType GetMainPart (IType t)
 
165
                {
 
166
                        return t.HasParts ? t.Parts.First () : t;
 
167
                }
 
168
                
 
169
                #region Insertion Points
 
170
                public static List<InsertionPoint> GetInsertionPoints (MonoDevelop.Ide.Gui.Document document, IType type)
 
171
                {
 
172
                        if (document == null)
 
173
                                throw new ArgumentNullException ("document");
 
174
                        return GetInsertionPoints (document.Editor, document.ParsedDocument, type);
 
175
                }
 
176
                
 
177
                public static List<InsertionPoint> GetInsertionPoints (TextEditorData data, ParsedDocument parsedDocument, IType type)
 
178
                {
 
179
                        if (data == null)
 
180
                                throw new ArgumentNullException ("data");
 
181
                        if (parsedDocument == null)
 
182
                                throw new ArgumentNullException ("parsedDocument");
 
183
                        if (type == null)
 
184
                                throw new ArgumentNullException ("type");
 
185
                        
 
186
                        // update type from parsed document, since this is always newer.
 
187
                        type = parsedDocument.CompilationUnit.GetTypeAt (type.Location) ?? type;
 
188
                        
 
189
                        List<InsertionPoint> result = new List<InsertionPoint> ();
 
190
                        int offset = data.LocationToOffset (type.BodyRegion.Start.Line, type.BodyRegion.Start.Column);
 
191
                        if (offset < 0)
 
192
                                return result;
 
193
                        while (offset < data.Length && data.GetCharAt (offset) != '{') {
 
194
                                offset++;
 
195
                        }
 
196
                        
 
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)
 
205
                                                continue;
 
206
                                        domLocation = new DomLocation (member.Location.Line, lineSegment.EditableLength + 1);
 
207
                                }
 
208
                                result.Add (GetInsertionPosition (data.Document, domLocation.Line, domLocation.Column));
 
209
                        }
 
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;
 
218
                                } else {
 
219
                                        insertLine = NewLineInsertion.Eol;
 
220
                                }
 
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)))
 
225
                                        col--;
 
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);
 
228
                        }
 
229
                        
 
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));
 
234
                        }
 
235
                        result.Sort ((left, right) => left.Location.CompareTo (right.Location));
 
236
                        return result;
 
237
                }
 
238
 
 
239
                static void CheckEndPoint (Document doc, InsertionPoint point, bool isStartPoint)
 
240
                {
 
241
                        LineSegment line = doc.GetLine (point.Location.Line);
 
242
                        if (line == null)
 
243
                                return;
 
244
                        
 
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;
 
249
                }
 
250
                
 
251
                static void CheckStartPoint (Document doc, InsertionPoint point, bool isEndPoint)
 
252
                {
 
253
                        LineSegment line = doc.GetLine (point.Location.Line);
 
254
                        if (line == null)
 
255
                                return;
 
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) {
 
259
                                        lineNr--;
 
260
                                }
 
261
                                line = doc.GetLine (lineNr);
 
262
                                point.Location = new DocumentLocation (lineNr, doc.GetLineIndent (line).Length + 1);
 
263
                        }
 
264
                        
 
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;
 
269
                }
 
270
                
 
271
                static InsertionPoint GetInsertionPosition (Document doc, int line, int column)
 
272
                {
 
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);
 
279
                                }
 
280
                        }
 
281
                        
 
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);
 
286
                        
 
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);
 
292
                                }
 
293
                        }
 
294
                        // case3: whitespace line
 
295
                        return new InsertionPoint (new DocumentLocation (line + 1, 1), NewLineInsertion.Eol, NewLineInsertion.None);
 
296
                }
 
297
                
 
298
                static InsertionPoint GetSuitableInsertionPoint (IEnumerable<InsertionPoint> points, IType cls, IMember member)
 
299
                {
 
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);
 
310
                        }
 
311
                        throw new InvalidOperationException ("Invalid member type: " + member.MemberType);
 
312
                }
 
313
                
 
314
                static InsertionPoint GetSuitableInsertionPoint (IEnumerable<InsertionPoint> points, IType cls, CodeTypeMember mem)
 
315
                {
 
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);
 
326
                }
 
327
                
 
328
                static InsertionPoint GetNewFieldPosition (IEnumerable<InsertionPoint> points, IType cls)
 
329
                {
 
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);
 
334
                }
 
335
                
 
336
                static InsertionPoint GetNewMethodPosition (IEnumerable<InsertionPoint> points, IType cls)
 
337
                {
 
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);
 
342
                }
 
343
                
 
344
                static InsertionPoint GetNewPropertyPosition (IEnumerable<InsertionPoint> points, IType cls)
 
345
                {
 
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);
 
350
                }
 
351
                
 
352
                static InsertionPoint GetNewEventPosition (IEnumerable<InsertionPoint> points, IType cls)
 
353
                {
 
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);
 
358
                }
 
359
                #endregion
 
360
        }
 
361
        
 
362
        public static class HelperMethods
 
363
        {
 
364
                public static DomLocation Convert (this DocumentLocation location)
 
365
                {
 
366
                        return new DomLocation (location.Line, location.Column);
 
367
                }
 
368
        }
 
369
}