forked from dotnet/fsharp
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy patheditor.fsx
267 lines (206 loc) · 10.2 KB
/
editor.fsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
(**
---
title: Tutorial: Editor services
category: FSharp.Compiler.Service
categoryindex: 300
index: 500
---
*)
(*** hide ***)
#I "../../artifacts/bin/FSharp.Compiler.Service/Debug/netstandard2.0"
(**
Compiler Services: Editor services
==================================
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](https://github.com/fsharp/fsharpbinding) project for more information).
Similarly to [the tutorial on using untyped AST](untypedtree.html), we start by
getting the `InteractiveChecker` object.
> **NOTE:** The FSharp.Compiler.Service API is subject to change when later versions of the nuget package are published
Type checking sample source code
--------------------------------
As in the [previous tutorial (using untyped AST)](untypedtree.html), we start by referencing
`FSharp.Compiler.Service.dll`, opening the relevant namespace and creating an instance
of `InteractiveChecker`:
*)
// Reference F# compiler API
#r "FSharp.Compiler.Service.dll"
open FSharp.Compiler.CodeAnalysis
open FSharp.Compiler.EditorServices
open FSharp.Compiler.Text
open FSharp.Compiler.Tokenization
// Create an interactive checker instance
let checker = FSharpChecker.Create()
(**
As [previously](untypedtree.html), 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, _diagnostics =
checker.GetProjectOptionsFromScript(file, SourceText.ofString input, assumeDotNetFramework=false)
|> Async.RunSynchronously
let parsingOptions, _diagnostics2 =
checker.GetParsingOptionsFromProjectOptions(projOptions)
(**
To perform type checking, we first need to parse the input using
`ParseFile`, which gives us access to the [untyped AST](untypedtree.html). 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
together.
*)
// Perform parsing
let parseFileResults =
checker.ParseFile(file, SourceText.ofString input, parsingOptions)
|> Async.RunSynchronously
(**
Before we look at the interesting operations provided by `TypeCheckResults`, we
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
type there).
Using type checking results
---------------------------
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.
### Getting a tool tip
To get a tool tip, you can use the `GetToolTip` 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 a tooltip for the `foo`
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 a tooltip for an
identifier (the other option lets you get a tooltip with full assembly location when using `#r "..."`).
*)
// Get tag of the IDENT token to be used as the last argument
let identToken = FSharpTokenTag.Identifier
// Get tool tip at the specified location
let tip =
checkFileResults.GetToolTip(4, 7, inputLines.[1], [ "foo" ], identToken)
printfn "%A" tip
(**
*)
(**
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 a tooltip for the `Random` identifier in a long name
`System.Random`, you would use a location somewhere in the string `Random` and you would pass
`["System"; "Random"]` as the `Names` value.
The returned value is of type `ToolTipText` which contains a discriminated union `ToolTipElement`.
The union represents different kinds of tool tips that you can get from the compiler.
### Getting auto-complete lists
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 `msg`.
To do this, we call `GetDeclarationListInfo` with the location of the `.` symbol on the last line
(ending with `printfn "%s" msg.`). The offsets are one-based, so the location is `7, 23`.
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.[6],
PartialLongName.Empty 23,
(fun () -> [])
)
// Print the names of available items
for item in decls.Items do
printfn " - %s" item.Name
(**
> **NOTE:** `v` is an alternative name for the old `GetDeclarations`. The old `GetDeclarations` was
deprecated because it accepted zero-based line numbers. At some point it will be removed, and `GetDeclarationListInfo` will be renamed back to `GetDeclarations`.
*)
(**
When you run the code, you should get a list containing the usual string methods such as
`Substring`, `ToUpper`, `ToLower` etc. The fourth argument of `GetDeclarations`, here `([], "msg")`,
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.
### Getting parameter information
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 the 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
changes):
*)
// Get overloads of the String.Concat method
let methods =
checkFileResults.GetMethods(5, 27, inputLines.[4], Some [ "String"; "Concat" ])
// Print concatenated parameter lists
for mi in methods.Methods do
[ for p in mi.Parameters do
for tt in p.Display do
yield tt.Text ]
|> String.concat ", "
|> printfn "%s(%s)" methods.MethodName
(**
The code uses the `Display` property to get the annotation for each parameter. This returns information
such as `arg0: obj` or `params args: obj[]` or `str0: string, str1: string`. We concatenate the parameters
and print a type annotation with the method name.
*)
(**
## Asynchronous and immediate operations
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 the background (automatically) and when
we call the `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 `FileTypeCheckStateIsDirty` event
is raised.
> The [fsharpbinding](https://github.com/fsharp/fsharpbinding) project has a more advanced
example of handling the background work where all requests are sent through an F# agent.
This may be more appropriate for implementing editor support.
*)
(**
Summary
-------
The `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.
Using the FSharpChecker component in multi-project, incremental and interactive editing situations may involve
knowledge of the [FSharpChecker operations queue](queue.html) and the [FSharpChecker caches](caches.html).
Finally, if you are implementing an editor support for an editor that cannot directly call the .NET API,
you can call many of the methods discussed here via a command line interface that is available in the
[FSharp.AutoComplete](https://github.com/fsharp/fsharpbinding/tree/master/FSharp.AutoComplete) project.
*)