Skip to content

Commit 057d349

Browse files
committed
Merge branch 'lsp-2' into lsp
2 parents 799dc73 + d557b62 commit 057d349

File tree

6 files changed

+256
-49
lines changed

6 files changed

+256
-49
lines changed

src/Compiler/FSharp.Compiler.Service.fsproj

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,9 @@
7777
<InternalsVisibleTo Include="LanguageServiceProfiling" />
7878
<InternalsVisibleTo Include="FSharp.Compiler.Benchmarks" />
7979
<InternalsVisibleTo Include="HistoricalBenchmark" />
80-
<InternalsVisibleTo Include="FSharp.Test.Utilities" />
81-
<InternalsVisibleTo Include="FSharp.Editor" />
80+
<InternalsVisibleTo Include="FSharp.Test.Utilities" />
81+
<InternalsVisibleTo Include="FSharp.Editor" />
82+
<InternalsVisibleTo Include="FSharp.Compiler.LanguageServer" />
8283
</ItemGroup>
8384

8485
<ItemGroup>

src/Compiler/Facilities/Hashing.fs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,11 @@ module internal Md5Hasher =
4646
let private md5 =
4747
new ThreadLocal<_>(fun () -> System.Security.Cryptography.MD5.Create())
4848

49-
let computeHash (bytes: byte array) = md5.Value.ComputeHash(bytes)
49+
let computeHash (bytes: byte array) =
50+
// md5.Value.ComputeHash(bytes) TODO: the threadlocal is not working in new VS extension
51+
ignore md5
52+
let md5 = System.Security.Cryptography.MD5.Create()
53+
md5.ComputeHash(bytes)
5054

5155
let empty = Array.empty
5256

src/FSharp.Compiler.LanguageServer/FSharpLanguageServer.fs

Lines changed: 48 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
namespace FSharp.Compiler.LanguageServer
22

3+
open System.Runtime.CompilerServices
4+
5+
#nowarn "57"
6+
37
open System
48
open System.Threading.Tasks
59
open System.Threading
@@ -13,27 +17,65 @@ open Nerdbank.Streams
1317
open System.Diagnostics
1418
open System.IO
1519
open Workspace
20+
open FSharp.Compiler.CodeAnalysis
1621

1722
[<AutoOpen>]
1823
module Stuff =
1924
[<Literal>]
2025
let FSharpLanguageName = "F#"
2126

22-
type FSharpRequestContext(lspServices: ILspServices, logger: ILspLogger, workspace: FSharpWorkspace) =
27+
[<Extension>]
28+
type Extensions =
29+
30+
[<Extension>]
31+
static member Please(this: Async<'t>, ct) =
32+
Async.StartAsTask(this, cancellationToken = ct)
33+
34+
type FSharpRequestContext(lspServices: ILspServices, logger: ILspLogger, workspace: FSharpWorkspace, checker: FSharpChecker) =
2335
member _.LspServices = lspServices
2436
member _.Logger = logger
2537
member _.Workspace = workspace
38+
member _.Checker = checker
39+
40+
// TODO: split to parse and check diagnostics
41+
member _.GetDiagnosticsForFile(file: Uri) =
42+
43+
workspace.GetSnapshotForFile file
44+
|> Option.map (fun snapshot ->
45+
async {
46+
let! parseResult, checkFileAnswer = checker.ParseAndCheckFileInProject(file.LocalPath, snapshot, "LSP Get diagnostics")
47+
48+
return
49+
match checkFileAnswer with
50+
| FSharpCheckFileAnswer.Succeeded result -> result.Diagnostics
51+
| FSharpCheckFileAnswer.Aborted -> parseResult.Diagnostics
52+
})
53+
|> Option.defaultValue (async.Return [||])
2654

2755
type ContextHolder(intialWorkspace, lspServices: ILspServices) =
2856

2957
let logger = lspServices.GetRequiredService<ILspLogger>()
3058

31-
let mutable context = FSharpRequestContext(lspServices, logger, intialWorkspace)
59+
// TODO: We need to get configuration for this somehow. Also make it replaceable when configuration changes.
60+
let checker =
61+
FSharpChecker.Create(
62+
keepAllBackgroundResolutions = true,
63+
keepAllBackgroundSymbolUses = true,
64+
enableBackgroundItemKeyStoreAndSemanticClassification = true,
65+
enablePartialTypeChecking = true,
66+
parallelReferenceResolution = true,
67+
captureIdentifiersWhenParsing = true,
68+
useSyntaxTreeCache = true,
69+
useTransparentCompiler = true
70+
)
71+
72+
let mutable context =
73+
FSharpRequestContext(lspServices, logger, intialWorkspace, checker)
3274

3375
member _.GetContext() = context
3476

3577
member _.UpdateWorkspace(f) =
36-
context <- FSharpRequestContext(lspServices, logger, f context.Workspace)
78+
context <- FSharpRequestContext(lspServices, logger, f context.Workspace, checker)
3779

3880
type FShapRequestContextFactory(lspServices: ILspServices) =
3981

@@ -63,10 +105,7 @@ type DocumentStateHandler() =
63105
) =
64106
let contextHolder = context.LspServices.GetRequiredService<ContextHolder>()
65107

