Visual Studio Keymaps using F#


I always try and avoid using mouse, for example even for browsing internet I don’t use mouse rather I use vimperator and within VS.NET I use ViEmu. So for VS.NET I am always looking for keyboard shortcuts for the things that I cannot do through ViEmu. To do that in VS.NET I would have jump through hoops to figure out a keyboard shortcut for a command, I would have to navigate to Tools/Options/Environment/Keyboard, which is a pain.

There is also a plugin for ReSharper to do the same.

So I wanted something like the plugin, but something that I can query for. So I was planning to write one ,as a Visual Studio addin which is supposed to couple of things

  1. Pass a partial command name , which returns list of commands and their corresponding keyboardshortcuts
  2. Pass a Partial keyboardshortcut , which returns list of commands that have the keys as binding

And that’s when I stumbled upon Accessing Visual Studio’s Automation API from F# Interactive . That’s awesome and it gives the great power for automation with succinct code.

Here is the code for doing the things that I wanted to write


let convert (c:Command) = ((c.Bindings) : ?> System.Object[] |> Seq.cast<string>)

let keymapsearch key =
 myDTE.Commands |>
 Seq.cast<Command> |>
 Seq.filter(fun k -> convert k |> Seq.exists(fun k -> k.Contains(key))) |>
 Seq.map(fun k -> (k.Name, (convert k ) |> Seq.head))

let commandkeysearch c =
 myDTE.Commands |>
 Seq.cast<Command> |>
 Seq.filter( fun k -> k.Name.StartsWith(c)) |>
 Seq.map( fun k -> (k.Name, convert k ))

FYI this code has to be sent to interactive FSI window within Visual Studio .NET and to use this you would still rest of the code which is available from the original post.

And here is the usage of the above functions

commandkeysearch “Debug.Bre”

>
val it : seq<string * seq<string>> =
seq
[(“Debug.BreakAll”, seq ["Global::Ctrl+Alt+Break"]);
(“Debug.Breakpoints”, seq []); (“Debug.BreakInFile”, seq []);
(“Debug.BreakatFunction”, seq []); …]

keymapsearch “Ctrl+F5″

val it : seq<string * string> =
seq
[("Debug.StartWithoutDebugging", "Global::Ctrl+F5");
("Data.SqlEditorValidateSqlSyntax", "Transact-SQL Editor::Ctrl+F5")]

The automation provides REPL to VS.NET which is really handy.

Use IntelliTrace without Visual Studio .NET


IntelliTrace is one of the best things in Visual Studio 2010. You can do “time travel” through your managed code. John Robbins has cool cool blog post on the working on IntelliTrace. I knew I could use IntelliTrace in field where the customer does not have VS.NET. To use in field ,I had to figure out the dependencies for IntelliTrace. So I fired up Windbg attached to the IntelliTrace process which would dump the all the modules loaded


