[svn] r7189: vs-plugin/trunk:
Nemerle.Compiler.Utils/FormCodeDomGenerator.n
Nemerle.Compiler.Utils/FormCod...
akhropov
svnadmin at nemerle.org
Mon Jan 1 18:47:57 CET 2007
Log:
add new Form Designer support
Author: akhropov
Date: Mon Jan 1 18:47:06 2007
New Revision: 7189
Added:
vs-plugin/trunk/Nemerle.Compiler.Utils/FormCodeDomGenerator.n
vs-plugin/trunk/Nemerle.Compiler.Utils/FormCodeDomParser.n
vs-plugin/trunk/Nemerle.Compiler.Utils/FormCodeInfo.n
vs-plugin/trunk/Nemerle.VsIntegration/Project/RDTFileTextMerger.cs
Modified:
vs-plugin/trunk/Nemerle.VsIntegration/Nemerle.VisualStudio.csproj
vs-plugin/trunk/Nemerle.VsIntegration/Project/NemerleFileNode.cs
vs-plugin/trunk/Nemerle.VsIntegration/Project/NemerleFileNodeCodeDomProvider.cs
Added: vs-plugin/trunk/Nemerle.Compiler.Utils/FormCodeDomGenerator.n
==============================================================================
--- (empty file)
+++ vs-plugin/trunk/Nemerle.Compiler.Utils/FormCodeDomGenerator.n Mon Jan 1 18:47:06 2007
@@ -0,0 +1,483 @@
+using System;
+using System.IO;
+using System.Collections;
+using System.Collections.Generic;
+using System.CodeDom;
+using System.CodeDom.Compiler;
+using System.ComponentModel.Design.Serialization;
+using System.Diagnostics;
+using System.Drawing;
+using SR = System.Reflection;
+
+using Nemerle.Assertions;
+using Nemerle.Collections;
+using Nemerle.Compiler;
+using Nemerle.Compiler.Parsetree;
+using Nemerle.Compiler.Typedtree;
+using Nemerle.Completion2;
+using Nemerle.Imperative;
+using Nemerle.Utility;
+
+namespace Nemerle.Compiler.Utils
+{
+ public interface IFileTextMerger
+ {
+ AddLines(start: int, newLines: IList[string]) : void;
+ ReplaceLines(start: int, end: int, newLines: IList[string]) : void;
+ RemoveLines(start: int, end: int) : void;
+
+ Flush() : void;
+ }
+
+ // now FormCodeDomGenerator only parses files from Project.CompileUnits
+ // it is not thread-safe at the moment!
+
+ // TODO : generation of new files
+ public class FormCodeDomGenerator : NemerleCodeGenerator
+ {
+ protected enum ChangeStatus { | Equal | NonEqual | Renamed };
+
+ mutable _project : Project;
+
+ mutable _mainFileIndex : int;
+ mutable _designerFileIndex : int;
+
+ mutable _mainFileMerger : IFileTextMerger;
+ mutable _designerFileMerger : IFileTextMerger;
+
+ mutable _formCodeInfo : FormCodeInfo;
+
+ mutable _codeDomMethods : list[CodeMemberMethod];
+ mutable _codeDomFields : list[CodeMemberField];
+
+ mutable _oldFields: list[FieldBuilder];
+ mutable _oldMethods: list[MethodBuilder];
+
+ /*
+ mutable _output : TextWriter; // changed before immediate processing
+
+ protected new Output : TextWriter
+ {
+ get { _output }
+ set { _output = value; }
+ }
+
+ mutable _currentFileIndex : int;
+
+ protected SetMainOutput() : void
+ {
+ _currentFileIndex = _mainFileIndex;
+ Output = _mainFile;
+ }
+
+ protected SetDesignerOutput() : void
+ {
+ _currentFileIndex = _designerFileIndex;
+ Output = _designerFile;
+ }
+ */
+
+ public MergeFormCodeFromCompileUnit([NotNull] project: Project,
+ mainFilePath: string, designerFilePath: string, e: CodeCompileUnit,
+ mainFileMerger : IFileTextMerger, designerFileMerger : IFileTextMerger,
+ o: CodeGeneratorOptions) : void
+ {
+ _project = project;
+
+ _mainFileMerger = mainFileMerger;
+ _designerFileMerger = designerFileMerger;
+
+ _mainFileIndex = _project.CompileUnits.GetFileIndex(mainFilePath);
+ _designerFileIndex = _project.CompileUnits.GetFileIndex(designerFilePath);
+
+ _formCodeInfo = e.UserData[typeof(FormCodeInfo)] :> FormCodeInfo;
+ Debug.Print($"Before merging: _formCodeInfo.newFieldInsertionLine = $(_formCodeInfo.newFieldInsertionLine)\n"
+ "_formCodeInfo.newMethodInsertionLine = $(_formCodeInfo.newMethodInsertionLine)\n");
+
+ Options = if (o != null) o else CodeGeneratorOptions();
+
+ assert(e.Namespaces.Count == 1,"CodeCompileUnit for a form should contain only one namespace!");
+
+ // TODO : do we need to bother about namespace Imports?
+
+ assert(e.Namespaces[0].Types.Count == 1,
+ $"CodeCompileUnit for a form should contain only one form class! (e.)");
+
+ def nsDecls = project.CompileUnits[_mainFileIndex].Decls.MapFilterByType.[Decl.Namespace]();
+
+ assert(nsDecls.Length == 1,
+ "Root Decl for a form CompileUnit should contain only one form class!" );
+ def nsDecl = nsDecls.Head;
+
+ // TODO : Check for form class
+ MergeClassDecl(e.Namespaces[0].Types[0],
+ match(nsDecl.Decls.Find( _ is Decl.Type ))
+ { | Some(typeBuilder) => (typeBuilder :> Decl.Type)
+ | _ => throw ApplicationException("Can't find typeBuilder for the Form class in CodeDom")
+ }.Builder);
+
+ _mainFileMerger.Flush();
+ _designerFileMerger.Flush();
+
+ Debug.Print($"After merging: _formCodeInfo.newFieldInsertionLine = $(_formCodeInfo.newFieldInsertionLine)\n"
+ "_formCodeInfo.newMethodInsertionLine = $(_formCodeInfo.newMethodInsertionLine)\n");
+
+ e.UserData[typeof(FormCodeInfo)] = _formCodeInfo;
+ }
+
+ private allDeclaredFlags : SR.BindingFlags = BindingFlags.DeclaredOnly |
+ BindingFlags.Instance |
+ BindingFlags.Static |
+ BindingFlags.Public |
+ BindingFlags.NonPublic;
+
+ protected MergeClassDecl(classDecl: CodeTypeDeclaration, typeBuilder: TypeBuilder) : void
+ {
+ _oldMethods = typeBuilder.GetMembers(allDeclaredFlags)
+ .Filter( m => (m.MemberType == MemberTypes.Method || m.MemberType == MemberTypes.Constructor) &&
+ !m.Name.StartsWith("_N_field_initialiser") )
+ .MapFilterByType.[MethodBuilder]();
+
+ _codeDomMethods = classDecl.Members.MapFilterByType.[CodeMemberMethod]();
+
+ _oldFields = typeBuilder.GetFields(allDeclaredFlags).MapFilterByType.[FieldBuilder]();
+ _codeDomFields = classDecl.Members.MapFilterByType.[CodeMemberField]();
+
+ MergeMethods(classDecl);
+ MergeFields();
+ }
+
+ protected MergeFields() : void
+ {
+ //classDecl.Members.Filter( _ is CodeMemberField).Map( _ : CodeMemberField);
+
+ // DEBUG
+ Debug.Print("oldFields:\n");
+ _oldFields.Iter( f => Debug.Print($" field $(f.Name)") );
+
+ //Debug.Print("allFields:\n");
+ //typeBuilder.GetFields().Iter( f => Debug.Print($" field $(f.Name)\n") );
+
+ Debug.Print("codedomFields:\n");
+ _codeDomFields.Iter( f => Debug.Print($" field $(f.Name)") );
+
+ def isAddLine = Options.BlankLinesBetweenMembers;
+
+ def AddField(cdf) // to designer
+ {
+ using(def sw = StringWriter())
+ {
+ Output = sw;
+ Indent = 2; // TODO: maybe we should change it dynamically?
+
+ if (isAddLine)
+ Output.WriteLine();
+ else
+ GenerateIndent();
+ GenerateField(cdf);
+
+ def linesList = CodeDomHelper.StringToListOfLines( sw.ToString() );
+ _designerFileMerger.AddLines(_formCodeInfo.newFieldInsertionLine,
+ linesList);
+
+ _formCodeInfo.newFieldInsertionLine += linesList.Count; // TODO : maybe we have to add additional lines
+ Debug.Print($"_formCodeInfo.newFieldInsertionLine = $(_formCodeInfo.newFieldInsertionLine) "
+ "after adding $(cdf.Name)\n");
+ }
+ }
+
+ def RemoveField(of)
+ {
+ def loc = of.Location;
+ def line = if (Options.BlankLinesBetweenMembers) loc.Line-1 else loc.Line;
+ _designerFileMerger.RemoveLines( line, loc.EndLine );
+
+ def lineOffset = line - loc.EndLine - 1;
+ Relocate(_designerFileIndex,line,lineOffset);
+
+ _formCodeInfo.newFieldInsertionLine += lineOffset;
+ Debug.Print($"_formCodeInfo.newFieldInsertionLine = $(_formCodeInfo.newFieldInsertionLine) "
+ "after removing $(of.Name)\n");
+ }
+
+ def RenameField(of,cdf)
+ {
+ using(def sw = StringWriter())
+ {
+ Output = sw;
+ Indent = 2; // TODO: maybe we should change it dynamically?
+
+ GenerateIndent();
+ GenerateField(cdf);
+
+ def linesList = CodeDomHelper.StringToListOfLines( sw.ToString() );
+
+ def oldLoc = of.Location;
+ _designerFileMerger.ReplaceLines(oldLoc.Line, oldLoc.EndLine,
+ linesList);
+ def lineOffset = linesList.Count - (oldLoc.EndLine - oldLoc.Line + 1);
+ Relocate(_designerFileIndex, oldLoc.Line, lineOffset);
+
+ _formCodeInfo.newFieldInsertionLine += lineOffset;
+ Debug.Print($"_formCodeInfo.newFieldInsertionLine = $(_formCodeInfo.newFieldInsertionLine) "
+ "after renaming $(of.Name)->$(cdf.Name)\n");
+ }
+ }
+
+ def sortedCodedomFields = _codeDomFields.Sort(
+ fun(f1,f2) {
+ mutable res : int;
+
+ res = if(f1.UserData.Contains(typeof(Location)))
+ {
+ if(f2.UserData.Contains(typeof(Location)))
+ {
+ (f1.UserData[typeof(Location)] :> Location).CompareTo(
+ (f2.UserData[typeof(Location)] :> Location))
+ }
+ else
+ -1
+ }
+ else if(f2.UserData.Contains(typeof(Location)))
+ 1
+ else
+ -1;
+
+ /*
+ when(f1.UserData.Contains(typeof(Location)))
+ Debug.Print($"$(f1.Name) has Location = $(f1.UserData[typeof(Location)] :> Location)\n");
+ when(f2.UserData.Contains(typeof(Location)))
+ Debug.Print($"$(f2.Name) has Location = $(f2.UserData[typeof(Location)] :> Location)\n");
+
+ Debug.Print($"Comparing1 $(f1.Name) and $(f2.Name) res = $res\n");
+ */
+ res
+ }
+ );
+
+ Debug.Print("codedomFields (after Sort):\n");
+ sortedCodedomFields.Iter( f => Debug.Print($" field $(f.Name)\n") );
+
+ AddRemoveRenameDispatcher( _oldFields, sortedCodedomFields,
+ GetChangeStatus,
+ AddField, RemoveField, RenameField );
+ }
+
+ protected MergeMethods(classDecl: CodeTypeDeclaration) : void
+ {
+ // DEBUG
+ Debug.Print("oldMethods:\n");
+ _oldMethods.Iter( m => Debug.Print($" method $(m.Name)") );
+
+ //Debug.Print("allMethods:\n");
+ //typeBuilder.GetMethods().Iter( m => Debug.Print($" method $(m.Name)\n") );
+
+ Debug.Print("codedomMethods:\n");
+ _codeDomMethods.Iter( m => Debug.Print($" method $(m.Name)") );
+
+ // Process InitializeComponent
+
+ match( (_oldMethods.Find( m => m.Name == "InitializeComponent" ),
+ _codeDomMethods.Find( m => m.Name == "InitializeComponent" )) )
+ {
+ | (om,Some(cdm)) =>
+ using(def sw = StringWriter())
+ {
+ Output = sw;
+ Indent = 2; // TODO: maybe we should change it dynamically?
+
+ GenerateIndent();
+ GenerateMethod(cdm,classDecl);
+
+ def linesList = CodeDomHelper.StringToListOfLines( sw.ToString() );
+
+ match(om)
+ {
+ | Some(om) =>
+ def oldLoc = om.Location;
+ _designerFileMerger.ReplaceLines(oldLoc.Line, oldLoc.EndLine,
+ linesList);
+ def lineOffset = linesList.Count - (oldLoc.EndLine - oldLoc.Line + 1);
+ Relocate(_designerFileIndex, oldLoc.Line, lineOffset);
+
+ _formCodeInfo.newFieldInsertionLine += lineOffset;
+ Debug.Print($"_formCodeInfo.newFieldInsertionLine = $(_formCodeInfo.newFieldInsertionLine) "
+ "after renewing InitializeComponent\n");
+
+ | None => // just give up for now
+ throw ApplicationException("InitializeComponent not found in old designer file");
+ }
+ }
+
+ | _ =>
+ throw ApplicationException("InitializeComponent not found in CodeDom tree");
+ }
+
+ def AddMethod(cdm)
+ {
+ using(def sw = StringWriter())
+ {
+ Output = sw;
+ Indent = 2; // TODO: maybe we should change it dynamically?
+
+ // TODO: correct way to insert empty method
+
+ def statements = cdm.Statements;
+ def isMethodBodyEmpty = (statements.Count == 0);
+ when (isMethodBodyEmpty)
+ // insert ';' for generation reverse this change later
+ _ = statements.Add( CodeMethodReturnStatement() );
+
+ // if( Options.BlankLinesBetweenMembers )
+ Output.WriteLine(); // TODO : blank lines should be regulated?
+
+ GenerateMethod(cdm,classDecl);
+
+ // clear body again
+ when (isMethodBodyEmpty)
+ statements.Clear();
+
+ def linesList = CodeDomHelper.StringToListOfLines( sw.ToString() );
+
+ _mainFileMerger.AddLines(_formCodeInfo.newMethodInsertionLine,
+ linesList);
+
+ // Add cursor position to codedom: (TODO: do it the right way)
+ cdm.UserData[typeof(Point)] =
+ Point(1 + Options.IndentString.Length*3,
+ 1 + _formCodeInfo.newMethodInsertionLine +
+ match (BracingStyle) { | Block | Indent => 2 | _ => 3 } );
+
+ _formCodeInfo.newMethodInsertionLine += linesList.Count; // TODO : maybe we have to add additional lines
+ Debug.Print($"_formCodeInfo.newMethodInsertionLine = $(_formCodeInfo.newMethodInsertionLine) "
+ "after adding $(cdm.Name)\n");
+ }
+ }
+
+ def RemoveMethod(om)
+ {
+ // TODO : blank lines should be regulated?
+ def (line,endLine) = (om.Location.Line-1,om.Location.EndLine);
+ def removedLines = endLine - line + 1;
+ _mainFileMerger.RemoveLines(line, endLine); // TODO
+
+ // adjust all other methods
+ Relocate(_mainFileIndex,line, -removedLines);
+
+ _formCodeInfo.newMethodInsertionLine -= removedLines;
+ Debug.Print($"_formCodeInfo.newMethodInsertionLine = $(_formCodeInfo.newMethodInsertionLine) "
+ "after removing $(om.Name)\n");
+ }
+
+ def RenameMethod(om,cdm)
+ {
+ using(def sw = StringWriter())
+ {
+ Output = sw;
+ Indent = 2; // TODO: maybe we should change it dynamically?
+
+ GenerateIndent();
+ GenerateMethod(cdm,classDecl);
+
+ def linesList = CodeDomHelper.StringToListOfLines( sw.ToString() );
+
+ def oldLoc = om.Location;
+ _designerFileMerger.ReplaceLines(oldLoc.Line, oldLoc.EndLine,
+ linesList);
+ def lineOffset = linesList.Count - (oldLoc.EndLine - oldLoc.Line + 1);
+ Relocate(_mainFileIndex, oldLoc.Line, lineOffset);
+
+ _formCodeInfo.newMethodInsertionLine += lineOffset;
+ Debug.Print($"_formCodeInfo.newMethodInsertionLine = $(_formCodeInfo.newMethodInsertionLine)"
+ "after renaming $(om.Name)->$(cdm.Name)\n");
+ }
+ }
+
+ AddRemoveRenameDispatcher( _oldMethods, _codeDomMethods,
+ GetChangeStatus,
+ AddMethod, RemoveMethod, RenameMethod );
+ }
+
+ protected GenerateIndent() : void
+ {
+ def output = Output;
+ def indentString = Options.IndentString;
+ repeat(Indent)
+ output.Write(indentString);
+ }
+
+ protected Relocate(fileIndex: int,
+ line: int, lineOffset: int) : void
+ {
+ if (fileIndex == _mainFileIndex)
+ {
+ _oldMethods.Iter( m =>
+ {
+ def loc = m.Location;
+ when (loc.FileIndex == _mainFileIndex)
+ m.Location = Completion.Relocate( loc, line, 0, lineOffset, 0)
+ } );
+
+ _codeDomMethods.Iter( m =>
+ {
+ def locRef = m.UserData[typeof(Location)];
+ when (locRef != null)
+ {
+ def loc = locRef :> Location;
+ when (loc.FileIndex == fileIndex)
+ {
+ def pt = m.UserData[typeof(Point)] :> Point;
+ when (pt.Y > line)
+ {
+ pt.Y += lineOffset;
+ m.UserData[typeof(Point)] = pt;
+ }
+ }
+ }
+ } );
+ }
+ else // fileIndex == _designerFileIndex
+ {
+ _oldFields.Iter( f =>
+ {
+ def loc = f.Location;
+ when (loc.FileIndex == fileIndex)
+ f.Location = Completion.Relocate( loc, line, 0, lineOffset, 0)
+ } );
+ }
+ }
+
+ // Removes can be everywhere, Adds only at the end
+ static protected AddRemoveRenameDispatcher[T1,T2]( old_list: list[T1], new_list: list[T2],
+ status: T1*T2->ChangeStatus,
+ addFunc : T2->void, removeFunc : T1->void, renameFunc : T1*T2->void ) : void
+ {
+ def dispatcher( old_l, new_l )
+ {
+ | (h1 :: t1, h2 :: t2) =>
+ match (status(h1,h2)) {
+ | Equal => dispatcher(t1,t2)
+ | NonEqual => { removeFunc(h1); dispatcher(t1,new_l) }
+ | Renamed => { renameFunc(h1,h2); dispatcher(t1,t2) }
+ }
+ | (_, []) => old_l.Iter( removeFunc(_) )
+ | ([], _) => new_l.Iter( addFunc(_) )
+ }
+ dispatcher( old_list, new_list )
+ }
+
+ static protected GetChangeStatus[T1,T2]( oldMember: T1, codeDomMember: T2 ) : ChangeStatus
+ where T1 : MemberBuilder where T2 : CodeTypeMember
+ {
+ //Debug.Print($"Comparing $(oldMember.Name) and $(codeDomMember.Name)\n");
+ def oldName = codeDomMember.UserData["Name"];
+ if (oldName != null && ((oldName :> string) == oldMember.Name))
+ if (oldMember.Name != codeDomMember.Name)
+ ChangeStatus.Renamed
+ else
+ ChangeStatus.Equal
+ else
+ ChangeStatus.NonEqual
+ }
+ }
+}
Added: vs-plugin/trunk/Nemerle.Compiler.Utils/FormCodeDomParser.n
==============================================================================
--- (empty file)
+++ vs-plugin/trunk/Nemerle.Compiler.Utils/FormCodeDomParser.n Mon Jan 1 18:47:06 2007
@@ -0,0 +1,240 @@
+using System;
+using System.IO;
+using System.Collections;
+using System.Collections.Generic;
+using System.CodeDom;
+using System.CodeDom.Compiler;
+using System.ComponentModel.Design.Serialization;
+using System.Diagnostics;
+using System.Drawing;
+using System.Reflection;
+
+using Nemerle.Assertions;
+using NC = Nemerle.Collections;
+using Nemerle.Compiler;
+using Nemerle.Compiler.Parsetree;
+using Nemerle.Compiler.Typedtree;
+using Nemerle.Completion2;
+using Nemerle.Utility;
+
+namespace Nemerle.Compiler.Utils
+{
+ // now CodeDomParser only parses files from Project.CompileUnits
+ // it is not thread-safe at the moment!
+ public class FormCodeDomParser : NemerleCodeParser
+ {
+ mutable _project : Project;
+
+ mutable _mainFileIndex : int;
+ mutable _designerFileIndex : int;
+
+ mutable _formCodeInfo : FormCodeInfo = FormCodeInfo(-1,-1);
+
+ /*
+ methods and fields in generated CodeCompileUnit contain
+ typeof(Location) - indicates that this member is already in file
+ and
+ typeof(Point) - is required by Designer
+ data in UserData
+
+ CodeCompileUnit itself contains typeof(FormCodeInfo) in UserData
+ */
+ public CreateCodeCompileUnit(project : Project,
+ mainFilePath: string, designerFilePath: string) : CodeCompileUnit
+ {
+ // Initialization
+ _project = project;
+ base._manager = _project.Engine;
+
+ _mainFileIndex = _project.CompileUnits.GetFileIndex(mainFilePath);
+ _designerFileIndex = if(designerFilePath != null) _project.CompileUnits.GetFileIndex(designerFilePath)
+ else -1;
+
+ // TODO: new object vs Clear
+ cachedObjs = NC.Hashtable();
+
+ // Processing
+ def unit = CodeCompileUnit();
+
+ def globalImports = List.[string]();
+
+ foreach(decl in project.CompileUnits[_mainFileIndex].Decls)
+ {
+ | Decl.Namespace as nsDecl =>
+ {
+ Debug.Write(nsDecl);
+ _ = unit.Namespaces.Add(ProcessNamespace(nsDecl));
+ }
+ | Decl.Using as usDecl =>
+ globalImports.Add(usDecl.Name.ToString("."));
+ //unit.Imports.Add(CodeNamespaceImport());
+ | d with(bodyLoc = project.CompileUnits[_mainFileIndex].BodyLocation) =>
+ throw CodeDomSerializerException($"Assumed root Declaration of CompileUnit is Decl.Namespace, got $d",
+ CodeLinePragma(bodyLoc.File,bodyLoc.Line));
+ }
+
+ foreach(import in globalImports)
+ _ = unit.ReferencedAssemblies.Add(import);
+
+ // Add FormCodeInfo
+ when (_designerFileIndex != -1)
+ assert(_formCodeInfo.newMethodInsertionLine != -1 && _formCodeInfo.newFieldInsertionLine != -1,
+ "Don't know where generated fields and methods should be placed");
+
+ unit.UserData.Add(typeof(FormCodeInfo),_formCodeInfo);
+
+ /*
+ Debug.Write(CodeDomHelper.ToString(unit,
+ NemerleCodeGenerator()));
+ Debug.Write(CodeDomHelper.ToString(unit,
+ Microsoft.CSharp.CSharpCodeProvider().CreateGenerator()));
+ */
+
+ Debug.Write($"_formCodeInfo.newMethodInsertionLine = $(_formCodeInfo.newMethodInsertionLine)\n");
+ Debug.Write($"_formCodeInfo.newFieldInsertionLine = $(_formCodeInfo.newFieldInsertionLine)\n");
+
+ unit;
+ }
+
+ private ProcessNamespace(decl: Decl.Namespace) : CodeNamespace
+ {
+ def res = CodeNamespace(decl.Name.ToString("."));
+
+ foreach(decl in decl.Decls)
+ match(decl)
+ {
+ | Decl.Type as typeDecl =>
+ {
+ assert( res.Types.Count < 2, "Form files should not contain more than one top class declaration");
+ Debug.Write(typeDecl.Builder);
+
+ InitFormCodeInfo(typeDecl.Builder);
+
+ _ = res.Types.Add(ProcessTypeDeclaration(typeDecl.Builder));
+ }
+
+ | Decl.Using as usDecl =>
+ res.Imports.Add(CodeNamespaceImport(usDecl.Name.ToString(".")));
+
+ | Decl.Namespace(BodyLocation = bodyLoc) =>
+ throw CodeDomSerializerException("Namespace declarations cannot contain inner namespace declarations",
+ CodeLinePragma(bodyLoc.File,bodyLoc.Line));
+
+ | _ => () // Ignored
+ }
+
+ res
+ }
+
+ protected override ProcessClassMembers(members: list[IMember],classDecl: CodeTypeDeclaration) : void
+ {
+ members.Iter(m : IMember =>
+ match(m)
+ {
+ | m is TypeBuilder =>
+ {_ = classDecl.Members.Add(CreateClass(m));}
+ | m is MemberBuilder when (!m.Name.StartsWith("_N_field_initialiser")) =>
+ {_ = classDecl.Members.Add(CreateMember(m));}
+ | _ =>
+ () //Debug.Print($"Declaration of external member $m ignored");
+ }
+ );
+ }
+
+ protected override CreateMember(member: MemberBuilder) : CodeTypeMember
+ {
+ def memberDecl = base.CreateMember(member);
+ memberDecl.UserData["Name"] = member.Name;
+ memberDecl
+ }
+
+ protected override CreateField(field: FieldBuilder) : CodeMemberField
+ {
+ when (field.Location.FileIndex == _designerFileIndex)
+ {
+ def lineAfterEnd = field.Location.EndLine+1;
+ when (lineAfterEnd > _formCodeInfo.newFieldInsertionLine)
+ _formCodeInfo.newFieldInsertionLine = lineAfterEnd;
+ }
+
+ base.CreateField(field);
+ }
+
+ protected override CreateMethod(method: MethodBuilder) : CodeMemberMethod
+ {
+ def methodDecl = base.CreateMethod(method);
+
+ if (method.Location.FileIndex == _mainFileIndex)
+ {
+ def lineAfterEnd = method.Location.EndLine+1;
+ when (lineAfterEnd > _formCodeInfo.newMethodInsertionLine)
+ _formCodeInfo.newMethodInsertionLine = lineAfterEnd;
+
+ // TODO: correct positioning
+ methodDecl.UserData.Add(typeof(Point),
+ Point( 1+ method.BodyTyped.Location.Column,
+ method.BodyTyped.Location.Line));
+ }
+ else when (method.Name == "InitializeComponent")
+ {
+ // TODO: correct region handling
+ // TODO: do we need extra space?
+ def lineAfterEnd = method.Location.EndLine+1;
+ when (lineAfterEnd > _formCodeInfo.newFieldInsertionLine)
+ _formCodeInfo.newFieldInsertionLine = lineAfterEnd;
+ }
+
+ methodDecl
+ }
+
+ private InitFormCodeInfo(typeBuilder: TypeBuilder) : void
+ {
+ // Make sure at least main and designer files are included in PartsLocation
+
+ // TODO: quite a brute approach
+
+ mutable partsLocations = typeBuilder.PartsLocation;
+ assert (partsLocations.Length == 2, "Form class declaration must reside in two files: main and designer's");
+
+ when (partsLocations.Head.FileIndex == _designerFileIndex)
+ partsLocations = partsLocations.Reverse();// TODO
+
+ assert (partsLocations.Head.FileIndex == _mainFileIndex &&
+ partsLocations.Last.FileIndex == _designerFileIndex,
+ "Main class is located in the wrong files!");
+
+ // TODO
+ _formCodeInfo.newMethodInsertionLine = partsLocations.Head.Line + 1;
+ _formCodeInfo.newFieldInsertionLine = partsLocations.Last.Line + 1;
+ }
+
+ // process cached objects because form designer doesn't understand 'em
+ #region Cached objects related
+
+ protected mutable cachedObjs : NC.Hashtable[string,CodeExpression];
+
+ protected override CreateStatements(expr : TExpr, statements: CodeStatementCollection) : void
+ {
+ | (TExpr.DefValIn(name,val,body),_) // name : LocalValue; val : TExpr; mutable body : TExpr; }
+ when (name.Name.StartsWith("_N_cached_obj")) =>
+ def initExpr = CreateExpression(val);
+ cachedObjs.Add(name.Name,initExpr);
+ // TODO: Should we include temporary object declaration?
+ //_ = statements.Add( CodeVariableDeclarationStatement(name.Type.SystemType, name.Name, initExpr) );
+ CreateStatements(body,statements);
+
+ | _ =>
+ base.CreateStatements(expr,statements);
+ }
+
+ protected override CreateExpression(expr : TExpr) : CodeExpression
+ {
+ | TExpr.LocalRef(decl) when (decl.Name.StartsWith("_N_cached_obj")) => //{ decl : LocalValue; }
+ cachedObjs[decl.Name]
+ | _ =>
+ base.CreateExpression(expr)
+ }
+
+ #endregion
+ }
+}
Added: vs-plugin/trunk/Nemerle.Compiler.Utils/FormCodeInfo.n
==============================================================================
--- (empty file)
+++ vs-plugin/trunk/Nemerle.Compiler.Utils/FormCodeInfo.n Mon Jan 1 18:47:06 2007
@@ -0,0 +1,25 @@
+using System;
+using System.CodeDom;
+using System.Reflection;
+
+using Nemerle.Compiler;
+using Nemerle.Compiler.Typedtree;
+
+namespace Nemerle.Compiler.Utils
+{
+ // This struct is inserted into CodeCompileUnit.UserData with typeof(FormCodeInfo) key.
+ public struct FormCodeInfo
+ {
+ // number of line in form main code file
+ public mutable newMethodInsertionLine : int; // indicates uninitialized position
+
+ // number of line in designer code file
+ public mutable newFieldInsertionLine : int; // indicates uninitialized position
+
+ public this(newMethodInsertionLine: int = -1, newFieldInsertionLine: int = -1)
+ {
+ this.newMethodInsertionLine = newMethodInsertionLine;
+ this.newFieldInsertionLine = newFieldInsertionLine;
+ }
+ }
+}
\ No newline at end of file
Modified: vs-plugin/trunk/Nemerle.VsIntegration/Nemerle.VisualStudio.csproj
==============================================================================
--- vs-plugin/trunk/Nemerle.VsIntegration/Nemerle.VisualStudio.csproj (original)
+++ vs-plugin/trunk/Nemerle.VsIntegration/Nemerle.VisualStudio.csproj Mon Jan 1 18:47:06 2007
@@ -192,7 +192,6 @@
<CtcFile Include="CtcComponents\PkgCmd.ctc">
<ResourceName>1000</ResourceName>
</CtcFile>
- <Content Include="!ToDo\ToDo.txt" />
<Content Include="CodeSnippets\Snippets\foreach.snippet">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -205,7 +204,6 @@
<Content Include="CodeSnippets\Snippets\match.snippet">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <None Include="Resources\Images_24bit.bmp" />
<None Include="CtcComponents\Guids.h" />
<None Include="CtcComponents\PkgCmdID.h" />
<None Include="nemerle.snk" />
@@ -267,6 +265,7 @@
<ZipProject Include="Templates\Projects\WindowsApplication\Program.n" />
</ItemGroup>
<ItemGroup>
+ <Compile Include="Project\RDTFileTextMerger.cs" />
<Compile Include="GUI\AstToolControl.cs">
<SubType>UserControl</SubType>
</Compile>
Modified: vs-plugin/trunk/Nemerle.VsIntegration/Project/NemerleFileNode.cs
==============================================================================
Modified: vs-plugin/trunk/Nemerle.VsIntegration/Project/NemerleFileNodeCodeDomProvider.cs
==============================================================================
--- vs-plugin/trunk/Nemerle.VsIntegration/Project/NemerleFileNodeCodeDomProvider.cs (original)
+++ vs-plugin/trunk/Nemerle.VsIntegration/Project/NemerleFileNodeCodeDomProvider.cs Mon Jan 1 18:47:06 2007
@@ -33,16 +33,38 @@
{
_fileNodeProvider.GenerateCodeFromCompileUnit(e,w,o);
}
+
+ public void GenerateCodeFromExpression(CodeExpression e, TextWriter w, CodeGeneratorOptions o)
+ {
+ //(base as ICodeGenerator).GenerateCodeFromExpression(e, w, o);
+ throw new NotImplementedException("GenerateCodeFromExpression");
+ }
+
+ public void GenerateCodeFromNamespace(CodeNamespace e, TextWriter w, CodeGeneratorOptions o)
+ {
+ //base.GenerateCodeFromNamespace(e, w, o);
+ throw new NotImplementedException("GenerateCodeFromNamespace");
+ }
+
+ public void GenerateCodeFromStatement(CodeStatement e, TextWriter w, CodeGeneratorOptions o)
+ {
+ //base.GenerateCodeFromStatement(e, w, o);
+ throw new NotImplementedException("GenerateCodeFromStatement");
+ }
+
+ public void GenerateCodeFromType(CodeTypeDeclaration e, TextWriter w, CodeGeneratorOptions o)
+ {
+ //base.GenerateCodeFromType(e, w, o);
+ throw new NotImplementedException("GenerateCodeFromType");
+ }
}
internal class NemerleFileNodeCodeDomProvider : CodeDomProvider, ICodeParser
// ICodeGenerator
{
- FileNode _fileNode;
- //NemerleCodeProvider _codeProvider;
- NCU.CodeDomParser _codeDomParser;
- //NemerleCodeGenerator _codeGen;
- ICodeGenerator _codeGen;
+ readonly FileNode _fileNode;
+ NCU.FormCodeDomParser _codeDomParser;
+ NCU.FormCodeDomGenerator _codeDomGenerator;
NemerleCodeGeneratorProxy _codeGenProxy;
// AKhropov: In fact these 2 constructors only to restrict possible file nodes,
@@ -50,8 +72,8 @@
private void Init()
{
- _codeDomParser = new NCU.CodeDomParser();
- _codeGen = new NemerleCodeGenerator();
+ _codeDomParser = new NCU.FormCodeDomParser();
+ _codeDomGenerator = new NCU.FormCodeDomGenerator();
_codeGenProxy = new NemerleCodeGeneratorProxy(this);
}
@@ -67,18 +89,6 @@
Init();
}
- /*
- internal NemerleCodeProvider CodeProvider
- {
- get
- {
- if (_codeProvider == null)
- _codeProvider = new NemerleCodeProvider();
- return _codeProvider;
- }
- }
- */
-
#region helper functions
private bool IsFormSubType
@@ -92,20 +102,19 @@
}
}
- private string NameOfFileToParse()
+ private string PathOfMainFile()
{
- return IsFormSubType ?
- Path.GetFileNameWithoutExtension(_fileNode.FileName) +
- ".Designer" + NemerleConstants.FileExtension
- : _fileNode.FileName;
+ return Path.Combine(Path.GetDirectoryName(_fileNode.GetMkDocument()), _fileNode.FileName);
}
- private string PathOfFileToParse()
+ private string PathOfDesignerFile()
{
- return Path.Combine(Path.GetDirectoryName(_fileNode.GetMkDocument()), NameOfFileToParse());
+ return Path.Combine(Path.GetDirectoryName(_fileNode.GetMkDocument()),
+ Path.GetFileNameWithoutExtension(_fileNode.FileName) +
+ ".Designer" + NemerleConstants.FileExtension);
}
- // AKhropov : I had to copy it from FileNode implementation because it was protected there
+ // AKhropov : I had to grab some code from FileNode implementation because it was protected there
void UpdateGeneratedCodeFile(string data, string filePath)
{
IVsRunningDocumentTable rdt = _fileNode.ProjectMgr.GetService(typeof(SVsRunningDocumentTable)) as IVsRunningDocumentTable;
@@ -119,8 +128,8 @@
IVsHierarchy hier;
uint itemid, cookie;
IntPtr docData = IntPtr.Zero;
- //Getting a read lock on the document. Must be released later.
- ErrorHandler.ThrowOnFailure(rdt.FindAndLockDocument((uint)(_VSRDTFLAGS.RDT_ReadLock), filePath, out hier, out itemid, out docData, out cookie));
+ //Getting a edit lock on the document. Must be released later.
+ ErrorHandler.ThrowOnFailure(rdt.FindAndLockDocument((uint)(_VSRDTFLAGS.RDT_EditLock), filePath, out hier, out itemid, out docData, out cookie));
if (docData != IntPtr.Zero)
{
IVsPersistDocData persistDocData = Marshal.GetObjectForIUnknown(docData) as IVsPersistDocData;
@@ -186,8 +195,6 @@
}
}
-
-
#endregion
#region Parser implementation
@@ -195,10 +202,12 @@
public override CodeCompileUnit Parse(TextReader codeStream)
{
// AKhropov - in fact codeStream is ignored for now
- string filePath = PathOfFileToParse();
+ string mainFilePath = PathOfMainFile();
- ProjectInfo projectInfo = ProjectInfo.FindProject(filePath);
- return _codeDomParser.CreateCodeCompileUnit(projectInfo.Engine.Project, filePath);
+ // TODO : can _project change for _fileNode?
+ return _codeDomParser.CreateCodeCompileUnit(
+ ProjectInfo.FindProject(mainFilePath).Project, mainFilePath,
+ (IsFormSubType) ? PathOfDesignerFile() : null);
}
#endregion
@@ -207,11 +216,35 @@
public override void GenerateCodeFromCompileUnit(CodeCompileUnit e, TextWriter w, CodeGeneratorOptions o)
{
+ if (IsFormSubType)
+ {
+ string mainFilePath = PathOfMainFile();
+ string designerFilePath = PathOfDesignerFile();
+
+ // Find designer FileNode
+ NemerleDependentFileNode designerFileNode =
+ _fileNode.FindChild(designerFilePath) as NemerleDependentFileNode;
+
+ if (designerFileNode == null)
+ throw new ApplicationException("Can't find designer file node");
+
+ // Distribute changes to Form.n and Form.designer.n files
+ using (RDTFileTextMerger mainMerger = new RDTFileTextMerger(_fileNode))
+ using (RDTFileTextMerger designerMerger = new RDTFileTextMerger(designerFileNode))
+ {
+ //ProjectInfo.FindProject(designerFilePath).Project.CompileUnits.
+ _codeDomGenerator.MergeFormCodeFromCompileUnit(
+ ProjectInfo.FindProject(mainFilePath).Project,
+ mainFilePath, designerFilePath,
+ e, mainMerger, designerMerger, o);
+ }
+ }
+ else
using (StringWriter sw = new StringWriter())
{
- _codeGen.GenerateCodeFromCompileUnit(e, sw, o);
+ (_codeDomGenerator as ICodeGenerator).GenerateCodeFromCompileUnit(e, sw, o);
- UpdateGeneratedCodeFile(sw.ToString(), PathOfFileToParse());
+ UpdateGeneratedCodeFile(sw.ToString(), PathOfMainFile());
}
}
Added: vs-plugin/trunk/Nemerle.VsIntegration/Project/RDTFileTextMerger.cs
==============================================================================
--- (empty file)
+++ vs-plugin/trunk/Nemerle.VsIntegration/Project/RDTFileTextMerger.cs Mon Jan 1 18:47:06 2007
@@ -0,0 +1,284 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+using System.Text;
+
+using Microsoft.VisualStudio;
+using Microsoft.VisualStudio.OLE.Interop;
+using Microsoft.VisualStudio.Package;
+using Microsoft.VisualStudio.Shell;
+using Microsoft.VisualStudio.Shell.Interop;
+using Microsoft.VisualStudio.TextManager.Interop;
+
+using Nemerle.Compiler.Utils;
+
+namespace Nemerle.VisualStudio.Project
+{
+ /// <summary>
+ /// Auxiliary class to merge file changes to files in RDT
+ /// </summary>
+ class RDTFileTextMerger : IFileTextMerger, IDisposable
+ {
+ #region FileChange variant
+
+ class FileChange{}
+ class AddLinesChange : FileChange
+ {
+ public readonly int Start;
+ public readonly List<string> NewLines;
+
+ public AddLinesChange(int start, IList<string> newLines)
+ {
+ Start = start;
+ NewLines = new List<string>(newLines);
+ }
+ }
+ class ReplaceLinesChange : FileChange
+ {
+ public readonly int Start;
+ public readonly int End;
+ public readonly List<string> NewLines;
+
+ public ReplaceLinesChange(int start, int end, IList<string> newLines)
+ {
+ Start = start;
+ End = end;
+ NewLines = new List<string>(newLines);
+ }
+ }
+ class RemoveLinesChange : FileChange
+ {
+ public readonly int Start;
+ public readonly int End;
+
+ public RemoveLinesChange(int start, int end)
+ {
+ Start = start;
+ End = end;
+ }
+ }
+
+ #endregion
+
+ #region fields
+
+ readonly FileNode _fileNode;
+
+ List<FileChange> _fileChanges;
+
+ #endregion
+
+ public RDTFileTextMerger(FileNode fileNode)
+ {
+ _fileNode = fileNode;
+ _fileChanges = new List<FileChange>();
+ }
+
+ public void Dispose()
+ {
+ Flush();
+ }
+
+ public void AddLines(int start, IList<string> newLines)
+ {
+ lock( _fileChanges ) // TODO : maybe more fine grained operation?
+ {
+ _fileChanges.Add(new AddLinesChange(start, newLines));
+ }
+ }
+
+ public void ReplaceLines(int start, int end, IList<string> newLines)
+ {
+ lock( _fileChanges ) // TODO : maybe more fine grained operation?
+ {
+ _fileChanges.Add(new ReplaceLinesChange(start, end, newLines));
+ }
+ }
+
+ public void RemoveLines(int start, int end)
+ {
+ lock( _fileChanges ) // TODO : maybe more fine grained operation?
+ {
+ _fileChanges.Add(new RemoveLinesChange(start, end));
+ }
+ }
+
+ private delegate void ProcessFileChangesFunc(IVsTextLines vsTextLines);
+
+ private void ProcessFileChanges(IVsTextLines vsTextLines)
+ {
+ TextSpan[] span = new TextSpan[1];
+ lock( _fileChanges ) // TODO : maybe more fine grained operation?
+ {
+ foreach (FileChange fileChange in _fileChanges)
+ {
+ {
+ AddLinesChange addLinesChange = fileChange as AddLinesChange;
+ if (addLinesChange != null)
+ {
+ Debug.Print("AddLinesChange : add to line " + addLinesChange.Start +
+ " lines ("+ addLinesChange.NewLines.Count + ") :\n" + GetText(addLinesChange.NewLines));
+
+ string text = GetText(addLinesChange.NewLines);
+ /*int endLine = addLinesChange.Start + addLinesChange.NewLines.Count;
+ int endLength;
+ ErrorHandler.ThrowOnFailure(vsTextLines.GetLengthOfLine(endLine, out endLength));
+ */
+ GCHandle handle = GCHandle.Alloc(text, GCHandleType.Pinned);
+ try
+ {
+ ErrorHandler.ThrowOnFailure(
+ vsTextLines.ReplaceLines(addLinesChange.Start-1, 0,
+ addLinesChange.Start-1, 0,
+ handle.AddrOfPinnedObject(), text.Length, span));
+ }
+ finally
+ {
+ handle.Free();
+ }
+ continue;
+ }
+ }
+
+ {
+ ReplaceLinesChange replaceLinesChange = fileChange as ReplaceLinesChange;
+ if (replaceLinesChange != null)
+ {
+ Debug.Print("ReplaceLinesChange : replace lines (" + replaceLinesChange.Start +
+ "," + replaceLinesChange.End + ") with\n" + GetText(replaceLinesChange.NewLines));
+
+ string text = GetText(replaceLinesChange.NewLines);
+ //int endLength;
+ //ErrorHandler.ThrowOnFailure(vsTextLines.GetLengthOfLine(replaceLinesChange.End-1, out endLength));
+
+ GCHandle handle = GCHandle.Alloc(text, GCHandleType.Pinned);
+ try
+ {
+ ErrorHandler.ThrowOnFailure(
+ vsTextLines.ReplaceLines(replaceLinesChange.Start-1, 0,
+ replaceLinesChange.End, 0, // endLength,
+ handle.AddrOfPinnedObject(), text.Length, span));
+ }
+ finally
+ {
+ handle.Free();
+ }
+ continue;
+ }
+ }
+
+ {
+ RemoveLinesChange removeLinesChange = fileChange as RemoveLinesChange;
+ if (removeLinesChange != null)
+ {
+ Debug.Print("RemoveLinesChange : remove lines ("+ removeLinesChange.Start +
+ ","+ removeLinesChange.End + ")");
+
+ string text = "";
+ //int endLength;
+ //ErrorHandler.ThrowOnFailure(vsTextLines.GetLengthOfLine(removeLinesChange.End-1, out endLength));
+
+ GCHandle handle = GCHandle.Alloc(text, GCHandleType.Pinned);
+ try
+ {
+ ErrorHandler.ThrowOnFailure(
+ vsTextLines.ReplaceLines(removeLinesChange.Start-1, 0,
+ removeLinesChange.End, 0, //endLength,
+ handle.AddrOfPinnedObject(), text.Length, span));
+ }
+ finally
+ {
+ handle.Free();
+ }
+ continue;
+ }
+ }
+ }
+
+ _fileChanges.Clear();
+ }
+ }
+
+ public void Flush()
+ {
+ ProcessFileChangesHelper( ProcessFileChanges );
+ // TODO - how to handle a case when file is not in RDT?
+ }
+
+ private void ProcessFileChangesHelper(ProcessFileChangesFunc processFunc)
+ {
+ string filePath = _fileNode.GetMkDocument();
+
+ IVsRunningDocumentTable rdt = _fileNode.ProjectMgr.GetService(typeof(SVsRunningDocumentTable)) as IVsRunningDocumentTable;
+
+ // (kberes) Shouldn't this be an InvalidOperationException instead with some not to annoying errormessage to the user?
+ if (rdt == null)
+ {
+ ErrorHandler.ThrowOnFailure(VSConstants.E_FAIL);
+ }
+
+ IVsHierarchy hier;
+ uint itemid, cookie;
+ IntPtr docData = IntPtr.Zero;
+ //Getting a edit lock on the document. Must be released later.
+ ErrorHandler.ThrowOnFailure(rdt.FindAndLockDocument((uint)(_VSRDTFLAGS.RDT_EditLock), filePath, out hier, out itemid, out docData, out cookie));
+ if (docData != IntPtr.Zero)
+ {
+ IVsPersistDocData persistDocData = Marshal.GetObjectForIUnknown(docData) as IVsPersistDocData;
+ Marshal.Release(docData);
+
+ try
+ {
+ // Try to get the Text lines
+ IVsTextLines srpTextLines = persistDocData as IVsTextLines;
+ if (srpTextLines == null)
+ {
+ // Try getting a text buffer provider first
+ IVsTextBufferProvider srpTextBufferProvider = persistDocData as IVsTextBufferProvider;
+ if (srpTextBufferProvider != null)
+ {
+ ErrorHandler.ThrowOnFailure(srpTextBufferProvider.GetTextBuffer(out srpTextLines));
+ }
+ // TODO : handle null case
+ }
+
+ //int endLine, endIndex;
+ //srpTextLines.GetLastLineIndex(out endLine, out endIndex);
+
+ // Lock the buffer before changing its content.
+ ErrorHandler.ThrowOnFailure(srpTextLines.LockBuffer());
+ try
+ {
+ processFunc( srpTextLines );
+ }
+ finally
+ {
+ // Make sure that the buffer is unlocked also in case of exception.
+ srpTextLines.UnlockBuffer();
+ }
+ }
+ finally
+ {
+ ErrorHandler.ThrowOnFailure(rdt.UnlockDocument((uint)(_VSRDTFLAGS.RDT_ReadLock | _VSRDTFLAGS.RDT_Unlock_NoSave), cookie));
+ }
+ }
+ else
+ {
+ //Debug.Print("Document hasn't been found in RDT");
+ throw new ApplicationException("Document hasn't been found in RDT");
+ }
+ }
+
+ // helper function
+ private string GetText(List<string> lines)
+ {
+ StringBuilder builder = new StringBuilder();
+
+ foreach (string s in lines)
+ builder.AppendLine(s);
+
+ return builder.ToString();
+ }
+ }
+}
More information about the svn
mailing list