This tutorial demonstrates how to use the editor services provided by the F# compiler.
This API is used to provide auto-complete, tool-tips, parameter info help, matching of
brackets and other functions in F# editors including Visual Studio, Xamarin Studio and Emacs
(see fsharpbindings project for more information).
Similarly to the tutorial on using untyped AST, we start by
NOTE: The FSharp.Compiler.Service API is subject to change when later versions of the nuget package are published
As in the previous tutorial (using untyped AST), we start by referencing
FSharp.Compiler.Service.dll, opening the relevant namespace and creating an instance
// Reference F# compiler API #r "FSharp.Compiler.Service.dll" open System open FSharp.Compiler.SourceCodeServices open FSharp.Compiler.Text // Create an interactive checker instance let checker = FSharpChecker.Create()
As previously, we use
GetProjectOptionsFromScriptRoot to get a context
where the specified input is the only file passed to the compiler (and it is treated as a
script file or stand-alone F# source code).
// Sample input as a multi-line string let input = """ open System let foo() = let msg = String.Concat("Hello"," ","world") if true then printfn "%s" msg. """ // Split the input & define file name let inputLines = input.Split('\n') let file = "/home/user/Test.fsx" let projOptions, errors = checker.GetProjectOptionsFromScript(file, SourceText.ofString input) |> Async.RunSynchronously let parsingOptions, _errors = checker.GetParsingOptionsFromProjectOptions(projOptions)
To perform type checking, we first need to parse the input using
ParseFile, which gives us access to the untyped AST. However,
then we need to call
CheckFileInProject to perform the full type checking. This function
also requires the result of
ParseFileInProject, so the two functions are often called
// Perform parsing let parseFileResults = checker.ParseFile(file, SourceText.ofString input, parsingOptions) |> Async.RunSynchronously
Before we look at the interesting operations provided by
need to run the type checker on a sample input. On F# code with errors, you would get some type checking
result (but it may contain incorrectly "guessed" results).
// Perform type checking let checkFileAnswer = checker.CheckFileInProject(parseFileResults, file, 0, SourceText.ofString input, projOptions) |> Async.RunSynchronously
Alternatively you can use
ParseAndCheckFileInProject to check both in one step:
let parseResults2, checkFileAnswer2 = checker.ParseAndCheckFileInProject(file, 0, SourceText.ofString input, projOptions) |> Async.RunSynchronously
The function returns both the untyped parse result (which we do not use in this
tutorial), but also a
CheckFileAnswer value, which gives us access to all
the interesting functionality...
let checkFileResults = match checkFileAnswer with | FSharpCheckFileAnswer.Succeeded(res) -> res | res -> failwithf "Parsing did not finish... (%A)" res
Here, we type check a simple function that (conditionally) prints "Hello world".
On the last line, we leave an additional dot in
msg. so that we can get the
completion list on the
msg value (we expect to see various methods on the string
Let's now look at some of the API that is exposed by the
TypeCheckResults type. In general,
this is the type that lets you implement most of the interesting F# source code editor services.
To get a tool tip, you can use
GetToolTipTextAlternate method. The method takes a line number and character
offset. Both of the numbers are zero-based. In the sample code, we want to get tooltip for the
function that is defined on line 3 (line 0 is blank) and the letter
f starts at index 7 (the tooltip
would work anywhere inside the identifier).
In addition, the method takes a tag of token which is typically
IDENT, when getting tooltip for an
identifier (the other option lets you get tooltip with full assembly location when using
// Get tag of the IDENT token to be used as the last argument open FSharp.Compiler let identToken = FSharpTokenTag.Identifier // Get tool tip at the specified location let tip = checkFileResults.GetToolTipText(4, 7, inputLines., ["foo"], identToken) printfn "%A" tip
GetToolTipTextAlternateis an alternative name for the old
GetToolTipText. The old
GetToolTipTextwas deprecated because it accepted zero-based line numbers. At some point it will be removed, and
GetToolTipTextAlternatewill be renamed back to
Aside from the location and token kind, the function also requires the current contents of the line
(useful when the source code changes) and a
Names value, which is a list of strings representing
the current long name. For example to get tooltip for the
Random identifier in a long name
System.Random, you would use location somewhere in the string
Random and you would pass
["System"; "Random"] as the
The returned value is of type
ToolTipText which contains a discriminated union
The union represents different kinds of tool tips that you can get from the compiler.
The next method exposed by
TypeCheckResults lets us perform auto-complete on a given location.
This can be called on any identifier or in any scope (in which case you get a list of names visible
in the scope) or immediately after
. to get a list of members of some object. Here, we get a
list of members of the string value
To do this, we call
GetDeclarationListInfo with the location of the
. symbol on the last line
printfn "%s" msg.). The offsets are one-based, so the location is
We also need to specify a function that says that the text has not changed and the current identifier
where we need to perform the completion.
// Get declarations (autocomplete) for a location let decls = checkFileResults.GetDeclarationListInfo (Some parseFileResults, 7, inputLines., PartialLongName.Empty 23, (fun () -> ), fun _ -> false) |> Async.RunSynchronously // Print the names of available items for item in decls.Items do printfn " - %s" item.Name
vis an alternative name for the old
GetDeclarations. The old
GetDeclarationswas deprecated because it accepted zero-based line numbers. At some point it will be removed, and
GetDeclarationListInfowill be renamed back to
When you run the code, you should get a list containing the usual string methods such as
ToLower etc. The fourth argument of
specifies the context for the auto-completion. Here, we want a completion on a complete name
msg, but you could for example use
(["System"; "Collections"], "Generic") to get a completion list
for a fully qualified namespace.
The next common feature of editors is to provide information about overloads of a method. In our
sample code, we use
String.Concat which has a number of overloads. We can get the list using
GetMethods operation. As previously, this takes zero-indexed offset of the location that we are
interested in (here, right at the end of the
String.Concat identifier) and we also need to provide
the identifier again (so that the compiler can provide up-to-date information when the source code
// Get overloads of the String.Concat method let methods = checkFileResults.GetMethods(5, 27, inputLines., Some ["String"; "Concat"]) |> Async.RunSynchronously // Print concatenated parameter lists for mi in methods.Methods do [ for p in mi.Parameters -> p.Display ] |> String.concat ", " |> printfn "%s(%s)" methods.MethodName
The code uses the
Display property to get the annotation for each parameter. This returns information
arg0: obj or
params args: obj or
str0: string, str1: string. We concatenate the parameters
and print a type annotation with the method name.
You may have noticed that
CheckFileInProject is an asynchronous operation.
This indicates that type checking of F# code can take some time.
The F# compiler performs the work in background (automatically) and when
CheckFileInProject method, it returns an asynchronous operation.
There is also the
CheckFileInProjectIfReady method. This returns immediately if the
type checking operation can't be started immediately, e.g. if other files in the project
are not yet type-checked. In this case, a background worker might choose to do other
work in the meantime, or give up on type checking the file until the
The fsharpbinding project has more advanced example of handling the background work where all requests are sent through an F# agent. This may be a more appropriate for implementing editor support.
CheckFileAnswer object contains other useful methods that were not covered in this tutorial. You
can use it to get location of a declaration for a given identifier, additional colorization information
(the F# 3.1 colorizes computation builder identifiers & query operators) and others.
Finally, if you are implementing an editor support for an editor that cannot directly call .NET API, you can call many of the methods discussed here via a command line interface that is available in the FSharp.AutoComplete project.