Symbol search path is: SRV*D:\symbols*http://msdl.microsoft.com/download/symbols
Executable search path is:
ModLoad: 01210000 01218000   D:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\TraceDebugger Tools\IntelliTrace.exe
ModLoad: 77d20000 77ea0000   C:\Windows\SysWOW64\ntdll.dll
ModLoad: 716b0000 716fa000   C:\Windows\SYSTEM32\MSCOREE.DLL
ModLoad: 76fe0000 770e0000   C:\Windows\syswow64\KERNEL32.dll
ModLoad: 75de0000 75e26000   C:\Windows\syswow64\KERNELBASE.dll
ModLoad: 75b90000 75c30000   C:\Windows\syswow64\ADVAPI32.dll
ModLoad: 77360000 7740c000   C:\Windows\syswow64\msvcrt.dll
ModLoad: 76000000 76019000   C:\Windows\SysWOW64\sechost.dll
ModLoad: 75cf0000 75de0000   C:\Windows\syswow64\RPCRT4.dll
ModLoad: 75890000 758f0000   C:\Windows\syswow64\SspiCli.dll
ModLoad: 75880000 7588c000   C:\Windows\syswow64\CRYPTBASE.dll
ModLoad: 71050000 710b6000   C:\Windows\Microsoft.NET\Framework\v4.0.30319\mscoreei.dll
ModLoad: 77440000 77497000   C:\Windows\syswow64\SHLWAPI.dll
ModLoad: 75910000 759a0000   C:\Windows\syswow64\GDI32.dll
ModLoad: 76cf0000 76df0000   C:\Windows\syswow64\USER32.dll
ModLoad: 774a0000 774aa000   C:\Windows\syswow64\LPK.dll
ModLoad: 759a0000 75a3d000   C:\Windows\syswow64\USP10.dll
ModLoad: 770e0000 77140000   C:\Windows\system32\IMM32.DLL
ModLoad: 77710000 777dc000   C:\Windows\syswow64\MSCTF.dll
ModLoad: 6b590000 6bbff000   C:\Windows\Microsoft.NET\Framework\v4.0.30319\clr.dll
ModLoad: 70c00000 70cbe000   C:\Windows\system32\MSVCR100_CLR0400.dll
ModLoad: 6a7c0000 6b583000   C:\Windows\assembly\NativeImages_v4.0.30319_32\mscorlib\246f1a5abb686b9dcdf22d3505b08cea\mscorlib.ni.dll
ModLoad: 71b50000 71b60000   C:\Windows\Microsoft.NET\Framework\v4.0.30319\nlssorting.dll
ModLoad: 76df0000 76f4c000   C:\Windows\syswow64\ole32.dll
ModLoad: 6d2b0000 6db48000   C:\Windows\assembly\NativeImages_v4.0.30319_32\System\964da027ebca3b263a05cadb8eaa20a3\System.ni.dll
ModLoad: 74180000 74188000   C:\Windows\assembly\NativeImages_v4.0.30319_32\IntelliTrace\947780232db1934c92cdfdaf2433bb59\IntelliTrace.ni.exe
ModLoad: 59660000 59a9d000   C:\Windows\assembly\NativeImages_v4.0.30319_32\Microsoft.VisualStu#\c828bb166e9d0df0e9b44a0a7616624a\Microsoft.VisualStudio.IntelliTrace.ni.dll
ModLoad: 618b0000 6196e000   C:\Windows\assembly\NativeImages_v4.0.30319_32\System.Runtime.Remo#\dc1f0dbf1d3ba856eccec90b62b55d79\System.Runtime.Remoting.ni.dll
ModLoad: 66330000 66883000   C:\Windows\assembly\NativeImages_v4.0.30319_32\System.Xml\e997d0200c25f7db6bd32313d50b729d\System.Xml.ni.dll
ModLoad: 75c60000 75cef000   C:\Windows\syswow64\oleaut32.DLL
ModLoad: 70a10000 70a70000   C:\Windows\Microsoft.NET\Framework\v4.0.30319\clrjit.dll
ModLoad: 58fe0000 59085000   C:\Program Files (x86)\Common Files\Microsoft Shared\VSTS 10.0\Trace Debugger\TraceLogProfiler.dll
ModLoad: 77cf0000 77cf5000   C:\Windows\syswow64\PSAPI.DLL
ModLoad: 76020000 76c69000   C:\Windows\syswow64\SHELL32.dll
ModLoad: 752a0000 752a9000   C:\Windows\system32\VERSION.dll
ModLoad: 753e0000 753f6000   C:\Windows\system32\CRYPTSP.dll
ModLoad: 75360000 7539b000   C:\Windows\system32\rsaenh.dll
ModLoad: 753d0000 753de000   C:\Windows\system32\RpcRtRemote.dll
ModLoad: 650e0000 650e5000   C:\Windows\system32\shfolder.dll
ModLoad: 75300000 7535f000   C:\Windows\system32\sxs.dll
(159c.6bc): Break instruction exception - code 80000003 (first chance)
eax=7ef39000 ebx=00000000 ecx=00000000 edx=77dbf50a esi=00000000 edi=00000000
eip=77d3000c esp=0683fca4 ebp=0683fcd0 iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
ntdll!DbgBreakPoint:
77d3000c cc              int     3

So from the above output I figure out the required files for running IntelliTrace without VS.NET is

  1. IntelliTrace.exe
  2. IntelliTrace.exe.config
  3. Microsoft.VisualStudio.IntelliTrace.dll
  4. TraceLogProfiler.dll

The next thing was to check the command line options for IntelliTrace

  1. start
  2. status
  3. stop
  4. run
  5. launch
  6. help

I knew the command line option should have something interesting. So when the IntelliTrace was running I looked at command line options in Process Explorer

