From 7c3df0db653a72d289c35f3b69e23de254081446 Mon Sep 17 00:00:00 2001 From: cartermp Date: Mon, 11 Dec 2017 18:05:41 -0800 Subject: [PATCH 1/8] Move ProjectSitesAndFiles to FSharp.Editor --- .../src/FSharp.Editor/FSharp.Editor.fsproj | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj b/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj index 0f11702c665..0a579b179f8 100644 --- a/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj +++ b/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj @@ -28,6 +28,19 @@ true true + + + + + + + + + Microsoft + StrongName + + + @@ -142,6 +155,7 @@ + From cbe67853be3de49013b2435f8ad85ade6c544a8d Mon Sep 17 00:00:00 2001 From: cartermp Date: Wed, 13 Dec 2017 09:31:03 -0800 Subject: [PATCH 2/8] Remove weird stuff and add the VSLang proj reference like it is in FSharp.LanguageService --- .../src/FSharp.Editor/FSharp.Editor.fsproj | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj b/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj index 0a579b179f8..0f11702c665 100644 --- a/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj +++ b/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj @@ -28,19 +28,6 @@ true true - - - - - - - - - Microsoft - StrongName - - - @@ -155,7 +142,6 @@ - From 5c497b27916fdd8033b2beb6e29bab673f839ffe Mon Sep 17 00:00:00 2001 From: cartermp Date: Wed, 13 Dec 2017 13:08:23 -0800 Subject: [PATCH 3/8] Remove dependency on FSharp.LanguageService --- vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj b/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj index 0f11702c665..5a3e615da56 100644 --- a/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj +++ b/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj @@ -112,6 +112,11 @@ {ee85aab7-cda0-4c4e-bda0-a64ccc413e3f} True + FSharp.LanguageService.Base {1c5c163c-37ea-4a3c-8ccc-0d34b74bf8ef} From 148dedccc42648fbf45003c49fb9c0a34085122e Mon Sep 17 00:00:00 2001 From: cartermp Date: Sat, 16 Dec 2017 22:25:19 -0800 Subject: [PATCH 4/8] Whoopsie on that legacy project system --- .../FSharp.ProjectSystem.FSharp/Project.fs | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/vsintegration/src/FSharp.ProjectSystem.FSharp/Project.fs b/vsintegration/src/FSharp.ProjectSystem.FSharp/Project.fs index 9e91f0c052c..fb870a75dc3 100644 --- a/vsintegration/src/FSharp.ProjectSystem.FSharp/Project.fs +++ b/vsintegration/src/FSharp.ProjectSystem.FSharp/Project.fs @@ -87,7 +87,7 @@ namespace rec Microsoft.VisualStudio.FSharp.ProjectSystem ////////////////////// // An IProjectSite object with hot-swappable inner implementation - type internal DynamicProjectSite(origInnerImpl : Microsoft.VisualStudio.FSharp.LanguageService.IProjectSite) = + type internal DynamicProjectSite(origInnerImpl : Microsoft.VisualStudio.FSharp.Editor.IProjectSite) = let mutable inner = origInnerImpl @@ -95,7 +95,7 @@ namespace rec Microsoft.VisualStudio.FSharp.ProjectSystem inner <- newInner // This interface is thread-safe, assuming "inner" is thread-safe - interface Microsoft.VisualStudio.FSharp.LanguageService.IProjectSite with + interface Microsoft.VisualStudio.FSharp.Editor.IProjectSite with member __.Description = inner.Description member __.CompilationSourceFiles = inner.CompilationSourceFiles member __.CompilationOptions = inner.CompilationOptions @@ -128,7 +128,7 @@ namespace rec Microsoft.VisualStudio.FSharp.ProjectSystem // This member is thread-safe member x.GetProjectSite() = Debug.Assert(state <> ProjectSiteOptionLifetimeState.Opening, "ProjectSite is not available") - projectSite.Value :> Microsoft.VisualStudio.FSharp.LanguageService.IProjectSite + projectSite.Value :> Microsoft.VisualStudio.FSharp.Editor.IProjectSite // This member is thread-safe member x.TryGetProjectSite() = @@ -136,7 +136,7 @@ namespace rec Microsoft.VisualStudio.FSharp.ProjectSystem | ProjectSiteOptionLifetimeState.Opening, _ | ProjectSiteOptionLifetimeState.Closed, _ -> None | _, None -> None - | _, Some x -> Some(x :> Microsoft.VisualStudio.FSharp.LanguageService.IProjectSite) + | _, Some x -> Some(x :> Microsoft.VisualStudio.FSharp.Editor.IProjectSite) member x.Open(site) = Debug.Assert((state = ProjectSiteOptionLifetimeState.Opening), "Called Open, but not in Opening state") @@ -159,7 +159,7 @@ namespace rec Microsoft.VisualStudio.FSharp.ProjectSystem static member ExploreFolderInWindows = 1635u type internal Notifier() = - let notificationsDict = new System.Collections.Generic.Dictionary() + let notificationsDict = new System.Collections.Generic.Dictionary() member this.Notify() = for kvp in notificationsDict do kvp.Value.Invoke() @@ -1446,7 +1446,7 @@ namespace rec Microsoft.VisualStudio.FSharp.ProjectSystem // Returns an IProjectSite that references "this" to get its information member private x.CreateRunningProjectSite() = let creationTime = System.DateTime.UtcNow - { new Microsoft.VisualStudio.FSharp.LanguageService.IProjectSite with + { new Microsoft.VisualStudio.FSharp.Editor.IProjectSite with member __.CompilationSourceFiles = x.CompilationSourceFiles member __.CompilationOptions = x.CompilationOptions @@ -1471,8 +1471,7 @@ namespace rec Microsoft.VisualStudio.FSharp.ProjectSystem member __.TargetFrameworkMoniker = x.GetTargetFrameworkMoniker() member __.ProjectGuid = x.GetProjectGuid() member __.LoadTime = creationTime - member __.ProjectProvider = Some (x :> IProvideProjectSite) - + member __.ProjectProvider = Some (x :> Microsoft.VisualStudio.FSharp.Editor.IProvideProjectSite) } // Snapshot-capture relevent values from "this", and returns an IProjectSite @@ -1490,7 +1489,7 @@ namespace rec Microsoft.VisualStudio.FSharp.ProjectSystem let creationTime = DateTime.UtcNow // This object is thread-safe - { new Microsoft.VisualStudio.FSharp.LanguageService.IProjectSite with + { new Microsoft.VisualStudio.FSharp.Editor.IProjectSite with member __.Description = description member __.CompilationSourceFiles = sourceFiles member __.CompilationOptions = options @@ -1507,11 +1506,15 @@ namespace rec Microsoft.VisualStudio.FSharp.ProjectSystem member __.TargetFrameworkMoniker = targetFrameworkMoniker member __.ProjectGuid = x.GetProjectGuid() member __.LoadTime = creationTime +<<<<<<< HEAD member __.ProjectProvider = Some (x :> IProvideProjectSite) +======= + member __.ProjectProvider = Some (x :> Microsoft.VisualStudio.FSharp.Editor.IProvideProjectSite) +>>>>>>> Whoopsie on that legacy project system } // let the language service ask us questions - interface Microsoft.VisualStudio.FSharp.LanguageService.IProvideProjectSite with + interface Microsoft.VisualStudio.FSharp.Editor.IProvideProjectSite with member x.GetProjectSite() = match projectSite.State with | ProjectSiteOptionLifetimeState.Opening -> From 4deb1b5b7a5ca2d13c40f8945a56f4b69a51abf4 Mon Sep 17 00:00:00 2001 From: cartermp Date: Sat, 16 Dec 2017 22:44:53 -0800 Subject: [PATCH 5/8] Update project system tests --- .../LegacyProjectSystem/Tests.ProjectSystem.Miscellaneous.fs | 3 ++- vsintegration/tests/unittests/TestLib.ProjectSystem.fs | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/vsintegration/tests/unittests/LegacyProjectSystem/Tests.ProjectSystem.Miscellaneous.fs b/vsintegration/tests/unittests/LegacyProjectSystem/Tests.ProjectSystem.Miscellaneous.fs index 361f6569b16..118cdc4af50 100644 --- a/vsintegration/tests/unittests/LegacyProjectSystem/Tests.ProjectSystem.Miscellaneous.fs +++ b/vsintegration/tests/unittests/LegacyProjectSystem/Tests.ProjectSystem.Miscellaneous.fs @@ -15,6 +15,7 @@ open Microsoft.VisualStudio open Microsoft.VisualStudio.Shell open Microsoft.VisualStudio.Shell.Interop open Microsoft.VisualStudio.FSharp.ProjectSystem +open Microsoft.VisualStudio.FSharp.Editor // Internal unittest namespaces open NUnit.Framework @@ -498,7 +499,7 @@ type Miscellaneous() = // Now the project system is in a state where ComputeSourcesAndFlags will fail. // Our goal is to at least be able to open individual source files and treat them like 'files outside a project' with regards to intellisense, etc. // Also, if the user does 'Build', he will get an error which will help diagnose the problem. - let ipps = project :> Microsoft.VisualStudio.FSharp.LanguageService.IProvideProjectSite + let ipps = project :> IProvideProjectSite let ips = ipps.GetProjectSite() let expected = [| |] // Ideal behavior is [|"foo.fs";"bar.fs"|], and we could choose to improve this in the future. For now we are just happy to now throw/crash. let actual = ips.CompilationSourceFiles diff --git a/vsintegration/tests/unittests/TestLib.ProjectSystem.fs b/vsintegration/tests/unittests/TestLib.ProjectSystem.fs index b353e548f72..24ba2a6cc8a 100644 --- a/vsintegration/tests/unittests/TestLib.ProjectSystem.fs +++ b/vsintegration/tests/unittests/TestLib.ProjectSystem.fs @@ -18,6 +18,7 @@ open Microsoft.Win32 open Microsoft.VisualStudio open Microsoft.VisualStudio.FSharp.ProjectSystem +open Microsoft.VisualStudio.FSharp.Editor open Microsoft.VisualStudio.Shell.Interop open Microsoft.Build.Execution @@ -366,10 +367,10 @@ type TheTests() = () member internal this.EnsureCausesNotification(project, code) = - let ipsf = project :> Microsoft.VisualStudio.FSharp.LanguageService.IProvideProjectSite + let ipsf = project :> IProvideProjectSite let ips = ipsf.GetProjectSite() let changed = ref false - let handle = ips.AdviseProjectSiteChanges("EnsureCausesNotificationTest", new Microsoft.VisualStudio.FSharp.LanguageService.AdviseProjectSiteChanges(fun () -> changed := true)) + let handle = ips.AdviseProjectSiteChanges("EnsureCausesNotificationTest", new AdviseProjectSiteChanges(fun () -> changed := true)) code() AssertEqual true (!changed) static member MsBuildCompileItems(project : Microsoft.Build.Evaluation.Project) = From 8b87ef89d021b7bad9c78b0964fa6debb5ccee83 Mon Sep 17 00:00:00 2001 From: cartermp Date: Sun, 17 Dec 2017 08:58:12 -0800 Subject: [PATCH 6/8] Update tests --- .../FSharp.LanguageService/LanguageServiceConstants.fs | 4 ++++ .../tests/Salsa/FSharpLanguageServiceTestable.fs | 9 ++++----- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/vsintegration/src/FSharp.LanguageService/LanguageServiceConstants.fs b/vsintegration/src/FSharp.LanguageService/LanguageServiceConstants.fs index b64d0405948..46bd6dd2f04 100644 --- a/vsintegration/src/FSharp.LanguageService/LanguageServiceConstants.fs +++ b/vsintegration/src/FSharp.LanguageService/LanguageServiceConstants.fs @@ -8,3 +8,7 @@ module internal LanguageServiceConstants = /// "F#" [] let FSharpLanguageName = "F#" + + [] + /// "F# Language Service" + let FSharpLanguageServiceCallbackName = "F# Language Service" \ No newline at end of file diff --git a/vsintegration/tests/Salsa/FSharpLanguageServiceTestable.fs b/vsintegration/tests/Salsa/FSharpLanguageServiceTestable.fs index 21b290c3819..a73f48ef46c 100644 --- a/vsintegration/tests/Salsa/FSharpLanguageServiceTestable.fs +++ b/vsintegration/tests/Salsa/FSharpLanguageServiceTestable.fs @@ -18,7 +18,6 @@ open Microsoft.FSharp.Compiler open Microsoft.FSharp.Compiler.SourceCodeServices open Microsoft.VisualStudio.FSharp.LanguageService open Microsoft.VisualStudio.FSharp.LanguageService.SiteProvider -open Microsoft.VisualStudio.FSharp.Editor type internal FSharpLanguageServiceTestable() as this = static let colorizerGuid = new Guid("{A2976312-7D71-4BB4-A5F8-66A08EBF46C8}") // Guid for colorized user data on IVsTextBuffer @@ -149,10 +148,10 @@ type internal FSharpLanguageServiceTestable() as this = match hier with | :? IProvideProjectSite as siteProvider -> let site = siteProvider.GetProjectSite() - site.AdviseProjectSiteChanges(FSharpConstants.FSharpLanguageServiceCallbackName, - new AdviseProjectSiteChanges(fun () -> this.OnProjectSettingsChanged(site))) - site.AdviseProjectSiteCleaned(FSharpConstants.FSharpLanguageServiceCallbackName, - new AdviseProjectSiteChanges(fun () -> this.OnProjectCleaned(site))) + site.AdviseProjectSiteChanges(LanguageServiceConstants.FSharpLanguageServiceCallbackName, + new Microsoft.VisualStudio.FSharp.LanguageService.AdviseProjectSiteChanges(fun () -> this.OnProjectSettingsChanged(site))) + site.AdviseProjectSiteCleaned(LanguageServiceConstants.FSharpLanguageServiceCallbackName, + new Microsoft.VisualStudio.FSharp.LanguageService.AdviseProjectSiteChanges(fun () -> this.OnProjectCleaned(site))) | _ -> // This can happen when the file is in a solution folder or in, say, a C# project. () From 19c495cb5db334b600399c6e8848a6b07181adc0 Mon Sep 17 00:00:00 2001 From: cartermp Date: Sun, 17 Dec 2017 09:22:25 -0800 Subject: [PATCH 7/8] Weird comment removal --- vsintegration/src/FSharp.ProjectSystem.FSharp/Project.fs | 2 -- 1 file changed, 2 deletions(-) diff --git a/vsintegration/src/FSharp.ProjectSystem.FSharp/Project.fs b/vsintegration/src/FSharp.ProjectSystem.FSharp/Project.fs index fb870a75dc3..2dec54ed051 100644 --- a/vsintegration/src/FSharp.ProjectSystem.FSharp/Project.fs +++ b/vsintegration/src/FSharp.ProjectSystem.FSharp/Project.fs @@ -84,8 +84,6 @@ namespace rec Microsoft.VisualStudio.FSharp.ProjectSystem let getConfigExtendedPropertyPages() = match lazyPropertyPages.Force() with (_,config,_) -> config let getPriorityExtendedPropertyPages() = match lazyPropertyPages.Force() with (_,_,priority) -> priority -////////////////////// - // An IProjectSite object with hot-swappable inner implementation type internal DynamicProjectSite(origInnerImpl : Microsoft.VisualStudio.FSharp.Editor.IProjectSite) = From a1bcca37076825f5d690fff12d4bd35bc4501d0d Mon Sep 17 00:00:00 2001 From: cartermp Date: Sun, 17 Dec 2017 09:45:22 -0800 Subject: [PATCH 8/8] Remove FSharp.Editor dependency on FSharp.Language service and update all other pieces --- .../src/FSharp.Editor/Common/Error.fs | 4 - vsintegration/src/FSharp.Editor/Common/Vs.fs | 70 +++- .../src/FSharp.Editor/FSharp.Editor.fsproj | 24 +- .../LanguageService/IProjectSite.fs | 61 ++++ .../LanguageService/LanguageService.fs | 3 +- .../LanguageService/ProjectSitesAndFiles.fs | 323 ++++++++++++++++++ .../FSharp.ProjectSystem.FSharp/Project.fs | 4 - .../Salsa/FSharpLanguageServiceTestable.fs | 4 +- 8 files changed, 463 insertions(+), 30 deletions(-) create mode 100644 vsintegration/src/FSharp.Editor/LanguageService/IProjectSite.fs create mode 100644 vsintegration/src/FSharp.Editor/LanguageService/ProjectSitesAndFiles.fs diff --git a/vsintegration/src/FSharp.Editor/Common/Error.fs b/vsintegration/src/FSharp.Editor/Common/Error.fs index 101baa10909..d6b6678f0de 100644 --- a/vsintegration/src/FSharp.Editor/Common/Error.fs +++ b/vsintegration/src/FSharp.Editor/Common/Error.fs @@ -8,7 +8,3 @@ type internal Assert() = /// Display a good exception for this error message and then rethrow. static member Exception(e:Exception) = System.Diagnostics.Debug.Assert(false, "Unexpected exception seen in language service", e.ToString()) - - - - diff --git a/vsintegration/src/FSharp.Editor/Common/Vs.fs b/vsintegration/src/FSharp.Editor/Common/Vs.fs index 33fd16e6930..526166f05d6 100644 --- a/vsintegration/src/FSharp.Editor/Common/Vs.fs +++ b/vsintegration/src/FSharp.Editor/Common/Vs.fs @@ -3,16 +3,84 @@ namespace Microsoft.VisualStudio.FSharp.Editor open System +open System.Runtime.InteropServices open Microsoft.VisualStudio +open Microsoft.VisualStudio.Editor open Microsoft.VisualStudio.Shell.Interop open Microsoft.VisualStudio.TextManager.Interop /// Helper methods for interoperating with COM -module internal Com = +module internal Com = + let ThrowOnFailure0(hr) = + ErrorHandler.ThrowOnFailure(hr) |> ignore + + let ThrowOnFailure1(hr,res) = + ErrorHandler.ThrowOnFailure(hr) |> ignore; + res + + let ThrowOnFailure2(hr,res1,res2) = + ErrorHandler.ThrowOnFailure(hr) |> ignore; + res1,res2 + + let ThrowOnFailure3(hr,res1,res2,res3) = + ErrorHandler.ThrowOnFailure(hr) |> ignore; + res1,res2,res3 + + let ThrowOnFailure4(hr,res1,res2,res3,res4) = + ErrorHandler.ThrowOnFailure(hr) |> ignore; + res1,res2,res3,res4 + let Succeeded hr = // REVIEW: Not the correct check for succeeded hr = VSConstants.S_OK +module internal VsUserData = + + let vsBufferMoniker = Guid("978A8E17-4DF8-432A-9623-D530A26452BC") + + // This is the file name of the buffer. + let GetBufferMonker(ud:IVsUserData) : string = + downcast Com.ThrowOnFailure1(ud.GetData(ref vsBufferMoniker)) + +module internal VsTextLines = + /// Get the length of the given line. + let LengthOfLine (buffer:IVsTextBuffer) (line:int) : int = + Com.ThrowOnFailure1(buffer.GetLengthOfLine(line)) + + /// Get the text for a particular line. + let LineText (buffer:IVsTextLines) line = + Com.ThrowOnFailure1(buffer.GetLineText(line, 0, line, LengthOfLine buffer line)) + + /// Get the color state + let TextColorState (buffer:IVsTextLines) : IVsTextColorState= unbox(box(buffer)) + + /// Get the filename of the given buffer (via IVsUserData). Not all buffers have a file. This will be an exception. + let GetFilename(buffer : IVsTextLines) = + let ud = (box buffer) :?> IVsUserData + VsUserData.GetBufferMonker(ud) + + /// Get the string contents of a given buffer (the current snapshot). + let GetFileContents(buffer: IVsTextBuffer, editorAdaptersFactoryService: IVsEditorAdaptersFactoryService) = + let dataBuffer = editorAdaptersFactoryService.GetDataBuffer(buffer) + dataBuffer.CurrentSnapshot.GetText() + +module internal VsRunningDocumentTable = + let FindDocumentWithoutLocking(rdt:IVsRunningDocumentTable, url:string) : (IVsHierarchy * IVsTextLines) option = + let (hr:int, hier:IVsHierarchy, _itemid:uint32, unkData:IntPtr, _cookie:uint32) = rdt.FindAndLockDocument(uint32 _VSRDTFLAGS.RDT_NoLock, url) + try + if Com.Succeeded(hr) then + let bufferObject = + if unkData=IntPtr.Zero then null + else Marshal.GetObjectForIUnknown(unkData) + let buffer = + match bufferObject with + | :? IVsTextLines as tl -> tl + | _ -> null + Some(hier, buffer) + else None + finally + if IntPtr.Zero <> unkData then Marshal.Release(unkData)|>ignore + [] module internal ServiceProviderExtensions = type internal System.IServiceProvider with diff --git a/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj b/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj index 5a3e615da56..2563745eadc 100644 --- a/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj +++ b/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj @@ -32,8 +32,6 @@ - - true Microsoft.VisualStudio.FSharp.Editor.SR @@ -42,11 +40,11 @@ + - @@ -54,6 +52,8 @@ + + @@ -107,16 +107,6 @@ {DED3BBD7-53F4-428A-8C9F-27968E768605} FSharp.Core - - FSharp.LanguageService - {ee85aab7-cda0-4c4e-bda0-a64ccc413e3f} - True - - FSharp.LanguageService.Base {1c5c163c-37ea-4a3c-8ccc-0d34b74bf8ef} @@ -137,8 +127,6 @@ {991dcf75-c2eb-42b6-9a0d-aa1d2409d519} True - - @@ -151,8 +139,6 @@ - - $(FSharpSourcesRoot)\..\packages\EnvDTE.8.0.1\lib\net10\EnvDTE.dll True @@ -161,6 +147,10 @@ $(FSharpSourcesRoot)\..\packages\EnvDTE80.8.0.1\lib\net10\EnvDTE80.dll True + + $(FSharpSourcesRoot)\..\packages\VSSDK.VSLangProj.7.0.4\lib\net20\VSLangProj.dll + True + $(FSharpSourcesRoot)\..\packages\Microsoft.VisualStudio.Threading.$(MicrosoftVisualStudioThreadingVersion)\lib\net45\Microsoft.VisualStudio.Threading.dll diff --git a/vsintegration/src/FSharp.Editor/LanguageService/IProjectSite.fs b/vsintegration/src/FSharp.Editor/LanguageService/IProjectSite.fs new file mode 100644 index 00000000000..71d53fe406f --- /dev/null +++ b/vsintegration/src/FSharp.Editor/LanguageService/IProjectSite.fs @@ -0,0 +1,61 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. + +namespace Microsoft.VisualStudio.FSharp.Editor + +open System.Runtime.InteropServices + +/// Narrow abstraction over the project system. +type internal AdviseProjectSiteChanges = delegate of unit -> unit + +[] +type internal IProvideProjectSite = + abstract GetProjectSite : unit -> IProjectSite + +/// Represents known F#-specific information about a project. +and internal IProjectSite = + + /// List of files in the project. In the correct order. + abstract CompilationSourceFiles : string[] + + /// Flags that the compiler would need to understand how to compile. Includes '-r' + /// options but not source files + abstract CompilationOptions : string[] + + /// The normalized '-r:' assembly references, without the '-r:' + abstract CompilationReferences : string [] + + /// The '-o:' output bin path, without the '-o:' + abstract CompilationBinOutputPath : string option + + /// The name of the project file. + abstract ProjectFileName : string + + /// Register for notifications for when the above change + abstract AdviseProjectSiteChanges : callbackOwnerKey: string * AdviseProjectSiteChanges -> unit + + /// Register for notifications when project is cleaned/rebuilt (and thus any live TypeProviders should be refreshed) + abstract AdviseProjectSiteCleaned : callbackOwnerKey: string * AdviseProjectSiteChanges -> unit + + // Register for notifications when project is closed. + abstract AdviseProjectSiteClosed : callbackOwnerKey: string * AdviseProjectSiteChanges -> unit + + /// A user-friendly description of the project. Used only for developer/DEBUG tooltips and such. + abstract Description : string + + /// The error list task reporter + abstract BuildErrorReporter : Microsoft.VisualStudio.Shell.Interop.IVsLanguageServiceBuildErrorReporter2 option with get, set + + /// False type resolution errors are invalid. This occurs with orphaned source files. The prior + /// type checking state is unknown. In this case we don't want to squiggle the type checking files. + abstract IsIncompleteTypeCheckEnvironment : bool + + /// target framework moniker + abstract TargetFrameworkMoniker : string + + /// Project Guid + abstract ProjectGuid : string + + /// timestamp the site was last loaded + abstract LoadTime : System.DateTime + + abstract ProjectProvider : IProvideProjectSite option \ No newline at end of file diff --git a/vsintegration/src/FSharp.Editor/LanguageService/LanguageService.fs b/vsintegration/src/FSharp.Editor/LanguageService/LanguageService.fs index 1686e96a3d5..8741f48e95c 100644 --- a/vsintegration/src/FSharp.Editor/LanguageService/LanguageService.fs +++ b/vsintegration/src/FSharp.Editor/LanguageService/LanguageService.fs @@ -23,8 +23,7 @@ open Microsoft.FSharp.Compiler.CompileOps open Microsoft.FSharp.Compiler.SourceCodeServices open Microsoft.VisualStudio open Microsoft.VisualStudio.Editor -open Microsoft.VisualStudio.FSharp.LanguageService -open Microsoft.VisualStudio.FSharp.LanguageService.SiteProvider +open Microsoft.VisualStudio.FSharp.Editor.SiteProvider open Microsoft.VisualStudio.TextManager.Interop open Microsoft.VisualStudio.LanguageServices open Microsoft.VisualStudio.LanguageServices.Implementation.LanguageService diff --git a/vsintegration/src/FSharp.Editor/LanguageService/ProjectSitesAndFiles.fs b/vsintegration/src/FSharp.Editor/LanguageService/ProjectSitesAndFiles.fs new file mode 100644 index 00000000000..6c5a2c61cb8 --- /dev/null +++ b/vsintegration/src/FSharp.Editor/LanguageService/ProjectSitesAndFiles.fs @@ -0,0 +1,323 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. + +module internal rec Microsoft.VisualStudio.FSharp.Editor.SiteProvider + +open System +open System.IO +open System.Collections.Concurrent +open System.Diagnostics + +open Microsoft.FSharp.Compiler.SourceCodeServices +open Microsoft.CodeAnalysis +open Microsoft.VisualStudio +open Microsoft.VisualStudio.LanguageServices +open Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem +open Microsoft.VisualStudio.LanguageServices.Implementation.TaskList +open Microsoft.VisualStudio.Shell.Interop +open Microsoft.VisualStudio.TextManager.Interop + +open VSLangProj + +/// An additional interface that an IProjectSite object can implement to indicate it has an FSharpProjectOptions +/// already available, so we don't have to recreate it +type private IHaveCheckOptions = + abstract OriginalCheckOptions : unit -> string[] * FSharpProjectOptions + +let projectDisplayNameOf projectFileName = + if String.IsNullOrWhiteSpace projectFileName then projectFileName + else Path.GetFileNameWithoutExtension projectFileName + +/// A value and a function to recompute/refresh the value. The function is passed a flag indicating if a refresh is happening. +type Refreshable<'T> = 'T * (bool -> 'T) + +/// Convert from FSharpProjectOptions into IProjectSite. +type private ProjectSiteOfScriptFile(filename:string, referencedProjectFileNames, checkOptions: FSharpProjectOptions) = + interface IProjectSite with + override __.Description = sprintf "Script Closure at Root %s" filename + override __.CompilationSourceFiles = checkOptions.SourceFiles + override __.CompilationOptions = checkOptions.OtherOptions + override __.CompilationReferences = + checkOptions.OtherOptions + |> Array.choose (fun flag -> if flag.StartsWith("-r:") then Some flag.[3..] else None) + override __.CompilationBinOutputPath = None + override __.ProjectFileName = checkOptions.ProjectFileName + override __.BuildErrorReporter with get() = None and set _ = () + override __.AdviseProjectSiteChanges(_,_) = () + override __.AdviseProjectSiteCleaned(_,_) = () + override __.AdviseProjectSiteClosed(_,_) = () + override __.IsIncompleteTypeCheckEnvironment = checkOptions.IsIncompleteTypeCheckEnvironment + override __.TargetFrameworkMoniker = "" + override __.ProjectGuid = "" + override __.LoadTime = checkOptions.LoadTime + override __.ProjectProvider = None + + interface IHaveCheckOptions with + override __.OriginalCheckOptions() = (referencedProjectFileNames, checkOptions) + + override __.ToString() = sprintf "ProjectSiteOfScriptFile(%s)" filename + +/// An orphan file project is a .fs, .ml, .fsi, .mli that is not associated with a .fsproj. +/// By design, these are never going to typecheck because there is no affiliated references. +/// We show many squiggles in this case because they're not particularly informational. +type private ProjectSiteOfSingleFile(sourceFile) = + // CompilerFlags() gets called a lot, so pre-compute what we can + static let compilerFlags = + let flags = ["--noframework";"--warn:3"] + let assumeDotNetFramework = true + let defaultReferences = + [ for r in CompilerEnvironment.DefaultReferencesForOrphanSources(assumeDotNetFramework) do + yield sprintf "-r:%s%s" r (if r.EndsWith(".dll",StringComparison.OrdinalIgnoreCase) then "" else ".dll") ] + (flags @ defaultReferences) + |> List.toArray + |> Array.choose (fun flag -> if flag.StartsWith("-r:") then Some flag.[3..] elif flag.StartsWith("--reference:") then Some flag.[12..] else None) + + let projectFileName = sourceFile + ".orphan.fsproj" + + interface IProjectSite with + override __.Description = projectFileName + override __.CompilationSourceFiles = [|sourceFile|] + override __.CompilationOptions = compilerFlags + override __.CompilationReferences = compilerFlags + override __.CompilationBinOutputPath = None + override __.ProjectFileName = projectFileName + override __.BuildErrorReporter with get() = None and set _v = () + override __.AdviseProjectSiteChanges(_,_) = () + override __.AdviseProjectSiteCleaned(_,_) = () + override __.AdviseProjectSiteClosed(_,_) = () + override __.IsIncompleteTypeCheckEnvironment = true + override __.TargetFrameworkMoniker = "" + override __.ProjectGuid = "" + override __.LoadTime = new DateTime(2000,1,1) // any constant time is fine, orphan files do not interact with reloading based on update time + override __.ProjectProvider = None + + override __.ToString() = sprintf "ProjectSiteOfSingleFile(%s)" sourceFile + +/// Manage Storage of FSharpProjectOptions the options for a project +type internal FSharpProjectOptionsTable () = + + // A table of information about projects, excluding single-file projects. + let projectTable = ConcurrentDictionary>() + let commandLineOptions = new ConcurrentDictionary() + + /// Re-fetch all of the options for everything that references projectId + let refreshInfoForProjectsThatReferenceThisProject (projectId:ProjectId) = + for KeyValue(otherProjectId, ((referencedProjectIds, _parsingOptions, _site, _options), refresh)) in projectTable.ToArray() do + for referencedProjectId in referencedProjectIds do + if referencedProjectId = projectId then + projectTable.[otherProjectId] <- (refresh true, refresh) + + /// Add or update a project in the project table + member __.AddOrUpdateProject(projectId:ProjectId, refresh) = + projectTable.[projectId] <- (refresh false, refresh) + refreshInfoForProjectsThatReferenceThisProject(projectId) + + /// Clear a project from the project table + member __.ClearInfoForProject(projectId:ProjectId) = + projectTable.TryRemove(projectId) |> ignore + refreshInfoForProjectsThatReferenceThisProject projectId + + /// Get the options for a project + member __.TryGetOptionsForProject(projectId:ProjectId) = + match projectTable.TryGetValue(projectId) with + | true, ((_referencedProjects, parsingOptions, site, projectOptions), _) -> Some (parsingOptions, site, projectOptions) + | _ -> None + + /// Given a projectId return the most recent set of command line options for it + member __.GetCommandLineOptionsWithProjectId(projectId:ProjectId) = + match commandLineOptions.TryGetValue projectId with + | true, (sources, references, options) -> sources, references, options + | _ -> [||], [||], [||] + + /// Store the command line options for a projectId + member __.SetOptionsWithProjectId(projectId:ProjectId, sourcePaths:string[], referencePaths:string[], options:string[]) = + commandLineOptions.[projectId] <- (sourcePaths, referencePaths, options) + + +let internal provideProjectSiteProvider(workspace:VisualStudioWorkspaceImpl, project:Project, serviceProvider:System.IServiceProvider, projectOptionsTable:FSharpProjectOptionsTable option) = + let hier = workspace.GetHierarchy(project.Id) + let getCommandLineOptionsWithProjectId (projectId) = + match projectOptionsTable with + | Some (options) -> options.GetCommandLineOptionsWithProjectId(projectId) + | None -> [||], [||], [||] + { + new IProvideProjectSite with + member x.GetProjectSite() = + let fst (a, _, _) = a + let snd (_, b, _) = b + let mutable errorReporter = + let reporter = ProjectExternalErrorReporter(project.Id, "FS", serviceProvider) + Some(reporter:> IVsLanguageServiceBuildErrorReporter2) + + { + new IProjectSite with + member __.Description = project.Name + member __.CompilationSourceFiles = getCommandLineOptionsWithProjectId(project.Id) |> fst + member __.CompilationOptions = + let _,references,options = getCommandLineOptionsWithProjectId(project.Id) + Array.concat [options; references |> Array.map(fun r -> "-r:" + r)] + member __.CompilationReferences = getCommandLineOptionsWithProjectId(project.Id) |> snd + member site.CompilationBinOutputPath = site.CompilationOptions |> Array.tryPick (fun s -> if s.StartsWith("-o:") then Some s.[3..] else None) + member __.ProjectFileName = project.FilePath + member __.AdviseProjectSiteChanges(_,_) = () + member __.AdviseProjectSiteCleaned(_,_) = () + member __.AdviseProjectSiteClosed(_,_) = () + member __.IsIncompleteTypeCheckEnvironment = false + member __.TargetFrameworkMoniker = "" + member __.ProjectGuid = project.Id.Id.ToString() + member __.LoadTime = System.DateTime.Now + member __.ProjectProvider = Some (x) + member __.BuildErrorReporter with get () = errorReporter and set (v) = errorReporter <- v + } + interface IVsHierarchy with + member __.SetSite(psp) = hier.SetSite(psp) + member __.GetSite(psp) = hier.GetSite(ref psp) + member __.QueryClose(pfCanClose)= hier.QueryClose(ref pfCanClose) + member __.Close() = hier.Close() + member __.GetGuidProperty(itemid, propid, pguid) = hier.GetGuidProperty(itemid, propid, ref pguid) + member __.SetGuidProperty(itemid, propid, rguid) = hier.SetGuidProperty(itemid, propid, ref rguid) + member __.GetProperty(itemid, propid, pvar) = hier.GetProperty(itemid, propid, ref pvar) + member __.SetProperty(itemid, propid, var) = hier.SetProperty(itemid, propid, var) + member __.GetNestedHierarchy(itemid, iidHierarchyNested, ppHierarchyNested, pitemidNested) = + hier.GetNestedHierarchy(itemid, ref iidHierarchyNested, ref ppHierarchyNested, ref pitemidNested) + member __.GetCanonicalName(itemid, pbstrName) = hier.GetCanonicalName(itemid, ref pbstrName) + member __.ParseCanonicalName(pszName, pitemid) = hier.ParseCanonicalName(pszName, ref pitemid) + member __.Unused0() = hier.Unused0() + member __.AdviseHierarchyEvents(pEventSink, pdwCookie) = hier.AdviseHierarchyEvents(pEventSink, ref pdwCookie) + member __.UnadviseHierarchyEvents(dwCookie) = hier.UnadviseHierarchyEvents(dwCookie) + member __.Unused1() = hier.Unused1() + member __.Unused2() = hier.Unused2() + member __.Unused3() = hier.Unused3() + member __.Unused4() = hier.Unused4() + } + +/// Information about projects, open files and other active artifacts in visual studio. +/// Keeps track of the relationship between IVsTextLines buffers, IFSharpSource_DEPRECATED objects, IProjectSite objects and FSharpProjectOptions +[] +type internal ProjectSitesAndFiles() = + static let mutable stamp = 0L + + static let fullOutputAssemblyPath (p:EnvDTE.Project) = + let getProperty tag = + try Some (p.Properties.[tag].Value.ToString()) with _ -> None + getProperty "FullPath" + |> Option.bind (fun fullPath -> + (try Some (p.ConfigurationManager.ActiveConfiguration.Properties.["OutputPath"].Value.ToString()) with _ -> None) + |> Option.bind (fun outputPath -> + getProperty "OutputFileName" + |> Option.map (fun outputFileName -> Path.Combine(fullPath, outputPath, outputFileName)))) + |> Option.bind (fun path -> try Some (Path.GetFullPath path) with _ -> None) + + static let referencedProjects (projectSite:IProjectSite) = + match projectSite.ProjectProvider with + | None -> None + | Some (:? IVsHierarchy as hier) -> + match hier.GetProperty(VSConstants.VSITEMID_ROOT, int __VSHPROPID.VSHPROPID_ExtObject) with + | VSConstants.S_OK, (:? EnvDTE.Project as p) when not (isNull p) -> + Some ((p.Object :?> VSLangProj.VSProject).References + |> Seq.cast + |> Seq.choose (fun r -> + Option.ofObj r + |> Option.bind (fun r -> try Option.ofObj r.SourceProject with _ -> None)) ) + | _ -> None + | Some _ -> None + + static let rec referencedProvideProjectSites(projectSite:IProjectSite, serviceProvider:System.IServiceProvider, extraProjectInfo:obj option, projectOptionsTable:FSharpProjectOptionsTable option) = + let getReferencesForSolutionService (solutionService:IVsSolution) = + [| + match referencedProjects projectSite, extraProjectInfo with + | None, Some (:? VisualStudioWorkspaceImpl as workspace) when not (isNull workspace.CurrentSolution)-> + let path = projectSite.ProjectFileName + if not (String.IsNullOrWhiteSpace(path)) then + let projectId = workspace.ProjectTracker.GetOrCreateProjectIdForPath(path, projectDisplayNameOf path) + let project = workspace.CurrentSolution.GetProject(projectId) + if not (isNull project) then + for reference in project.ProjectReferences do + let project = workspace.CurrentSolution.GetProject(reference.ProjectId) + if not (isNull project) && project.Language = FSharpConstants.FSharpLanguageName then + let siteProvider = provideProjectSiteProvider (workspace, project, serviceProvider, projectOptionsTable) + let referenceProject = workspace.ProjectTracker.GetProject(reference.ProjectId) + let outputPath = referenceProject.BinOutputPath + yield Some projectId, project.FilePath, outputPath, siteProvider + + | (Some references), _ -> + for p in references do + match solutionService.GetProjectOfUniqueName(p.UniqueName) with + | VSConstants.S_OK, (:? IProvideProjectSite as ps) -> + yield None, p.FileName, (fullOutputAssemblyPath p) |> Option.defaultValue "", ps + | _ -> () + | None, _ -> () + |] + let solutionService = try Some (serviceProvider.GetService(typeof) :?> IVsSolution) with _ -> None + seq { match solutionService with + | Some solutionService -> + for reference in getReferencesForSolutionService solutionService do + yield reference + | None -> () + } + + static let rec referencedProjectsOf(enableInMemoryCrossProjectReferences, tryGetOptionsForReferencedProject, projectSite, serviceProvider, extraProjectInfo, projectOptionsTable, useUniqueStamp) = + [| for (projectId, projectFileName, outputPath, projectSiteProvider) in referencedProvideProjectSites (projectSite, serviceProvider, extraProjectInfo, projectOptionsTable) do + let referencedProjectOptions = + // Lookup may not succeed if the project has not been established yet + // In this case we go and compute the options recursively. + match tryGetOptionsForReferencedProject projectFileName with + | None -> getProjectOptionsForProjectSite (enableInMemoryCrossProjectReferences, tryGetOptionsForReferencedProject, projectSiteProvider.GetProjectSite(), serviceProvider, projectId, projectFileName, extraProjectInfo, projectOptionsTable, useUniqueStamp) |> snd + | Some options -> options + yield projectFileName, (outputPath, referencedProjectOptions) |] + + and getProjectOptionsForProjectSite(enableInMemoryCrossProjectReferences, tryGetOptionsForReferencedProject, projectSite, serviceProvider, projectId, fileName, extraProjectInfo, projectOptionsTable, useUniqueStamp) = + let referencedProjectFileNames, referencedProjectOptions = + if enableInMemoryCrossProjectReferences then + referencedProjectsOf(enableInMemoryCrossProjectReferences, tryGetOptionsForReferencedProject, projectSite, serviceProvider, extraProjectInfo, projectOptionsTable, useUniqueStamp) + |> Array.unzip + else [| |], [| |] + let option = + let newOption () = { + ProjectFileName = projectSite.ProjectFileName + SourceFiles = projectSite.CompilationSourceFiles + OtherOptions = projectSite.CompilationOptions + ReferencedProjects = referencedProjectOptions + IsIncompleteTypeCheckEnvironment = projectSite.IsIncompleteTypeCheckEnvironment + UseScriptResolutionRules = SourceFile.MustBeSingleFileProject fileName + LoadTime = projectSite.LoadTime + UnresolvedReferences = None + OriginalLoadReferences = [] + ExtraProjectInfo=extraProjectInfo + Stamp = if useUniqueStamp then (stamp <- stamp + 1L; Some stamp) else None + } + match projectId, projectOptionsTable with + | Some id, Some optionsTable -> + // Get options from cache + match optionsTable.TryGetOptionsForProject(id) with + | Some (_parsingOptions, _site, projectOptions) -> + if projectSite.CompilationSourceFiles <> projectOptions.SourceFiles || + projectSite.CompilationOptions <> projectOptions.OtherOptions || + referencedProjectOptions <> projectOptions.ReferencedProjects then + newOption() + else + projectOptions + | _ -> newOption() + | _ -> newOption() + referencedProjectFileNames, option + + /// Construct a project site for a single file. May be a single file project (for scripts) or an orphan project site (for everything else). + static member ProjectSiteOfSingleFile(filename:string) : IProjectSite = + if SourceFile.MustBeSingleFileProject(filename) then + Debug.Assert(false, ".fsx or .fsscript should have been treated as implicit project") + failwith ".fsx or .fsscript should have been treated as implicit project" + new ProjectSiteOfSingleFile(filename) :> IProjectSite + + static member GetReferencedProjectSites(projectSite:IProjectSite, serviceProvider:System.IServiceProvider, extraProjectInfo, projectOptions) = + referencedProvideProjectSites (projectSite, serviceProvider, extraProjectInfo, projectOptions) + |> Seq.map (fun (_, _, _, ps) -> ps.GetProjectSite()) + |> Seq.toArray + + /// Create project options for this project site. + static member GetProjectOptionsForProjectSite(enableInMemoryCrossProjectReferences, tryGetOptionsForReferencedProject, projectSite:IProjectSite, serviceProvider, projectId, filename, extraProjectInfo, projectOptionsTable, useUniqueStamp) = + match projectSite with + | :? IHaveCheckOptions as hco -> hco.OriginalCheckOptions() + | _ -> getProjectOptionsForProjectSite(enableInMemoryCrossProjectReferences, tryGetOptionsForReferencedProject, projectSite, serviceProvider, projectId, filename, extraProjectInfo, projectOptionsTable, useUniqueStamp) + + /// Create project site for these project options + static member CreateProjectSiteForScript (filename, referencedProjectFileNames, checkOptions) = + ProjectSiteOfScriptFile (filename, referencedProjectFileNames, checkOptions) :> IProjectSite \ No newline at end of file diff --git a/vsintegration/src/FSharp.ProjectSystem.FSharp/Project.fs b/vsintegration/src/FSharp.ProjectSystem.FSharp/Project.fs index 2dec54ed051..477cc45afb8 100644 --- a/vsintegration/src/FSharp.ProjectSystem.FSharp/Project.fs +++ b/vsintegration/src/FSharp.ProjectSystem.FSharp/Project.fs @@ -1504,11 +1504,7 @@ namespace rec Microsoft.VisualStudio.FSharp.ProjectSystem member __.TargetFrameworkMoniker = targetFrameworkMoniker member __.ProjectGuid = x.GetProjectGuid() member __.LoadTime = creationTime -<<<<<<< HEAD - member __.ProjectProvider = Some (x :> IProvideProjectSite) -======= member __.ProjectProvider = Some (x :> Microsoft.VisualStudio.FSharp.Editor.IProvideProjectSite) ->>>>>>> Whoopsie on that legacy project system } // let the language service ask us questions diff --git a/vsintegration/tests/Salsa/FSharpLanguageServiceTestable.fs b/vsintegration/tests/Salsa/FSharpLanguageServiceTestable.fs index a73f48ef46c..ee82ab0807f 100644 --- a/vsintegration/tests/Salsa/FSharpLanguageServiceTestable.fs +++ b/vsintegration/tests/Salsa/FSharpLanguageServiceTestable.fs @@ -149,9 +149,9 @@ type internal FSharpLanguageServiceTestable() as this = | :? IProvideProjectSite as siteProvider -> let site = siteProvider.GetProjectSite() site.AdviseProjectSiteChanges(LanguageServiceConstants.FSharpLanguageServiceCallbackName, - new Microsoft.VisualStudio.FSharp.LanguageService.AdviseProjectSiteChanges(fun () -> this.OnProjectSettingsChanged(site))) + new AdviseProjectSiteChanges(fun () -> this.OnProjectSettingsChanged(site))) site.AdviseProjectSiteCleaned(LanguageServiceConstants.FSharpLanguageServiceCallbackName, - new Microsoft.VisualStudio.FSharp.LanguageService.AdviseProjectSiteChanges(fun () -> this.OnProjectCleaned(site))) + new AdviseProjectSiteChanges(fun () -> this.OnProjectCleaned(site))) | _ -> // This can happen when the file is in a solution folder or in, say, a C# project. ()