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 theif
. 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
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.
Published: Sunday, 1 January 2012, 11:02 PM
Author: Tomas Petricek
Typos: Send me a pull request!
Tags: functional, mono