I’ve been struggling today with nasty issue in my Help Builder Add-in. One of the features of the environment is the ability to easily determine which type of code element you’re sitting in the code editor (at least in C# code). So you can tell whether you’re sitting in a method, a variable definition, even and so on. Once you get a reference you can retrieve information about the method or property etc.

 

In theory the Environment should be able to return this to you with just this:

 

TextSelection selection =

(TextSelection) this.DTE.ActiveWindow.Selection;

 

EditPoint selPoint = selection.ActivePoint.CreateEditPoint();

 

CodeElement element =

 this.DTE.ActiveDocument.ProjectItem.FileCodeModel.CodeElementFromPoint(selPoint,0);

 

Unfortunately it’s not as easy as that because of funky behavior of the edit point. Specifically the selection point must be inside the definition EXACTLY. This means if you’re moving the cursor pointer around using the selPoint and you do things like StartOfLine() a lot of times you actually end up outside of the definition.

 

I ran across this post that hints at this same problem, which suggests moving the cursor pointer one line down. Unfortunatlely this also causes problems with one liners like property definitions, events and interface methods.

 

private string Test = "Hellop World";

 

or interface definitions like:

 

public void Update();

 

If you don’t move the pointer and you place it inside the body – no problem. So why do I want to move the pointer? Well for one thing if the user has a comment highlighted I want to be able to skip over the comments. According to the code model the XML Comments belong to the class not the method or property that it attaches to.

 

Long story short what you need to do is go to the line of your choice then jump to the end and backwards one character to make sure that you are inside the definition. Here’s a generic routine (at least for C#) that generically selects the proper CodeElement including support for CSharp XML comments.

 

/// <summary>

/// Returns an adjusted CodeElement. Walks comments 'down' to the next real element.

/// </summary>

/// <returns></returns>

public CodeElement GetCodeElement(CodeLanguages CodeLanguage)

{

      TextSelection selection =

            (TextSelection) this.DTE.ActiveWindow.Selection;

 

      EditPoint selPoint = selection.ActivePoint.CreateEditPoint();

      selPoint.StartOfLine();

 

      while(true)

      {                                  

            string BlockText = selPoint.GetText(selPoint.LineLength).Trim();

           

            // *** Skip over any XML Doc comments

            if (CodeLanguage == CodeLanguages.CSharp && BlockText.StartsWith("//")  ||

                  CodeLanguage == CodeLanguages.VB && BlockText.StartsWith("' ")  )

            {

                  selPoint.LineDown(1);

                  selPoint.StartOfLine();

            }

            else

                  break;

      }                                  

 

      // *** Make sure the cursor is placed inside of the definition always

      // *** Especially required for single line methods/fields/events etc.

      selPoint.EndOfLine();

      selPoint.CharLeft(1);  // Force into the text

 

      // get the element under the cursor

      CodeElement element = this.DTE.ActiveDocument.ProjectItem.FileCodeModel.CodeElementFromPoint(selPoint,0);

 

      return element;

}

 

Note the selPoint.EndOfLine() and selPoint.CharLeft(1) which are the key that got this to work reliably. Without these two lines of code the above code would always return class values for any one line definitions. I hope this saves somebody the time it took me today… grrr.