"D:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\TraceDebugger Tools\IntelliTrace.EXE" run /n:"test.exe_00001330_01cae6f016c66e1e"
/cp:"C:\Users\naveen\AppData\Local\Microsoft\VisualStudio\10.0\TraceDebugger\Settings\aoqsbu4g.fpj" /f:"Test_00001330_100428_123018.iTrace"

When i tried to open the “C:\Users\naveen\AppData\Local\Microsoft\VisualStudio\10.0\TraceDebugger\Settings\aoqsbu4g.fpj”, I got this error

So I went to the directory and  just made a copy of the file and then I was able to open the file. It was a xml file which contains most of the settings. It is collection plan settings file. The things that I updated in this file were <LogFileDirectory useDefault=”false”>C:\temp\</LogFileDirectory> and <DeleteLogOnExit>false</DeleteLogOnExit> for my test run. There are lot of settings in this file, which can be tweaked based on need,do explore this file.

With all this information, the next thing was to get a clean Installation of  Windows without VS.NET, good thing I had my laptop which had that. So I copied the required dependencies mentioned above along with a simple test.exe  which I used for figuring out Watson Buckets and here are the contents of the directory

And here is the command line option to start tracing Test.exe

C:\Users\naveen\intellitrace\intellitrace>IntelliTrace.exe launch /cp:q0mmz2ch.ixp Test.exe

The /cp:q0mmz2ch.ixp is the collection plan file. Here is the output from the above command

C:\Users\Naveen\intellitrace\intellitrace>IntelliTrace.exe launch /cp:q0mmz2ch.i
xp Test.exe
Microsoft (R) Visual Studio Logger. Version 10.0.30319.1
Copyright (C) Microsoft Corporation. All rights reserved.
C:\Users\Naveen\intellitrace\intellitrace\IntelliTrace.exe run /name:c__temp_9ce
6b8f1-19a5-44b7-909d-512a4c74e632.itrace /logfile:C:\temp\9ce6b8f1-19a5-44b7-909
d-512a4c74e632.iTrace /buffersize:65536 /buffercount:512 /watch:-1 /help- /nolog
o+ /collectionplan:q0mmz2ch.ixp /hidden-
Logger name is ‘c__temp_9ce6b8f1-19a5-44b7-909d-512a4c74e632.itrace’
Log file path ‘C:\temp\9ce6b8f1-19a5-44b7-909d-512a4c74e632.iTrace’
Using 512 buffers of 65536 bytes each.
Logger started.
Logger started.
Press Ctrl+C to stop logging or use ‘IntelliTrace stop /name:c__temp_9ce6b8f1-19
a5-44b7-909d-512a4c74e632.itrace’ from another command line.
Starting process ‘C:\Users\Naveen\intellitrace\intellitrace\Test.exe’
Waiting for process to exit
WatsonTest.WatsonBuckets

Unhandled Exception: System.NullReferenceException: Object reference not set to
an instance of an object.
at WatsonTest.Test.Main(String[] args)
Process exited with exit code -532462766.
Process execution time: 5097 ms
Logger name is ‘c__temp_9ce6b8f1-19a5-44b7-909d-512a4c74e632.itrace’
Stopping logger

That’s cool. I was able to trace it without VS.NET on the box and  I brought the trace file back to dev machine I was able load it up in VS.NET and could see stacks. And also from the above output I figured out  there are other undocumented command line options.

VS2010 AddIn for setting Break-points on partial function names


I was recently watching cool debugging video from John Robbins. Its got some amazing tips for debugging. In the video, John had mentioned that  VS2010 does not have a way to set partial function name break-points in managed code. He also wanted someone to implement that. So I though why not pick up the gauntlet.   So went ahead and implemented a VS2010 addin that can use wildcard /regex function names for setting break-points. I reused lot of code from John’s macro for setting break-points. The source code and the compiled version can be download from here. I have tested this only my box. If there are any issues please comment back. I could have used Intellisense for the function names, probably I will do it as next release.

  1. The AddIn function name can have wildcard / regular expression for setting break-points.
  2. By default it uses only function name and not the full qualifier like Namespace.Class.FunctionName
  3. To fully qualify with NameSpace.Class.FunctionName , check the FullName check box
  4. By default function name search is not case sensitive , to have case sensitive search check the case sensitive check box

