[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