14 changed files with 424 additions and 59 deletions
@ -0,0 +1,329 @@
@@ -0,0 +1,329 @@
|
||||
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
|
||||
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
|
||||
|
||||
using System; |
||||
using System.CodeDom; |
||||
using System.CodeDom.Compiler; |
||||
using System.Collections.Generic; |
||||
using System.ComponentModel; |
||||
using System.Diagnostics; |
||||
using System.IO; |
||||
using System.Linq; |
||||
using ICSharpCode.Core; |
||||
using ICSharpCode.FormsDesigner; |
||||
using ICSharpCode.NRefactory; |
||||
using ICSharpCode.NRefactory.CSharp; |
||||
using ICSharpCode.NRefactory.CSharp.Refactoring; |
||||
using ICSharpCode.NRefactory.Editor; |
||||
using ICSharpCode.NRefactory.TypeSystem; |
||||
using ICSharpCode.SharpDevelop; |
||||
using ICSharpCode.SharpDevelop.Editor; |
||||
using ICSharpCode.SharpDevelop.Project; |
||||
using Microsoft.CSharp; |
||||
using CSharpBinding.Parser; |
||||
using CSharpBinding.Refactoring; |
||||
|
||||
namespace CSharpBinding.FormsDesigner |
||||
{ |
||||
public class CSharpDesignerGenerator |
||||
{ |
||||
readonly CSharpFullParseInformation primaryParseInfo; |
||||
readonly ICSharpDesignerLoaderContext context; |
||||
readonly ICompilation compilation; |
||||
readonly IUnresolvedTypeDefinition primaryPart; |
||||
readonly ITypeDefinition formClass; |
||||
readonly IMethod initializeComponents; |
||||
|
||||
public CSharpDesignerGenerator(ICSharpDesignerLoaderContext context) |
||||
{ |
||||
this.context = context; |
||||
this.primaryParseInfo = context.GetPrimaryFileParseInformation(); |
||||
this.compilation = context.GetCompilation(); |
||||
|
||||
// Find designer class
|
||||
formClass = FormsDesignerSecondaryDisplayBinding.GetDesignableClass(primaryParseInfo.UnresolvedFile, compilation, out primaryPart); |
||||
initializeComponents = FormsDesignerSecondaryDisplayBinding.GetInitializeComponents(formClass); |
||||
if (initializeComponents == null) |
||||
throw new FormsDesignerLoadException("Could not find InitializeComponents"); |
||||
} |
||||
|
||||
public void MergeFormChanges(CodeCompileUnit codeUnit) |
||||
{ |
||||
var codeNamespace = codeUnit.Namespaces.Cast<CodeNamespace>().Single(); |
||||
var codeClass = codeNamespace.Types.Cast<CodeTypeDeclaration>().Single(); |
||||
var codeMethod = codeClass.Members.OfType<CodeMemberMethod>().Single(m => m.Name == "InitializeComponent"); |
||||
var codeFields = codeClass.Members.OfType<CodeMemberField>().ToList(); |
||||
RemoveUnsupportedCode(codeClass, codeMethod); |
||||
|
||||
SaveInitializeComponents(codeMethod); |
||||
MergeFields(codeFields); |
||||
ApplyScripts(); |
||||
} |
||||
|
||||
#region RemoveUnsupportedCode
|
||||
/// <summary>
|
||||
/// This method solves all problems that are caused if the designer generates
|
||||
/// code that is not supported in a previous version of .NET. (3.5 and below)
|
||||
/// Currently it fixes:
|
||||
/// - remove calls to ISupportInitialize.BeginInit/EndInit, if the interface is not implemented by the type in the target framework.
|
||||
/// </summary>
|
||||
/// <remarks>When adding new workarounds make sure that the code does not remove too much code!</remarks>
|
||||
void RemoveUnsupportedCode(CodeTypeDeclaration codeClass, CodeMemberMethod initializeComponent) |
||||
{ |
||||
if (compilation.GetProject() is MSBuildBasedProject) { |
||||
MSBuildBasedProject p = (MSBuildBasedProject)compilation.GetProject(); |
||||
string v = (p.GetEvaluatedProperty("TargetFrameworkVersion") ?? "").Trim('v'); |
||||
Version version; |
||||
if (!Version.TryParse(v, out version) || version.Major >= 4) |
||||
return; |
||||
} |
||||
|
||||
List<CodeStatement> stmtsToRemove = new List<CodeStatement>(); |
||||
var iSupportInitializeInterface = compilation.FindType(typeof(ISupportInitialize)).GetDefinition(); |
||||
|
||||
if (iSupportInitializeInterface == null) |
||||
return; |
||||
|
||||
foreach (var stmt in initializeComponent.Statements.OfType<CodeExpressionStatement>().Where(ces => ces.Expression is CodeMethodInvokeExpression)) { |
||||
CodeMethodInvokeExpression invocation = (CodeMethodInvokeExpression)stmt.Expression; |
||||
CodeCastExpression expr = invocation.Method.TargetObject as CodeCastExpression; |
||||
if (expr != null) { |
||||
if (expr.TargetType.BaseType != "System.ComponentModel.ISupportInitialize") |
||||
continue; |
||||
var fieldType = GetTypeOfControl(expr.Expression, initializeComponent, codeClass).GetDefinition(); |
||||
if (fieldType == null) |
||||
continue; |
||||
if (!fieldType.IsDerivedFrom(iSupportInitializeInterface)) |
||||
stmtsToRemove.Add(stmt); |
||||
} |
||||
} |
||||
|
||||
foreach (var stmt in stmtsToRemove) { |
||||
initializeComponent.Statements.Remove(stmt); |
||||
} |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Tries to find the type of the expression.
|
||||
/// </summary>
|
||||
IType GetTypeOfControl(CodeExpression expression, CodeMemberMethod initializeComponentMethod, CodeTypeDeclaration formTypeDeclaration) |
||||
{ |
||||
if (expression is CodeVariableReferenceExpression) { |
||||
string name = ((CodeVariableReferenceExpression)expression).VariableName; |
||||
var decl = initializeComponentMethod.Statements.OfType<CodeVariableDeclarationStatement>().Single(v => v.Name == name); |
||||
return ReflectionHelper.ParseReflectionName(decl.Type.BaseType).Resolve(compilation); |
||||
} |
||||
if (expression is CodeFieldReferenceExpression && ((CodeFieldReferenceExpression)expression).TargetObject is CodeThisReferenceExpression) { |
||||
string name = ((CodeFieldReferenceExpression)expression).FieldName; |
||||
var decl = formTypeDeclaration.Members.OfType<CodeMemberField>().FirstOrDefault(f => name == f.Name); |
||||
if (decl != null) |
||||
return ReflectionHelper.ParseReflectionName(decl.Type.BaseType).Resolve(compilation); |
||||
var field = formClass.GetFields(f => f.Name == name).LastOrDefault(); |
||||
if (field == null) |
||||
return SpecialType.UnknownType; |
||||
return field.Type; |
||||
} |
||||
return SpecialType.UnknownType; |
||||
} |
||||
#endregion
|
||||
|
||||
#region Script management
|
||||
Dictionary<FileName, DocumentScript> scripts = new Dictionary<FileName, DocumentScript>(); |
||||
|
||||
DocumentScript GetScript(string fileName) |
||||
{ |
||||
DocumentScript script; |
||||
var fileNameObj = FileName.Create(fileName); |
||||
if (scripts.TryGetValue(fileNameObj, out script)) |
||||
return script; |
||||
|
||||
IDocument document = context.GetDocument(fileNameObj); |
||||
var ctx = SDRefactoringContext.Create(fileNameObj, document); |
||||
script = new DocumentScript(document, FormattingOptionsFactory.CreateSharpDevelop(), new TextEditorOptions()); |
||||
scripts.Add(fileNameObj, script); |
||||
return script; |
||||
} |
||||
|
||||
void ApplyScripts() |
||||
{ |
||||
foreach (var pair in scripts) { |
||||
var script = pair.Value; |
||||
IDocument newDocument = script.CurrentDocument; |
||||
script.Dispose(); |
||||
SD.ParserService.ParseFileAsync(pair.Key, newDocument).FireAndForget(); |
||||
Debug.Assert(FileName.Create(newDocument.FileName) == pair.Key); |
||||
} |
||||
scripts.Clear(); |
||||
} |
||||
#endregion
|
||||
|
||||
#region SaveInitializeComponents
|
||||
void SaveInitializeComponents(CodeMemberMethod codeMethod) |
||||
{ |
||||
var bodyRegion = initializeComponents.BodyRegion; |
||||
DocumentScript script = GetScript(bodyRegion.FileName); |
||||
|
||||
string newline = DocumentUtilities.GetLineTerminator(script.OriginalDocument, bodyRegion.BeginLine); |
||||
string indentation = DocumentUtilities.GetIndentation(script.OriginalDocument, bodyRegion.BeginLine); |
||||
string code = "{" + newline + GenerateInitializeComponents(codeMethod, indentation, newline) + indentation + "}"; |
||||
|
||||
int startOffset = script.GetCurrentOffset(bodyRegion.Begin); |
||||
int endOffset = script.GetCurrentOffset(bodyRegion.End); |
||||
script.Replace(startOffset, endOffset - startOffset, code); |
||||
} |
||||
|
||||
string GenerateInitializeComponents(CodeMemberMethod codeMethod, string indentation, string newline) |
||||
{ |
||||
var writer = new StringWriter(); |
||||
writer.NewLine = newline; |
||||
var options = new CodeGeneratorOptions(); |
||||
options.IndentString = SD.EditorControlService.GlobalOptions.IndentationString; |
||||
var codeProvider = new CSharpCodeProvider(); |
||||
foreach (CodeStatement statement in codeMethod.Statements) { |
||||
writer.Write(indentation); |
||||
// indentation isn't generated when calling GenerateCodeFromStatement
|
||||
writer.Write(options.IndentString); |
||||
try { |
||||
codeProvider.GenerateCodeFromStatement(statement, writer, options); |
||||
} catch (Exception e) { |
||||
writer.WriteLine("// TODO: Error while generating statement : " + e.Message); |
||||
SD.Log.Error(e); |
||||
} |
||||
} |
||||
|
||||
return writer.ToString(); |
||||
} |
||||
#endregion
|
||||
|
||||
#region MergeFields
|
||||
void MergeFields(List<CodeMemberField> codeFields) |
||||
{ |
||||
// apply changes the designer made to field declarations
|
||||
// first loop looks for added and changed fields
|
||||
foreach (CodeMemberField newField in codeFields) { |
||||
IField oldField = formClass.Fields.FirstOrDefault(f => f.Name == newField.Name); |
||||
if (oldField == null) { |
||||
CreateField(newField); |
||||
} else if (FieldChanged(oldField, newField)) { |
||||
UpdateField(oldField, newField); |
||||
} |
||||
} |
||||
|
||||
// second loop looks for removed fields
|
||||
foreach (IField field in formClass.Fields) { |
||||
if (!codeFields.Any(f => f.Name == field.Name)) { |
||||
RemoveField(field); |
||||
} |
||||
} |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Compares the SharpDevelop.Dom field declaration oldField to
|
||||
/// the CodeDom field declaration newField.
|
||||
/// </summary>
|
||||
/// <returns>true, if the fields are different in type or modifiers, otherwise false.</returns>
|
||||
static bool FieldChanged(IField oldField, CodeMemberField newField) |
||||
{ |
||||
// compare types
|
||||
if (AreTypesDifferent(oldField.ReturnType, newField.Type)) { |
||||
SD.Log.Debug("FieldChanged (type): "+oldField.Name+", "+oldField.ReturnType.FullName+" -> "+newField.Type.BaseType); |
||||
return true; |
||||
} |
||||
|
||||
// compare accessibility modifiers
|
||||
Accessibility oldModifiers = oldField.Accessibility; |
||||
MemberAttributes newModifiers = newField.Attributes & MemberAttributes.AccessMask; |
||||
|
||||
// SharpDevelop.Dom always adds Private modifier, even if not specified
|
||||
// CodeDom omits Private modifier if not present (although it is the default)
|
||||
if (oldModifiers == Accessibility.Private) { |
||||
if (newModifiers != 0 && newModifiers != MemberAttributes.Private) { |
||||
return true; |
||||
} |
||||
} |
||||
|
||||
Accessibility[] sdModifiers = {Accessibility.Protected, Accessibility.ProtectedAndInternal, Accessibility.Internal, Accessibility.Public}; |
||||
MemberAttributes[] cdModifiers = {MemberAttributes.Family, MemberAttributes.FamilyOrAssembly, MemberAttributes.Assembly, MemberAttributes.Public}; |
||||
for (int i = 0; i < sdModifiers.Length; i++) { |
||||
if ((oldModifiers == sdModifiers[i]) ^ (newModifiers == cdModifiers[i])) { |
||||
return true; |
||||
} |
||||
} |
||||
|
||||
return false; |
||||
} |
||||
|
||||
static bool AreTypesDifferent(IType oldType, CodeTypeReference newType) |
||||
{ |
||||
IType oldClass = oldType.GetDefinition(); |
||||
if (oldClass == null) { |
||||
// ignore type changes to fields with unresolved type
|
||||
return false; |
||||
} |
||||
if (newType == null || newType.BaseType == "System.Void") { |
||||
// field types get replaced with System.Void if the type cannot be resolved
|
||||
// (e.g. generic fields in the Boo designer which aren't converted to CodeDom)
|
||||
// we'll ignore such type changes (fields should never have the type void)
|
||||
return false; |
||||
} |
||||
|
||||
return oldType.ReflectionName != newType.BaseType; |
||||
} |
||||
|
||||
string GenerateField(CodeMemberField newField) |
||||
{ |
||||
StringWriter writer = new StringWriter(); |
||||
var provider = new CSharpCodeProvider(); |
||||
provider.GenerateCodeFromMember(newField, writer, new CodeGeneratorOptions()); |
||||
return writer.ToString().Trim(); |
||||
} |
||||
|
||||
void CreateField(CodeMemberField newField) |
||||
{ |
||||
// insert new field below InitializeComponents()
|
||||
|
||||
var bodyRegion = initializeComponents.BodyRegion; |
||||
DocumentScript script = GetScript(bodyRegion.FileName); |
||||
string newline = DocumentUtilities.GetLineTerminator(script.OriginalDocument, bodyRegion.BeginLine); |
||||
string indentation = DocumentUtilities.GetIndentation(script.OriginalDocument, bodyRegion.BeginLine); |
||||
|
||||
var insertionLocation = new TextLocation(bodyRegion.EndLine + 1, 1); |
||||
int insertionOffset = script.GetCurrentOffset(insertionLocation); |
||||
string code = indentation + GenerateField(newField) + newline; |
||||
script.InsertText(insertionOffset, code); |
||||
} |
||||
|
||||
void UpdateField(IField oldField, CodeMemberField newField) |
||||
{ |
||||
DomRegion region = oldField.Region; |
||||
DocumentScript script = GetScript(region.FileName); |
||||
|
||||
int offset = script.GetCurrentOffset(region.Begin); |
||||
int endOffset = script.GetCurrentOffset(region.End); |
||||
string code = GenerateField(newField); |
||||
script.Replace(offset, endOffset - offset, code); |
||||
} |
||||
|
||||
void RemoveField(IField field) |
||||
{ |
||||
DomRegion region = field.Region; |
||||
DocumentScript script = GetScript(region.FileName); |
||||
int offset = script.GetCurrentOffset(region.Begin); |
||||
int endOffset = script.GetCurrentOffset(region.End); |
||||
IDocumentLine line = script.CurrentDocument.GetLineByOffset(endOffset); |
||||
if (endOffset == line.EndOffset) { |
||||
endOffset += line.DelimiterLength; // delete the whole line
|
||||
// delete indentation in front of the line
|
||||
while (offset > 0 && IsTabOrSpace(script.CurrentDocument.GetCharAt(offset - 1))) |
||||
offset--; |
||||
} |
||||
script.RemoveText(offset, endOffset - offset); |
||||
} |
||||
|
||||
static bool IsTabOrSpace(char c) |
||||
{ |
||||
return c == '\t' || c == ' '; |
||||
} |
||||
#endregion
|
||||
} |
||||
} |
Loading…
Reference in new issue