66-
contextHolder.UpdateWorkspace(fun w ->
67-
{ w with
68-
OpenFiles = Map.add request.TextDocument.Uri.AbsolutePath request.TextDocument.Text w.OpenFiles
69-
})
108+
contextHolder.UpdateWorkspace _.OpenFile(request.TextDocument.Uri, request.TextDocument.Text)
70109

71110
Task.FromResult(SemanticTokensDeltaPartialResult())
72111

@@ -80,10 +119,7 @@ type DocumentStateHandler() =
80119
) =
81120
let contextHolder = context.LspServices.GetRequiredService<ContextHolder>()
82121

83-
contextHolder.UpdateWorkspace(fun w ->
84-
{ w with
85-
OpenFiles = Map.add request.TextDocument.Uri.AbsolutePath request.ContentChanges.[0].Text w.OpenFiles
86-
})
122+
contextHolder.UpdateWorkspace _.ChangeFile(request.TextDocument.Uri, request.ContentChanges.[0].Text)
87123

88124
Task.FromResult(SemanticTokensDeltaPartialResult())
89125

@@ -97,10 +133,7 @@ type DocumentStateHandler() =
97133
) =
98134
let contextHolder = context.LspServices.GetRequiredService<ContextHolder>()
99135

100-
contextHolder.UpdateWorkspace(fun w ->
101-
{ w with
102-
OpenFiles = Map.remove request.TextDocument.Uri.AbsolutePath w.OpenFiles
103-
})
136+
contextHolder.UpdateWorkspace _.CloseFile(request.TextDocument.Uri)
104137

105138
Task.CompletedTask
106139

Lines changed: 125 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,134 @@
11
module FSharp.Compiler.LanguageServer.Workspace
22

3+
open FSharp.Compiler.Text
4+
35
#nowarn "57"
46

57
open System
8+
open System.Threading.Tasks
69
open FSharp.Compiler.CodeAnalysis.ProjectSnapshot
710

