diff --git a/VSFSharpExtension.sln b/VSFSharpExtension.sln
new file mode 100644
index 00000000000..a4e19ff2fae
--- /dev/null
+++ b/VSFSharpExtension.sln
@@ -0,0 +1,74 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.0.31903.59
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{B4A1E626-4A48-4977-B291-219882DB3413}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FSharp.VisualStudio.Extension", "src\FSharp.VisualStudio.Extension\FSharp.VisualStudio.Extension.csproj", "{14B9AB0E-2FC0-43F1-9775-20C262202D12}"
+EndProject
+Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "FSharp.Compiler.LanguageServer", "src\FSharp.Compiler.LanguageServer\FSharp.Compiler.LanguageServer.fsproj", "{7EDDFB35-2428-4433-8ABF-47233480B746}"
+EndProject
+Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "FSharp.Compiler.Service", "src\Compiler\FSharp.Compiler.Service.fsproj", "{0B24DCA6-902F-409E-A18C-7B1BFDDC8849}"
+EndProject
+Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "FSharp.Core", "src\FSharp.Core\FSharp.Core.fsproj", "{5A4B66D5-A6C3-402C-A010-7A3635098DA7}"
+EndProject
+Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "FSharp.DependencyManager.Nuget", "src\FSharp.DependencyManager.Nuget\FSharp.DependencyManager.Nuget.fsproj", "{860808CF-D092-4511-B011-6205B654031D}"
+EndProject
+Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FSharp.Editor", "vsintegration\src\FSharp.Editor\FSharp.Editor.fsproj", "{3D4A95CB-1563-CD9A-3949-FE01922CD5BD}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Proto|Any CPU = Proto|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {14B9AB0E-2FC0-43F1-9775-20C262202D12}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {14B9AB0E-2FC0-43F1-9775-20C262202D12}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {14B9AB0E-2FC0-43F1-9775-20C262202D12}.Proto|Any CPU.ActiveCfg = Release|Any CPU
+ {14B9AB0E-2FC0-43F1-9775-20C262202D12}.Proto|Any CPU.Build.0 = Release|Any CPU
+ {14B9AB0E-2FC0-43F1-9775-20C262202D12}.Proto|Any CPU.Deploy.0 = Release|Any CPU
+ {14B9AB0E-2FC0-43F1-9775-20C262202D12}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {14B9AB0E-2FC0-43F1-9775-20C262202D12}.Release|Any CPU.Build.0 = Release|Any CPU
+ {7EDDFB35-2428-4433-8ABF-47233480B746}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {7EDDFB35-2428-4433-8ABF-47233480B746}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {7EDDFB35-2428-4433-8ABF-47233480B746}.Proto|Any CPU.ActiveCfg = Release|Any CPU
+ {7EDDFB35-2428-4433-8ABF-47233480B746}.Proto|Any CPU.Build.0 = Release|Any CPU
+ {7EDDFB35-2428-4433-8ABF-47233480B746}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {7EDDFB35-2428-4433-8ABF-47233480B746}.Release|Any CPU.Build.0 = Release|Any CPU
+ {0B24DCA6-902F-409E-A18C-7B1BFDDC8849}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {0B24DCA6-902F-409E-A18C-7B1BFDDC8849}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {0B24DCA6-902F-409E-A18C-7B1BFDDC8849}.Proto|Any CPU.ActiveCfg = Debug|Any CPU
+ {0B24DCA6-902F-409E-A18C-7B1BFDDC8849}.Proto|Any CPU.Build.0 = Debug|Any CPU
+ {0B24DCA6-902F-409E-A18C-7B1BFDDC8849}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {0B24DCA6-902F-409E-A18C-7B1BFDDC8849}.Release|Any CPU.Build.0 = Release|Any CPU
+ {5A4B66D5-A6C3-402C-A010-7A3635098DA7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {5A4B66D5-A6C3-402C-A010-7A3635098DA7}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {5A4B66D5-A6C3-402C-A010-7A3635098DA7}.Proto|Any CPU.ActiveCfg = Proto|Any CPU
+ {5A4B66D5-A6C3-402C-A010-7A3635098DA7}.Proto|Any CPU.Build.0 = Proto|Any CPU
+ {5A4B66D5-A6C3-402C-A010-7A3635098DA7}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {5A4B66D5-A6C3-402C-A010-7A3635098DA7}.Release|Any CPU.Build.0 = Release|Any CPU
+ {860808CF-D092-4511-B011-6205B654031D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {860808CF-D092-4511-B011-6205B654031D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {860808CF-D092-4511-B011-6205B654031D}.Proto|Any CPU.ActiveCfg = Debug|Any CPU
+ {860808CF-D092-4511-B011-6205B654031D}.Proto|Any CPU.Build.0 = Debug|Any CPU
+ {860808CF-D092-4511-B011-6205B654031D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {860808CF-D092-4511-B011-6205B654031D}.Release|Any CPU.Build.0 = Release|Any CPU
+ {3D4A95CB-1563-CD9A-3949-FE01922CD5BD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {3D4A95CB-1563-CD9A-3949-FE01922CD5BD}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {3D4A95CB-1563-CD9A-3949-FE01922CD5BD}.Proto|Any CPU.ActiveCfg = Debug|Any CPU
+ {3D4A95CB-1563-CD9A-3949-FE01922CD5BD}.Proto|Any CPU.Build.0 = Debug|Any CPU
+ {3D4A95CB-1563-CD9A-3949-FE01922CD5BD}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {3D4A95CB-1563-CD9A-3949-FE01922CD5BD}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(NestedProjects) = preSolution
+ {14B9AB0E-2FC0-43F1-9775-20C262202D12} = {B4A1E626-4A48-4977-B291-219882DB3413}
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {BCE01FEF-1B00-471B-81E0-A06994EC90ED}
+ EndGlobalSection
+EndGlobal
diff --git a/VisualFSharp.sln b/VisualFSharp.sln
index 4f1ab0fa06f..df0b6fe31ce 100644
--- a/VisualFSharp.sln
+++ b/VisualFSharp.sln
@@ -193,6 +193,14 @@ Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "MicroPerf", "tests\benchmar
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MicroPerfCSharp", "tests\benchmarks\CompiledCodeBenchmarks\MicroPerf\CS\MicroPerfCSharp.csproj", "{9F9DD315-37DA-4413-928E-1CFC6924B64F}"
EndProject
+Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "FSharp.Compiler.LanguageServer", "src\FSharp.Compiler.LanguageServer\FSharp.Compiler.LanguageServer.fsproj", "{D72F6593-DB2D-47AC-8E15-8DCE8527972E}"
+EndProject
+Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "FSharp.Compiler.LanguageServer.Tests", "tests\FSharp.Compiler.LanguageServer.Tests\FSharp.Compiler.LanguageServer.Tests.fsproj", "{1E83A6C8-FA4D-42BD-B4A5-B7F9AAD1B388}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FSharp.VisualStudio.Extension", "src\FSharp.VisualStudio.Extension\FSharp.VisualStudio.Extension.csproj", "{E1013576-6257-47BA-AAFB-F95B68DAB1FF}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CommonLanguageServerProtocol.Framework.Proxy", "src\Microsoft.CommonLanguageServerProtocol.Framework.Proxy\Microsoft.CommonLanguageServerProtocol.Framework.Proxy.csproj", "{FAFF15B5-F7C4-1CE5-9A18-2F6DC93E03ED}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -1019,6 +1027,54 @@ Global
{9F9DD315-37DA-4413-928E-1CFC6924B64F}.Release|Any CPU.Build.0 = Release|Any CPU
{9F9DD315-37DA-4413-928E-1CFC6924B64F}.Release|x86.ActiveCfg = Release|Any CPU
{9F9DD315-37DA-4413-928E-1CFC6924B64F}.Release|x86.Build.0 = Release|Any CPU
+ {D72F6593-DB2D-47AC-8E15-8DCE8527972E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D72F6593-DB2D-47AC-8E15-8DCE8527972E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D72F6593-DB2D-47AC-8E15-8DCE8527972E}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {D72F6593-DB2D-47AC-8E15-8DCE8527972E}.Debug|x86.Build.0 = Debug|Any CPU
+ {D72F6593-DB2D-47AC-8E15-8DCE8527972E}.Proto|Any CPU.ActiveCfg = Debug|Any CPU
+ {D72F6593-DB2D-47AC-8E15-8DCE8527972E}.Proto|Any CPU.Build.0 = Debug|Any CPU
+ {D72F6593-DB2D-47AC-8E15-8DCE8527972E}.Proto|x86.ActiveCfg = Debug|Any CPU
+ {D72F6593-DB2D-47AC-8E15-8DCE8527972E}.Proto|x86.Build.0 = Debug|Any CPU
+ {D72F6593-DB2D-47AC-8E15-8DCE8527972E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D72F6593-DB2D-47AC-8E15-8DCE8527972E}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D72F6593-DB2D-47AC-8E15-8DCE8527972E}.Release|x86.ActiveCfg = Release|Any CPU
+ {D72F6593-DB2D-47AC-8E15-8DCE8527972E}.Release|x86.Build.0 = Release|Any CPU
+ {1E83A6C8-FA4D-42BD-B4A5-B7F9AAD1B388}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {1E83A6C8-FA4D-42BD-B4A5-B7F9AAD1B388}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {1E83A6C8-FA4D-42BD-B4A5-B7F9AAD1B388}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {1E83A6C8-FA4D-42BD-B4A5-B7F9AAD1B388}.Debug|x86.Build.0 = Debug|Any CPU
+ {1E83A6C8-FA4D-42BD-B4A5-B7F9AAD1B388}.Proto|Any CPU.ActiveCfg = Debug|Any CPU
+ {1E83A6C8-FA4D-42BD-B4A5-B7F9AAD1B388}.Proto|Any CPU.Build.0 = Debug|Any CPU
+ {1E83A6C8-FA4D-42BD-B4A5-B7F9AAD1B388}.Proto|x86.ActiveCfg = Debug|Any CPU
+ {1E83A6C8-FA4D-42BD-B4A5-B7F9AAD1B388}.Proto|x86.Build.0 = Debug|Any CPU
+ {1E83A6C8-FA4D-42BD-B4A5-B7F9AAD1B388}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {1E83A6C8-FA4D-42BD-B4A5-B7F9AAD1B388}.Release|Any CPU.Build.0 = Release|Any CPU
+ {1E83A6C8-FA4D-42BD-B4A5-B7F9AAD1B388}.Release|x86.ActiveCfg = Release|Any CPU
+ {1E83A6C8-FA4D-42BD-B4A5-B7F9AAD1B388}.Release|x86.Build.0 = Release|Any CPU
+ {E1013576-6257-47BA-AAFB-F95B68DAB1FF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {E1013576-6257-47BA-AAFB-F95B68DAB1FF}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {E1013576-6257-47BA-AAFB-F95B68DAB1FF}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {E1013576-6257-47BA-AAFB-F95B68DAB1FF}.Debug|x86.Build.0 = Debug|Any CPU
+ {E1013576-6257-47BA-AAFB-F95B68DAB1FF}.Proto|Any CPU.ActiveCfg = Debug|Any CPU
+ {E1013576-6257-47BA-AAFB-F95B68DAB1FF}.Proto|Any CPU.Build.0 = Debug|Any CPU
+ {E1013576-6257-47BA-AAFB-F95B68DAB1FF}.Proto|x86.ActiveCfg = Debug|Any CPU
+ {E1013576-6257-47BA-AAFB-F95B68DAB1FF}.Proto|x86.Build.0 = Debug|Any CPU
+ {E1013576-6257-47BA-AAFB-F95B68DAB1FF}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {E1013576-6257-47BA-AAFB-F95B68DAB1FF}.Release|Any CPU.Build.0 = Release|Any CPU
+ {E1013576-6257-47BA-AAFB-F95B68DAB1FF}.Release|x86.ActiveCfg = Release|Any CPU
+ {E1013576-6257-47BA-AAFB-F95B68DAB1FF}.Release|x86.Build.0 = Release|Any CPU
+ {FAFF15B5-F7C4-1CE5-9A18-2F6DC93E03ED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {FAFF15B5-F7C4-1CE5-9A18-2F6DC93E03ED}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {FAFF15B5-F7C4-1CE5-9A18-2F6DC93E03ED}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {FAFF15B5-F7C4-1CE5-9A18-2F6DC93E03ED}.Debug|x86.Build.0 = Debug|Any CPU
+ {FAFF15B5-F7C4-1CE5-9A18-2F6DC93E03ED}.Proto|Any CPU.ActiveCfg = Debug|Any CPU
+ {FAFF15B5-F7C4-1CE5-9A18-2F6DC93E03ED}.Proto|Any CPU.Build.0 = Debug|Any CPU
+ {FAFF15B5-F7C4-1CE5-9A18-2F6DC93E03ED}.Proto|x86.ActiveCfg = Debug|Any CPU
+ {FAFF15B5-F7C4-1CE5-9A18-2F6DC93E03ED}.Proto|x86.Build.0 = Debug|Any CPU
+ {FAFF15B5-F7C4-1CE5-9A18-2F6DC93E03ED}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {FAFF15B5-F7C4-1CE5-9A18-2F6DC93E03ED}.Release|Any CPU.Build.0 = Release|Any CPU
+ {FAFF15B5-F7C4-1CE5-9A18-2F6DC93E03ED}.Release|x86.ActiveCfg = Release|Any CPU
+ {FAFF15B5-F7C4-1CE5-9A18-2F6DC93E03ED}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -1099,6 +1155,7 @@ Global
{6734FC6F-B5F3-45E1-9A72-720378BB49C9} = {DFB6ADD7-3149-43D9-AFA0-FC4A818B472B}
{601CD5C1-EAFA-4AE3-8FB9-F667B5728213} = {DFB6ADD7-3149-43D9-AFA0-FC4A818B472B}
{9F9DD315-37DA-4413-928E-1CFC6924B64F} = {DFB6ADD7-3149-43D9-AFA0-FC4A818B472B}
+ {1E83A6C8-FA4D-42BD-B4A5-B7F9AAD1B388} = {CFE3259A-2D30-4EB0-80D5-E8B5F3D01449}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {48EDBBBE-C8EE-4E3C-8B19-97184A487B37}
diff --git a/buildtools/AssemblyCheck/SkipVerifyEmbeddedPdb.txt b/buildtools/AssemblyCheck/SkipVerifyEmbeddedPdb.txt
index 7af2a7f58cb..f7d25f2ca25 100644
--- a/buildtools/AssemblyCheck/SkipVerifyEmbeddedPdb.txt
+++ b/buildtools/AssemblyCheck/SkipVerifyEmbeddedPdb.txt
@@ -3,7 +3,8 @@ FSharp.Benchmarks.Common.dll
FSharp.Compiler.Benchmarks.dll
FSharp.Compiler.ComponentTests.dll
FSharp.Test.Utilities.dll
+FSharp.Compiler.LanguageServer.Tests.dll
FSharp.Compiler.Private.Scripting.UnitTests.dll
FSharp.Compiler.Service.Tests.dll
FSharp.Core.UnitTests.dll
-FSharpSuite.Tests.dll
+FSharpSuite.Tests.dll
\ No newline at end of file
diff --git a/src/Compiler/FSharp.Compiler.Service.fsproj b/src/Compiler/FSharp.Compiler.Service.fsproj
index 6c087fa99fe..3953da89448 100644
--- a/src/Compiler/FSharp.Compiler.Service.fsproj
+++ b/src/Compiler/FSharp.Compiler.Service.fsproj
@@ -97,6 +97,8 @@
+
+
diff --git a/src/Compiler/Facilities/AsyncMemoize.fs b/src/Compiler/Facilities/AsyncMemoize.fs
index ac52eaf8607..e776fe0aae0 100644
--- a/src/Compiler/Facilities/AsyncMemoize.fs
+++ b/src/Compiler/Facilities/AsyncMemoize.fs
@@ -114,7 +114,7 @@ module internal Utils =
/// Return file name with one directory above it
let shortPath (path: string) =
- let dirPath = !!Path.GetDirectoryName(path)
+ let dirPath = Path.GetDirectoryName(path) |> Option.ofObj |> Option.defaultValue ""
let dir =
dirPath.Split Path.DirectorySeparatorChar
diff --git a/src/Compiler/Facilities/Hashing.fs b/src/Compiler/Facilities/Hashing.fs
index dd58495c59e..020b50e2663 100644
--- a/src/Compiler/Facilities/Hashing.fs
+++ b/src/Compiler/Facilities/Hashing.fs
@@ -46,7 +46,11 @@ module internal Md5Hasher =
let private md5 =
new ThreadLocal<_>(fun () -> System.Security.Cryptography.MD5.Create())
- let computeHash (bytes: byte array) = md5.Value.ComputeHash(bytes)
+ let computeHash (bytes: byte array) =
+ // md5.Value.ComputeHash(bytes) TODO: the threadlocal is not working in new VS extension
+ ignore md5
+ let md5 = System.Security.Cryptography.MD5.Create()
+ md5.ComputeHash(bytes)
let empty = Array.empty
diff --git a/src/Compiler/Service/FSharpProjectSnapshot.fs b/src/Compiler/Service/FSharpProjectSnapshot.fs
index 8ca44143037..91a6efd472a 100644
--- a/src/Compiler/Service/FSharpProjectSnapshot.fs
+++ b/src/Compiler/Service/FSharpProjectSnapshot.fs
@@ -619,6 +619,7 @@ and [] FSha
member _.OriginalLoadReferences = projectSnapshot.OriginalLoadReferences
member _.Stamp = projectSnapshot.Stamp
member _.OutputFileName = projectSnapshot.OutputFileName
+ member _.ProjectConfig = projectSnapshot.ProjectConfig
static member Create
(
@@ -745,6 +746,69 @@ and [] FSha
FSharpProjectSnapshot.FromOptions(options, getFileSnapshot)
+ static member FromResponseFile(responseFile: FileInfo, projectFileName) =
+ if not responseFile.Exists then
+ failwith $"%s{responseFile.FullName} does not exist"
+
+ let compilerArgs = File.ReadAllLines responseFile.FullName
+
+ let directoryName: string =
+ match responseFile.DirectoryName with
+ | null -> failwith "Directory name of the response file is null"
+ | str -> str
+
+ FSharpProjectSnapshot.FromCommandLineArgs(compilerArgs, directoryName, projectFileName)
+
+ static member FromCommandLineArgs(compilerArgs: string array, directoryPath: string, projectFileName) =
+ let fsharpFileExtensions = set [| ".fs"; ".fsi"; ".fsx" |]
+
+ let isFSharpFile (file: string) =
+ Set.exists (fun (ext: string) -> file.EndsWith(ext, StringComparison.Ordinal)) fsharpFileExtensions
+
+ let isReference: string -> bool = _.StartsWith("-r:")
+
+ let fsharpFiles =
+ compilerArgs
+ |> Array.choose (fun (line: string) ->
+ if not (isFSharpFile line) then
+ None
+ else
+
+ let fullPath = Path.Combine(directoryPath, line)
+ if not (File.Exists fullPath) then None else Some fullPath)
+ |> Array.toList
+
+ let referencesOnDisk =
+ compilerArgs |> Seq.filter isReference |> Seq.map _.Substring(3) |> Seq.toList
+
+ let otherOptions =
+ compilerArgs
+ |> Seq.filter (not << isReference)
+ |> Seq.filter (not << isFSharpFile)
+ |> Seq.toList
+
+ FSharpProjectSnapshot.Create(
+ projectFileName = projectFileName,
+ outputFileName = None,
+ projectId = None,
+ sourceFiles = (fsharpFiles |> List.map FSharpFileSnapshot.CreateFromFileSystem),
+ referencesOnDisk =
+ (referencesOnDisk
+ |> List.map (fun x ->
+ {
+ Path = x
+ LastModified = FileSystem.GetLastWriteTimeShim(x)
+ })),
+ otherOptions = otherOptions,
+ referencedProjects = [],
+ isIncompleteTypeCheckEnvironment = false,
+ useScriptResolutionRules = false,
+ loadTime = DateTime.Now,
+ unresolvedReferences = None,
+ originalLoadReferences = [],
+ stamp = None
+ )
+
let internal snapshotTable =
ConditionalWeakTable()
diff --git a/src/Compiler/Service/FSharpWorkspace.fs b/src/Compiler/Service/FSharpWorkspace.fs
index ccde4b98fb1..0b557386d07 100644
--- a/src/Compiler/Service/FSharpWorkspace.fs
+++ b/src/Compiler/Service/FSharpWorkspace.fs
@@ -46,14 +46,7 @@ type FSharpWorkspace(checker: FSharpChecker) =
)
)
- member internal this.Debug_DumpMermaid(path) =
- let content =
- depGraph.Debug_RenderMermaid (function
- // Collapse all reference on disk nodes into one. Otherwise the graph is too big to render.
- | WorkspaceGraphTypes.WorkspaceNodeKey.ReferenceOnDisk _ -> WorkspaceGraphTypes.WorkspaceNodeKey.ReferenceOnDisk "..."
- | x -> x)
-
- File.WriteAllText(__SOURCE_DIRECTORY__ + path, content)
+ member internal _.DepGraph = depGraph
/// The `FSharpChecker` instance used by this workspace.
member _.Checker = checker
diff --git a/src/Compiler/Service/FSharpWorkspaceQuery.fs b/src/Compiler/Service/FSharpWorkspaceQuery.fs
index b720608b27f..39c87094928 100644
--- a/src/Compiler/Service/FSharpWorkspaceQuery.fs
+++ b/src/Compiler/Service/FSharpWorkspaceQuery.fs
@@ -5,12 +5,15 @@ module FSharp.Compiler.CodeAnalysis.Workspace.FSharpWorkspaceQuery
open System
open System.Collections.Generic
+open FSharp.Compiler.Diagnostics
open System.Threading
open FSharp.Compiler.CodeAnalysis
open Internal.Utilities.DependencyGraph
+open Internal.Utilities.Library.Extras
open FSharpWorkspaceState
+open Internal.Utilities.Library
#nowarn "57"
@@ -32,13 +35,20 @@ type FSharpWorkspaceQuery internal (depGraph: IThreadSafeDependencyGraph<_, _>,
// in order to be able to clear previous diagnostics
let getDiagnosticResultId () = Interlocked.Increment(&resultIdCounter)
+ member internal _.Checker = checker
+
member _.GetProjectSnapshot projectIdentifier =
+ use _ =
+ Activity.start "GetProjectSnapshot" [ Activity.Tags.project, projectIdentifier.ToString() |> (!!) ]
+
try
depGraph.GetProjectSnapshot projectIdentifier |> Some
with :? KeyNotFoundException ->
None
member _.GetProjectSnapshotForFile(file: Uri) =
+ use _ =
+ Activity.start "GetProjectSnapshotForFile" [ Activity.Tags.fileName, file.LocalPath ]
depGraph.GetProjectsContaining file.LocalPath
@@ -47,28 +57,48 @@ type FSharpWorkspaceQuery internal (depGraph: IThreadSafeDependencyGraph<_, _>,
// Otherwise we have to keep track of which project/configuration is active
|> Seq.tryHead // For now just get the first one
- // TODO: split to parse and check diagnostics
- member this.GetDiagnosticsForFile(file: Uri) =
+ member this.GetParseAndCheckResultsForFile(file: Uri) =
async {
- let! diagnostics =
+ use _ =
+ Activity.start "GetParseAndCheckResultsForFile" [ Activity.Tags.fileName, file.LocalPath ]
+
+ return!
this.GetProjectSnapshotForFile file
|> Option.map (fun snapshot ->
async {
- let! parseResult, checkFileAnswer =
- checker.ParseAndCheckFileInProject(file.LocalPath, snapshot, "LSP Get diagnostics")
+ let! parseResult, checkFileAnswer = checker.ParseAndCheckFileInProject(file.LocalPath, snapshot)
return
match checkFileAnswer with
- | FSharpCheckFileAnswer.Succeeded result -> result.Diagnostics
- | FSharpCheckFileAnswer.Aborted -> parseResult.Diagnostics
+ | FSharpCheckFileAnswer.Succeeded result -> Some parseResult, Some result
+ | FSharpCheckFileAnswer.Aborted -> Some parseResult, None
})
- |> Option.defaultValue (async.Return [||])
+ |> Option.defaultValue (async.Return(None, None))
- return FSharpDiagnosticReport(diagnostics, getDiagnosticResultId ())
}
- member this.GetSemanticClassification(file) =
+ member this.GetCheckResultsForFile(file) =
+ this.GetParseAndCheckResultsForFile file |> Async.map snd
+
+ // TODO: split to parse and check diagnostics
+ member this.GetDiagnosticsForFile(file: Uri) =
+ use _ =
+ Activity.start "GetDiagnosticsForFile" [ Activity.Tags.fileName, file.LocalPath ]
+
+ this.GetParseAndCheckResultsForFile file
+ |> Async.map (fun results ->
+ let diagnostics =
+ match results with
+ | _, Some checkResult -> checkResult.Diagnostics
+ | Some parseResult, _ -> parseResult.Diagnostics
+ | _ -> [||]
+
+ FSharpDiagnosticReport(diagnostics, getDiagnosticResultId ()))
+
+ member this.GetSemanticClassification(file: Uri) =
+ use _ =
+ Activity.start "GetSemanticClassification" [ Activity.Tags.fileName, file.LocalPath ]
this.GetProjectSnapshotForFile file
|> Option.map (fun snapshot ->
diff --git a/src/Compiler/Service/FSharpWorkspaceState.fs b/src/Compiler/Service/FSharpWorkspaceState.fs
index b7a3a70dd89..ff112a7977c 100644
--- a/src/Compiler/Service/FSharpWorkspaceState.fs
+++ b/src/Compiler/Service/FSharpWorkspaceState.fs
@@ -9,8 +9,10 @@ open System.Runtime.CompilerServices
open System.Collections.Concurrent
open FSharp.Compiler.CodeAnalysis.ProjectSnapshot
+open FSharp.Compiler.Diagnostics
open Internal.Utilities.Collections
open Internal.Utilities.DependencyGraph
+open Internal.Utilities.Library
#nowarn "57"
@@ -200,9 +202,7 @@ module internal WorkspaceDependencyGraphExtensions =
[]
static member GetProjectReferencesOf(this: IDependencyGraph<_, _>, project) =
this.GetDependenciesOf(WorkspaceNodeKey.ProjectWithoutFiles project)
- |> Seq.choose (function
- | WorkspaceNodeKey.ProjectSnapshot projectId -> Some projectId
- | _ -> None)
+ |> Seq.choose WorkspaceNode.projectSnapshotKey
[]
static member GetProjectsThatReference(this: IDependencyGraph<_, _>, dllPath) =
@@ -216,8 +216,26 @@ module internal WorkspaceDependencyGraphExtensions =
[]
static member GetSourceFile(this: IDependencyGraph<_, _>, file) =
- this.GetValue(WorkspaceNodeKey.SourceFile file)
- |> _.Unpack(WorkspaceNode.sourceFile)
+ this.GetValue(WorkspaceNodeKey.SourceFile file).Unpack(WorkspaceNode.sourceFile)
+
+ []
+ static member GetFilesOf(this: IDependencyGraph<_, _>, project) =
+ this.GetDependenciesOf(WorkspaceNodeKey.ProjectSnapshot project)
+ |> Seq.map this.GetValue
+ |> Seq.choose WorkspaceNode.sourceFile
+
+ []
+ static member ReplaceSourceFiles(this: IDependencyGraph<_, _>, project, sourceFiles) =
+ let projectId = WorkspaceNodeKey.ProjectSnapshot project
+
+ this.GetDependenciesOf(WorkspaceNodeKey.ProjectSnapshot project)
+ |> Seq.where (WorkspaceNode.sourceFileKey >> _.IsSome)
+ |> Seq.iter (fun fileId -> this.RemoveDependency(projectId, noLongerDependsOn = fileId))
+
+ sourceFiles
+ |> Seq.map (fun (file, snapshot) -> WorkspaceNodeKey.SourceFile file, WorkspaceNodeValue.SourceFile snapshot)
+ |> this.AddList
+ |> Seq.iter (fun fileId -> this.AddDependency(WorkspaceNodeKey.ProjectSnapshot project, dependsOn = fileId))
/// Interface for managing files in an F# workspace.
[]
@@ -227,21 +245,33 @@ type FSharpWorkspaceFiles internal (depGraph: IThreadSafeDependencyGraph<_, _>)
let openFiles = ConcurrentDictionary()
/// Indicates that a file has been opened and has the given content. Any updates to the file should be done through `Files.Edit`.
- member this.Open = this.Edit
+ member _.Open(file: Uri, content) =
+ use _ = Activity.start "Files.Open" [ Activity.Tags.fileName, file.LocalPath ]
+
+ openFiles.AddOrUpdate(file.LocalPath, content, (fun _ _ -> content)) |> ignore
+ depGraph.AddOrUpdateFile(file.LocalPath, FSharpFileSnapshot.CreateFromString(file.LocalPath, content))
/// Indicates that a file has been changed and now has the given content. If it wasn't previously open it is considered open now.
member _.Edit(file: Uri, content) =
+ use _ = Activity.start "Files.Edit" [ Activity.Tags.fileName, file.LocalPath ]
+
openFiles.AddOrUpdate(file.LocalPath, content, (fun _ _ -> content)) |> ignore
depGraph.AddOrUpdateFile(file.LocalPath, FSharpFileSnapshot.CreateFromString(file.LocalPath, content))
/// Indicates that a file has been closed. Any changes that were not saved to disk are undone and any further reading
/// of the file's contents will be from the filesystem.
member _.Close(file: Uri) =
+ use _ = Activity.start "Files.Close" [ Activity.Tags.fileName, file.LocalPath ]
+
openFiles.TryRemove(file.LocalPath) |> ignore
// The file may have had changes that weren't saved to disk and are therefore undone by closing it.
depGraph.AddOrUpdateFile(file.LocalPath, FSharpFileSnapshot.CreateFromFileSystem(file.LocalPath))
+ /// Returns file paths for all source files of the given project.
+ member _.OfProject(projectIdentifier: FSharpProjectIdentifier) =
+ depGraph.GetFilesOf projectIdentifier |> Seq.map _.FileName
+
member internal _.GetFileContentIfOpen(path: string) =
match openFiles.TryGetValue(path) with
| true, content -> Some content
@@ -254,8 +284,34 @@ type FSharpWorkspaceProjects internal (depGraph: IThreadSafeDependencyGraph<_, _
/// A map from project output path to project identifier.
let outputPathMap = ConcurrentDictionary()
+ let createFileSnapshot path =
+ files.GetFileContentIfOpen path
+ |> Option.map (fun content -> FSharpFileSnapshot.CreateFromString(path, content))
+ |> Option.defaultWith (fun () -> FSharpFileSnapshot.CreateFromFileSystem path)
+
+ member val internal Debug_DumpGraphOnEveryChange: string option = None with get, set
+
+ member internal _.files = files
+
+ member internal this.Debug_DumpMermaid(path) =
+ let content =
+ depGraph.Debug_RenderMermaid (function
+ // Collapse all reference on disk nodes into one. Otherwise the graph is too big to render.
+ | WorkspaceGraphTypes.WorkspaceNodeKey.ReferenceOnDisk _ -> WorkspaceGraphTypes.WorkspaceNodeKey.ReferenceOnDisk "..."
+ | x -> x)
+
+ File.WriteAllText(path, content)
+
/// Adds or updates an F# project in the workspace. Project is identified by the project file and output path or FSharpProjectIdentifier.
- member _.AddOrUpdate(projectConfig: ProjectConfig, sourceFilePaths: string seq) =
+ member this.AddOrUpdate(projectConfig: ProjectConfig, sourceFilePaths: string seq) =
+
+ use _ =
+ Activity.start
+ "Projects.AddOrUpdate"
+ [
+ Activity.Tags.project, projectConfig.Identifier.ToString()
+ "sourceFiles", sourceFilePaths |> String.concat "; "
+ ]
let projectIdentifier = projectConfig.Identifier
@@ -293,14 +349,7 @@ type FSharpWorkspaceProjects internal (depGraph: IThreadSafeDependencyGraph<_, _
projectConfig, referencedProjects)
)
- .AddSourceFiles(
- sourceFilePaths
- |> Seq.map (fun path ->
- path,
- files.GetFileContentIfOpen path
- |> Option.map (fun content -> FSharpFileSnapshot.CreateFromString(path, content))
- |> Option.defaultWith (fun () -> FSharpFileSnapshot.CreateFromFileSystem path))
- )
+ .AddSourceFiles(sourceFilePaths |> Seq.map (fun path -> path, createFileSnapshot path))
.AddProjectSnapshot(
(fun ((projectConfig, referencedProjects), sourceFiles) ->
ProjectSnapshot(projectConfig, referencedProjects, sourceFiles |> Seq.toList)
@@ -326,8 +375,13 @@ type FSharpWorkspaceProjects internal (depGraph: IThreadSafeDependencyGraph<_, _
for dependentProjectId in dependentProjectIds do
depGraph.AddProjectReference(dependentProjectId, projectIdentifier)
+ this.Debug_DumpGraphOnEveryChange |> Option.iter (this.Debug_DumpMermaid)
+
projectIdentifier)
+ member this.AddOrUpdate(projectConfig, sourceFilePaths: Uri seq) =
+ this.AddOrUpdate(projectConfig, sourceFilePaths |> Seq.map _.LocalPath)
+
member this.AddOrUpdate(projectPath: string, outputPath, compilerArgs) =
let directoryPath =
@@ -336,7 +390,9 @@ type FSharpWorkspaceProjects internal (depGraph: IThreadSafeDependencyGraph<_, _
let fsharpFileExtensions = set [| ".fs"; ".fsi"; ".fsx" |]
let isFSharpFile (file: string) =
- Set.exists (fun (ext: string) -> file.EndsWith(ext, StringComparison.Ordinal)) fsharpFileExtensions
+ file.Length > 0
+ && file[0] <> '-'
+ && Set.exists (fun (ext: string) -> file.EndsWith(ext, StringComparison.Ordinal)) fsharpFileExtensions
let isReference: string -> bool = _.StartsWith("-r:")
@@ -365,3 +421,31 @@ type FSharpWorkspaceProjects internal (depGraph: IThreadSafeDependencyGraph<_, _
ProjectConfig(projectFileName, Some outputFileName, referencesOnDisk, otherOptions)
this.AddOrUpdate(projectConfig, sourceFiles)
+
+ member this.Update(projectIdentifier, newSourceFiles) =
+
+ let newSourceFiles = newSourceFiles |> Seq.cache
+
+ use _ =
+ Activity.start
+ "Projects.Update"
+ [
+ Activity.Tags.project, !!projectIdentifier.ToString()
+ "sourceFiles", newSourceFiles |> String.concat "; "
+ ]
+
+ depGraph.Transact(fun depGraph ->
+
+ let existingSourceFiles =
+ depGraph.GetFilesOf projectIdentifier |> Seq.map (fun f -> f.FileName, f) |> Map
+
+ let newFilesWithSnapshots =
+ newSourceFiles
+ |> Seq.map (fun path ->
+ path,
+ existingSourceFiles.TryFind path
+ |> Option.defaultWith (fun () -> createFileSnapshot path))
+
+ depGraph.ReplaceSourceFiles(projectIdentifier, newFilesWithSnapshots)
+
+ this.Debug_DumpGraphOnEveryChange |> Option.iter (this.Debug_DumpMermaid))
diff --git a/src/Compiler/Service/TransparentCompiler.fs b/src/Compiler/Service/TransparentCompiler.fs
index 33e98037f00..0c3f01d4a3a 100644
--- a/src/Compiler/Service/TransparentCompiler.fs
+++ b/src/Compiler/Service/TransparentCompiler.fs
@@ -1770,6 +1770,12 @@ type internal TransparentCompiler
caches.ProjectExtras.Get(
projectSnapshot.SignatureKey,
async {
+ use _ =
+ Activity.start
+ "ComputeProjectExtras"
+ [|
+ Activity.Tags.project, projectSnapshot.ProjectFileName |> Path.GetFileName |> (!!)
+ |]
let! results, finalInfo, parseDiagnostics = ComputeParseAndCheckAllFilesInProject bootstrapInfo projectSnapshot
@@ -1863,6 +1869,13 @@ type internal TransparentCompiler
caches.AssemblyData.Get(
projectSnapshot.SignatureKey,
async {
+ use _ =
+ Activity.start
+ "ComputeAssemblyData"
+ [|
+ Activity.Tags.project, projectSnapshot.ProjectFileName |> Path.GetFileName |> (!!)
+ |]
+
use! _holder = Cancellable.UseToken()
try
diff --git a/src/Compiler/Utilities/DependencyGraph.fs b/src/Compiler/Utilities/DependencyGraph.fs
index 55eb5e0d746..1156018e994 100644
--- a/src/Compiler/Utilities/DependencyGraph.fs
+++ b/src/Compiler/Utilities/DependencyGraph.fs
@@ -33,6 +33,7 @@ type IDependencyGraph<'Id, 'Val when 'Id: equality> =
abstract member RemoveDependency: node: 'Id * noLongerDependsOn: 'Id -> unit
abstract member UpdateNode: id: 'Id * update: ('Val -> 'Val) -> unit
abstract member RemoveNode: id: 'Id -> unit
+ abstract member Debug_GetNodes: ('Id -> bool) -> DependencyNode<'Id, 'Val> seq
abstract member Debug_RenderMermaid: ?mapping: ('Id -> 'Id) -> string
abstract member OnWarning: (string -> unit) -> unit
@@ -193,6 +194,9 @@ module Internal =
| false, _ -> ()
| false, _ -> ()
+ member this.Debug_GetNodes(predicate: 'Id -> bool) : DependencyNode<'Id, 'Val> seq =
+ nodes.Values |> Seq.filter (fun node -> predicate node.Id)
+
member _.Debug_RenderMermaid(?mapping) =
let mapping = defaultArg mapping id
@@ -224,6 +228,8 @@ module Internal =
interface IDependencyGraph<'Id, 'Val> with
+ member this.Debug_GetNodes(predicate) = self.Debug_GetNodes(predicate)
+
member _.AddOrUpdateNode(id, value) = self.AddOrUpdateNode(id, value)
member _.AddList(nodes) = self.AddList(nodes)
@@ -310,6 +316,9 @@ type LockOperatedDependencyGraph<'Id, 'Val when 'Id: equality and 'Id: not null>
member _.OnWarning(f) =
lock lockObj (fun () -> graph.OnWarning f)
+ member _.Debug_GetNodes(predicate) =
+ lock lockObj (fun () -> graph.Debug_GetNodes(predicate))
+
member _.Debug_RenderMermaid(m) =
lock lockObj (fun () -> graph.Debug_RenderMermaid(?mapping = m))
diff --git a/src/Compiler/Utilities/lib.fs b/src/Compiler/Utilities/lib.fs
index 8acde600ec4..e3b08d3c98f 100755
--- a/src/Compiler/Utilities/lib.fs
+++ b/src/Compiler/Utilities/lib.fs
@@ -24,7 +24,7 @@ let isEnvVarSet s =
let GetEnvInteger e dflt = match Environment.GetEnvironmentVariable(e) with null -> dflt | t -> try int t with _ -> dflt
-let dispose (x: IDisposable MaybeNull) =
+let dispose (x: IDisposable MaybeNull) =
match x with
| Null -> ()
| NonNull x -> x.Dispose()
@@ -451,4 +451,10 @@ module ListParallel =
|> ArrayParallel.map f
|> Array.toList
-
\ No newline at end of file
+[]
+module Async =
+ let map f a =
+ async {
+ let! a = a
+ return f a
+ }
diff --git a/src/Compiler/Utilities/lib.fsi b/src/Compiler/Utilities/lib.fsi
index 5d82031642f..566d6779148 100644
--- a/src/Compiler/Utilities/lib.fsi
+++ b/src/Compiler/Utilities/lib.fsi
@@ -291,3 +291,7 @@ module ListParallel =
val map: ('T -> 'U) -> 'T list -> 'U list
//val inline mapi: (int -> 'T -> 'U) -> 'T list -> 'U list
+
+[]
+module Async =
+ val map: ('T -> 'U) -> Async<'T> -> Async<'U>
diff --git a/src/FSharp.Compiler.LanguageServer/Common/CapabilitiesManager.fs b/src/FSharp.Compiler.LanguageServer/Common/CapabilitiesManager.fs
new file mode 100644
index 00000000000..556a1d96edc
--- /dev/null
+++ b/src/FSharp.Compiler.LanguageServer/Common/CapabilitiesManager.fs
@@ -0,0 +1,63 @@
+namespace FSharp.Compiler.LanguageServer.Common
+
+open Microsoft.VisualStudio.LanguageServer.Protocol
+open Microsoft.CommonLanguageServerProtocol.Framework
+open FSharp.Compiler.LanguageServer
+
+type IServerCapabilitiesOverride =
+ abstract member OverrideServerCapabilities: FSharpLanguageServerConfig * ServerCapabilities * ClientCapabilities -> ServerCapabilities
+
+type CapabilitiesManager(config: FSharpLanguageServerConfig, scOverrides: IServerCapabilitiesOverride seq) =
+
+ let mutable initializeParams = None
+
+ let getInitializeParams () =
+ match initializeParams with
+ | Some params' -> params'
+ | None -> failwith "InitializeParams is null"
+
+ let addIf (enabled: bool) (capability: 'a) =
+ if enabled then capability |> withNull else null
+
+ let defaultCapabilities (_clientCapabilities: ClientCapabilities) =
+ // TODO: don't register if dynamic registraion is supported
+ ServerCapabilities(
+ TextDocumentSync = TextDocumentSyncOptions(OpenClose = true, Change = TextDocumentSyncKind.Full),
+ DiagnosticOptions =
+ addIf
+ config.EnabledFeatures.Diagnostics
+ (DiagnosticOptions(
+ WorkDoneProgress = true,
+ InterFileDependencies = true,
+ Identifier = "potato",
+ WorkspaceDiagnostics = true
+ )),
+ //CompletionProvider = CompletionOptions(TriggerCharacters = [| "."; " " |], ResolveProvider = true, WorkDoneProgress = true),
+ //HoverProvider = SumType(HoverOptions(WorkDoneProgress = true))
+ SemanticTokensOptions =
+ addIf
+ config.EnabledFeatures.SemanticHighlighting
+
+ (SemanticTokensOptions(
+ Legend =
+ SemanticTokensLegend(
+ TokenTypes = (SemanticTokenTypes.AllTypes |> Seq.toArray), // XXX should be extended
+ TokenModifiers = (SemanticTokenModifiers.AllModifiers |> Seq.toArray)
+ ),
+ Range = false
+ ))
+ )
+
+ interface IInitializeManager with
+ member this.SetInitializeParams(request) = initializeParams <- Some request
+
+ member this.GetInitializeParams() = getInitializeParams ()
+
+ member this.GetInitializeResult() =
+ let clientCapabilities = getInitializeParams().Capabilities
+
+ let serverCapabilities =
+ (defaultCapabilities clientCapabilities, scOverrides)
+ ||> Seq.fold (fun acc (x: IServerCapabilitiesOverride) -> x.OverrideServerCapabilities(config, acc, clientCapabilities))
+
+ InitializeResult(Capabilities = serverCapabilities)
diff --git a/src/FSharp.Compiler.LanguageServer/Common/FSharpRequestContext.fs b/src/FSharp.Compiler.LanguageServer/Common/FSharpRequestContext.fs
new file mode 100644
index 00000000000..fd096d3e582
--- /dev/null
+++ b/src/FSharp.Compiler.LanguageServer/Common/FSharpRequestContext.fs
@@ -0,0 +1,187 @@
+namespace FSharp.Compiler.LanguageServer.Common
+
+open Microsoft.CommonLanguageServerProtocol.Framework
+open System.Threading
+open System.Threading.Tasks
+
+open System
+open System.Collections.Generic
+open Microsoft.VisualStudio.LanguageServer.Protocol
+
+open FSharp.Compiler.CodeAnalysis
+open FSharp.Compiler.EditorServices
+open FSharp.Compiler.Tokenization
+
+open System.Threading
+open FSharp.Compiler.CodeAnalysis.Workspace
+
+#nowarn "57"
+
+module TokenTypes =
+
+ []
+ let (|LexicalClassification|_|) (tok: FSharpToken) =
+ if tok.IsKeyword then
+ ValueSome SemanticTokenTypes.Keyword
+ elif tok.IsNumericLiteral then
+ ValueSome SemanticTokenTypes.Number
+ elif tok.IsCommentTrivia then
+ ValueSome SemanticTokenTypes.Comment
+ elif tok.IsStringLiteral then
+ ValueSome SemanticTokenTypes.String
+ else
+ ValueNone
+
+ // Tokenizes the source code and returns a list of token ranges and their SemanticTokenTypes
+ let GetSyntacticTokenTypes (source: FSharp.Compiler.Text.ISourceText) (fileName: string) =
+ let mutable tokRangesAndTypes = []
+
+ let tokenCallback =
+ fun (tok: FSharpToken) ->
+ match tok with
+ | LexicalClassification tokType -> tokRangesAndTypes <- (tok.Range, tokType) :: tokRangesAndTypes
+ | _ -> ()
+
+ FSharpLexer.Tokenize(
+ source,
+ tokenCallback,
+ flags =
+ (FSharpLexerFlags.Default
+ &&& ~~~FSharpLexerFlags.Compiling
+ &&& ~~~FSharpLexerFlags.UseLexFilter),
+ filePath = fileName
+ )
+
+ tokRangesAndTypes
+
+ let FSharpTokenTypeToLSP (fst: SemanticClassificationType) =
+ // XXX kinda arbitrary mapping
+ match fst with
+ | SemanticClassificationType.ReferenceType -> SemanticTokenTypes.Class
+ | SemanticClassificationType.ValueType -> SemanticTokenTypes.Struct
+ | SemanticClassificationType.UnionCase -> SemanticTokenTypes.Enum
+ | SemanticClassificationType.UnionCaseField -> SemanticTokenTypes.EnumMember
+ | SemanticClassificationType.Function -> SemanticTokenTypes.Function
+ | SemanticClassificationType.Property -> SemanticTokenTypes.Property
+ | SemanticClassificationType.Module -> SemanticTokenTypes.Type
+ | SemanticClassificationType.Namespace -> SemanticTokenTypes.Namespace
+ | SemanticClassificationType.Interface -> SemanticTokenTypes.Interface
+ | SemanticClassificationType.TypeArgument -> SemanticTokenTypes.TypeParameter
+ | SemanticClassificationType.Operator -> SemanticTokenTypes.Operator
+ | SemanticClassificationType.Method -> SemanticTokenTypes.Method
+ | SemanticClassificationType.ExtensionMethod -> SemanticTokenTypes.Method
+ | SemanticClassificationType.Field -> SemanticTokenTypes.Property
+ | SemanticClassificationType.Event -> SemanticTokenTypes.Event
+ | SemanticClassificationType.Delegate -> SemanticTokenTypes.Function
+ | SemanticClassificationType.NamedArgument -> SemanticTokenTypes.Parameter
+ | SemanticClassificationType.LocalValue -> SemanticTokenTypes.Variable
+ | SemanticClassificationType.Plaintext -> SemanticTokenTypes.String
+ | SemanticClassificationType.Type -> SemanticTokenTypes.Type
+ | SemanticClassificationType.Printf -> SemanticTokenTypes.Keyword
+ | _ -> SemanticTokenTypes.Comment
+
+ let toIndex (x: string) =
+ SemanticTokenTypes.AllTypes |> Seq.findIndex (fun y -> y = x)
+
+type FSharpRequestContext(lspServices: ILspServices, logger: ILspLogger, workspace: FSharpWorkspace) =
+ member _.LspServices = lspServices
+ member _.Logger = logger
+ member _.Workspace = workspace
+
+ member this.GetSemanticTokensForFile(file) =
+ task {
+ let! scv = this.Workspace.Query.GetSemanticClassification file
+ let! source = this.Workspace.Query.GetSource file
+
+ match scv, source with
+ | Some view, Some source ->
+
+ let tokens = ResizeArray()
+
+ view.ForEach(fun item ->
+ let range = item.Range
+ let tokenType = item.Type |> TokenTypes.FSharpTokenTypeToLSP |> TokenTypes.toIndex
+ tokens.Add(range, tokenType))
+
+ let syntacticClassifications =
+ TokenTypes.GetSyntacticTokenTypes source file.LocalPath
+ |> Seq.map (fun (r, t) -> (r, TokenTypes.toIndex t))
+
+ let allTokens = Seq.append tokens syntacticClassifications
+
+ let lspFormatTokens =
+ allTokens
+ |> Seq.map (fun (r, tokType) ->
+ let length = r.EndColumn - r.StartColumn // XXX Does not deal with multiline tokens?
+
+ {|
+ startLine = r.StartLine - 1
+ startCol = r.StartColumn
+ length = length
+ tokType = tokType
+ tokMods = 0
+ |})
+ //(startLine, startCol, length, tokType, tokMods))
+ |> Seq.sortWith (fun x1 x2 ->
+ let c = x1.startLine.CompareTo(x2.startLine)
+ if c <> 0 then c else x1.startCol.CompareTo(x2.startCol))
+
+ let tokensRelative =
+ lspFormatTokens
+ |> Seq.append
+ [|
+ {|
+ startLine = 0
+ startCol = 0
+ length = 0
+ tokType = 0
+ tokMods = 0
+ |}
+ |]
+ |> Seq.pairwise
+ |> Seq.map (fun (prev, this) ->
+ {|
+ startLine = this.startLine - prev.startLine
+ startCol =
+ (if prev.startLine = this.startLine then
+ this.startCol - prev.startCol
+ else
+ this.startCol)
+ length = this.length
+ tokType = this.tokType
+ tokMods = this.tokMods
+ |})
+
+ return
+ tokensRelative
+ |> Seq.map (fun tok -> [| tok.startLine; tok.startCol; tok.length; tok.tokType; tok.tokMods |])
+ |> Seq.concat
+ |> Seq.toArray
+
+ | _ -> return [||]
+ }
+
+type ContextHolder(workspace, lspServices: ILspServices) =
+
+ let logger = lspServices.GetRequiredService()
+
+ let context = FSharpRequestContext(lspServices, logger, workspace)
+
+ member _.GetContext() = context
+
+ member _.UpdateWorkspace(f) = f context.Workspace
+
+type FShapRequestContextFactory(lspServices: ILspServices) =
+
+ inherit AbstractRequestContextFactory()
+
+ override _.CreateRequestContextAsync<'TRequestParam>
+ (
+ _queueItem: IQueueItem,
+ _methodHandler: IMethodHandler,
+ _requestParam: 'TRequestParam,
+ _cancellationToken: CancellationToken
+ ) =
+ lspServices.GetRequiredService()
+ |> _.GetContext()
+ |> Task.FromResult
diff --git a/src/FSharp.Compiler.LanguageServer/Common/LifecycleManager.fs b/src/FSharp.Compiler.LanguageServer/Common/LifecycleManager.fs
new file mode 100644
index 00000000000..1919512375e
--- /dev/null
+++ b/src/FSharp.Compiler.LanguageServer/Common/LifecycleManager.fs
@@ -0,0 +1,45 @@
+namespace FSharp.Compiler.LanguageServer.Common
+
+open Microsoft.CommonLanguageServerProtocol.Framework
+open System
+open StreamJsonRpc
+open System.Threading.Tasks
+open Microsoft.Extensions.DependencyInjection
+
+#nowarn "3261"
+
+type LspServiceLifeCycleManager() =
+
+ interface ILifeCycleManager with
+ member _.ShutdownAsync(_message: string) =
+ task {
+ try
+ printfn "Shutting down"
+ with
+ | :? ObjectDisposedException
+ | :? ConnectionLostException -> ()
+ }
+
+ member _.ExitAsync() = Task.CompletedTask
+
+type FSharpLspServices(serviceCollection: IServiceCollection) as this =
+
+ do serviceCollection.AddSingleton(this) |> ignore
+
+ let serviceProvider = serviceCollection.BuildServiceProvider()
+
+ interface ILspServices with
+ member _.GetRequiredService() = serviceProvider.GetRequiredService()
+
+ member _.GetService() = serviceProvider.GetService()
+
+ member _.GetRequiredServices() = serviceProvider.GetServices()
+
+ member _.Dispose() = serviceProvider.Dispose()
+
+ member _.TryGetService(``type``, service) =
+ match serviceProvider.GetService(``type``) with
+ | NonNull x ->
+ service <- x
+ true
+ | Null -> false
diff --git a/src/FSharp.Compiler.LanguageServer/Executable.fs b/src/FSharp.Compiler.LanguageServer/Executable.fs
new file mode 100644
index 00000000000..acc8e869fdc
--- /dev/null
+++ b/src/FSharp.Compiler.LanguageServer/Executable.fs
@@ -0,0 +1,21 @@
+module FSharp.Compiler.LanguageServer.Executable
+
+open System
+open StreamJsonRpc
+
+[]
+let main _argv =
+
+ let jsonRpc = new JsonRpc(Console.OpenStandardOutput(), Console.OpenStandardInput())
+
+ let _s = new FSharpLanguageServer(jsonRpc, (LspLogger Console.Out.Write))
+
+ jsonRpc.StartListening()
+
+ async {
+ while true do
+ do! Async.Sleep 1000
+ }
+ |> Async.RunSynchronously
+
+ 0
diff --git a/src/FSharp.Compiler.LanguageServer/FSharp.Compiler.LanguageServer.fsproj b/src/FSharp.Compiler.LanguageServer/FSharp.Compiler.LanguageServer.fsproj
new file mode 100644
index 00000000000..d6ebfc1c6f6
--- /dev/null
+++ b/src/FSharp.Compiler.LanguageServer/FSharp.Compiler.LanguageServer.fsproj
@@ -0,0 +1,52 @@
+
+
+
+ Exe
+ net8.0
+ true
+ enable
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/FSharp.Compiler.LanguageServer/FSharpLanguageServer.fs b/src/FSharp.Compiler.LanguageServer/FSharpLanguageServer.fs
new file mode 100644
index 00000000000..3aadb29b2b7
--- /dev/null
+++ b/src/FSharp.Compiler.LanguageServer/FSharpLanguageServer.fs
@@ -0,0 +1,116 @@
+namespace FSharp.Compiler.LanguageServer
+
+open System.Runtime.CompilerServices
+open FSharp.Compiler.LanguageServer.Common
+open FSharp.Compiler.LanguageServer.Handlers
+
+open System
+open Microsoft.CommonLanguageServerProtocol.Framework.Handlers
+open Microsoft.CommonLanguageServerProtocol.Framework
+open Microsoft.Extensions.DependencyInjection
+open Microsoft.VisualStudio.LanguageServer.Protocol
+
+open StreamJsonRpc
+open Nerdbank.Streams
+open System.Diagnostics
+open FSharp.Compiler.CodeAnalysis.Workspace
+
+#nowarn "57"
+
+[]
+module Stuff =
+ []
+ let FSharpLanguageName = "F#"
+
+[]
+type Extensions =
+
+ []
+ static member Please(this: Async<'t>, ct) =
+ Async.StartAsTask(this, cancellationToken = ct)
+
+type FSharpLanguageServer
+ (
+ jsonRpc: JsonRpc,
+ logger: ILspLogger,
+ ?initialWorkspace: FSharpWorkspace,
+ ?addExtraHandlers: Action,
+ ?config: FSharpLanguageServerConfig
+ ) =
+
+ // TODO: Switch to SystemTextJsonLanguageServer
+ inherit NewtonsoftLanguageServer(jsonRpc, Newtonsoft.Json.JsonSerializer.CreateDefault(), logger)
+
+ let config = defaultArg config FSharpLanguageServerConfig.Default
+ let initialWorkspace = defaultArg initialWorkspace (FSharpWorkspace())
+
+ do
+ // This spins up the queue and ensure the LSP is ready to start receiving requests
+ base.Initialize()
+
+ member _.JsonRpc: JsonRpc = jsonRpc
+
+ override this.ConstructLspServices() =
+ let serviceCollection = new ServiceCollection()
+
+ let _ =
+ serviceCollection
+ .AddSingleton(initialWorkspace)
+ .AddSingleton()
+ .AddSingleton(config)
+ .AddSingleton>()
+ .AddSingleton>()
+ .AddSingleton()
+ .AddSingleton()
+ .AddSingleton(logger)
+ .AddSingleton, FShapRequestContextFactory>()
+ .AddSingleton, CapabilitiesManager>()
+ .AddSingleton(this)
+ .AddSingleton(new LspServiceLifeCycleManager())
+
+ match addExtraHandlers with
+ | Some handler -> handler.Invoke(serviceCollection)
+ | None -> ()
+
+ let lspServices = new FSharpLspServices(serviceCollection)
+
+ lspServices :> ILspServices
+
+ static member Create() =
+ FSharpLanguageServer.Create(FSharpWorkspace())
+
+ static member Create(initialWorkspace) =
+ FSharpLanguageServer.Create(initialWorkspace, (fun _ -> ()))
+
+ static member Create(initialWorkspace, addExtraHandlers: Action) =
+ FSharpLanguageServer.Create(LspLogger System.Diagnostics.Trace.TraceInformation, initialWorkspace, addExtraHandlers)
+
+ static member Create(initialWorkspace, config: FSharpLanguageServerConfig, addExtraHandlers: Action) =
+ FSharpLanguageServer.Create(LspLogger System.Diagnostics.Trace.TraceInformation, initialWorkspace, addExtraHandlers, config)
+
+ static member Create
+ (logger: ILspLogger, initialWorkspace, ?addExtraHandlers: Action, ?config: FSharpLanguageServerConfig)
+ =
+
+ let struct (clientStream, serverStream) = FullDuplexStream.CreatePair()
+
+ // TODO: handle disposal of these
+ let formatter = new JsonMessageFormatter()
+
+ let messageHandler =
+ new HeaderDelimitedMessageHandler(serverStream, serverStream, formatter)
+
+ let jsonRpc = new JsonRpc(messageHandler)
+
+ let listener = new TextWriterTraceListener(Console.Out)
+
+ jsonRpc.TraceSource.Listeners.Add(listener) |> ignore
+
+ jsonRpc.TraceSource.Switch.Level <- SourceLevels.All
+
+ let server =
+ new FSharpLanguageServer(jsonRpc, logger, initialWorkspace, ?addExtraHandlers = addExtraHandlers, ?config = config)
+
+ jsonRpc.StartListening()
+
+ (clientStream, clientStream), server
diff --git a/src/FSharp.Compiler.LanguageServer/FSharpLanguageServerConfig.fs b/src/FSharp.Compiler.LanguageServer/FSharpLanguageServerConfig.fs
new file mode 100644
index 00000000000..a1acd2b88da
--- /dev/null
+++ b/src/FSharp.Compiler.LanguageServer/FSharpLanguageServerConfig.fs
@@ -0,0 +1,23 @@
+namespace FSharp.Compiler.LanguageServer
+
+type FSharpLanguageServerFeatures =
+ {
+ Diagnostics: bool
+ SemanticHighlighting: bool
+ }
+
+ static member Default =
+ {
+ Diagnostics = true
+ SemanticHighlighting = true
+ }
+
+type FSharpLanguageServerConfig =
+ {
+ EnabledFeatures: FSharpLanguageServerFeatures
+ }
+
+ static member Default =
+ {
+ EnabledFeatures = FSharpLanguageServerFeatures.Default
+ }
diff --git a/src/FSharp.Compiler.LanguageServer/Handlers/DocumentStateHandler.fs b/src/FSharp.Compiler.LanguageServer/Handlers/DocumentStateHandler.fs
new file mode 100644
index 00000000000..b7f281ed9e8
--- /dev/null
+++ b/src/FSharp.Compiler.LanguageServer/Handlers/DocumentStateHandler.fs
@@ -0,0 +1,47 @@
+namespace FSharp.Compiler.LanguageServer.Handlers
+
+open Microsoft.CommonLanguageServerProtocol.Framework
+open Microsoft.VisualStudio.LanguageServer.Protocol
+open FSharp.Compiler.LanguageServer.Common
+
+open System.Threading
+open System.Threading.Tasks
+
+#nowarn "57"
+
+type DocumentStateHandler() =
+ interface IMethodHandler with
+ member _.MutatesSolutionState = true
+
+ interface IRequestHandler with
+ []
+ member _.HandleRequestAsync
+ (request: DidOpenTextDocumentParams, context: FSharpRequestContext, _cancellationToken: CancellationToken)
+ =
+ let contextHolder = context.LspServices.GetRequiredService()
+
+ contextHolder.UpdateWorkspace _.Files.Open(request.TextDocument.Uri, request.TextDocument.Text)
+
+ Task.FromResult(SemanticTokensDeltaPartialResult())
+
+ interface IRequestHandler with
+ []
+ member _.HandleRequestAsync
+ (request: DidChangeTextDocumentParams, context: FSharpRequestContext, _cancellationToken: CancellationToken)
+ =
+ let contextHolder = context.LspServices.GetRequiredService()
+
+ contextHolder.UpdateWorkspace _.Files.Edit(request.TextDocument.Uri, request.ContentChanges.[0].Text)
+
+ Task.FromResult(SemanticTokensDeltaPartialResult())
+
+ interface INotificationHandler with
+ []
+ member _.HandleNotificationAsync
+ (request: DidCloseTextDocumentParams, context: FSharpRequestContext, _cancellationToken: CancellationToken)
+ =
+ let contextHolder = context.LspServices.GetRequiredService()
+
+ contextHolder.UpdateWorkspace _.Files.Close(request.TextDocument.Uri)
+
+ Task.CompletedTask
diff --git a/src/FSharp.Compiler.LanguageServer/Handlers/LanguageFeaturesHandler.fs b/src/FSharp.Compiler.LanguageServer/Handlers/LanguageFeaturesHandler.fs
new file mode 100644
index 00000000000..474758924d0
--- /dev/null
+++ b/src/FSharp.Compiler.LanguageServer/Handlers/LanguageFeaturesHandler.fs
@@ -0,0 +1,59 @@
+namespace FSharp.Compiler.LanguageServer.Handlers
+
+open Microsoft.CommonLanguageServerProtocol.Framework
+open Microsoft.VisualStudio.LanguageServer.Protocol
+open Microsoft.VisualStudio.FSharp.Editor.CancellableTasks
+open FSharp.Compiler.LanguageServer.Common
+open FSharp.Compiler.LanguageServer
+open System.Threading.Tasks
+open System.Threading
+open System.Collections.Generic
+open Microsoft.VisualStudio.FSharp.Editor
+
+#nowarn "57"
+
+type LanguageFeaturesHandler() =
+ interface IMethodHandler with
+ member _.MutatesSolutionState = false
+
+ interface IRequestHandler<
+ DocumentDiagnosticParams,
+ SumType,
+ FSharpRequestContext
+ > with
+ []
+ member _.HandleRequestAsync
+ (request: DocumentDiagnosticParams, context: FSharpRequestContext, cancellationToken: CancellationToken)
+ =
+ cancellableTask {
+
+ let! fsharpDiagnosticReport = context.Workspace.Query.GetDiagnosticsForFile request.TextDocument.Uri
+
+ let report =
+ FullDocumentDiagnosticReport(
+ Items = (fsharpDiagnosticReport.Diagnostics |> Array.map (_.ToLspDiagnostic())),
+ ResultId = fsharpDiagnosticReport.ResultId
+ )
+
+ let relatedDocuments = Dictionary()
+
+ relatedDocuments.Add(
+ request.TextDocument.Uri,
+ SumType report
+ )
+
+ return
+ SumType(
+ RelatedFullDocumentDiagnosticReport(RelatedDocuments = relatedDocuments)
+ )
+ }
+ |> CancellableTask.start cancellationToken
+
+ interface IRequestHandler with
+ []
+ member _.HandleRequestAsync(request: SemanticTokensParams, context: FSharpRequestContext, cancellationToken: CancellationToken) =
+ cancellableTask {
+ let! tokens = context.GetSemanticTokensForFile(request.TextDocument.Uri)
+ return SemanticTokens(Data = tokens)
+ }
+ |> CancellableTask.start cancellationToken
diff --git a/src/FSharp.Compiler.LanguageServer/Utils.fs b/src/FSharp.Compiler.LanguageServer/Utils.fs
new file mode 100644
index 00000000000..c5283b6fb76
--- /dev/null
+++ b/src/FSharp.Compiler.LanguageServer/Utils.fs
@@ -0,0 +1,90 @@
+namespace FSharp.Compiler.LanguageServer
+
+open Microsoft.CommonLanguageServerProtocol.Framework
+open Microsoft.VisualStudio.LanguageServer.Protocol
+
+open System.Diagnostics
+
+open FSharp.Compiler.Diagnostics
+open System.Runtime.CompilerServices
+
+[]
+module Utils =
+
+ type LspRange = Microsoft.VisualStudio.LanguageServer.Protocol.Range
+
+ let LspLogger (output: string -> unit) =
+ { new ILspLogger with
+ member this.LogEndContext(message: string, ``params``: obj array) : unit =
+ output $"EndContext :: {message} %A{``params``}"
+
+ member this.LogError(message: string, ``params``: obj array) : unit =
+ output $"ERROR :: {message} %A{``params``}"
+
+ member this.LogException(``exception``: exn, message: string, ``params``: obj array) : unit =
+ output $"EXCEPTION :: %A{``exception``} {message} %A{``params``}"
+
+ member this.LogInformation(message: string, ``params``: obj array) : unit =
+ output $"INFO :: {message} %A{``params``}"
+
+ member this.LogStartContext(message: string, ``params``: obj array) : unit =
+ output $"StartContext :: {message} %A{``params``}"
+
+ member this.LogWarning(message: string, ``params``: obj array) : unit =
+ output $"WARNING :: {message} %A{``params``}"
+ }
+
+ type FSharp.Compiler.Text.Range with
+
+ member this.ToLspRange() =
+ LspRange(
+ Start = Position(Line = this.StartLine - 1, Character = this.StartColumn),
+ End = Position(Line = this.EndLine - 1, Character = this.EndColumn)
+ )
+
+[]
+type FSharpDiagnosticExtensions =
+
+ []
+ static member ToLspDiagnostic(this: FSharpDiagnostic) =
+ Diagnostic(
+ Range = this.Range.ToLspRange(),
+ Severity = DiagnosticSeverity.Error,
+ Message = $"LSP: {this.Message}",
+ //Source = "Intellisense",
+ Code = SumType this.ErrorNumberText
+ )
+
+module Activity =
+ let listen (filter) logMsg =
+ let indent (activity: Activity) =
+ let rec loop (activity: Activity) n =
+ match activity.Parent with
+ | Null -> n
+ | NonNull parent -> loop (parent) (n + 1)
+
+ String.replicate (loop activity 0) " "
+
+ let collectTags (activity: Activity) =
+ [ for tag in activity.Tags -> $"{tag.Key}: %A{tag.Value}" ]
+ |> String.concat ", "
+
+ let listener =
+ new ActivityListener(
+ ShouldListenTo = (fun source -> source.Name = FSharp.Compiler.Diagnostics.ActivityNames.FscSourceName),
+ Sample =
+ (fun context ->
+ if filter context.Name then
+ ActivitySamplingResult.AllDataAndRecorded
+ else
+ ActivitySamplingResult.None),
+ ActivityStarted = (fun a -> logMsg $"{indent a}{a.OperationName} {collectTags a}")
+ )
+
+ ActivitySource.AddActivityListener(listener)
+
+ let listenToAll () =
+ listen (fun _ -> true) Trace.TraceInformation
+
+ let listenToSome () =
+ listen (fun x -> not <| x.Contains "StackGuard") Trace.TraceInformation
diff --git a/src/FSharp.VisualStudio.Extension/.vsextension/string-resources.json b/src/FSharp.VisualStudio.Extension/.vsextension/string-resources.json
new file mode 100644
index 00000000000..5f1633293d7
--- /dev/null
+++ b/src/FSharp.VisualStudio.Extension/.vsextension/string-resources.json
@@ -0,0 +1,4 @@
+{
+ "FSharpLspExtension.FSharpLanguageServerProvider.DisplayName": "FSharp Analyzer LSP server"
+
+}
diff --git a/src/FSharp.VisualStudio.Extension/ExtensionEntrypoint.cs b/src/FSharp.VisualStudio.Extension/ExtensionEntrypoint.cs
new file mode 100644
index 00000000000..ba47320b925
--- /dev/null
+++ b/src/FSharp.VisualStudio.Extension/ExtensionEntrypoint.cs
@@ -0,0 +1,34 @@
+namespace FSharp.VisualStudio.Extension;
+
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.VisualStudio.Extensibility;
+using System.Threading;
+using System;
+using Extension = Microsoft.VisualStudio.Extensibility.Extension;
+
+///
+/// Extension entrypoint for the VisualStudio.Extensibility extension.
+///
+[VisualStudioContribution]
+internal class ExtensionEntrypoint : Extension
+{
+
+ ///
+ public override ExtensionConfiguration ExtensionConfiguration => new()
+ {
+ Metadata = new(
+ id: "FSharp.VisualStudio.Extension.4fd40904-7bdd-40b0-82ab-588cbee624d1",
+ version: this.ExtensionAssemblyVersion,
+ publisherName: "Publisher name",
+ displayName: "FSharp.VisualStudio.Extension",
+ description: "Extension description"),
+ };
+
+ ///
+ protected override void InitializeServices(IServiceCollection serviceCollection)
+ {
+ base.InitializeServices(serviceCollection);
+
+ // You can configure dependency injection here by adding services to the serviceCollection.
+ }
+}
diff --git a/src/FSharp.VisualStudio.Extension/FSharp.VisualStudio.Extension.csproj b/src/FSharp.VisualStudio.Extension/FSharp.VisualStudio.Extension.csproj
new file mode 100644
index 00000000000..fae776deb73
--- /dev/null
+++ b/src/FSharp.VisualStudio.Extension/FSharp.VisualStudio.Extension.csproj
@@ -0,0 +1,41 @@
+
+
+ net8.0-windows
+ enable
+ 12
+ en-US
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ false
+
+
+
+
+
diff --git a/src/FSharp.VisualStudio.Extension/FSharpExtensionSettings.cs b/src/FSharp.VisualStudio.Extension/FSharpExtensionSettings.cs
new file mode 100644
index 00000000000..edc3e7adcff
--- /dev/null
+++ b/src/FSharp.VisualStudio.Extension/FSharpExtensionSettings.cs
@@ -0,0 +1,55 @@
+using Microsoft.VisualStudio.Extensibility;
+using Microsoft.VisualStudio.Extensibility.Settings;
+
+namespace FSharp.VisualStudio.Extension
+{
+#pragma warning disable VSEXTPREVIEW_SETTINGS // The settings API is currently in preview and marked as experimental
+
+ internal static class FSharpExtensionSettings
+ {
+ public const string OLD = "old";
+ public const string LSP = "lsp";
+ public const string BOTH = "both";
+ public const string UNSET = "unset";
+
+ public static EnumSettingEntry[] ExtensionChoice { get; } =
+ [
+ new(OLD, "%FSharpSettings.Old%"),
+ new(LSP, "%FSharpSettings.LSP%"),
+ new(BOTH, "%FSharpSettings.Both%"),
+ new(UNSET, "%FSharpSettings.Unset%"),
+ ];
+
+
+ [VisualStudioContribution]
+ public static SettingCategory FSharpCategory { get; } = new("fsharp", "%F#%");
+
+ [VisualStudioContribution]
+ public static Setting.Enum GetDiagnosticsFrom { get; } = new(
+ "getDiagnosticsFrom",
+ "%FSharpSettings.GetDiagnosticsFrom%",
+ FSharpCategory,
+ ExtensionChoice,
+ defaultValue: UNSET)
+ {
+ Description = "%Which extension should be used to provide diagnostics%",
+ };
+
+ [VisualStudioContribution]
+ public static Setting.Enum GetSemanticHighlightingFrom { get; } = new(
+ "getSemanticHighlightingFrom",
+ "%FSharpSettings.GetSemanticHighlightingFrom%",
+ FSharpCategory,
+ ExtensionChoice,
+ defaultValue: UNSET)
+ {
+ Description = "%Which extension should be used to provide semantic highlighting%",
+ };
+
+ public static Setting[] AllStringSettings { get; } =
+ [
+ GetDiagnosticsFrom,
+ GetSemanticHighlightingFrom,
+ ];
+ }
+}
diff --git a/src/FSharp.VisualStudio.Extension/FSharpLanguageServerProvider.cs b/src/FSharp.VisualStudio.Extension/FSharpLanguageServerProvider.cs
new file mode 100644
index 00000000000..c4f90a7ca1d
--- /dev/null
+++ b/src/FSharp.VisualStudio.Extension/FSharpLanguageServerProvider.cs
@@ -0,0 +1,412 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+namespace FSharp.VisualStudio.Extension;
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.IO.Pipelines;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using FSharp.Compiler.CodeAnalysis.Workspace;
+using FSharp.Compiler.Diagnostics;
+using FSharp.Compiler.LanguageServer;
+using FSharp.Compiler.LanguageServer.Common;
+
+using Microsoft.CommonLanguageServerProtocol.Framework;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.FSharp.Core;
+using Microsoft.VisualStudio.Extensibility;
+using Microsoft.VisualStudio.Extensibility.Editor;
+using Microsoft.VisualStudio.Extensibility.LanguageServer;
+using Microsoft.VisualStudio.Extensibility.Settings;
+using Microsoft.VisualStudio.LanguageServer.Protocol;
+using Microsoft.VisualStudio.ProjectSystem.Query;
+using Microsoft.VisualStudio.RpcContracts.LanguageServerProvider;
+using Nerdbank.Streams;
+
+///
+#pragma warning disable VSEXTPREVIEW_LSP // Type is for evaluation purposes only and is subject to change or removal in future updates.
+
+#pragma warning disable VSEXTPREVIEW_PROJECTQUERY_PROPERTIES_BUILDPROPERTIES // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
+
+
+internal static class Extensions
+{
+ public static List> Please(this IAsyncQueryable x) => x.QueryAsync(CancellationToken.None).ToBlockingEnumerable().ToList();
+}
+
+
+internal class VsServerCapabilitiesOverride : IServerCapabilitiesOverride
+{
+ public ServerCapabilities OverrideServerCapabilities(FSharpLanguageServerConfig config, ServerCapabilities value, ClientCapabilities clientCapabilities)
+ {
+ var capabilities = new VSInternalServerCapabilities
+ {
+ TextDocumentSync = value.TextDocumentSync,
+ SupportsDiagnosticRequests = true,
+ ProjectContextProvider = true,
+ DiagnosticProvider =
+ config.EnabledFeatures.Diagnostics ?
+
+ new()
+ {
+ SupportsMultipleContextsDiagnostics = true,
+ DiagnosticKinds = [
+ // Support a specialized requests dedicated to task-list items. This way the client can ask just
+ // for these, independently of other diagnostics. They can also throttle themselves to not ask if
+ // the task list would not be visible.
+ //VSInternalDiagnosticKind.Task,
+ // Dedicated request for workspace-diagnostics only. We will only respond to these if FSA is on.
+ VSInternalDiagnosticKind.Syntax,
+ // Fine-grained diagnostics requests. Importantly, this separates out syntactic vs semantic
+ // requests, allowing the former to quickly reach the user without blocking on the latter. In a
+ // similar vein, compiler diagnostics are explicitly distinct from analyzer-diagnostics, allowing
+ // the former to appear as soon as possible as they are much more critical for the user and should
+ // not be delayed by a slow analyzer.
+ //new("Semantic"),
+ //new(PullDiagnosticCategories.DocumentAnalyzerSyntax),
+ //new(PullDiagnosticCategories.DocumentAnalyzerSemantic),
+ ]
+ } : null,
+ SemanticTokensOptions = config.EnabledFeatures.SemanticHighlighting ? new()
+ {
+ Legend = new()
+ {
+ TokenTypes = [.. SemanticTokenTypes.AllTypes], // XXX should be extended
+ TokenModifiers = [.. SemanticTokenModifiers.AllModifiers]
+ },
+ Full = new SemanticTokensFullOptions()
+ {
+ Delta = false
+ },
+ Range = false
+ } : null,
+ //,
+ //HoverProvider = new HoverOptions()
+ //{
+ // WorkDoneProgress = true
+ //}
+ };
+ return capabilities;
+ }
+}
+
+internal class VsDiagnosticsHandler
+ : IRequestHandler,
+ IRequestHandler
+{
+ public bool MutatesSolutionState => false;
+
+ [LanguageServerEndpoint(VSInternalMethods.DocumentPullDiagnosticName, LanguageServerConstants.DefaultLanguageName)]
+ public async Task HandleRequestAsync(VSInternalDiagnosticParams request, FSharpRequestContext context, CancellationToken cancellationToken)
+ {
+ var report = await context.Workspace.Query.GetDiagnosticsForFile(request!.TextDocument!.Uri).Please(cancellationToken);
+
+ var vsReport = new VSInternalDiagnosticReport
+ {
+ ResultId = report.ResultId,
+ //Identifier = 1,
+ //Version = 1,
+ Diagnostics = [.. report.Diagnostics.Select(FSharpDiagnosticExtensions.ToLspDiagnostic)]
+ };
+
+ return [vsReport];
+ }
+
+ [LanguageServerEndpoint("textDocument/_vs_getProjectContexts", LanguageServerConstants.DefaultLanguageName)]
+ public Task HandleRequestAsync(VSGetProjectContextsParams request, FSharpRequestContext context, CancellationToken cancellationToken)
+ {
+ return Task.FromResult(new VSProjectContextList()
+ {
+ DefaultIndex = 0,
+ ProjectContexts = [
+ //new() {
+ // Id = "potato",
+ // Label = "Potato",
+ // // PR for F# project kind: https://devdiv.visualstudio.com/DevDiv/_git/VSLanguageServerClient/pullrequest/529882
+ // Kind = VSProjectKind.FSharp
+ //},
+ //new () {
+ // Id = "potato2",
+ // Label = "Potato2",
+ // Kind = VSProjectKind.FSharp
+ //}
+
+ ]
+ });
+ }
+}
+
+
+internal class SolutionObserver : IObserver>
+{
+ public void OnCompleted()
+ {
+
+ }
+
+ public void OnError(Exception error)
+ {
+ }
+
+ public void OnNext(IQueryResults value)
+ {
+ Trace.TraceInformation("Solution was updated");
+ }
+
+}
+
+internal class ProjectObserver(FSharpWorkspace workspace) : IObserver>
+{
+ private readonly FSharpWorkspace workspace = workspace;
+
+ internal void ProcessProject(IProjectSnapshot project)
+ {
+ project.Id.TryGetValue("ProjectPath", out var projectPath);
+
+ List<(string, string)> projectInfos = [];
+
+ if (projectPath != null && projectPath.ToLower().EndsWith(".fsproj"))
+ {
+ var configs = project.ActiveConfigurations.ToList();
+
+ foreach (var config in configs)
+ {
+ if (config != null)
+ {
+ // Extract bin output path for each active config
+ var data = config.OutputGroups;
+
+ string? outputPath = null;
+ foreach (var group in data)
+ {
+ if (group.Name == "Built")
+ {
+ foreach (var output in group.Outputs)
+ {
+ if (output.FinalOutputPath != null && (output.FinalOutputPath.ToLower().EndsWith(".dll") || output.FinalOutputPath.ToLower().EndsWith(".exe")))
+ {
+ outputPath = output.FinalOutputPath;
+ break;
+ }
+ }
+ if (outputPath != null)
+ {
+ break;
+ }
+ }
+ }
+
+ foreach (var ruleResults in config.RuleResults)
+ {
+ // XXX Idk why `.Where` does not work with these IAsyncQueryable type
+ if (ruleResults?.RuleName == "CompilerCommandLineArgs")
+ {
+ // XXX Not sure why there would be more than one item for this rule result
+ // Taking first one, ignoring the rest
+ var args = ruleResults?.Items?.FirstOrDefault()?.Name;
+ if (args != null && outputPath != null) projectInfos.Add((outputPath, args));
+ }
+ }
+ }
+ }
+
+ foreach (var projectInfo in projectInfos)
+ {
+ workspace.Projects.AddOrUpdate(projectPath, projectInfo.Item1, projectInfo.Item2.Split(';'));
+ }
+
+ //var graphPath = Path.Combine(Path.GetDirectoryName(projectPath) ?? ".", "..", "depGraph.md");
+
+ //workspace.projects.Debug_DumpGraphOnEveryChange = FSharpOption.Some(graphPath);
+
+ //Trace.TraceInformation($"Auto-saving workspace graph to {graphPath}");
+
+ }
+ }
+
+ public void OnNext(IQueryResults result)
+ {
+ foreach (var project in result)
+ {
+ this.ProcessProject(project);
+ }
+ }
+
+ public void OnCompleted()
+ {
+ }
+
+ public void OnError(Exception error)
+ {
+ }
+}
+
+
+[VisualStudioContribution]
+internal class FSharpLanguageServerProvider : LanguageServerProvider
+{
+ ///
+ /// Gets the document type for FSharp code files.
+ ///
+ [VisualStudioContribution]
+ public static DocumentTypeConfiguration FSharpDocumentType => new("F#")
+ {
+ FileExtensions = [".fs", ".fsi", ".fsx"],
+ BaseDocumentType = LanguageServerBaseDocumentType,
+ };
+
+ ///
+ public override LanguageServerProviderConfiguration LanguageServerProviderConfiguration => new(
+ "%FSharpLspExtension.FSharpLanguageServerProvider.DisplayName%",
+ [Microsoft.VisualStudio.Extensibility.DocumentFilter.FromDocumentType(FSharpDocumentType)]);
+
+ ///
+ public override async Task CreateServerConnectionAsync(CancellationToken cancellationToken)
+ {
+ var activitySourceName = "fsc";
+
+ FSharp.Compiler.LanguageServer.Activity.listenToSome();
+
+#pragma warning disable VSEXTPREVIEW_SETTINGS // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
+
+ // Write default settings unless they're overridden. Otherwise users can't even find which settings exist.
+
+ var settingsReadResult = await this.Extensibility.Settings().ReadEffectiveValuesAsync(FSharpExtensionSettings.AllStringSettings, cancellationToken);
+
+ var settingValues = FSharpExtensionSettings.AllStringSettings.Select(
+ setting => (setting, settingsReadResult.ValueOrDefault(setting, defaultValue: FSharpExtensionSettings.UNSET)));
+
+ foreach (var (setting, value) in settingValues.Where(x => x.Item2 == FSharpExtensionSettings.UNSET))
+ {
+ await this.Extensibility.Settings().WriteAsync(batch =>
+ batch.WriteSetting(setting, FSharpExtensionSettings.BOTH), "write default settings", cancellationToken);
+ }
+
+ var enabled = new[] { FSharpExtensionSettings.LSP, FSharpExtensionSettings.BOTH };
+
+ var serverConfig = new FSharpLanguageServerConfig(
+ new FSharpLanguageServerFeatures(
+ diagnostics: enabled.Contains(settingsReadResult.ValueOrDefault(FSharpExtensionSettings.GetDiagnosticsFrom, defaultValue: FSharpExtensionSettings.BOTH)),
+ semanticHighlighting: enabled.Contains(settingsReadResult.ValueOrDefault(FSharpExtensionSettings.GetSemanticHighlightingFrom, defaultValue: FSharpExtensionSettings.BOTH))
+ ));
+
+ var disposeToEndSubscription =
+ this.Extensibility.Settings().SubscribeAsync(
+ [FSharpExtensionSettings.FSharpCategory],
+ cancellationToken,
+ changeHandler: result =>
+ {
+ Trace.TraceInformation($"Settings update", result);
+ });
+
+#pragma warning restore VSEXTPREVIEW_SETTINGS // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
+
+
+ //const string vsMajorVersion = "17.0";
+
+ //var settings = OpenTelemetryExporterSettingsBuilder
+ // .CreateVSDefault(vsMajorVersion)
+ // .Build();
+
+ //try
+ //{
+ // var tracerProvider = Sdk.CreateTracerProviderBuilder()
+ // .AddVisualStudioDefaultTraceExporter(settings)
+ // //.AddConsoleExporter()
+ // .AddOtlpExporter()
+ // .Build();
+ //}
+ //catch (Exception e)
+ //{
+ // Trace.TraceError($"Failed to create OpenTelemetry tracer provider: {e}");
+ //}
+
+
+ var activitySource = new ActivitySource(activitySourceName);
+ var activity = activitySource.CreateActivity("CreateServerConnectionAsync", ActivityKind.Internal);
+
+ if (activity != null)
+ {
+ activity.Start();
+ }
+ else
+ {
+ Trace.TraceWarning("Failed to start OpenTelemetry activity, there are no listeners");
+ }
+
+ var ws = this.Extensibility.Workspaces();
+
+ var projectQuery = (IAsyncQueryable project) => project
+ .With(p => p.ActiveConfigurations
+ .With(c => c.ConfigurationDimensions.With(d => d.Name).With(d => d.Value))
+ .With(c => c.Properties.With(p => p.Name).With(p => p.Value))
+ .With(c => c.OutputGroups.With(g => g.Name).With(g => g.Outputs.With(o => o.Name).With(o => o.FinalOutputPath).With(o => o.RootRelativeURL)))
+ .With(c => c.RuleResultsByRuleName("CompilerCommandLineArgs")
+ .With(r => r.RuleName)
+ .With(r => r.Items)))
+ .With(p => p.ProjectReferences
+ .With(r => r.ReferencedProjectPath)
+ .With(r => r.CanonicalName)
+ .With(r => r.Id)
+ .With(r => r.Name)
+ .With(r => r.ProjectGuid)
+ .With(r => r.ReferencedProjectId)
+ .With(r => r.ReferenceType));
+
+ IQueryResults? result = await ws.QueryProjectsAsync(p => projectQuery(p).With(p => new { p.ActiveConfigurations, p.Id, p.Guid }), cancellationToken);
+
+ var workspace = new FSharpWorkspace();
+
+ foreach (var project in result)
+ {
+ var observer = new ProjectObserver(workspace);
+
+ await projectQuery(project.AsQueryable()).SubscribeAsync(observer, CancellationToken.None);
+
+ // TODO: should we do this, or are we guaranteed it will get processed?
+ // observer.ProcessProject(project);
+ }
+
+ var ((inputStream, outputStream), _server) = FSharpLanguageServer.Create(workspace, serverConfig, (serviceCollection) =>
+ {
+ serviceCollection.AddSingleton();
+ serviceCollection.AddSingleton();
+ });
+
+ var solutions = await ws.QuerySolutionAsync(
+ solution => solution.With(solution => solution.FileName),
+ cancellationToken);
+
+ var singleSolution = solutions.FirstOrDefault();
+
+ if (singleSolution != null)
+ {
+ var unsubscriber = await singleSolution
+ .AsQueryable()
+ .With(p => p.Projects.With(p => p.Files))
+ .SubscribeAsync(new SolutionObserver(), CancellationToken.None);
+ }
+
+ return new DuplexPipe(
+ PipeReader.Create(inputStream),
+ PipeWriter.Create(outputStream));
+ }
+
+ ///
+ public override Task OnServerInitializationResultAsync(ServerInitializationResult serverInitializationResult, LanguageServerInitializationFailureInfo? initializationFailureInfo, CancellationToken cancellationToken)
+ {
+ if (serverInitializationResult == ServerInitializationResult.Failed)
+ {
+ // Log telemetry for failure and disable the server from being activated again.
+ this.Enabled = false;
+ }
+
+ return base.OnServerInitializationResultAsync(serverInitializationResult, initializationFailureInfo, cancellationToken);
+ }
+}
+#pragma warning restore VSEXTPREVIEW_LSP // Type is for evaluation purposes only and is subject to change or removal in future updates.
diff --git a/src/Microsoft.CommonLanguageServerProtocol.Framework.Proxy/Microsoft.CommonLanguageServerProtocol.Framework.Proxy.csproj b/src/Microsoft.CommonLanguageServerProtocol.Framework.Proxy/Microsoft.CommonLanguageServerProtocol.Framework.Proxy.csproj
new file mode 100644
index 00000000000..97ac5ccaebe
--- /dev/null
+++ b/src/Microsoft.CommonLanguageServerProtocol.Framework.Proxy/Microsoft.CommonLanguageServerProtocol.Framework.Proxy.csproj
@@ -0,0 +1,19 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/FSharp.Compiler.ComponentTests/CompilerService/FSharpWorkspace.fs b/tests/FSharp.Compiler.ComponentTests/CompilerService/FSharpWorkspace.fs
index fbae320131d..6fd5062d35e 100644
--- a/tests/FSharp.Compiler.ComponentTests/CompilerService/FSharpWorkspace.fs
+++ b/tests/FSharp.Compiler.ComponentTests/CompilerService/FSharpWorkspace.fs
@@ -1,44 +1,61 @@
module CompilerService.FSharpWorkspace
open System
+
open Xunit
open FSharp.Compiler.CodeAnalysis.ProjectSnapshot
-open TestFramework
-open FSharp.Compiler.IO
open FSharp.Compiler.CodeAnalysis.Workspace
+open FSharp.Compiler.Diagnostics
+open FSharp.Test.ProjectGeneration.WorkspaceHelpers
+open OpenTelemetry
+open OpenTelemetry.Resources
+open OpenTelemetry.Trace
+open OpenTelemetry.Exporter
+open System.IO
#nowarn "57"
-type ProjectConfig with
+// System.Diagnostics.DiagnosticSource seems to be missing in NET FW. Might investigate this later
+#if !NETFRAMEWORK
- static member Minimal(?name, ?outputPath, ?referencesOnDisk) =
- let name = defaultArg name "test"
- let projectFileName = $"{name}.fsproj"
- let outputPath = defaultArg outputPath $"{name}.dll"
- let referencesOnDisk = defaultArg referencesOnDisk []
- ProjectConfig(projectFileName, Some outputPath, referencesOnDisk, [])
+//type FilteredJaegerExporter(predicate) =
-let getReferencedSnapshot (projectIdentifier: FSharpProjectIdentifier) (projectSnapshot: FSharpProjectSnapshot) =
- projectSnapshot.ReferencedProjects
- |> Seq.pick (function
- | FSharpReference(x, snapshot) when x = projectIdentifier.OutputFileName -> Some snapshot
- | _ -> None)
+// inherit SimpleActivityExportProcessor(new JaegerExporter(new JaegerExporterOptions()))
-let sourceFileOnDisk (content: string) =
- let path = getTemporaryFileName () + ".fs"
- FileSystem.OpenFileForWriteShim(path).Write(content)
- Uri(path)
+// override _.OnEnd(activity: System.Diagnostics.Activity) =
+// if predicate activity then
+// base.OnEnd activity
-let assertFileHasContent filePath expectedContent (projectSnapshot: FSharpProjectSnapshot) =
- let fileSnapshot =
- projectSnapshot.SourceFiles |> Seq.find (_.FileName >> (=) filePath)
+/// Wrapper for FSharpWorkspace to use in tests. Provides OpenTelemetry tracing.
+type TestingWorkspace(testName) as _this =
+ inherit FSharpWorkspace()
+
+ let debugGraphPath = __SOURCE_DIRECTORY__ ++ $"{testName}.md"
+
+ //let tracerProvider =
+ // Sdk
+ // .CreateTracerProviderBuilder()
+ // .AddSource("fsc")
+ // .SetResourceBuilder(ResourceBuilder.CreateDefault().AddService(serviceName="F#", serviceVersion = "1"))
+ // .AddProcessor(new FilteredJaegerExporter(_.DisplayName >> (<>) "DiagnosticsLogger.StackGuard.Guard"))
+ // .Build()
+
+ let activity = Activity.start $"Test FSharpWorkspace {testName}" []
+
+ do _this.Projects.Debug_DumpGraphOnEveryChange <- Some debugGraphPath
+
+ member _.DebugGraphPath = debugGraphPath
- Assert.Equal(expectedContent, fileSnapshot.GetSource().Result.ToString())
+ interface IDisposable with
+ member _.Dispose() =
+ use _ = activity in ()
+ //tracerProvider.ForceFlush() |> ignore
+ //tracerProvider.Dispose()
[]
let ``Add project to workspace`` () =
- let workspace = FSharpWorkspace()
+ use workspace = new TestingWorkspace("Add project to workspace")
let projectPath = "test.fsproj"
let outputPath = "test.dll"
let compilerArgs = [| "test.fs" |]
@@ -51,7 +68,7 @@ let ``Add project to workspace`` () =
[]
let ``Open file in workspace`` () =
- let workspace = FSharpWorkspace()
+ use workspace = new TestingWorkspace("Open file in workspace")
let fileUri = Uri("file:///test.fs")
let content = "let x = 1"
@@ -83,13 +100,13 @@ let ``Open file in workspace`` () =
[]
let ``Close file in workspace`` () =
- let workspace = FSharpWorkspace()
+ use workspace = new TestingWorkspace("Close file in workspace")
let contentOnDisk = "let x = 1"
let fileOnDisk = sourceFileOnDisk contentOnDisk
let _projectIdentifier =
- workspace.Projects.AddOrUpdate(ProjectConfig.Minimal(), [ fileOnDisk.LocalPath ])
+ workspace.Projects.AddOrUpdate(ProjectConfig.Empty(), [ fileOnDisk.LocalPath ])
workspace.Files.Open(fileOnDisk, contentOnDisk)
@@ -118,12 +135,12 @@ let ``Close file in workspace`` () =
[]
let ``Change file in workspace`` () =
- let workspace = FSharpWorkspace()
+ use workspace = new TestingWorkspace("Change file in workspace")
let fileUri = Uri("file:///test.fs")
let _projectIdentifier =
- workspace.Projects.AddOrUpdate(ProjectConfig.Minimal(), [ fileUri.LocalPath ])
+ workspace.Projects.AddOrUpdate(ProjectConfig.Empty(), [ fileUri.LocalPath ])
let initialContent = "let x = 2"
@@ -144,7 +161,7 @@ let ``Change file in workspace`` () =
[]
let ``Add multiple projects with references`` () =
- let workspace = FSharpWorkspace()
+ use workspace = new TestingWorkspace("Add multiple projects with references")
let projectPath1 = "test1.fsproj"
let outputPath1 = "test1.dll"
let compilerArgs1 = [| "test1.fs" |]
@@ -174,20 +191,20 @@ let ``Add multiple projects with references`` () =
[]
let ``Propagate changes to snapshots`` () =
- let workspace = FSharpWorkspace()
+ use workspace = new TestingWorkspace("Propagate changes to snapshots")
let file1 = sourceFileOnDisk "let x = 1"
- let pid1 = workspace.Projects.AddOrUpdate(ProjectConfig.Minimal("p1"), [ file1.LocalPath ])
+ let pid1 = workspace.Projects.AddOrUpdate(ProjectConfig.Empty("p1"), [ file1.LocalPath ])
let file2 = sourceFileOnDisk "let y = 2"
let pid2 =
- workspace.Projects.AddOrUpdate(ProjectConfig.Minimal("p2", referencesOnDisk = [ pid1.OutputFileName ]), [ file2.LocalPath ])
+ workspace.Projects.AddOrUpdate(ProjectConfig.Empty("p2", referencesOnDisk = [ pid1.OutputFileName ]), [ file2.LocalPath ])
let file3 = sourceFileOnDisk "let z = 3"
let pid3 =
- workspace.Projects.AddOrUpdate(ProjectConfig.Minimal("p3", referencesOnDisk = [ pid2.OutputFileName ]), [ file3.LocalPath ])
+ workspace.Projects.AddOrUpdate(ProjectConfig.Empty("p3", referencesOnDisk = [ pid2.OutputFileName ]), [ file3.LocalPath ])
let s3 = workspace.Query.GetProjectSnapshot(pid3).Value
@@ -208,8 +225,8 @@ let ``Propagate changes to snapshots`` () =
|> assertFileHasContent file1.LocalPath updatedContent
[]
-let ``Update project by adding a source file`` () =
- let workspace = FSharpWorkspace()
+let ``AddOrUpdate project by adding a source file`` () =
+ use workspace = new TestingWorkspace("Update project by adding a source file")
let projectPath = "test.fsproj"
let outputPath = "test.dll"
let compilerArgs = [| "test.fs" |]
@@ -222,9 +239,36 @@ let ``Update project by adding a source file`` () =
Assert.Contains("test.fs", projectSnapshot.SourceFiles |> Seq.map (fun f -> f.FileName))
Assert.Contains(newSourceFile, projectSnapshot.SourceFiles |> Seq.map (fun f -> f.FileName))
+[]
+let ``Update project by adding a source file`` () =
+ use workspace = new TestingWorkspace("Update project by adding a source file")
+ let projectPath = "test.fsproj"
+ let outputPath = "test.dll"
+ let compilerArgs = [| "test.fs" |]
+ let projectIdentifier = workspace.Projects.AddOrUpdate(projectPath, outputPath, compilerArgs)
+ let newSourceFile = "newTest.fs"
+ let newSourceFiles = [| "test.fs"; newSourceFile |]
+ workspace.Projects.Update(projectIdentifier, newSourceFiles) |> ignore
+ let projectSnapshot = workspace.Query.GetProjectSnapshot(projectIdentifier).Value
+ Assert.NotNull(projectSnapshot)
+ Assert.Contains("test.fs", projectSnapshot.SourceFiles |> Seq.map (fun f -> f.FileName))
+ Assert.Contains(newSourceFile, projectSnapshot.SourceFiles |> Seq.map (fun f -> f.FileName))
+
+[]
+let ``Update project by removing a source file`` () =
+ use workspace = new TestingWorkspace("Update project by removing a source file")
+ let projectPath = "test.fsproj"
+ let outputPath = "test.dll"
+ let compilerArgs = [| "test.fs"; "newTest.fs" |]
+ let projectIdentifier = workspace.Projects.AddOrUpdate(projectPath, outputPath, compilerArgs)
+ let newCompilerArgs = [| "test.fs" |]
+ workspace.Projects.AddOrUpdate(projectPath, outputPath, newCompilerArgs) |> ignore
+ let files = workspace.Files.OfProject(projectIdentifier) |> Seq.toArray
+ Assert.Equal([| "test.fs" |], files)
+
[]
let ``Update project by adding a reference`` () =
- let workspace = FSharpWorkspace()
+ use workspace = new TestingWorkspace("Update project by adding a reference")
let projectPath1 = "test1.fsproj"
let outputPath1 = "test1.dll"
let compilerArgs1 = [| "test1.fs" |]
@@ -254,7 +298,7 @@ let ``Update project by adding a reference`` () =
[]
let ``Create references in existing projects`` () =
- let workspace = FSharpWorkspace()
+ use workspace = new TestingWorkspace("Create references in existing projects")
let projectPath1 = "test1.fsproj"
let outputPath1 = "test1.dll"
let compilerArgs1 = [| "test1.fs" |]
@@ -296,6 +340,105 @@ let ``Create references in existing projects`` () =
[]
let ``Asking for an unknown project snapshot returns None`` () =
- let workspace = FSharpWorkspace()
+ use workspace = new TestingWorkspace("Asking for an unknown project snapshot returns None")
Assert.Equal(None, workspace.Query.GetProjectSnapshot(FSharpProjectIdentifier("hello", "world")))
+
+
+[]
+let ``Works with signature files`` () =
+ task {
+
+ use workspace = new TestingWorkspace("Works with signature files")
+
+ let projectConfig = ProjectConfig.Create()
+
+ let sourceFileUri = projectConfig.FileUri "test.fs"
+
+ let source = "let x = 1"
+
+ let projectIdentifier = workspace.Projects.AddOrUpdate(projectConfig, [ sourceFileUri ])
+
+ workspace.Files.Open(sourceFileUri, source)
+
+ let! signatureUri, _signatureSource = workspace.AddSignatureFile(projectIdentifier, sourceFileUri, writeToDisk=false)
+
+ let! diag = workspace.Query.GetDiagnosticsForFile(signatureUri)
+
+ Assert.Equal(0, diag.Diagnostics.Length)
+
+ workspace.Files.Edit(signatureUri, "module Test\n\nval x: potato")
+
+ let! diag = workspace.Query.GetDiagnosticsForFile(signatureUri)
+
+ Assert.Equal(1, diag.Diagnostics.Length)
+ Assert.Equal("The type 'potato' is not defined.", diag.Diagnostics[0].Message)
+
+ workspace.Files.Edit(signatureUri, "module Test\n\nval y: int")
+
+ let! diag = workspace.Query.GetDiagnosticsForFile(sourceFileUri)
+
+ Assert.Equal(1, diag.Diagnostics.Length)
+ Assert.Equal("Module 'Test' requires a value 'y'", diag.Diagnostics[0].Message)
+ }
+
+
+let reposDir = __SOURCE_DIRECTORY__ ++ ".." ++ ".." ++ ".." ++ ".."
+let giraffeDir = reposDir ++ "Giraffe" ++ "src" ++ "Giraffe" |> Path.GetFullPath
+let giraffeTestsDir = reposDir ++ "Giraffe" ++ "tests" ++ "Giraffe.Tests" |> Path.GetFullPath
+let giraffeSampleDir = reposDir ++ "Giraffe" ++ "samples" ++ "EndpointRoutingApp" ++ "EndpointRoutingApp" |> Path.GetFullPath
+let giraffeSignaturesDir = reposDir ++ "giraffe-signatures" ++ "src" ++ "Giraffe" |> Path.GetFullPath
+let giraffeSignaturesTestsDir = reposDir ++ "giraffe-signatures" ++ "tests" ++ "Giraffe.Tests" |> Path.GetFullPath
+let giraffeSignaturesSampleDir = reposDir ++ "giraffe-signatures" ++ "samples" ++ "EndpointRoutingApp" ++ "EndpointRoutingApp" |> Path.GetFullPath
+
+type GiraffeFactAttribute() =
+ inherit Xunit.FactAttribute()
+ do
+ if not (Directory.Exists giraffeDir) then
+ do base.Skip <- $"Giraffe not found ({giraffeDir}). You can get it here: https://github.com/giraffe-fsharp/Giraffe"
+ if not (Directory.Exists giraffeSignaturesDir) then
+ do base.Skip <- $"Giraffe (with signatures) not found ({giraffeSignaturesDir}). You can get it here: https://github.com/nojaf/Giraffe/tree/signatures"
+
+
+[]
+let ``Giraffe signature test`` () =
+ task {
+ use workspace = new TestingWorkspace("Giraffe signature test")
+
+ let responseFileName = "compilerArgs.rsp"
+
+ let _identifiers =
+ [
+ giraffeSignaturesDir
+ giraffeSignaturesTestsDir
+ giraffeSignaturesSampleDir ]
+ |> Seq.map (fun dir ->
+ let projectName = Path.GetFileName dir
+ let dllName = $"{projectName}.dll"
+ let responseFile = dir ++ responseFileName
+ let outputFile = dir ++ "bin" ++ "Debug" ++ "net6.0" ++ dllName
+ let projectFile = dir ++ projectName + ".fsproj"
+ let compilerArgs = File.ReadAllLines responseFile
+ workspace.Projects.AddOrUpdate(projectFile, outputFile, compilerArgs)
+ )
+ |> Seq.toList
+
+ let _ = workspace.Files.OpenFromDisk(giraffeSignaturesSampleDir ++ "Program.fs")
+
+ let! diag = workspace.Query.GetDiagnosticsForFile(Uri(giraffeSignaturesSampleDir ++ "Program.fs"))
+ Assert.Equal(0, diag.Diagnostics.Length)
+
+ let middlewareFsiSource = workspace.Files.OpenFromDisk(giraffeSignaturesDir ++ "Middleware.fsi")
+ let middlewareFsiNewSource = middlewareFsiSource.Replace("static member AddGiraffe:", "static member AddGiraffe2:")
+
+ let! diag = workspace.Query.GetDiagnosticsForFile(Uri(giraffeSignaturesDir ++ "Middleware.fsi"))
+ Assert.Equal(0, diag.Diagnostics.Length)
+
+ workspace.Files.Edit(Uri(giraffeSignaturesDir ++ "Middleware.fsi"), middlewareFsiNewSource)
+
+ let! diag = workspace.Query.GetDiagnosticsForFile(Uri(giraffeSignaturesSampleDir ++ "Program.fs"))
+ Assert.Equal(1, diag.Diagnostics.Length)
+ Assert.Equal("The type 'IServiceCollection' does not define the field, constructor or member 'AddGiraffe'.", diag.Diagnostics[0].Message)
+ }
+
+#endif
\ No newline at end of file
diff --git a/tests/FSharp.Compiler.ComponentTests/FSharpChecker/CommonWorkflows.fs b/tests/FSharp.Compiler.ComponentTests/FSharpChecker/CommonWorkflows.fs
index d8bd1f6e262..7ee7322a752 100644
--- a/tests/FSharp.Compiler.ComponentTests/FSharpChecker/CommonWorkflows.fs
+++ b/tests/FSharp.Compiler.ComponentTests/FSharpChecker/CommonWorkflows.fs
@@ -161,10 +161,10 @@ let ``We don't lose subsequent diagnostics when there's error in one file`` () =
let project =
{ SyntheticProject.Create(
{ sourceFile "First" [] with
- Source = """module AbstractBaseClass.File1
-
+ Source = """module AbstractBaseClass.File1
+
let foo x = ()
-
+
a""" },
{ sourceFile "Second" [] with
Source = """module AbstractBaseClass.File2
@@ -178,7 +178,7 @@ let ``We don't lose subsequent diagnostics when there's error in one file`` () =
abstract P: int""" }) with
AutoAddModules = false
SkipInitialCheck = true }
-
+
project.Workflow {
checkFile "First" (expectErrorCodes ["FS0039"])
checkFile "Second" (expectErrorCodes ["FS0054"; "FS0365"])
diff --git a/tests/FSharp.Compiler.LanguageServer.Tests/FSharp.Compiler.LanguageServer.Tests.fsproj b/tests/FSharp.Compiler.LanguageServer.Tests/FSharp.Compiler.LanguageServer.Tests.fsproj
new file mode 100644
index 00000000000..046357aafe6
--- /dev/null
+++ b/tests/FSharp.Compiler.LanguageServer.Tests/FSharp.Compiler.LanguageServer.Tests.fsproj
@@ -0,0 +1,59 @@
+
+
+
+ $(FSharpNetCoreProductTargetFramework)
+ false
+ false
+ true
+ true
+ xunit
+ true
+ false
+ false
+ $(OtherFlags) --warnon:1182
+ $(NoWarn);FS0988
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/FSharp.Compiler.LanguageServer.Tests/Program.fs b/tests/FSharp.Compiler.LanguageServer.Tests/Program.fs
new file mode 100644
index 00000000000..80c6d842785
--- /dev/null
+++ b/tests/FSharp.Compiler.LanguageServer.Tests/Program.fs
@@ -0,0 +1,3 @@
+module Program =
+ []
+ let main _ = 0
diff --git a/tests/FSharp.Compiler.LanguageServer.Tests/Protocol.fs b/tests/FSharp.Compiler.LanguageServer.Tests/Protocol.fs
new file mode 100644
index 00000000000..9072fbfb460
--- /dev/null
+++ b/tests/FSharp.Compiler.LanguageServer.Tests/Protocol.fs
@@ -0,0 +1,193 @@
+module LanguageServer.Protocol
+
+open System
+open Xunit
+
+open FSharp.Compiler.LanguageServer
+open StreamJsonRpc
+open System.IO
+open System.Diagnostics
+
+open Microsoft.VisualStudio.LanguageServer.Protocol
+open Nerdbank.Streams
+
+open FSharp.Test.ProjectGeneration.WorkspaceHelpers
+open FSharp.Compiler.CodeAnalysis.Workspace
+open FSharp.Compiler.CodeAnalysis.ProjectSnapshot
+
+#nowarn "57"
+
+type TestRpcClient(jsonRpc, rpcTrace, workspace, initializeResult: InitializeResult) =
+
+ member val JsonRpc = jsonRpc
+ member val RpcTraceWriter = rpcTrace
+ member val Workspace = workspace
+ member val Capabilities = initializeResult.Capabilities
+
+ member _.RpcTrace = rpcTrace.ToString()
+
+let initializeLanguageServer (workspace) =
+
+ let workspace = defaultArg workspace (FSharpWorkspace())
+
+ // Create a StringWriter to capture the output
+ let rpcTrace = new StringWriter()
+
+ let (inputStream, outputStream), server = FSharpLanguageServer.Create(workspace)
+
+ let formatter = new JsonMessageFormatter()
+
+ let messageHandler =
+ new HeaderDelimitedMessageHandler(inputStream, outputStream, formatter)
+
+ let jsonRpc = new JsonRpc(messageHandler)
+
+ // Create a new TraceListener with the StringWriter
+ let listener = new TextWriterTraceListener(rpcTrace)
+
+ // Add the listener to the JsonRpc TraceSource
+ server.JsonRpc.TraceSource.Listeners.Add(listener) |> ignore
+
+ // Set the TraceLevel to Information to get all informational, warning and error messages
+ server.JsonRpc.TraceSource.Switch.Level <- SourceLevels.All
+
+ let initializeParams =
+ InitializeParams(
+ ProcessId = System.Diagnostics.Process.GetCurrentProcess().Id,
+ RootUri = Uri("file:///c:/temp"),
+ InitializationOptions = None
+ )
+
+ jsonRpc.StartListening()
+
+ task {
+ let! response = jsonRpc.InvokeAsync("initialize", initializeParams)
+ return TestRpcClient(jsonRpc, rpcTrace, workspace, response)
+ }
+
+[]
+let ``The server can process the initialization message`` () =
+ task {
+
+ let! client = initializeLanguageServer None
+ Assert.NotNull(client.Capabilities)
+
+ }
+
+/// Initialize workspace, open a document, get diagnostics, edit the document, get diagnostics, close the document
+[]
+let ``Basic server workflow`` () =
+ task {
+
+ let! client = initializeLanguageServer None
+
+ let workspace = client.Workspace
+
+ let contentOnDisk = "let x = 1"
+ let fileOnDisk = sourceFileOnDisk contentOnDisk
+
+ let _projectIdentifier =
+ workspace.Projects.AddOrUpdate(ProjectConfig.Create(), [ fileOnDisk.LocalPath ])
+
+ do!
+ client.JsonRpc.NotifyAsync(
+ Methods.TextDocumentDidOpenName,
+ DidOpenTextDocumentParams(
+ TextDocument = TextDocumentItem(Uri = fileOnDisk, LanguageId = "F#", Version = 1, Text = contentOnDisk)
+ )
+ )
+
+ let! diagnosticsResponse =
+ client.JsonRpc.InvokeAsync>(
+ Methods.TextDocumentDiagnosticName,
+ DocumentDiagnosticParams(TextDocument = TextDocumentIdentifier(Uri = fileOnDisk))
+ )
+
+ Assert.Equal(
+ 0,
+ (diagnosticsResponse.First.RelatedDocuments
+ |> Seq.head
+ |> _.Value.First.Items.Length)
+ )
+
+ let contentEdit = $"{contentOnDisk}\nx <- 2"
+
+ do!
+ client.JsonRpc.NotifyAsync(
+ Methods.TextDocumentDidChangeName,
+ DidChangeTextDocumentParams(
+ TextDocument = VersionedTextDocumentIdentifier(Uri = fileOnDisk, Version = 2),
+ ContentChanges = [| TextDocumentContentChangeEvent(Text = contentEdit) |]
+ )
+ )
+
+ let! diagnosticsResponse =
+ client.JsonRpc.InvokeAsync>(
+ Methods.TextDocumentDiagnosticName,
+ DocumentDiagnosticParams(TextDocument = TextDocumentIdentifier(Uri = fileOnDisk))
+ )
+
+ let diagnostics =
+ diagnosticsResponse.First.RelatedDocuments |> Seq.head |> _.Value.First.Items
+
+ Assert.Equal(1, diagnostics.Length)
+ Assert.Contains("This value is not mutable", diagnostics[0].Message)
+
+ do!
+ client.JsonRpc.NotifyAsync(
+ Methods.TextDocumentDidCloseName,
+ DidCloseTextDocumentParams(TextDocument = TextDocumentIdentifier(Uri = fileOnDisk))
+ )
+
+ let! diagnosticsResponse =
+ client.JsonRpc.InvokeAsync>(
+ Methods.TextDocumentDiagnosticName,
+ DocumentDiagnosticParams(TextDocument = TextDocumentIdentifier(Uri = fileOnDisk))
+ )
+
+ // We didn't save the file, so it should be again read from disk and have no diagnostics
+ Assert.Equal(
+ 0,
+ (diagnosticsResponse.First.RelatedDocuments
+ |> Seq.head
+ |> _.Value.First.Items.Length)
+ )
+ }
+
+[]
+let ``Full semantic tokens`` () =
+
+ task {
+ let! client = initializeLanguageServer None
+ let workspace = client.Workspace
+ let contentOnDisk = "let x = 1"
+ let fileOnDisk = sourceFileOnDisk contentOnDisk
+ let _projectIdentifier =
+ workspace.Projects.AddOrUpdate(ProjectConfig.Create(), [ fileOnDisk.LocalPath ])
+ do!
+ client.JsonRpc.NotifyAsync(
+ Methods.TextDocumentDidOpenName,
+ DidOpenTextDocumentParams(
+ TextDocument = TextDocumentItem(Uri = fileOnDisk, LanguageId = "F#", Version = 1, Text = contentOnDisk)
+ )
+ )
+ let! semanticTokensResponse =
+ client.JsonRpc.InvokeAsync(
+ Methods.TextDocumentSemanticTokensFullName,
+ SemanticTokensParams(TextDocument = TextDocumentIdentifier(Uri = fileOnDisk))
+ )
+
+ let expected = [| 0; 0; 0; 1; 0; 0; 0; 3; 15; 0; 0; 4; 1; 17; 0; 0; 4; 1; 19; 0 |]
+
+ Assert.Equal(expected, semanticTokensResponse.Data)
+ }
+
+[]
+let ``Shutdown and exit`` () =
+ task {
+ let! client = initializeLanguageServer None
+
+ let! _respone = client.JsonRpc.InvokeAsync<_>(Methods.ShutdownName)
+
+ do! client.JsonRpc.NotifyAsync(Methods.ExitName)
+ }
diff --git a/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.debug.bsl b/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.debug.bsl
index 6fccd622eca..4f1bda7afd1 100644
--- a/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.debug.bsl
+++ b/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.debug.bsl
@@ -2420,6 +2420,8 @@ FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectSnapshot: Boolean get_
FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectSnapshot: FSharpProjectIdentifier Identifier
FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectSnapshot: FSharpProjectIdentifier get_Identifier()
FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectSnapshot: FSharpProjectSnapshot Create(System.String, Microsoft.FSharp.Core.FSharpOption`1[System.String], Microsoft.FSharp.Core.FSharpOption`1[System.String], Microsoft.FSharp.Collections.FSharpList`1[FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpFileSnapshot], Microsoft.FSharp.Collections.FSharpList`1[FSharp.Compiler.CodeAnalysis.ProjectSnapshot+ReferenceOnDisk], Microsoft.FSharp.Collections.FSharpList`1[System.String], Microsoft.FSharp.Collections.FSharpList`1[FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot], Boolean, Boolean, System.DateTime, Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.CodeAnalysis.FSharpUnresolvedReferencesSet], Microsoft.FSharp.Collections.FSharpList`1[System.Tuple`3[FSharp.Compiler.Text.Range,System.String,System.String]], Microsoft.FSharp.Core.FSharpOption`1[System.Int64])
+FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectSnapshot: FSharpProjectSnapshot FromCommandLineArgs(System.String[], System.String, System.String)
+FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectSnapshot: FSharpProjectSnapshot FromResponseFile(System.IO.FileInfo, System.String)
FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectSnapshot: FSharpProjectSnapshot Replace(Microsoft.FSharp.Collections.FSharpList`1[FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpFileSnapshot])
FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectSnapshot: Microsoft.FSharp.Collections.FSharpList`1[FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpFileSnapshot] SourceFiles
FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectSnapshot: Microsoft.FSharp.Collections.FSharpList`1[FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpFileSnapshot] get_SourceFiles()
@@ -2442,6 +2444,8 @@ FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectSnapshot: Microsoft.FS
FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectSnapshot: Microsoft.FSharp.Core.FSharpOption`1[System.String] ProjectId
FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectSnapshot: Microsoft.FSharp.Core.FSharpOption`1[System.String] get_OutputFileName()
FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectSnapshot: Microsoft.FSharp.Core.FSharpOption`1[System.String] get_ProjectId()
+FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectSnapshot: ProjectConfig ProjectConfig
+FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectSnapshot: ProjectConfig get_ProjectConfig()
FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectSnapshot: System.DateTime LoadTime
FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectSnapshot: System.DateTime get_LoadTime()
FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectSnapshot: System.String Label
@@ -2635,19 +2639,23 @@ FSharp.Compiler.CodeAnalysis.Workspace.FSharpWorkspaceQuery+FSharpDiagnosticRepo
FSharp.Compiler.CodeAnalysis.Workspace.FSharpWorkspaceQuery+FSharpDiagnosticReport: System.String ResultId
FSharp.Compiler.CodeAnalysis.Workspace.FSharpWorkspaceQuery+FSharpDiagnosticReport: System.String get_ResultId()
FSharp.Compiler.CodeAnalysis.Workspace.FSharpWorkspaceQuery+FSharpWorkspaceQuery: Microsoft.FSharp.Control.FSharpAsync`1[FSharp.Compiler.CodeAnalysis.Workspace.FSharpWorkspaceQuery+FSharpDiagnosticReport] GetDiagnosticsForFile(System.Uri)
+FSharp.Compiler.CodeAnalysis.Workspace.FSharpWorkspaceQuery+FSharpWorkspaceQuery: Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.CodeAnalysis.FSharpCheckFileResults]] GetCheckResultsForFile(System.Uri)
FSharp.Compiler.CodeAnalysis.Workspace.FSharpWorkspaceQuery+FSharpWorkspaceQuery: Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.EditorServices.SemanticClassificationView]] GetSemanticClassification(System.Uri)
+FSharp.Compiler.CodeAnalysis.Workspace.FSharpWorkspaceQuery+FSharpWorkspaceQuery: Microsoft.FSharp.Control.FSharpAsync`1[System.Tuple`2[Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.CodeAnalysis.FSharpParseFileResults],Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.CodeAnalysis.FSharpCheckFileResults]]] GetParseAndCheckResultsForFile(System.Uri)
FSharp.Compiler.CodeAnalysis.Workspace.FSharpWorkspaceQuery+FSharpWorkspaceQuery: Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectSnapshot] GetProjectSnapshot(FSharpProjectIdentifier)
FSharp.Compiler.CodeAnalysis.Workspace.FSharpWorkspaceQuery+FSharpWorkspaceQuery: Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectSnapshot] GetProjectSnapshotForFile(System.Uri)
FSharp.Compiler.CodeAnalysis.Workspace.FSharpWorkspaceQuery+FSharpWorkspaceQuery: System.Threading.Tasks.Task`1[Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.Text.ISourceTextNew]] GetSource(System.Uri)
FSharp.Compiler.CodeAnalysis.Workspace.FSharpWorkspaceQuery: FSharp.Compiler.CodeAnalysis.Workspace.FSharpWorkspaceQuery+FSharpDiagnosticReport
FSharp.Compiler.CodeAnalysis.Workspace.FSharpWorkspaceQuery: FSharp.Compiler.CodeAnalysis.Workspace.FSharpWorkspaceQuery+FSharpWorkspaceQuery
-FSharp.Compiler.CodeAnalysis.Workspace.FSharpWorkspaceState+FSharpWorkspaceFiles: Microsoft.FSharp.Core.FSharpFunc`2[System.Tuple`2[System.Uri,System.String],Microsoft.FSharp.Core.Unit] Open
-FSharp.Compiler.CodeAnalysis.Workspace.FSharpWorkspaceState+FSharpWorkspaceFiles: Microsoft.FSharp.Core.FSharpFunc`2[System.Tuple`2[System.Uri,System.String],Microsoft.FSharp.Core.Unit] get_Open()
+FSharp.Compiler.CodeAnalysis.Workspace.FSharpWorkspaceState+FSharpWorkspaceFiles: System.Collections.Generic.IEnumerable`1[System.String] OfProject(FSharpProjectIdentifier)
FSharp.Compiler.CodeAnalysis.Workspace.FSharpWorkspaceState+FSharpWorkspaceFiles: Void Close(System.Uri)
FSharp.Compiler.CodeAnalysis.Workspace.FSharpWorkspaceState+FSharpWorkspaceFiles: Void Edit(System.Uri, System.String)
+FSharp.Compiler.CodeAnalysis.Workspace.FSharpWorkspaceState+FSharpWorkspaceFiles: Void Open(System.Uri, System.String)
FSharp.Compiler.CodeAnalysis.Workspace.FSharpWorkspaceState+FSharpWorkspaceProjects: FSharpProjectIdentifier AddOrUpdate(ProjectConfig, System.Collections.Generic.IEnumerable`1[System.String])
+FSharp.Compiler.CodeAnalysis.Workspace.FSharpWorkspaceState+FSharpWorkspaceProjects: FSharpProjectIdentifier AddOrUpdate(ProjectConfig, System.Collections.Generic.IEnumerable`1[System.Uri])
FSharp.Compiler.CodeAnalysis.Workspace.FSharpWorkspaceState+FSharpWorkspaceProjects: FSharpProjectIdentifier AddOrUpdate(System.String, System.String, System.Collections.Generic.IEnumerable`1[System.String])
FSharp.Compiler.CodeAnalysis.Workspace.FSharpWorkspaceState+FSharpWorkspaceProjects: FSharpProjectIdentifier AddOrUpdate(System.String, System.String, System.Collections.Generic.IEnumerable`1[System.String], System.Collections.Generic.IEnumerable`1[System.String], Microsoft.FSharp.Collections.FSharpList`1[System.String])
+FSharp.Compiler.CodeAnalysis.Workspace.FSharpWorkspaceState+FSharpWorkspaceProjects: Void Update(FSharpProjectIdentifier, System.Collections.Generic.IEnumerable`1[System.String])
FSharp.Compiler.CodeAnalysis.Workspace.FSharpWorkspaceState: FSharp.Compiler.CodeAnalysis.Workspace.FSharpWorkspaceState+FSharpWorkspaceFiles
FSharp.Compiler.CodeAnalysis.Workspace.FSharpWorkspaceState: FSharp.Compiler.CodeAnalysis.Workspace.FSharpWorkspaceState+FSharpWorkspaceProjects
FSharp.Compiler.CompilerEnvironment: Boolean IsCheckerSupportedSubcategory(System.String)
diff --git a/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.release.bsl b/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.release.bsl
index 566ff039e93..703e5029e32 100644
--- a/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.release.bsl
+++ b/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.release.bsl
@@ -2420,6 +2420,8 @@ FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectSnapshot: Boolean get_
FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectSnapshot: FSharpProjectIdentifier Identifier
FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectSnapshot: FSharpProjectIdentifier get_Identifier()
FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectSnapshot: FSharpProjectSnapshot Create(System.String, Microsoft.FSharp.Core.FSharpOption`1[System.String], Microsoft.FSharp.Core.FSharpOption`1[System.String], Microsoft.FSharp.Collections.FSharpList`1[FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpFileSnapshot], Microsoft.FSharp.Collections.FSharpList`1[FSharp.Compiler.CodeAnalysis.ProjectSnapshot+ReferenceOnDisk], Microsoft.FSharp.Collections.FSharpList`1[System.String], Microsoft.FSharp.Collections.FSharpList`1[FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot], Boolean, Boolean, System.DateTime, Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.CodeAnalysis.FSharpUnresolvedReferencesSet], Microsoft.FSharp.Collections.FSharpList`1[System.Tuple`3[FSharp.Compiler.Text.Range,System.String,System.String]], Microsoft.FSharp.Core.FSharpOption`1[System.Int64])
+FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectSnapshot: FSharpProjectSnapshot FromCommandLineArgs(System.String[], System.String, System.String)
+FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectSnapshot: FSharpProjectSnapshot FromResponseFile(System.IO.FileInfo, System.String)
FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectSnapshot: FSharpProjectSnapshot Replace(Microsoft.FSharp.Collections.FSharpList`1[FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpFileSnapshot])
FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectSnapshot: Microsoft.FSharp.Collections.FSharpList`1[FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpFileSnapshot] SourceFiles
FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectSnapshot: Microsoft.FSharp.Collections.FSharpList`1[FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpFileSnapshot] get_SourceFiles()
@@ -2442,6 +2444,8 @@ FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectSnapshot: Microsoft.FS
FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectSnapshot: Microsoft.FSharp.Core.FSharpOption`1[System.String] ProjectId
FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectSnapshot: Microsoft.FSharp.Core.FSharpOption`1[System.String] get_OutputFileName()
FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectSnapshot: Microsoft.FSharp.Core.FSharpOption`1[System.String] get_ProjectId()
+FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectSnapshot: ProjectConfig ProjectConfig
+FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectSnapshot: ProjectConfig get_ProjectConfig()
FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectSnapshot: System.DateTime LoadTime
FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectSnapshot: System.DateTime get_LoadTime()
FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectSnapshot: System.String Label
@@ -2635,19 +2639,23 @@ FSharp.Compiler.CodeAnalysis.Workspace.FSharpWorkspaceQuery+FSharpDiagnosticRepo
FSharp.Compiler.CodeAnalysis.Workspace.FSharpWorkspaceQuery+FSharpDiagnosticReport: System.String ResultId
FSharp.Compiler.CodeAnalysis.Workspace.FSharpWorkspaceQuery+FSharpDiagnosticReport: System.String get_ResultId()
FSharp.Compiler.CodeAnalysis.Workspace.FSharpWorkspaceQuery+FSharpWorkspaceQuery: Microsoft.FSharp.Control.FSharpAsync`1[FSharp.Compiler.CodeAnalysis.Workspace.FSharpWorkspaceQuery+FSharpDiagnosticReport] GetDiagnosticsForFile(System.Uri)
+FSharp.Compiler.CodeAnalysis.Workspace.FSharpWorkspaceQuery+FSharpWorkspaceQuery: Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.CodeAnalysis.FSharpCheckFileResults]] GetCheckResultsForFile(System.Uri)
FSharp.Compiler.CodeAnalysis.Workspace.FSharpWorkspaceQuery+FSharpWorkspaceQuery: Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.EditorServices.SemanticClassificationView]] GetSemanticClassification(System.Uri)
+FSharp.Compiler.CodeAnalysis.Workspace.FSharpWorkspaceQuery+FSharpWorkspaceQuery: Microsoft.FSharp.Control.FSharpAsync`1[System.Tuple`2[Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.CodeAnalysis.FSharpParseFileResults],Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.CodeAnalysis.FSharpCheckFileResults]]] GetParseAndCheckResultsForFile(System.Uri)
FSharp.Compiler.CodeAnalysis.Workspace.FSharpWorkspaceQuery+FSharpWorkspaceQuery: Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectSnapshot] GetProjectSnapshot(FSharpProjectIdentifier)
FSharp.Compiler.CodeAnalysis.Workspace.FSharpWorkspaceQuery+FSharpWorkspaceQuery: Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectSnapshot] GetProjectSnapshotForFile(System.Uri)
FSharp.Compiler.CodeAnalysis.Workspace.FSharpWorkspaceQuery+FSharpWorkspaceQuery: System.Threading.Tasks.Task`1[Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.Text.ISourceTextNew]] GetSource(System.Uri)
FSharp.Compiler.CodeAnalysis.Workspace.FSharpWorkspaceQuery: FSharp.Compiler.CodeAnalysis.Workspace.FSharpWorkspaceQuery+FSharpDiagnosticReport
FSharp.Compiler.CodeAnalysis.Workspace.FSharpWorkspaceQuery: FSharp.Compiler.CodeAnalysis.Workspace.FSharpWorkspaceQuery+FSharpWorkspaceQuery
-FSharp.Compiler.CodeAnalysis.Workspace.FSharpWorkspaceState+FSharpWorkspaceFiles: Microsoft.FSharp.Core.FSharpFunc`2[System.Tuple`2[System.Uri,System.String],Microsoft.FSharp.Core.Unit] Open
-FSharp.Compiler.CodeAnalysis.Workspace.FSharpWorkspaceState+FSharpWorkspaceFiles: Microsoft.FSharp.Core.FSharpFunc`2[System.Tuple`2[System.Uri,System.String],Microsoft.FSharp.Core.Unit] get_Open()
+FSharp.Compiler.CodeAnalysis.Workspace.FSharpWorkspaceState+FSharpWorkspaceFiles: System.Collections.Generic.IEnumerable`1[System.String] OfProject(FSharpProjectIdentifier)
FSharp.Compiler.CodeAnalysis.Workspace.FSharpWorkspaceState+FSharpWorkspaceFiles: Void Close(System.Uri)
FSharp.Compiler.CodeAnalysis.Workspace.FSharpWorkspaceState+FSharpWorkspaceFiles: Void Edit(System.Uri, System.String)
+FSharp.Compiler.CodeAnalysis.Workspace.FSharpWorkspaceState+FSharpWorkspaceFiles: Void Open(System.Uri, System.String)
FSharp.Compiler.CodeAnalysis.Workspace.FSharpWorkspaceState+FSharpWorkspaceProjects: FSharpProjectIdentifier AddOrUpdate(ProjectConfig, System.Collections.Generic.IEnumerable`1[System.String])
+FSharp.Compiler.CodeAnalysis.Workspace.FSharpWorkspaceState+FSharpWorkspaceProjects: FSharpProjectIdentifier AddOrUpdate(ProjectConfig, System.Collections.Generic.IEnumerable`1[System.Uri])
FSharp.Compiler.CodeAnalysis.Workspace.FSharpWorkspaceState+FSharpWorkspaceProjects: FSharpProjectIdentifier AddOrUpdate(System.String, System.String, System.Collections.Generic.IEnumerable`1[System.String])
FSharp.Compiler.CodeAnalysis.Workspace.FSharpWorkspaceState+FSharpWorkspaceProjects: FSharpProjectIdentifier AddOrUpdate(System.String, System.String, System.Collections.Generic.IEnumerable`1[System.String], System.Collections.Generic.IEnumerable`1[System.String], Microsoft.FSharp.Collections.FSharpList`1[System.String])
+FSharp.Compiler.CodeAnalysis.Workspace.FSharpWorkspaceState+FSharpWorkspaceProjects: Void Update(FSharpProjectIdentifier, System.Collections.Generic.IEnumerable`1[System.String])
FSharp.Compiler.CodeAnalysis.Workspace.FSharpWorkspaceState: FSharp.Compiler.CodeAnalysis.Workspace.FSharpWorkspaceState+FSharpWorkspaceFiles
FSharp.Compiler.CodeAnalysis.Workspace.FSharpWorkspaceState: FSharp.Compiler.CodeAnalysis.Workspace.FSharpWorkspaceState+FSharpWorkspaceProjects
FSharp.Compiler.CompilerEnvironment: Boolean IsCheckerSupportedSubcategory(System.String)
diff --git a/tests/FSharp.Test.Utilities/ProjectGeneration.fs b/tests/FSharp.Test.Utilities/ProjectGeneration.fs
index 92256c2b09b..fe10fb4f0fc 100644
--- a/tests/FSharp.Test.Utilities/ProjectGeneration.fs
+++ b/tests/FSharp.Test.Utilities/ProjectGeneration.fs
@@ -24,6 +24,7 @@ open System.Text.RegularExpressions
open System.Threading.Tasks
open System.Xml
+open Internal.Utilities.Library.Extras
open FSharp.Compiler.CodeAnalysis
open FSharp.Compiler.CodeAnalysis.ProjectSnapshot
open FSharp.Compiler.Diagnostics
@@ -36,6 +37,11 @@ open FSharp.Test.Utilities
open OpenTelemetry
open OpenTelemetry.Resources
open OpenTelemetry.Trace
+open TestFramework
+open FSharp.Compiler.IO
+open FSharp.Compiler.CodeAnalysis.Workspace.FSharpWorkspaceState
+open FSharp.Compiler.CodeAnalysis.Workspace.FSharpWorkspaceQuery
+open FSharp.Compiler.CodeAnalysis.Workspace
#nowarn "57" // Experimental feature use
@@ -95,7 +101,7 @@ module ReferenceHelpers =
{ Name = matches.Groups.[1].Value
Version = version
- Path = DirectoryInfo(Path.Combine(matches.Groups[3].Value, version)) })
+ Path = DirectoryInfo(Path.Combine(matches.Groups[3].Value, version)) })
|> Seq.toList)
let getFrameworkReference (reference: Reference) =
@@ -264,7 +270,7 @@ type SyntheticProject =
static member Create(name: string, [] sourceFiles: SyntheticSourceFile[]) =
{ SyntheticProject.Create(name) with SourceFiles = sourceFiles |> List.ofArray }
-
+
static member CreateForScript(scriptFile: SyntheticSourceFile) =
{ SyntheticProject.Create() with SourceFiles = [scriptFile]; UseScriptResolutionRules = true }
@@ -485,14 +491,14 @@ let private writeFile (p: SyntheticProject) (f: SyntheticSourceFile) =
let mkSyntheticProjectForResponseFile (responseFile: FileInfo) : SyntheticProject =
if not responseFile.Exists then
failwith $"%s{responseFile.FullName} does not exist"
-
+
let compilerArgs = File.ReadAllLines responseFile.FullName
let fsharpFileExtensions = set [| ".fs" ; ".fsi" ; ".fsx" |]
let isFSharpFile (file : string) =
Set.exists (fun (ext : string) -> file.EndsWith (ext, StringComparison.Ordinal)) fsharpFileExtensions
-
+
let fsharpFiles =
compilerArgs
|> Array.choose (fun (line : string) ->
@@ -517,7 +523,7 @@ let mkSyntheticProjectForResponseFile (responseFile: FileInfo) : SyntheticProjec
implementationFiles
|> List.map (fun implPath ->
let id =
- let fileNameWithoutExtension = Path.GetFileNameWithoutExtension implPath
+ let fileNameWithoutExtension = Path.GetFileNameWithoutExtension implPath
let directoryOfFile = FileInfo(implPath).DirectoryName
let relativeUri = Uri(responseFile.FullName).MakeRelativeUri(Uri(directoryOfFile))
let relativeFolderPath = Uri.UnescapeDataString(relativeUri.ToString()).Replace('/', Path.DirectorySeparatorChar)
@@ -536,10 +542,10 @@ let mkSyntheticProjectForResponseFile (responseFile: FileInfo) : SyntheticProjec
Source = File.ReadAllText implPath
ExtraSource = ""
EntryPoint = false
- IsPhysicalFile = true
+ IsPhysicalFile = true
}
)
-
+
let otherOptions =
compilerArgs
|> Array.filter (fun line -> not (isFSharpFile line))
@@ -770,13 +776,13 @@ module ProjectOperations =
|> Seq.toArray
Assert.Equal<(string * int * int * int)[]>(expected |> Seq.sort |> Seq.toArray, actual)
-
+
let expectNone x =
if Option.isSome x then failwith "expected None, but was Some"
-
+
let expectSome x =
if Option.isNone x then failwith "expected Some, but was None"
-
+
let rec saveProject (p: SyntheticProject) generateSignatureFiles checker =
async {
Directory.CreateDirectory(p.ProjectDir) |> ignore
@@ -809,19 +815,19 @@ module ProjectOperations =
let rec absorbAutoGeneratedSignatures checker (p: SyntheticProject) =
async {
do! saveProject p true checker
- let files = [
+ let files = [
for file in p.SourceFiles do
if file.SignatureFile = AutoGenerated then
let text = file |> getSignatureFilePath p |> File.ReadAllText
{ file with SignatureFile = Custom text }
- else file
+ else file
]
- let! projects =
- p.DependsOn
+ let! projects =
+ p.DependsOn
|> Seq.map (absorbAutoGeneratedSignatures checker)
|> Async.Sequential
- return
- { p with
+ return
+ { p with
SourceFiles = files
AutoAddModules = false
DependsOn = projects |> Array.toList }
@@ -937,7 +943,7 @@ let SaveAndCheckProject project checker isExistingProject =
Cursor = None }
}
-type MoveFileDirection = Up | Down
+type MoveFileDirection = Up | Down
type ProjectWorkflowBuilder
(
@@ -1349,7 +1355,7 @@ type ProjectWorkflowBuilder
if ex.IsSome then raise ex.Value
return ctx
}
-
+
[]
member this.TryGetRecentCheckResults(workflow: Async, fileId: string, expected) =
async {
@@ -1360,15 +1366,15 @@ type ProjectWorkflowBuilder
let! snapshot = FSharpProjectSnapshot.FromOptions(options, getFileSnapshot ctx.Project)
let r = checker.TryGetRecentCheckResultsForFile(fileName, snapshot)
expected r
-
+
match r with
| Some(parseFileResults, checkFileResults) ->
- let signature = getSignature(parseFileResults, FSharpCheckFileAnswer.Succeeded(checkFileResults))
+ let signature = getSignature(parseFileResults, FSharpCheckFileAnswer.Succeeded(checkFileResults))
match ctx.Signatures.TryFind(fileId) with
| Some priorSignature -> Assert.Equal(priorSignature, signature)
| None -> ()
| None -> ()
-
+
return ctx
}
@@ -1411,7 +1417,7 @@ type SyntheticProject with
projectDir ++ node.Attributes["Include"].InnerText ]
|> List.partition (fun path -> path.EndsWith ".fsi")
let signatureFiles = set signatureFiles
-
+
let parseReferences refType =
[ for node in fsproj.DocumentElement.SelectNodes($"//{refType}") do
{ Name = node.Attributes["Include"].InnerText
@@ -1437,3 +1443,101 @@ type SyntheticProject with
OtherOptions =
[ for w in nowarns do
$"--nowarn:{w}" ] }
+
+
+module WorkspaceHelpers =
+
+ let createTestProjectDirName () =
+ let projectDirectoryName = $"FSharp-Test-Project-{Guid.NewGuid().ToString()[..8]}"
+ Path.GetTempPath() ++ projectDirectoryName
+
+ type ProjectConfig with
+
+ /// Creates an empty project configuration with optional parameters for name, output path, and references on disk.
+ static member Empty(?name, ?outputPath, ?referencesOnDisk) =
+ let directory = createTestProjectDirName()
+ let name = defaultArg name "test"
+ let projectFileName = directory ++ $"{name}.fsproj"
+ let outputPath = defaultArg outputPath (directory ++ $"{name}.dll")
+ let referencesOnDisk = defaultArg referencesOnDisk []
+ ProjectConfig(projectFileName, Some outputPath, referencesOnDisk, [])
+
+ static member Create(?name) =
+ let name = defaultArg name "test"
+ let fullPath = createTestProjectDirName() ++ $"{name}.fsproj"
+ let snapshot, _ =
+ CompilerAssertHelpers.checker.GetProjectSnapshotFromScript(fullPath, SourceTextNew.ofString "", assumeDotNetFramework = false)
+ |> Async.RunSynchronously
+ snapshot.ProjectConfig
+
+ /// Returns URI of a file in the project directory.
+ member this.FileUri(fileName) =
+ this.ProjectDirectory ++ fileName |> Uri
+
+ type FSharpWorkspaceQuery with
+
+ member this.GetSignature(sourceFile: Uri) =
+ use _ = Activity.start "FSharpWorkspace.GetSignature" [ Activity.Tags.fileName, sourceFile.LocalPath ]
+ this.GetCheckResultsForFile(sourceFile)
+ |> Async.map (Option.bind(_.GenerateSignature()))
+
+ type FSharpWorkspaceProjects with
+
+ member projects.AddFileBefore(projectIdentifier, newFile: Uri, addBefore: Uri) =
+ use _ = Activity.start "FSharpWorkspace.AddFileBefore" [ Activity.Tags.project, projectIdentifier.ToString(); "newFile", newFile.LocalPath; "addBefore", addBefore.LocalPath ]
+
+ let existingFiles = projects.files.OfProject projectIdentifier
+ let newFiles = seq {
+ for file in existingFiles do
+ if file = addBefore.LocalPath then
+ newFile.LocalPath
+ file
+ }
+ projects.Update(projectIdentifier, newFiles)
+
+ type FSharpWorkspaceFiles with
+
+ /// Opens the file from a given path in the Workspace using content read from disk.
+ member this.OpenFromDisk(path) =
+ let content = FileSystem.OpenFileForReadShim(path).ReadAllText()
+ this.Open(Uri path, content)
+ content
+
+ type FSharpWorkspace with
+
+ member this.AddSignatureFile(projectIdentifier, sourceFile: Uri, ?writeToDisk) =
+ use _ = Activity.start "FSharpWorkspace.AddSignatureFile" [ Activity.Tags.project, projectIdentifier.ToString(); "sourceFile", sourceFile.LocalPath ]
+
+ let writeToDisk = defaultArg writeToDisk true
+ async {
+ match! this.Query.GetSignature sourceFile with
+ | None -> return failwith $"Couldn't get signature for {sourceFile}"
+ | Some signature ->
+ let signatureFileUri = Uri $"{sourceFile.LocalPath}i"
+ if writeToDisk then
+ FileSystem.OpenFileForWriteShim(signatureFileUri.LocalPath).Write(signature)
+ else
+ this.Files.Open(signatureFileUri, signature.ToString())
+ this.Projects.AddFileBefore(projectIdentifier, newFile=signatureFileUri, addBefore=sourceFile)
+ return signatureFileUri, signature
+ }
+
+ /// Retrieves the referenced project snapshot from the given project snapshot based on the project identifier.
+ let getReferencedSnapshot (projectIdentifier: FSharpProjectIdentifier) (projectSnapshot: FSharpProjectSnapshot) =
+ projectSnapshot.ReferencedProjects
+ |> Seq.pick (function
+ | FSharpReference(x, snapshot) when x = projectIdentifier.OutputFileName -> Some snapshot
+ | _ -> None)
+
+ /// Creates a temporary source file on disk with the given content and returns its URI.
+ let sourceFileOnDisk (content: string) =
+ let path = getTemporaryFileName () + ".fs"
+ FileSystem.OpenFileForWriteShim(path).Write(content)
+ Uri(path)
+
+ /// Asserts that the file at the given path within the project snapshot has the expected content.
+ let assertFileHasContent filePath expectedContent (projectSnapshot: FSharpProjectSnapshot) =
+ let fileSnapshot =
+ projectSnapshot.SourceFiles |> Seq.find (_.FileName >> (=) filePath)
+
+ Assert.Equal(expectedContent, fileSnapshot.GetSource().Result.ToString())
diff --git a/tests/ILVerify/ilverify_FSharp.Compiler.Service_Debug_net9.0.bsl b/tests/ILVerify/ilverify_FSharp.Compiler.Service_Debug_net9.0.bsl
index da08a645140..f1a7a58efc2 100644
--- a/tests/ILVerify/ilverify_FSharp.Compiler.Service_Debug_net9.0.bsl
+++ b/tests/ILVerify/ilverify_FSharp.Compiler.Service_Debug_net9.0.bsl
@@ -21,7 +21,7 @@
[IL]: Error [StackUnexpected]: : FSharp.Compiler.CodeAnalysis.Hosted.CompilerHelpers::fscCompile([FSharp.Compiler.Service]FSharp.Compiler.CodeAnalysis.LegacyReferenceResolver, string, string[])][offset 0x00000082][found Char] Unexpected type on the stack.
[IL]: Error [StackUnexpected]: : FSharp.Compiler.CodeAnalysis.Hosted.CompilerHelpers::fscCompile([FSharp.Compiler.Service]FSharp.Compiler.CodeAnalysis.LegacyReferenceResolver, string, string[])][offset 0x0000008B][found Char] Unexpected type on the stack.
[IL]: Error [StackUnexpected]: : FSharp.Compiler.Interactive.Shell+MagicAssemblyResolution::ResolveAssemblyCore([FSharp.Compiler.Service]Internal.Utilities.Library.CompilationThreadToken, [FSharp.Compiler.Service]FSharp.Compiler.Text.Range, [FSharp.Compiler.Service]FSharp.Compiler.CompilerConfig+TcConfigBuilder, [FSharp.Compiler.Service]FSharp.Compiler.CompilerImports+TcImports, [FSharp.Compiler.Service]FSharp.Compiler.Interactive.Shell+FsiDynamicCompiler, [FSharp.Compiler.Service]FSharp.Compiler.Interactive.Shell+FsiConsoleOutput, string)][offset 0x00000015][found Char] Unexpected type on the stack.
-[IL]: Error [StackUnexpected]: : FSharp.Compiler.Interactive.Shell+clo@3494-811::Invoke([S.P.CoreLib]System.Tuple`3)][offset 0x000001E5][found Char] Unexpected type on the stack.
+[IL]: Error [StackUnexpected]: : FSharp.Compiler.Interactive.Shell+clo@3494-813::Invoke([S.P.CoreLib]System.Tuple`3)][offset 0x000001E5][found Char] Unexpected type on the stack.
[IL]: Error [UnmanagedPointer]: : FSharp.Compiler.Interactive.Shell+Utilities+pointerToNativeInt@106::Invoke(object)][offset 0x00000007] Unmanaged pointers are not a verifiable type.
[IL]: Error [StackUnexpected]: : .$FSharpCheckerResults+dataTipOfReferences@2225::Invoke([FSharp.Core]Microsoft.FSharp.Core.Unit)][offset 0x00000084][found Char] Unexpected type on the stack.
[IL]: Error [StackUnexpected]: : .$ServiceLexing+clo@924-515::Invoke([FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2,Microsoft.FSharp.Core.Unit>)][offset 0x00000032][found Char] Unexpected type on the stack.
diff --git a/tests/ILVerify/ilverify_FSharp.Compiler.Service_Debug_netstandard2.0.bsl b/tests/ILVerify/ilverify_FSharp.Compiler.Service_Debug_netstandard2.0.bsl
index 92de58c4615..092da45d2e9 100644
--- a/tests/ILVerify/ilverify_FSharp.Compiler.Service_Debug_netstandard2.0.bsl
+++ b/tests/ILVerify/ilverify_FSharp.Compiler.Service_Debug_netstandard2.0.bsl
@@ -28,7 +28,7 @@
[IL]: Error [StackUnexpected]: : FSharp.Compiler.CodeAnalysis.Hosted.CompilerHelpers::fscCompile([FSharp.Compiler.Service]FSharp.Compiler.CodeAnalysis.LegacyReferenceResolver, string, string[])][offset 0x0000008B][found Char] Unexpected type on the stack.
[IL]: Error [StackUnexpected]: : FSharp.Compiler.Interactive.Shell+FsiStdinSyphon::GetLine(string, int32)][offset 0x00000039][found Char] Unexpected type on the stack.
[IL]: Error [StackUnexpected]: : FSharp.Compiler.Interactive.Shell+MagicAssemblyResolution::ResolveAssemblyCore([FSharp.Compiler.Service]Internal.Utilities.Library.CompilationThreadToken, [FSharp.Compiler.Service]FSharp.Compiler.Text.Range, [FSharp.Compiler.Service]FSharp.Compiler.CompilerConfig+TcConfigBuilder, [FSharp.Compiler.Service]FSharp.Compiler.CompilerImports+TcImports, [FSharp.Compiler.Service]FSharp.Compiler.Interactive.Shell+FsiDynamicCompiler, [FSharp.Compiler.Service]FSharp.Compiler.Interactive.Shell+FsiConsoleOutput, string)][offset 0x00000015][found Char] Unexpected type on the stack.
-[IL]: Error [StackUnexpected]: : FSharp.Compiler.Interactive.Shell+clo@3494-811::Invoke([S.P.CoreLib]System.Tuple`3)][offset 0x000001E5][found Char] Unexpected type on the stack.
+[IL]: Error [StackUnexpected]: : FSharp.Compiler.Interactive.Shell+clo@3494-813::Invoke([S.P.CoreLib]System.Tuple`3)][offset 0x000001E5][found Char] Unexpected type on the stack.
[IL]: Error [StackUnexpected]: : FSharp.Compiler.Interactive.Shell+FsiInteractionProcessor::CompletionsForPartialLID([FSharp.Compiler.Service]FSharp.Compiler.Interactive.Shell+FsiDynamicCompilerState, string)][offset 0x0000001B][found Char] Unexpected type on the stack.
[IL]: Error [UnmanagedPointer]: : FSharp.Compiler.Interactive.Shell+Utilities+pointerToNativeInt@106::Invoke(object)][offset 0x00000007] Unmanaged pointers are not a verifiable type.
[IL]: Error [StackUnexpected]: : .$FSharpCheckerResults+dataTipOfReferences@2225::Invoke([FSharp.Core]Microsoft.FSharp.Core.Unit)][offset 0x00000084][found Char] Unexpected type on the stack.
@@ -80,7 +80,7 @@
[IL]: Error [StackUnexpected]: : FSharp.Compiler.AbstractIL.IL::parseILVersion(string)][offset 0x0000000B][found Char] Unexpected type on the stack.
[IL]: Error [StackUnexpected]: : FSharp.Compiler.AbstractIL.IL::parseILVersion(string)][offset 0x00000021][found Char] Unexpected type on the stack.
[IL]: Error [StackUnexpected]: : FSharp.Compiler.AbstractIL.IL+parseNamed@5311::Invoke([FSharp.Core]Microsoft.FSharp.Collections.FSharpList`1>, int32, int32)][offset 0x00000087][found Char] Unexpected type on the stack.
-[IL]: Error [StackUnexpected]: : Internal.Utilities.Collections.Utils::shortPath(string)][offset 0x00000015][found Char] Unexpected type on the stack.
+[IL]: Error [StackUnexpected]: : Internal.Utilities.Collections.Utils::shortPath(string)][offset 0x00000048][found Char] Unexpected type on the stack.
[IL]: Error [StackUnexpected]: : Internal.Utilities.FSharpEnvironment+probePathForDotnetHost@317::Invoke([FSharp.Core]Microsoft.FSharp.Core.Unit)][offset 0x00000028][found Char] Unexpected type on the stack.
[IL]: Error [StackUnexpected]: : FSharp.Compiler.CodeAnalysis.SimulatedMSBuildReferenceResolver+Pipe #6 input at line 68@68::FSharp.Compiler.CodeAnalysis.ILegacyReferenceResolver.Resolve([FSharp.Compiler.Service]FSharp.Compiler.CodeAnalysis.LegacyResolutionEnvironment, [S.P.CoreLib]System.Tuple`2[], string, [FSharp.Core]Microsoft.FSharp.Collections.FSharpList`1, string, string, [FSharp.Core]Microsoft.FSharp.Collections.FSharpList`1, string, [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2, [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2>>)][offset 0x0000034D][found Char] Unexpected type on the stack.
[IL]: Error [StackUnexpected]: : .$FSharp.Compiler.DiagnosticsLogger::.cctor()][offset 0x000000CD][found Char] Unexpected type on the stack.
diff --git a/tests/ILVerify/ilverify_FSharp.Compiler.Service_Release_net9.0.bsl b/tests/ILVerify/ilverify_FSharp.Compiler.Service_Release_net9.0.bsl
index 7126edcff60..e6e4e6c9d6a 100644
--- a/tests/ILVerify/ilverify_FSharp.Compiler.Service_Release_net9.0.bsl
+++ b/tests/ILVerify/ilverify_FSharp.Compiler.Service_Release_net9.0.bsl
@@ -21,7 +21,7 @@
[IL]: Error [StackUnexpected]: : FSharp.Compiler.CodeAnalysis.Hosted.CompilerHelpers::fscCompile([FSharp.Compiler.Service]FSharp.Compiler.CodeAnalysis.LegacyReferenceResolver, string, string[])][offset 0x00000082][found Char] Unexpected type on the stack.
[IL]: Error [StackUnexpected]: : FSharp.Compiler.CodeAnalysis.Hosted.CompilerHelpers::fscCompile([FSharp.Compiler.Service]FSharp.Compiler.CodeAnalysis.LegacyReferenceResolver, string, string[])][offset 0x0000008B][found Char] Unexpected type on the stack.
[IL]: Error [StackUnexpected]: : FSharp.Compiler.Interactive.Shell+MagicAssemblyResolution::ResolveAssemblyCore([FSharp.Compiler.Service]Internal.Utilities.Library.CompilationThreadToken, [FSharp.Compiler.Service]FSharp.Compiler.Text.Range, [FSharp.Compiler.Service]FSharp.Compiler.CompilerConfig+TcConfigBuilder, [FSharp.Compiler.Service]FSharp.Compiler.CompilerImports+TcImports, [FSharp.Compiler.Service]FSharp.Compiler.Interactive.Shell+FsiDynamicCompiler, [FSharp.Compiler.Service]FSharp.Compiler.Interactive.Shell+FsiConsoleOutput, string)][offset 0x00000015][found Char] Unexpected type on the stack.
-[IL]: Error [StackUnexpected]: : FSharp.Compiler.Interactive.Shell+clo@3494-849::Invoke([S.P.CoreLib]System.Tuple`3)][offset 0x000001C7][found Char] Unexpected type on the stack.
+[IL]: Error [StackUnexpected]: : FSharp.Compiler.Interactive.Shell+clo@3494-851::Invoke([S.P.CoreLib]System.Tuple`3)][offset 0x000001C7][found Char] Unexpected type on the stack.
[IL]: Error [StackUnexpected]: : .$FSharpCheckerResults+GetReferenceResolutionStructuredToolTipText@2225::Invoke([FSharp.Core]Microsoft.FSharp.Core.Unit)][offset 0x00000076][found Char] Unexpected type on the stack.
[IL]: Error [StackUnexpected]: : .$ServiceLexing+clo@924-530::Invoke([FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2,Microsoft.FSharp.Core.Unit>)][offset 0x00000032][found Char] Unexpected type on the stack.
[IL]: Error [StackUnexpected]: : .$ServiceLexing+clo@924-530::Invoke([FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2,Microsoft.FSharp.Core.Unit>)][offset 0x0000003B][found Char] Unexpected type on the stack.
diff --git a/tests/ILVerify/ilverify_FSharp.Compiler.Service_Release_netstandard2.0.bsl b/tests/ILVerify/ilverify_FSharp.Compiler.Service_Release_netstandard2.0.bsl
index 60128c71130..1273c54bca1 100644
--- a/tests/ILVerify/ilverify_FSharp.Compiler.Service_Release_netstandard2.0.bsl
+++ b/tests/ILVerify/ilverify_FSharp.Compiler.Service_Release_netstandard2.0.bsl
@@ -28,7 +28,7 @@
[IL]: Error [StackUnexpected]: : FSharp.Compiler.CodeAnalysis.Hosted.CompilerHelpers::fscCompile([FSharp.Compiler.Service]FSharp.Compiler.CodeAnalysis.LegacyReferenceResolver, string, string[])][offset 0x0000008B][found Char] Unexpected type on the stack.
[IL]: Error [StackUnexpected]: : FSharp.Compiler.Interactive.Shell+FsiStdinSyphon::GetLine(string, int32)][offset 0x00000032][found Char] Unexpected type on the stack.
[IL]: Error [StackUnexpected]: : FSharp.Compiler.Interactive.Shell+MagicAssemblyResolution::ResolveAssemblyCore([FSharp.Compiler.Service]Internal.Utilities.Library.CompilationThreadToken, [FSharp.Compiler.Service]FSharp.Compiler.Text.Range, [FSharp.Compiler.Service]FSharp.Compiler.CompilerConfig+TcConfigBuilder, [FSharp.Compiler.Service]FSharp.Compiler.CompilerImports+TcImports, [FSharp.Compiler.Service]FSharp.Compiler.Interactive.Shell+FsiDynamicCompiler, [FSharp.Compiler.Service]FSharp.Compiler.Interactive.Shell+FsiConsoleOutput, string)][offset 0x00000015][found Char] Unexpected type on the stack.
-[IL]: Error [StackUnexpected]: : FSharp.Compiler.Interactive.Shell+clo@3494-849::Invoke([S.P.CoreLib]System.Tuple`3)][offset 0x000001C7][found Char] Unexpected type on the stack.
+[IL]: Error [StackUnexpected]: : FSharp.Compiler.Interactive.Shell+clo@3494-851::Invoke([S.P.CoreLib]System.Tuple`3)][offset 0x000001C7][found Char] Unexpected type on the stack.
[IL]: Error [StackUnexpected]: : FSharp.Compiler.Interactive.Shell+FsiInteractionProcessor::CompletionsForPartialLID([FSharp.Compiler.Service]FSharp.Compiler.Interactive.Shell+FsiDynamicCompilerState, string)][offset 0x00000024][found Char] Unexpected type on the stack.
[IL]: Error [StackUnexpected]: : .$FSharpCheckerResults+GetReferenceResolutionStructuredToolTipText@2225::Invoke([FSharp.Core]Microsoft.FSharp.Core.Unit)][offset 0x00000076][found Char] Unexpected type on the stack.
[IL]: Error [StackUnexpected]: : FSharp.Compiler.EditorServices.AssemblyContent+traverseMemberFunctionAndValues@176::Invoke([FSharp.Compiler.Service]FSharp.Compiler.Symbols.FSharpMemberOrFunctionOrValue)][offset 0x0000002B][found Char] Unexpected type on the stack.
@@ -107,7 +107,7 @@
[IL]: Error [StackUnexpected]: : FSharp.Compiler.AbstractIL.IL::parseILVersion(string)][offset 0x0000000B][found Char] Unexpected type on the stack.
[IL]: Error [StackUnexpected]: : FSharp.Compiler.AbstractIL.IL::parseILVersion(string)][offset 0x00000021][found Char] Unexpected type on the stack.
[IL]: Error [StackUnexpected]: : FSharp.Compiler.AbstractIL.IL::parseNamed@5310(uint8[], [FSharp.Core]Microsoft.FSharp.Collections.FSharpList`1>, int32, int32)][offset 0x0000007E][found Char] Unexpected type on the stack.
-[IL]: Error [StackUnexpected]: : Internal.Utilities.Collections.Utils::shortPath(string)][offset 0x00000016][found Char] Unexpected type on the stack.
+[IL]: Error [StackUnexpected]: : Internal.Utilities.Collections.Utils::shortPath(string)][offset 0x0000003A][found Char] Unexpected type on the stack.
[IL]: Error [StackUnexpected]: : Internal.Utilities.FSharpEnvironment::probePathForDotnetHost@316([FSharp.Core]Microsoft.FSharp.Core.Unit)][offset 0x0000002A][found Char] Unexpected type on the stack.
[IL]: Error [StackUnexpected]: : FSharp.Compiler.CodeAnalysis.SimulatedMSBuildReferenceResolver+SimulatedMSBuildResolver@68::FSharp.Compiler.CodeAnalysis.ILegacyReferenceResolver.Resolve([FSharp.Compiler.Service]FSharp.Compiler.CodeAnalysis.LegacyResolutionEnvironment, [S.P.CoreLib]System.Tuple`2[], string, [FSharp.Core]Microsoft.FSharp.Collections.FSharpList`1, string, string, [FSharp.Core]Microsoft.FSharp.Collections.FSharpList`1, string, [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2, [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2>>)][offset 0x000002F5][found Char] Unexpected type on the stack.
[IL]: Error [StackUnexpected]: : .$FSharp.Compiler.DiagnosticsLogger::.cctor()][offset 0x000000B6][found Char] Unexpected type on the stack.
diff --git a/vsintegration/src/FSharp.Editor/Classification/ClassificationService.fs b/vsintegration/src/FSharp.Editor/Classification/ClassificationService.fs
index fa17d938f10..e6e07761a75 100644
--- a/vsintegration/src/FSharp.Editor/Classification/ClassificationService.fs
+++ b/vsintegration/src/FSharp.Editor/Classification/ClassificationService.fs
@@ -32,6 +32,9 @@ type SemanticClassificationLookup = IReadOnlyDictionary)>]
type internal FSharpClassificationService [] () =
+ static let shouldProduceClassification (document: Document) =
+ document.Project.Solution.GetFSharpExtensionConfig().ShouldProduceSemanticHighlighting()
+
static let getLexicalClassifications (filePath: string, defines, text: SourceText, textSpan: TextSpan, ct: CancellationToken) =
let text = text.GetSubText(textSpan)
@@ -151,144 +154,154 @@ type internal FSharpClassificationService [] () =
member _.AddSyntacticClassificationsAsync
(document: Document, textSpan: TextSpan, result: List, cancellationToken: CancellationToken)
=
- cancellableTask {
- use _logBlock = Logger.LogBlock(LogEditorFunctionId.Classification_Syntactic)
-
- let! cancellationToken = CancellableTask.getCancellationToken ()
-
- let defines, langVersion, strictIndentation = document.GetFsharpParsingOptions()
-
- let! sourceText = document.GetTextAsync(cancellationToken)
-
- // For closed documents, only get classification for the text within the span.
- // This may be inaccurate for multi-line tokens such as string literals, but this is ok for now
- // as it's better than having to tokenize a big part of a file which in return will allocate a lot and hurt find all references performance.
- let isOpenDocument = document.Project.Solution.Workspace.IsDocumentOpen document.Id
-
- let eventProps: (string * obj) array =
- [|
- "context.document.project.id", document.Project.Id.Id.ToString()
- "context.document.id", document.Id.Id.ToString()
- "isOpenDocument", isOpenDocument
- "textSpanLength", textSpan.Length
- |]
-
- use _eventDuration =
- TelemetryReporter.ReportSingleEventWithDuration(TelemetryEvents.AddSyntacticClassifications, eventProps)
-
- if not isOpenDocument then
- let classifiedSpans =
- getLexicalClassifications (document.FilePath, defines, sourceText, textSpan, cancellationToken)
-
- result.AddRange(classifiedSpans)
- else
- Tokenizer.classifySpans (
- document.Id,
- sourceText,
- textSpan,
- Some(document.FilePath),
- defines,
- Some langVersion,
- strictIndentation,
- result,
- cancellationToken
- )
- }
- |> CancellableTask.startAsTask cancellationToken
+
+ if not (document |> shouldProduceClassification) then
+ System.Threading.Tasks.Task.CompletedTask
+ else
+
+ cancellableTask {
+ use _logBlock = Logger.LogBlock(LogEditorFunctionId.Classification_Syntactic)
+
+ let! cancellationToken = CancellableTask.getCancellationToken ()
+
+ let defines, langVersion, strictIndentation = document.GetFsharpParsingOptions()
+
+ let! sourceText = document.GetTextAsync(cancellationToken)
+
+ // For closed documents, only get classification for the text within the span.
+ // This may be inaccurate for multi-line tokens such as string literals, but this is ok for now
+ // as it's better than having to tokenize a big part of a file which in return will allocate a lot and hurt find all references performance.
+ let isOpenDocument = document.Project.Solution.Workspace.IsDocumentOpen document.Id
+
+ let eventProps: (string * obj) array =
+ [|
+ "context.document.project.id", document.Project.Id.Id.ToString()
+ "context.document.id", document.Id.Id.ToString()
+ "isOpenDocument", isOpenDocument
+ "textSpanLength", textSpan.Length
+ |]
+
+ use _eventDuration =
+ TelemetryReporter.ReportSingleEventWithDuration(TelemetryEvents.AddSyntacticClassifications, eventProps)
+
+ if not isOpenDocument then
+ let classifiedSpans =
+ getLexicalClassifications (document.FilePath, defines, sourceText, textSpan, cancellationToken)
+
+ result.AddRange(classifiedSpans)
+ else
+ Tokenizer.classifySpans (
+ document.Id,
+ sourceText,
+ textSpan,
+ Some(document.FilePath),
+ defines,
+ Some langVersion,
+ strictIndentation,
+ result,
+ cancellationToken
+ )
+ }
+ |> CancellableTask.startAsTask cancellationToken
member _.AddSemanticClassificationsAsync
(document: Document, textSpan: TextSpan, result: List, cancellationToken: CancellationToken)
=
- cancellableTask {
- use _logBlock = Logger.LogBlock(LogEditorFunctionId.Classification_Semantic)
-
- let! sourceText = document.GetTextAsync(cancellationToken)
-
- // If we are trying to get semantic classification for a document that is not open, get the results from the background and cache it.
- // We do this for find all references when it is populating results.
- // We cache it temporarily so we do not have to continuously call into the checker and perform a background operation.
- let isOpenDocument = document.Project.Solution.Workspace.IsDocumentOpen document.Id
-
- if not isOpenDocument then
- match! unopenedDocumentsSemanticClassificationCache.TryGetValueAsync document with
- | ValueSome classificationDataLookup ->
- let eventProps: (string * obj) array =
- [|
- "context.document.project.id", document.Project.Id.Id.ToString()
- "context.document.id", document.Id.Id.ToString()
- "isOpenDocument", isOpenDocument
- "textSpanLength", textSpan.Length
- "cacheHit", true
- |]
-
- use _eventDuration =
- TelemetryReporter.ReportSingleEventWithDuration(TelemetryEvents.AddSemanticClassifications, eventProps)
-
- addSemanticClassificationByLookup sourceText textSpan classificationDataLookup result
- | ValueNone ->
- let eventProps: (string * obj) array =
- [|
- "context.document.project.id", document.Project.Id.Id.ToString()
- "context.document.id", document.Id.Id.ToString()
- "isOpenDocument", isOpenDocument
- "textSpanLength", textSpan.Length
- "cacheHit", false
- |]
-
- use _eventDuration =
- TelemetryReporter.ReportSingleEventWithDuration(TelemetryEvents.AddSemanticClassifications, eventProps)
-
- let! classificationData = document.GetFSharpSemanticClassificationAsync(nameof (FSharpClassificationService))
-
- let classificationDataLookup = toSemanticClassificationLookup classificationData
- do! unopenedDocumentsSemanticClassificationCache.SetAsync(document, classificationDataLookup)
- addSemanticClassificationByLookup sourceText textSpan classificationDataLookup result
- else
-
- match! openedDocumentsSemanticClassificationCache.TryGetValueAsync document with
- | ValueSome classificationDataLookup ->
- let eventProps: (string * obj) array =
- [|
- "context.document.project.id", document.Project.Id.Id.ToString()
- "context.document.id", document.Id.Id.ToString()
- "isOpenDocument", isOpenDocument
- "textSpanLength", textSpan.Length
- "cacheHit", true
- |]
-
- use _eventDuration =
- TelemetryReporter.ReportSingleEventWithDuration(TelemetryEvents.AddSemanticClassifications, eventProps)
-
- addSemanticClassificationByLookup sourceText textSpan classificationDataLookup result
- | ValueNone ->
-
- let eventProps: (string * obj) array =
- [|
- "context.document.project.id", document.Project.Id.Id.ToString()
- "context.document.id", document.Id.Id.ToString()
- "isOpenDocument", isOpenDocument
- "textSpanLength", textSpan.Length
- "cacheHit", false
- |]
-
- use _eventDuration =
- TelemetryReporter.ReportSingleEventWithDuration(TelemetryEvents.AddSemanticClassifications, eventProps)
-
- let! _, checkResults = document.GetFSharpParseAndCheckResultsAsync(nameof (IFSharpClassificationService))
-
- let targetRange =
- RoslynHelpers.TextSpanToFSharpRange(document.FilePath, textSpan, sourceText)
-
- let classificationData = checkResults.GetSemanticClassification(Some targetRange)
-
- if classificationData.Length > 0 then
- let classificationDataLookup = itemToSemanticClassificationLookup classificationData
+
+ if not (document |> shouldProduceClassification) then
+ System.Threading.Tasks.Task.CompletedTask
+ else
+
+ cancellableTask {
+ use _logBlock = Logger.LogBlock(LogEditorFunctionId.Classification_Semantic)
+
+ let! sourceText = document.GetTextAsync(cancellationToken)
+
+ // If we are trying to get semantic classification for a document that is not open, get the results from the background and cache it.
+ // We do this for find all references when it is populating results.
+ // We cache it temporarily so we do not have to continuously call into the checker and perform a background operation.
+ let isOpenDocument = document.Project.Solution.Workspace.IsDocumentOpen document.Id
+
+ if not isOpenDocument then
+ match! unopenedDocumentsSemanticClassificationCache.TryGetValueAsync document with
+ | ValueSome classificationDataLookup ->
+ let eventProps: (string * obj) array =
+ [|
+ "context.document.project.id", document.Project.Id.Id.ToString()
+ "context.document.id", document.Id.Id.ToString()
+ "isOpenDocument", isOpenDocument
+ "textSpanLength", textSpan.Length
+ "cacheHit", true
+ |]
+
+ use _eventDuration =
+ TelemetryReporter.ReportSingleEventWithDuration(TelemetryEvents.AddSemanticClassifications, eventProps)
+
+ addSemanticClassificationByLookup sourceText textSpan classificationDataLookup result
+ | ValueNone ->
+ let eventProps: (string * obj) array =
+ [|
+ "context.document.project.id", document.Project.Id.Id.ToString()
+ "context.document.id", document.Id.Id.ToString()
+ "isOpenDocument", isOpenDocument
+ "textSpanLength", textSpan.Length
+ "cacheHit", false
+ |]
+
+ use _eventDuration =
+ TelemetryReporter.ReportSingleEventWithDuration(TelemetryEvents.AddSemanticClassifications, eventProps)
+
+ let! classificationData = document.GetFSharpSemanticClassificationAsync(nameof (FSharpClassificationService))
+
+ let classificationDataLookup = toSemanticClassificationLookup classificationData
do! unopenedDocumentsSemanticClassificationCache.SetAsync(document, classificationDataLookup)
+ addSemanticClassificationByLookup sourceText textSpan classificationDataLookup result
+ else
- addSemanticClassification sourceText textSpan classificationData result
- }
- |> CancellableTask.ifCanceledReturn ()
- |> CancellableTask.startAsTask cancellationToken
+ match! openedDocumentsSemanticClassificationCache.TryGetValueAsync document with
+ | ValueSome classificationDataLookup ->
+ let eventProps: (string * obj) array =
+ [|
+ "context.document.project.id", document.Project.Id.Id.ToString()
+ "context.document.id", document.Id.Id.ToString()
+ "isOpenDocument", isOpenDocument
+ "textSpanLength", textSpan.Length
+ "cacheHit", true
+ |]
+
+ use _eventDuration =
+ TelemetryReporter.ReportSingleEventWithDuration(TelemetryEvents.AddSemanticClassifications, eventProps)
+
+ addSemanticClassificationByLookup sourceText textSpan classificationDataLookup result
+ | ValueNone ->
+
+ let eventProps: (string * obj) array =
+ [|
+ "context.document.project.id", document.Project.Id.Id.ToString()
+ "context.document.id", document.Id.Id.ToString()
+ "isOpenDocument", isOpenDocument
+ "textSpanLength", textSpan.Length
+ "cacheHit", false
+ |]
+
+ use _eventDuration =
+ TelemetryReporter.ReportSingleEventWithDuration(TelemetryEvents.AddSemanticClassifications, eventProps)
+
+ let! _, checkResults = document.GetFSharpParseAndCheckResultsAsync(nameof (IFSharpClassificationService))
+
+ let targetRange =
+ RoslynHelpers.TextSpanToFSharpRange(document.FilePath, textSpan, sourceText)
+
+ let classificationData = checkResults.GetSemanticClassification(Some targetRange)
+
+ if classificationData.Length > 0 then
+ let classificationDataLookup = itemToSemanticClassificationLookup classificationData
+ do! unopenedDocumentsSemanticClassificationCache.SetAsync(document, classificationDataLookup)
+
+ addSemanticClassification sourceText textSpan classificationData result
+ }
+ |> CancellableTask.ifCanceledReturn ()
+ |> CancellableTask.startAsTask cancellationToken
// Do not perform classification if we don't have project options (#defines matter)
member _.AdjustStaleClassification(_: SourceText, classifiedSpan: ClassifiedSpan) : ClassifiedSpan = classifiedSpan
diff --git a/vsintegration/src/FSharp.Editor/Common/CancellableTasks.fs b/vsintegration/src/FSharp.Editor/Common/CancellableTasks.fs
index 78a28eb0de2..4299936d05a 100644
--- a/vsintegration/src/FSharp.Editor/Common/CancellableTasks.fs
+++ b/vsintegration/src/FSharp.Editor/Common/CancellableTasks.fs
@@ -336,7 +336,7 @@ module CancellableTasks =
else
let mutable awaiter =
sm.ResumptionDynamicInfo.ResumptionData
- :?> ICriticalNotifyCompletion
+ :?> ICriticalNotifyCompletion | null
assert not (isNull awaiter)
sm.Data.MethodBuilder.AwaitUnsafeOnCompleted(&awaiter, &sm)
@@ -386,7 +386,7 @@ module CancellableTasks =
(MoveNextMethodImpl<_>(fun sm ->
//-- RESUMABLE CODE START
__resumeAt sm.ResumptionPoint
- let mutable __stack_exn: Exception = null
+ let mutable __stack_exn: Exception | null = null
try
let __stack_code_fin = code.Invoke(&sm)
diff --git a/vsintegration/src/FSharp.Editor/Diagnostics/DocumentDiagnosticAnalyzer.fs b/vsintegration/src/FSharp.Editor/Diagnostics/DocumentDiagnosticAnalyzer.fs
index 36eb5bc7a0e..b7f7ed3d4cf 100644
--- a/vsintegration/src/FSharp.Editor/Diagnostics/DocumentDiagnosticAnalyzer.fs
+++ b/vsintegration/src/FSharp.Editor/Diagnostics/DocumentDiagnosticAnalyzer.fs
@@ -24,6 +24,9 @@ type internal DiagnosticsType =
[)>]
type internal FSharpDocumentDiagnosticAnalyzer [] () =
+ let shouldProduceDiagnostics (document: Document) =
+ document.Project.Solution.GetFSharpExtensionConfig().ShouldProduceDiagnostics()
+
static let diagnosticEqualityComparer =
{ new IEqualityComparer with
@@ -148,14 +151,20 @@ type internal FSharpDocumentDiagnosticAnalyzer [] () =
interface IFSharpDocumentDiagnosticAnalyzer with
member _.AnalyzeSyntaxAsync(document: Document, cancellationToken: CancellationToken) : Task> =
- if document.Project.IsFSharpMetadata then
+ if
+ document.Project.IsFSharpMetadata
+ || (not (document |> shouldProduceDiagnostics))
+ then
Task.FromResult ImmutableArray.Empty
else
FSharpDocumentDiagnosticAnalyzer.GetDiagnostics(document, DiagnosticsType.Syntax)
|> CancellableTask.start cancellationToken
member _.AnalyzeSemanticsAsync(document: Document, cancellationToken: CancellationToken) : Task> =
- if document.Project.IsFSharpMiscellaneousOrMetadata && not document.IsFSharpScript then
+ if
+ document.Project.IsFSharpMiscellaneousOrMetadata && not document.IsFSharpScript
+ || (not (document |> shouldProduceDiagnostics))
+ then
Task.FromResult ImmutableArray.Empty
else
FSharpDocumentDiagnosticAnalyzer.GetDiagnostics(document, DiagnosticsType.Semantic)
diff --git a/vsintegration/src/FSharp.Editor/LanguageService/WorkspaceExtensions.fs b/vsintegration/src/FSharp.Editor/LanguageService/WorkspaceExtensions.fs
index 1c09806d0ca..518bf88bc74 100644
--- a/vsintegration/src/FSharp.Editor/LanguageService/WorkspaceExtensions.fs
+++ b/vsintegration/src/FSharp.Editor/LanguageService/WorkspaceExtensions.fs
@@ -15,6 +15,8 @@ open FSharp.Compiler.BuildGraph
open CancellableTasks
+open System.IO
+
open Internal.Utilities.Collections
open Newtonsoft.Json
open Newtonsoft.Json.Linq
@@ -31,12 +33,72 @@ module internal ProjectCache =
let Projects =
ConditionalWeakTable()
+module internal SolutionConfigCache =
+
+ type FSharpExtensionConfig =
+ {
+ GetDiagnosticsFrom: string
+ GetSemanticHighlightingFrom: string
+ }
+
+ static member Old = "old"
+ static member Lsp = "lsp"
+ static member Both = "both"
+
+ static member Default =
+ {
+ GetDiagnosticsFrom = FSharpExtensionConfig.Both
+ GetSemanticHighlightingFrom = FSharpExtensionConfig.Both
+ }
+
+ member this.ShouldProduceDiagnostics() =
+ Set.contains this.GetDiagnosticsFrom (set [ FSharpExtensionConfig.Old; FSharpExtensionConfig.Both ])
+
+ member this.ShouldProduceSemanticHighlighting() =
+ Set.contains this.GetSemanticHighlightingFrom (set [ FSharpExtensionConfig.Old; FSharpExtensionConfig.Both ])
+
+ let readFSharpExtensionConfig (solutionPath: string) =
+ if String.IsNullOrEmpty(solutionPath) then
+ System.Diagnostics.Trace.TraceWarning("Solution path is null or empty. Using default config.")
+ FSharpExtensionConfig.Default
+ else
+ let configFilePath =
+ Path.Combine(solutionPath, "extensibility.settings.VisualStudio.json")
+
+ if File.Exists configFilePath then
+ try
+ let json = File.ReadAllText configFilePath
+ let jObject = JObject.Parse json
+
+ {
+ GetDiagnosticsFrom = jObject["fsharp.getDiagnosticsFrom"].ToString().ToLower()
+ GetSemanticHighlightingFrom = jObject["fsharp.getSemanticHighlightingFrom"].ToString().ToLower()
+ }
+ with ex ->
+ System.Diagnostics.Trace.TraceError($"Error reading FSharpExtensionConfig from {configFilePath}", ex)
+ FSharpExtensionConfig.Default
+ else
+ System.Diagnostics.Trace.TraceInformation(
+ $"extensibility.settings.VisualStudio.json not found in {solutionPath}. Using default config."
+ )
+
+ FSharpExtensionConfig.Default
+
+ let ExtensionConfig = ConditionalWeakTable()
+
type Solution with
/// Get the instance of IFSharpWorkspaceService.
member internal this.GetFSharpWorkspaceService() =
this.Workspace.Services.GetRequiredService()
+ member internal this.GetFSharpExtensionConfig() =
+ SolutionConfigCache.ExtensionConfig.GetValue(
+ this,
+ ConditionalWeakTable<_, _>.CreateValueCallback(fun _ ->
+ SolutionConfigCache.readFSharpExtensionConfig (Path.GetDirectoryName this.FilePath))
+ )
+
module internal FSharpProjectSnapshotSerialization =
let serializeFileSnapshot (snapshot: FSharpFileSnapshot) =