KnowDotNet Visual Organizer

C# Add-In - Use the CodeModel to retrieve a procedure from a code window.

GetWholeProcedure

by Les Smith
Print this Article Discuss in Forums

How can I retrieve all of the code from a procedure, in which the cursor is positioned in a code window?  I had previously written an article on this, but the code for it would not work consistently.  For example, if a VB.NET Function or Sub definition line was continued, the code would raise an exception.  Additionally, the code would not pick up C# "Block Comments".  In order to get the code to work for all cases, both C# and VB.NET, I have rewritten this code and since it is fairly extensive, encapsulated all of the code in a Class.  To get the code to work consistently, in all cases, for projects developed in both VB.NET and C#, I have resorted to the use of the FileCodeModel.

This article will describe the
the CSharp Code for this process.  It will also describe the code for getting the desired method and any leading comments.

This article assumes that the developer has positioned the cursor somewhere in a Visual Basic or CSharp (C#) code window, in the Visual Studio .NET IDE.  If the user has positioned the cursor, anywhere but inside a method, the GetWholeProcedure method will return an empty string.  The add-in code will use the TextSelection and EditPoint objects to return the text of the whole procedure, in which the cursor was positioned, prior to triggering the add-in code to call the procedure.  

The code for getting the whole procedure is now encapsulated in a class, since the complete code is quite extensive.  To use the code in a macro, change the "applicationObject" to "DTE".  Also, since only Subs may be called from the IDE, you will have to call a Sub which, in turn, calls the respective function.

The first thing that GetWholeProcedure does is to call a method named SearchForFunctionInCurrentWindow.  This method, and its helper method, GetFunction, use the FileCodeModel to find a method that encompasses the current cursor position.  If found, the CodeElement object exposes the start and end lines for the method.  It places them in the Structure dimensioned as OneFunction.  It then returns to the GetWholeProcedure.  If the search method finds a method that encompasses the cursor position, the GetWholeProcedure method proceeds by calling a method, based on the code language for the project, to move the EditPoint object to the top of any comments that precede the selected method in the code window.  If there are no comments, or there is an invalid block comment (C# only), the EditPoint will be left, or moved back to the beginning of the procedure.  Finally, a determination is made to see whether the caller wanted the code to be highlighted or not.  If the code is to be replaced, deleted, or moved, the caller will specify that the desired code is highlighted with the TextSelection object.  Leaving the code highlighted will allow the caller to delete the code at a later time by using the Delete method of the TextSelection object.  If the user does not want the code highlighted, I use the EditPoint object to retreive the code selection.

The call to GetWholeProcedure to retrieve a method from a VB.NET project code window, do the following:

   string S = GetWholeProcedure(True, True, 8);

To retrieve a method from a C# project, use the following line of code:

   string S = GetWholeProcedure(True, True, 9);


Figure 1 - C# CGetProc Code.

using System;
using Extensibility;
using EnvDTE;
namespace CSharp2003Test
{
  
/// <summary>
  /// Summary description for CGetProc.
  ///
  public class CGetProc
  {
    
private EnvDTE.DTE oVS;
    
private struct SFunction
    {
      
public string FNAME;
      
public int StPtr;
      
public int EndPt;
      
public int CCaret;
      
public bool BDone;
    }
    
private SFunction OneFunction;
    
// Returns code for procedure where caret was in
    // the ActiveWindow.  Lang must be 8 or 9 for VB or C#.
    // Set withComments to True if you want any preceding comments
    // returned with the proc.  Set hiLite to true if you want the code
    // selected in the code window.
    public string GetWholeProcedure(bool withComments,
      
bool hiLite,short lang)
    {
      TextSelection ts = (TextSelection) oVS.ActiveDocument.Selection;
      EditPoint ep = ts.ActivePoint.CreateEditPoint();
      OneFunction.CCaret=ep.Line;
      OneFunction.BDone=
false;

      SearchForFunctionInCurrentWindow();
      
// if we found the function, get the code
      if(OneFunction.BDone)
      {
        ep.MoveToLineAndOffset(OneFunction.StPtr,1);
        
if(withComments)
        {
          
// back the ep up to the beginning of any comments
          if(lang==8)
            PositionToTopOfVBComments(
ref ep);
          
else
            PositionToTopOfCSComments(ref ep);

          
// get the code
          if(!hiLite)
          {
            
string s = ep.GetLines(ep.Line,
              OneFunction.EndPt+1);
            
return s;
          }
          
else
          {
            ts.MoveToLineAndOffset(ep.Line,1,
false);
            ts.MoveToLineAndOffset(OneFunction.EndPt+1,
              1,
true);
            
return ts.Text;
          }
        }
        
else
          return string.Empty;
      }
      
return string.Empty;
    }

    
// Move the editpoint to the first line of comments
    // above the proc def line if any are there
    private void PositionToTopOfVBComments(ref EditPoint ep)
    {
      
string sLine;
      
do
      {
        ep.LineUp(1);
        ep.StartOfLine();
        sLine = ep.GetText(ep.LineLength).Trim();
        
if(sLine.Length==0 || !sLine.StartsWith("'"))
        {
          ep.LineDown(1);
          ep.StartOfLine();
          
break;
        }
      }
while(true);
    }
    
// move the editpoint to the first line of comments
    // above the function line if any comments extant
    private void PositionToTopOfCSComments(ref EditPoint ep)
    {
      
string s;
      
int stPT = ep.Line; // remember where we started
      ep.LineUp(1);
      ep.StartOfLine();
      s = ep.GetText(ep.LineLength).Trim();

      
if(s.EndsWith("*/") && !s.StartsWith("//"))
      {
        
if (s.StartsWith("/*") &&
          (s.IndexOf("/*") == s.LastIndexOf("/*")))
          
// success, stop moving
          return;
        
else if (s.IndexOf("/*") == -1)
        {
          
do
          {
            ep.LineUp(1);
            
if(ep.AtStartOfDocument)
            {
              ep.MoveToLineAndOffset(stPT,1);
              
return;
            }
            ep.StartOfLine();
            s = ep.GetText(ep.LineLength).Trim();
            
if(s.IndexOf("/*")>-1)
            {
              
if(s.StartsWith("/*") &&
                (s.IndexOf("/*")==s.LastIndexOf("/*")))
                
return;
              
else
              {
                ep.MoveToLineAndOffset(stPT,1);
                
break;
              }
            }
          }
while(true);
        }
        
else
        {
          ep.MoveToLineAndOffset(stPT,1);
          
return;
        }
      }
      
else if(s.StartsWith("//"))
      {
        
do
        {
          ep.LineUp(1);
          
if(ep.AtStartOfDocument)
          {
            ep.MoveToLineAndOffset(stPT,1);
            
return;
          }
          ep.StartOfLine();
          s=ep.GetText(ep.LineLength).Trim();
          
if(!s.StartsWith("//"))
          {
            ep.LineDown(1);
            ep.StartOfLine();
            
return;
          }
        }
while(true);
      }
      
else
        ep.LineDown(1);
    }
    
// enumerates all classes in all namespaces in a window
    private void SearchForFunctionInCurrentWindow()
    {
      ProjectItem pi = (ProjectItem) oVS.ActiveWindow.ProjectItem;
      FileCodeModel fcm = pi.FileCodeModel;

      
if ( fcm!=null)
        GetFunction(fcm.CodeElements);
    }

    
private void GetFunction(CodeElements elements)
    {
      
// ct could be a namespace or a class.
      // we are looking only for functions
      // and in particular, one that encapsulates ccaret
      // this method will recursively drill down until
      // it finds a function, if there is one and test
      // its boundaries
      CodeElements members;
      CCLassList oneClass =
new CCLassList();
      
foreach(CodeElement elt in elements)
      {
        
if(OneFunction.BDone)
          
return;

        
if (elt.Kind==vsCMElement.vsCMElementFunction)
        {
          OneFunction.FNAME=elt.Name;
          OneFunction.StPtr=elt.StartPoint.Line;
          OneFunction.EndPt=elt.EndPoint.Line;
          
if(OneFunction.CCaret>= OneFunction.StPtr &&
             OneFunction.CCaret <= OneFunction.EndPt)<BR>           {
            OneFunction.BDone=
true;
            
return;
          }
        }
        
else
        {
          members = GetMembers(elt);
          
if(members != null)
            GetFunction(members);
        }
      }
    }
    
private CodeElements GetMembers(CodeElement elt)
    {
      CodeElements members =
null;
      
if(elt!=null)
      {
        
switch( elt.Kind)
        {
          
case (vsCMElement.vsCMElementNamespace):
          {
            CodeNamespace cdeNS = (CodeNamespace) elt;
            members = cdeNS.Members;
            
break;
          }
          
case (vsCMElement.vsCMElementClass):
          {
            CodeClass cdeCL = (CodeClass) elt;
            members = cdeCL.Members;
            
break;
          }
          
case (vsCMElement.vsCMElementFunction):
          {
            
// functions dont have members
            members=null;
            
break;
          }
        }
      }
      
return members;
    }
    
public CGetProc(EnvDTE.DTE roVS)
    {
      oVS=roVS;
    }
  }
  
public class CCLassList
  {
    
public string CName;
    
public int StPt;
    
public int EndPt;
    
public string sCode;
    
public bool Usable;
  }
}
Go to Top

Writing Add-Ins for Visual Studio .NET
Writing Add-ins for Visual Studio .NET
by Les Smith
Apress Publishing