Regions and navigation bar for F# in Visual Studio

The beginning of a new year may be a good time for writing one lightweight blog post that I wanted to publish for some time now. During my last internship with the F# team at MSR, I did some work on improving the F# IntelliSense in Visual Studio. This mostly involved the usual features - automatic completion, colouring and tooltips. The F# IntelliSense in Visual Studio 2010 still isn't perfect, but I think it is safe to claim that it is the best IDE experience for a typed functional programming language (aside, you can vote for some F# IDE features here and here).

Regions and navigation bar

Anyway, I also spent some time with prototyping and I implemented and experimental version of navigation bar and code collapsing. You can see both of the features in the attached screenshot.

The implementation is really just a prototype that proves that it can be done. There are some bugs and it doesn't implement all features that the C# version does (for example, if you collapse some definitions and reopen the file, all definitions will be expanded again). Nevertheless, it can be sometimes useful.

Both of the features can be enabled by modifying the devenv.exe.config file in the Visual Studio installation folder. By default, you can find it in the Common7\IDE subdirectory in C:\Program Files (x86)\Microsoft Visual Studio 10.0. You need to add the following appSettings section to the root configuration element:

<appSettings>
    <add key="fsharp-navigationbar-enabled" value="true" />
    <add key="fsharp-regions-enabled" value="true" />
</appSettings>

After adding these two keys, you should see navigation bar and buttons for collapsing type and module definitions in F# source files. Keep in mind that this is just a prototype, so it may not always work well!

I always had both of the features enabled on my machine (and some people noticed during various F# talks :-)), but I have to say that I haven't been using them very often. The collapsing of definitions is occasionally nice if you have multiple types in a single file, but F# type definitions are usually quite terse. Similarly, the navigation bar is nice if you're editting large file (like F# type checker in tc.fs), but I usually find what I'm looking for just using search. If you enable and use the feature, I'd be quite interested to hear about your experience. I'm sure that the F# team will find the feedback useful too (you can always vote for features at Visual Studio user voice page.

Plugins & implementation notes

With the open-source release of F#, it has become a lot easier to implement a Visual Studio plugin that extends the F# editor. F# parser to get information about F# source code without writing a custom parser and type-checker. There are quite a few projects that do that, including some that implement support for collapsing of F# declarations:

  • F# Outlining makes it possible to collapse blocks of F# code. In addition to the built-in functionality that can be enabled using a special key, the plugin also supports marking regions of code with //#region tags.
  • FSharpJump provides an alternative navigation menu for F# files. It displays a hierarchical list of F# definitions such as modules, types, members and let bindings. The plugin is available with a source code that implements its own simplified F# parser.
  • Aside from navigation, F# Depth Colorizer fills the background of F# source code depending on the nesting. For example, the body of if is filled with different color than the code following the if. This makes the indentation-based syntax even easier to read. The plugin is written by Brian McNamara from the F# team, so it may be the best place to look if you want to write similar tool yourself.

Using F# parsing service

GitHub logo

The code that implements navigation bars and collapsing in the F# IntelliSense for Visual Studio is divided into two parts. The first part (which is not publicly available) passes information from F# compiler service to Visual Studio. The second part extracts information about declarations from the F# syntax tree (AST) obtained from the compiler service. The second part is available in the open-source release and so it can be used by various tools based on the F# compiler code (including the F# plugin for MonoDevelop).

I'll give a couple of pointers for those who might be interested in using (and extending) the source code. The F# compiler API used by Visual Studio is implemented in the FSharp.Compiler.dll. The most important type to look at is InteractiveChecker, which can be found in service.fsi file (see F# source code at GitHub).

If you're interested in a sample that uses the InteractiveChecker type, then take a look at the F# binding for MonoDevelop. The simplest example is probably a command line tool fsintellisense that can be used to get IntelliSense information from via a command line (i.e. from Emacs or Vi editors). The tool can be found on CodePlex.

If you're writing plugin using FSharp.Compiler.dll, it is easy to get the declarations using the InteractiveChecker type. Assuming we have an instance checker and a few other values that are required to run the parsing, we can write:

// Run parsing of a file loaded in 'source'
let untypedInfo = checker.UntypedParse(fileName, source, checkOptions) 
// Get declaration items for the file
let declarations = untypedInfo.GetNavigationItems().Declarations

// Iterate over all top-level declarations (modules, types)
for topLevel in decls do
  let (s1, e1), (s2, e2) = topLevel.Declaration.Range
  printfn "[%d:%d-%d:%d] %s" s1 e1 s2 e2 topLevel.Declaration.Name
  
  // Print all child declarations (members, functions, values)
  for nested in topLevel.Nested do
    let (s1, e1), (s2, e2) = nested.Range
    printfn "  - [%d:%d-%d:%d] %s" s1 e1 s2 e2 nested.Name

The function GetNavigationItems is used by the Visual Studio code that displays navigation bar and collapsible regions. This means that it is not fully tested (and may not return all the information that you'd like). However, it is a part of the open-source release, and so it should be possible to improve it a bit.

Discuss on twitter, .
Send corrections via GitHub pull requests.