8-
type FSharpWorkspace =
9-
{
10-
Projects: Map<FSharpProjectIdentifier, FSharpProjectSnapshot>
11-
OpenFiles: Map<string, string>
12-
}
11+
/// Holds a project snapshot and a queue of changes that will be applied to it when it's requested
12+
///
13+
/// The assumption is that this is faster than actually applying the changes to the snapshot immediately and that
14+
/// we will be doing this on potentially every keystroke. But this should probably be measured at some point.
15+
type SnapshotHolder(snapshot: FSharpProjectSnapshot, changedFiles: Set<string>, openFiles: Map<string, string>) =
16+
17+
let applyFileChangesToSnapshot () =
18+
let files =
19+
changedFiles
20+
|> Seq.map (fun filePath ->
21+
match openFiles.TryFind filePath with
22+
| Some content ->
23+
FSharpFileSnapshot.Create(
24+
filePath,
25+
DateTime.Now.Ticks.ToString(),
26+
fun () -> content |> SourceTextNew.ofString |> Task.FromResult
27+
)
28+
| None -> FSharpFileSnapshot.CreateFromFileSystem(filePath))
29+
|> Seq.toList
30+
31+
snapshot.Replace files
32+
33+
// We don't want to mutate the workspace by applying the changes when snapshot is requested because that would force the language
34+
// requests to be processed sequentially. So instead we keep the change application under lazy so it's still only computed if needed
35+
// and only once and workspace doesn't change.
36+
let appliedChanges =
37+
lazy SnapshotHolder(applyFileChangesToSnapshot (), Set.empty, openFiles)
38+
39+
member private _.snapshot = snapshot
40+
member private _.changedFiles = changedFiles
41+
42+
member private this.GetMostUpToDateInstance() =
43+
if appliedChanges.IsValueCreated then
44+
appliedChanges.Value
45+
else
46+
this
47+
48+
member this.WithFileChanged(file, openFiles) =
49+
let previous = this.GetMostUpToDateInstance()
50+
SnapshotHolder(previous.snapshot, previous.changedFiles.Add file, openFiles)
51+
52+
member this.WithoutFileChanged(file, openFiles) =
53+
let previous = this.GetMostUpToDateInstance()
54+
SnapshotHolder(previous.snapshot, previous.changedFiles.Remove file, openFiles)
55+
56+
member _.GetSnapshot() = appliedChanges.Value.snapshot
57+
58+
static member Of(snapshot: FSharpProjectSnapshot) =
59+
SnapshotHolder(snapshot, Set.empty, Map.empty)
60+
61+
type FSharpWorkspace
62+
private
63+
(
64+
projects: Map<FSharpProjectIdentifier, SnapshotHolder>,
65+
openFiles: Map<string, string>,
66+
fileMap: Map<string, Set<FSharpProjectIdentifier>>
67+
) =
68+
69+
let updateProjectsWithFile (file: Uri) f (projects: Map<FSharpProjectIdentifier, SnapshotHolder>) =
70+
fileMap
71+
|> Map.tryFind file.LocalPath
72+
|> Option.map (fun identifier ->
73+
(projects, identifier)
74+
||> Seq.fold (fun projects identifier ->
75+
let snapshotHolder = projects[identifier]
76+
projects.Add(identifier, f snapshotHolder)))
77+
|> Option.defaultValue projects
78+
79+
member _.Projects = projects
80+
member _.OpenFiles = openFiles
81+
member _.FileMap = fileMap
82+
83+
member this.OpenFile(file: Uri, content: string) = this.ChangeFile(file, content)
84+
85+
member _.CloseFile(file: Uri) =
86+
let openFiles = openFiles.Remove(file.LocalPath)
87+
88+
FSharpWorkspace(
89+
projects =
90+
(projects
91+
|> updateProjectsWithFile file _.WithoutFileChanged(file.LocalPath, openFiles)),
92+
openFiles = openFiles,
93+
fileMap = fileMap
94+
)
95+
96+
member _.ChangeFile(file: Uri, content: string) =
97+
98+
// TODO: should we assert that the file is open?
99+
100+
let openFiles = openFiles.Add(file.LocalPath, content)
101+
102+
FSharpWorkspace(
103+
projects =
104+
(projects
105+
|> updateProjectsWithFile file _.WithFileChanged(file.LocalPath, openFiles)),
106+
openFiles = openFiles,
107+
fileMap = fileMap
108+
)
109+
110+
member _.GetSnapshotForFile(file: Uri) =
111+
fileMap
112+
|> Map.tryFind file.LocalPath
113+
114+
// TODO: eventually we need to deal with choosing the appropriate project here
115+
// Hopefully we will be able to do it through receiving project context from LSP
116+
// Otherwise we have to keep track of which project/configuration is active
117+
|> Option.bind Seq.tryHead
118+
119+
|> Option.bind projects.TryFind
120+
|> Option.map _.GetSnapshot()
13121

14122
static member Create(projects: FSharpProjectSnapshot seq) =
15-
{
16-
Projects = Map.ofSeq (projects |> Seq.map (fun p -> p.Identifier, p))
17-
OpenFiles = Map.empty
18-
}
123+
FSharpWorkspace(
124+
projects = Map.ofSeq (projects |> Seq.map (fun p -> p.Identifier, SnapshotHolder.Of p)),
125+
openFiles = Map.empty,
126+
fileMap =
127+
(projects
128+
|> Seq.collect (fun p ->
129+
p.ProjectSnapshot.SourceFileNames
130+
|> Seq.map (fun f -> Uri(f).LocalPath, p.Identifier))
131+
|> Seq.groupBy fst
132+
|> Seq.map (fun (f, ps) -> f, Set.ofSeq (ps |> Seq.map snd))
133+
|> Map.ofSeq)
134+
)

src/FSharp.VisualStudio.Extension/FSharp.VisualStudio.Extension.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<Project Sdk="Microsoft.NET.Sdk">
1+
<Project Sdk="Microsoft.NET.Sdk">
22
<PropertyGroup>
33
<TargetFramework>net8.0-windows</TargetFramework>
44
<Nullable>enable</Nullable>
@@ -12,7 +12,7 @@
1212
<PackageReference Include="Microsoft.VisualStudio.Extensibility.Sdk" Version="17.10.17-preview-1" />
1313
<PackageReference Include="Microsoft.VisualStudio.Extensibility.Build" Version="17.10.17-preview-1" />
1414

15-
<PackageReference Include="Microsoft.VisualStudio.LanguageServer.Protocol.Internal" Version="17.10.13-preview" />
15+
<PackageReference Include="Microsoft.VisualStudio.LanguageServer.Protocol.Internal" Version="17.10.14-preview" />
1616
</ItemGroup>
1717

1818
<ItemGroup>

0 commit comments

Comments
 (0)