I have not created an installer. To deploy the addin extract the zip file and copy “RegularExpressionBP.AddIn” to addins directory which happened to “C:\Users\naveen\Documents\Visual Studio 2010\Addins” . The addin locations are loaded from the following locations

Update the RegularExpressionBP.AddIn to point to the location of the RegularExpressionBP.dll

<Assembly>c:\users\naveen\documents\visual studio 2010\Projects\RegularExpressionBP\bin\RegularExpressionBP.dll</Assembly>

The addin will be loaded in the tools menu by default.

Here is the partial code for setting the break-point on partial function names . The full source code can be downloaded from here


private void SetPartialNameBreakpointsMethods(string funcName)
 {
 var count = _dte2.Solution.Projects.Count;
 for (var i = 1; i <= count; i++)
 {
 for (var j = 1; j <= _dte2.Solution.Projects.Item(i).ProjectItems.Count; j++)
 {
 if (_dte2.Solution.Projects.Item(i).ProjectItems.Item(j).FileCodeModel == null)
 {
 continue;
 }
 else
 {
 var srcFile = _dte2.Solution.Projects.Item(i).ProjectItems.Item(j).Name;
 ProcessCodeElements(_dte2.Solution.Projects.Item(i).ProjectItems.Item(j).FileCodeModel.CodeElements, srcFile,funcName);
 }
 }
 }
 }
 private void ProcessCodeElements(CodeElements elems, string srcFile, string funcName)
 {
 foreach (CodeElement currElem in elems.Cast<CodeElement>())
 {
 var ns = default(CodeNamespace);
 var codeType = default(CodeType);
 var codeFunction = default(CodeFunction);

 if (currElem is CodeNamespace)
 {
 ns = (CodeNamespace) currElem;
 ProcessCodeElements(ns.Members, srcFile, funcName);
 }
 else if (currElem is CodeType)
 {
 codeType = (CodeType) currElem;
 ProcessCodeElements(codeType.Members, srcFile, funcName);
 }
 else if (currElem is CodeFunction | currElem is CodeProperty)
 {
 try
 {
 codeFunction = (CodeFunction) currElem;
 }
 catch
 {
 }
 TextPoint txtPt = default(TextPoint);
 try
 {
 txtPt = currElem.StartPoint;
 }
 catch
 {
 txtPt = null;
 }

 if ((txtPt != null && codeFunction != null &&
 new Wildcard(funcName,caseSensitive.Checked == false ? RegexOptions.IgnoreCase: RegexOptions.None).
 Match(fullName.Checked ? codeFunction.FullName: codeFunction.Name).Success))
 {
 SafelySetBreakpoint(srcFile, txtPt.Line);
 }
 }
 }
 }
 private void SafelySetBreakpoint(string srcFile, int lineNum)
 {
 try
 {
 _dte2.Debugger.Breakpoints.Add("", srcFile, lineNum);

 }
 catch (COMException ex)
 {
 }
 }

using System.Text.RegularExpressions;

namespace RegularExpressionBP
{
 /// <summary>
 /// Represents a wildcard running on the
 /// <see cref="System.Text.RegularExpressions"/> engine.
 /// </summary>
 public class Wildcard : Regex
 {
 /// <summary>
 /// Initializes a wildcard with the given search pattern.
 /// </summary>
 /// <param name="pattern">The wildcard pattern to match.</param>
 public Wildcard(string pattern)
 : base(WildcardToRegex(pattern))
 {
 }

 /// <summary>
 /// Initializes a wildcard with the given search pattern and options.
 /// </summary>
 /// <param name="pattern">The wildcard pattern to match.</param>
 /// <param name="options">A combination of one or more
 /// <see cref="RegexOptions"/>.</param>
 public Wildcard(string pattern, RegexOptions options)
 : base(WildcardToRegex(pattern), options)
 {
 }

 /// <summary>
 /// Converts a wildcard to a regex.
 /// </summary>
 /// <param name="pattern">The wildcard pattern to convert.</param>
 /// <returns>A regex equivalent of the given wildcard.</returns>
 public static string WildcardToRegex(string pattern)
 {
 return "^" + Escape(pattern).
 Replace("\\*", ".*").
 Replace("\\?", ".") + "$";
 }
 }
}

I used the VS2010 automation object model chart for discovering object hierarchy, which was extremely useful.

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: