I'm trying to resolve the fully qualified name of a c# identifier at a certain point (cursor) of a code window, using a Macro (or even an Add-in) in Visual Studio 2008.
For example, if the cursor is in "Rectangle", I would like "System.Drawing.Rectangle" returned.
I've tried FileCodeModel.CodeElements
and .CodeElementFromPoint
but they only retrieve the containing method or class (and others).
If this can't be done using a macro or add-in (even though VS does know the information via intellisense), would it be possible to use Reflection read in the c# file and get the desired info?
It can be done. Here's one solution (albeit a somewhat hacky one): use F1 Help Context. In order to make F1 help work, Visual Studio pushes the fully-qualified type name of the current selection or insertion point into a bag of name/value pairs called "F1 Help Context". And there are public APIs in the Visual Studio SDK for querying the contents of F1 Help Context.
In order to stay sane, you'll want to enable the debugging registry key for F1 Help Context. This lets you see what's in Help Context at any time via the oft-maligned Dynamic Help window. To do this:
Here's the registry key:
As you'll see from looking at the F1 debug output, Visual Studio doesn't explicitly tell you "this is the identifier's type". Instead, it simply sticks the fully-qualified type name at the head of one or more "Help Keywords" which F1 uses to bring up help. For example, you can have System.String, VS.TextEditor, and VS.Ambient in your help context, and only the first one is related to the current code.
The trick to make this easier is this: Visual Studio can mark keywords as case-sensitive or case-insensitive. AFAIK, the only part of Visual Studio which injects case-sensitive keywords is the code editor of case-sensitive languages (C#, C++) in response to code context. Therefore, if you filter all keywords to case-sensitive keywords, you know you're looking at code.
Unfortunately, the C# editor also pushes language keywords (not just identifiers) into help context if the insertion point is on top of a language keyword. So you'll need to screen out language keywords. There are two ways to do this. You can simply try to look them up in the type system, and since they're not valid type names (especially not the way VS mangles them, e.g. "string_CSharpKeyword" for the string keyword) you can just fail silently. Or you can detect the lack of dots and assume it's not a type name. Or you can detect the _CSharpKeyword suffix and hope the VS team doesn't change it. :-)
Another potential issue is generics. The type name you'll get from VS for a generic type looks like this:
and methods look like this:
You'll need to be smart about detecting the back-tick and dealing with it.
Also, you might get interesting behavior in cases like ASP.NET MVC .ASPX files where there's both C# code and other case-sensitive code (e.g. javascript) on the page. In that case, you'll need to look at the attributes as well. In addition to keywords, Help Context also has "attributes", which are name/value pairs describing the current context. For example, devlang=csharp is one attribute. The code below can be used to pull out attributes too. You'll need to experiment to figure out the right attributes to look for so you don't end up acting on javascript or other odd code.
Anyway, now that you understand (or at least have been exposed to!) all the caveats, here's some code to pull out the case-sensitive keyword (if it exists) from help context, as well as the rest of the name/value pairs. (keywords are simply name/value pairs whose name is "keyword").
Keep in mind that this code requires the Visual Studio SDK (not just the regular VS install) in order to build, in order to get the Microsoft.VisualStudio.Shell.Interop, Microsoft.VisualStudio.Shell, and Microsoft.VisualStudio.OLE.Interop namespaces (which you'll need to add as references in your addin project).
OK, have fun and good luck!