diff --git a/.gitignore b/.gitignore index e4d0670940f..0733a5fdbce 100644 --- a/.gitignore +++ b/.gitignore @@ -78,8 +78,7 @@ tests/*FSharp_Failures.lst tests/fsharpqa/Source/CodeGen/EmittedIL/StaticInit/StaticInit_Module01.dll tests/fsharpqa/Source/CodeGen/EmittedIL/StaticInit/StaticInit_Module01.pdb tests/XFSharpQA_Failures.log.* -vsintegration/src/vs/FsPkgs/FSharp.Project/FS/FSharp.ProjectSystem.FSharp.fsi -vsintegration/src/vs/FsPkgs/FSharp.Project/FS/ctofiles/ +vsintegration/src/FSharp.ProjectSystem.FSharp/ctofiles tests/fsharpqa/Source/CodeGen/EmittedIL/QueryExpressionStepping/Utils.dll tests/fsharpqa/Source/CodeGen/EmittedIL/ComputationExpressions/ComputationExprLibrary.dll tests/fsharpqa/Source/*FSharpQA_Failures.env diff --git a/.nuget/NuGet.Config b/.nuget/NuGet.Config index baef485daec..f2dee4af8f9 100644 --- a/.nuget/NuGet.Config +++ b/.nuget/NuGet.Config @@ -7,6 +7,7 @@ + \ No newline at end of file diff --git a/README.md b/README.md index adfd5dd3412..f9e3eef659e 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,7 @@ For F# 4.0 development - [.NET 3.5](http://www.microsoft.com/en-us/download/details.aspx?id=21) - [.NET 4.5](http://www.microsoft.com/en-us/download/details.aspx?id=30653) - [.NET 4.5.1](http://www.microsoft.com/en-us/download/details.aspx?id=40779) +- [.NET 4.6](https://www.microsoft.com/en-us/download/details.aspx?id=48137) - [MSBuild 12.0](http://www.microsoft.com/en-us/download/details.aspx?id=40760) - [Windows 7 SDK](http://www.microsoft.com/en-us/download/details.aspx?id=8279) - [Windows 8 SDK](http://msdn.microsoft.com/en-us/windows/desktop/hh852363.aspx) diff --git a/packages.config b/packages.config index f311d7fb273..b507dc8c264 100644 --- a/packages.config +++ b/packages.config @@ -4,4 +4,12 @@ + + + + + + + + \ No newline at end of file diff --git a/src/FSharpSource.targets b/src/FSharpSource.targets index d987dd2cb21..709c33924ef 100644 --- a/src/FSharpSource.targets +++ b/src/FSharpSource.targets @@ -112,11 +112,14 @@ 2.0.3 2.0.3.0 $(FSharpSourcesRoot)\..\packages\FsCheck.$(FsCheckVersion)\lib\ + 1.0.30 + 1.1.37 + 1.2.0-beta1-20160218-02 - v3.5 + v3.5 $(DefineConstants);FSHARP_CORE_2_0 $(DefineConstants);RUNTIME $(DefineConstants);FX_ATLEAST_35 @@ -136,7 +139,7 @@ - v4.5 + v4.5 $(DefineConstants);FSHARP_CORE_4_5 $(DefineConstants);FX_ATLEAST_45 $(DefineConstants);FX_ATLEAST_40 @@ -203,7 +206,7 @@ $(DefineConstants);DONT_INCLUDE_DEPRECATED $(DefineConstants);QUERIES_IN_FSLIB Profile47 - v4.0 + v4.0 @@ -239,7 +242,7 @@ $(DefineConstants);FX_EVENTWAITHANDLE_NO_IDISPOSABLE true Profile7 - v4.5 + v4.5 @@ -275,7 +278,7 @@ $(DefineConstants);FX_NO_CONCURRENT_DICTIONARY $(DefineConstants);FX_ATLEAST_LINQ Profile78 - v4.5 + v4.5 @@ -311,12 +314,12 @@ $(DefineConstants);FX_NO_CONCURRENT_DICTIONARY $(DefineConstants);FX_ATLEAST_LINQ Profile259 - v4.5 + v4.5 - v3.0 + v3.0 $(DefineConstants);SILVERLIGHT $(DefineConstants);FX_NO_CANCELLATIONTOKEN_CLASSES $(DefineConstants);FX_NO_EXCEPTIONDISPATCHINFO @@ -396,7 +399,7 @@ $(DefineConstants);PUT_TYPE_PROVIDERS_IN_FSCORE; $(DefineConstants);FX_ATLEAST_LINQ Silverlight - v4.0 + v4.0 v4.0 @@ -439,14 +442,14 @@ $(DefineConstants);FX_ATLEAST_LINQ $(DefineConstants);TARGET_SILVERLIGHT_5_0 Silverlight - v5.0 + v5.0 v5.0 Software\Microsoft\Microsoft SDKs\$(TargetFrameworkIdentifier) $(MSBuildExtensionsPath32)\..\Reference Assemblies\Microsoft\Framework\Silverlight\v5.0 - v4.0 + v4.0 WindowsPhone Silverlight $(DefineConstants);SILVERLIGHT @@ -494,7 +497,7 @@ - v2.0 + v2.0 CompactFramework $(DefineConstants);FX_ATLEAST_COMPACT_FRAMEWORK_20 $(DefineConstants);FX_NO_CANCELLATIONTOKEN_CLASSES @@ -583,7 +586,7 @@ - v3.5 + v3.5 CompactFramework $(DefineConstants);FX_ATLEAST_COMPACT_FRAMEWORK_35 $(DefineConstants);FX_NO_CANCELLATIONTOKEN_CLASSES diff --git a/src/fsharp/FSharp.Compiler/InternalsVisibleTo.fs b/src/fsharp/FSharp.Compiler/InternalsVisibleTo.fs index b3a4f75fc52..bcf9fa96293 100644 --- a/src/fsharp/FSharp.Compiler/InternalsVisibleTo.fs +++ b/src/fsharp/FSharp.Compiler/InternalsVisibleTo.fs @@ -8,6 +8,7 @@ open System.Reflection [] [] [] +[] [] [] [] diff --git a/src/fsharp/FSharp.Data.TypeProviders/FSharp.Data.TypeProviders.fsproj b/src/fsharp/FSharp.Data.TypeProviders/FSharp.Data.TypeProviders.fsproj index ccb67526e5c..810ecac2122 100644 --- a/src/fsharp/FSharp.Data.TypeProviders/FSharp.Data.TypeProviders.fsproj +++ b/src/fsharp/FSharp.Data.TypeProviders/FSharp.Data.TypeProviders.fsproj @@ -11,7 +11,6 @@ Library FSharp.Data.TypeProviders true - v4.0 {cb7d20c4-6506-406d-9144-5342c3595f03} $(OtherFlags) --warnon:1182 diff --git a/src/fsharp/FSharp.LanguageService.Compiler/InternalsVisibleTo.fs b/src/fsharp/FSharp.LanguageService.Compiler/InternalsVisibleTo.fs index 7ecc3a74ff8..f41722d1326 100644 --- a/src/fsharp/FSharp.LanguageService.Compiler/InternalsVisibleTo.fs +++ b/src/fsharp/FSharp.LanguageService.Compiler/InternalsVisibleTo.fs @@ -8,6 +8,7 @@ open System.Reflection [] [] [] +[] [] [] [] diff --git a/vsintegration/deployment/EnableOpenSource/EnableOpenSource.csproj b/vsintegration/deployment/EnableOpenSource/EnableOpenSource.csproj index ac7cee170a2..5249cdb7525 100644 --- a/vsintegration/deployment/EnableOpenSource/EnableOpenSource.csproj +++ b/vsintegration/deployment/EnableOpenSource/EnableOpenSource.csproj @@ -37,7 +37,7 @@ Properties EnableOpenSource EnableOpenSource - v4.5 + v4.6 false false false diff --git a/vsintegration/deployment/EnableOpenSource/source.extension.vsixmanifest b/vsintegration/deployment/EnableOpenSource/source.extension.vsixmanifest index 217604fe881..7f550ceb259 100644 --- a/vsintegration/deployment/EnableOpenSource/source.extension.vsixmanifest +++ b/vsintegration/deployment/EnableOpenSource/source.extension.vsixmanifest @@ -29,5 +29,7 @@ + + \ No newline at end of file diff --git a/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj b/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj index c0a6d9798ca..39544c4d460 100644 --- a/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj +++ b/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj @@ -5,6 +5,7 @@ ..\..\..\src FSharp true + v4.6 @@ -23,10 +24,12 @@ - false + + + @@ -50,6 +53,7 @@ + @@ -64,6 +68,30 @@ + + $(FSharpSourcesRoot)\..\packages\Microsoft.Composition.$(MicrosoftCompositionVersion)\lib\portable-net45+win8+wp8+wpa81\System.Composition.AttributedModel.dll + + + $(FSharpSourcesRoot)\..\packages\System.Collections.Immutable.$(SystemCollectionsImmutableVersion)\lib\dotnet\System.Collections.Immutable.dll + + + $(FSharpSourcesRoot)\..\packages\Microsoft.CodeAnalysis.Common.$(RoslynVersion)\lib\net45\Microsoft.CodeAnalysis.dll + + + $(FSharpSourcesRoot)\..\packages\Microsoft.CodeAnalysis.Workspaces.Common.$(RoslynVersion)\lib\net45\Microsoft.CodeAnalysis.Workspaces.dll + + + $(FSharpSourcesRoot)\..\packages\Microsoft.CodeAnalysis.Features.$(RoslynVersion)\lib\net45\Microsoft.CodeAnalysis.Features.dll + + + $(FSharpSourcesRoot)\..\packages\Microsoft.CodeAnalysis.EditorFeatures.$(RoslynVersion)\lib\net46\Microsoft.CodeAnalysis.EditorFeatures.dll + + + $(FSharpSourcesRoot)\..\packages\Microsoft.CodeAnalysis.EditorFeatures.Text.$(RoslynVersion)\lib\net46\Microsoft.CodeAnalysis.EditorFeatures.Text.dll + + + $(FSharpSourcesRoot)\..\packages\Microsoft.VisualStudio.LanguageServices.$(RoslynVersion)\lib\net46\Microsoft.VisualStudio.LanguageServices.dll + \ No newline at end of file diff --git a/vsintegration/src/FSharp.Editor/FSharpContentType.fs b/vsintegration/src/FSharp.Editor/FSharpContentType.fs new file mode 100644 index 00000000000..849f1587fc9 --- /dev/null +++ b/vsintegration/src/FSharp.Editor/FSharpContentType.fs @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Microsoft.VisualStudio.FSharp.Editor + +open System.ComponentModel.Composition + +open Microsoft.CodeAnalysis.Editor +open Microsoft.VisualStudio.Utilities + +open Microsoft.VisualStudio.FSharp.LanguageService + +module FSharpStaticTypeDefinitions = + [] + [] + [] + let FSharpContentTypeDefinition = ContentTypeDefinition() + +[] +type FSharpContentType [](contentTypeRegistry : IContentTypeRegistryService) = + member this.contentTypeRegistryService = contentTypeRegistry + + interface IContentTypeLanguageService with + member this.GetDefaultContentType() = + this.contentTypeRegistryService.GetContentType(FSharpCommonConstants.FSharpContentTypeName); \ No newline at end of file diff --git a/vsintegration/src/FSharp.Editor/FSharpProjectSiteService.fs b/vsintegration/src/FSharp.Editor/FSharpProjectSiteService.fs new file mode 100644 index 00000000000..acd1e712205 --- /dev/null +++ b/vsintegration/src/FSharp.Editor/FSharpProjectSiteService.fs @@ -0,0 +1,31 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Microsoft.VisualStudio.FSharp.Editor + +open System.Composition + +open Microsoft.CodeAnalysis +open Microsoft.CodeAnalysis.Host +open Microsoft.CodeAnalysis.Host.Mef + +open Microsoft.VisualStudio.FSharp.LanguageService +open Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem + +// Currently the CompilationOptions type in Roslyn is sealed and there's no way to set the compilation options for a project. +// There's no property bag on a project either. So this service is a means to get the host project for a given Roslyn project +// so that extra F# specific information can be stored on the host project. +// Note that the FSharpProject is available only through the VS Workspace although we might call this service from projects of +// some other workspace like the PreviewWorkspace. +type internal IHostProjectService = + inherit IWorkspaceService + + abstract member GetHostProject : id : ProjectId -> FSharpProjectSite + +[, ServiceLayer.Default); Shared>] +type internal FSharpProjectSiteService [] (vsWorkspace : VisualStudioWorkspaceImpl) = + interface IWorkspaceServiceFactory with + member this.CreateService(_) = upcast this + + interface IHostProjectService with + member this.GetHostProject(id:ProjectId) = + downcast vsWorkspace.GetHostProject(id) \ No newline at end of file diff --git a/vsintegration/src/FSharp.Editor/extension.vsixmanifest b/vsintegration/src/FSharp.Editor/extension.vsixmanifest index ec8e9ea37d5..a38c05d0066 100644 --- a/vsintegration/src/FSharp.Editor/extension.vsixmanifest +++ b/vsintegration/src/FSharp.Editor/extension.vsixmanifest @@ -1,24 +1,23 @@ - - - Microsoft Visual FSharp Editor Extensions - Microsoft Corporation - 14.0 - Microsoft Visual FSharp Editor Extensions - 1033 - - - Pro - VWDExpress - WDExpress - - - + + + + Microsoft Visual FSharp Editor Extensions + Microsoft Visual FSharp Editor Extensions + + + + + + true - - - - FSharp.Editor.dll - - \ No newline at end of file + + + + + + + + + \ No newline at end of file diff --git a/vsintegration/src/FSharp.LanguageService/FSharp.LanguageService.fsproj b/vsintegration/src/FSharp.LanguageService/FSharp.LanguageService.fsproj index f0aeeeca0a6..e0b5e612e84 100644 --- a/vsintegration/src/FSharp.LanguageService/FSharp.LanguageService.fsproj +++ b/vsintegration/src/FSharp.LanguageService/FSharp.LanguageService.fsproj @@ -5,6 +5,7 @@ ..\..\..\src FSharp true + v4.6 Debug @@ -25,6 +26,7 @@ + @@ -37,8 +39,8 @@ + - @@ -86,6 +88,21 @@ + + $(FSharpSourcesRoot)\..\packages\System.Collections.Immutable.$(SystemCollectionsImmutableVersion)\lib\dotnet\System.Collections.Immutable.dll + + + $(FSharpSourcesRoot)\..\packages\Microsoft.CodeAnalysis.Common.$(RoslynVersion)\lib\net45\Microsoft.CodeAnalysis.dll + + + $(FSharpSourcesRoot)\..\packages\Microsoft.CodeAnalysis.Workspaces.Common.$(RoslynVersion)\lib\net45\Microsoft.CodeAnalysis.Workspaces.dll + + + $(FSharpSourcesRoot)\..\packages\Microsoft.CodeAnalysis.Features.$(RoslynVersion)\lib\net45\Microsoft.CodeAnalysis.Features.dll + + + $(FSharpSourcesRoot)\..\packages\Microsoft.VisualStudio.LanguageServices.$(RoslynVersion)\lib\net46\Microsoft.VisualStudio.LanguageServices.dll + {DED3BBD7-53F4-428A-8C9F-27968E768605} FSharp.Core diff --git a/vsintegration/src/FSharp.LanguageService/FSharpCommonConstants.fs b/vsintegration/src/FSharp.LanguageService/FSharpCommonConstants.fs new file mode 100644 index 00000000000..b1133e186eb --- /dev/null +++ b/vsintegration/src/FSharp.LanguageService/FSharpCommonConstants.fs @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Microsoft.VisualStudio.FSharp.LanguageService + +open System +open System.Configuration +open System.Diagnostics + +module internal FSharpCommonConstants = + [] + let packageGuidString = "871D2A70-12A2-4e42-9440-425DD92A4116" + [] + let languageServiceGuidString = "BC6DD5A5-D4D6-4dab-A00D-A51242DBAF1B" + [] + let editorFactoryGuidString = "4EB7CCB7-4336-4FFD-B12B-396E9FD079A9" + [] + let codePageEditorFactoryGuidString = "82A16493-EF43-47E0-B42D-D87BAAB5335D" + [] + let svsSettingsPersistenceManagerGuidString = "9B164E40-C3A2-4363-9BC5-EB4039DEF653" + [] + let FSharpLanguageName = "F#" + [] + let FSharpContentTypeName = "F#" + [] + let FSharpLanguageServiceCallbackName = "F# Language Service" diff --git a/vsintegration/src/FSharp.LanguageService/FSharpLanguageService.fs b/vsintegration/src/FSharp.LanguageService/FSharpLanguageService.fs index 54b2110fbd5..ec6522776fc 100644 --- a/vsintegration/src/FSharp.LanguageService/FSharpLanguageService.fs +++ b/vsintegration/src/FSharp.LanguageService/FSharpLanguageService.fs @@ -3,618 +3,120 @@ namespace Microsoft.VisualStudio.FSharp.LanguageService open System -open System.IO open System.Collections.Generic -open System.Configuration -open System.Globalization open System.Runtime.InteropServices -open Microsoft.VisualStudio -open Microsoft.VisualStudio.Shell -open Microsoft.VisualStudio.Shell.Interop -open Microsoft.VisualStudio.TextManager.Interop -open Microsoft.VisualStudio.Text -open Microsoft.VisualStudio.OLE.Interop -open Microsoft.FSharp.Compiler -open Microsoft.FSharp.Compiler.SourceCodeServices - -// This is the list of known owners of callbacks that may register themselves. -// Consumers not in this list should use a GUID our some other strongly unique key string -module internal KnownAdviseProjectSiteChangesCallbackOwners = - let LanguageService = "F# Language Service" - -module internal FSharpCommonConstants = - [] - let languageServiceGuidString = "BC6DD5A5-D4D6-4dab-A00D-A51242DBAF1B" - - -module internal FSharpConstants = - let fsharpLanguageName = "F#" - - // These are the IDs from fslangservice.dll - let packageGuidString = "871D2A70-12A2-4e42-9440-425DD92A4116" - [] - let languageServiceGuidString = FSharpCommonConstants.languageServiceGuidString - - // These are the IDs from the Python sample: - let intellisenseProviderGuidString = "8b1807ea-d222-4765-afa8-c092d480e451" - - // These are the entries from fslangservice.dll - let PLKMinEdition = "standard" - let PLKCompanyName = "Microsoft" // "Microsoft Corporation" - let PLKProductName = "f#" // "Visual Studio Integration of FSharp Language Service" - let PLKProductVersion = "1.0" - let PLKResourceID = 1s - let enableLanguageService = "fsharp-language-service-enabled" +open Microsoft.FSharp.Compiler.SourceCodeServices - -/// This class defines capabilities of the language service. -/// CodeSense = true\false, for example -type internal FSharpLanguagePreferences(site, langSvc, name) = - inherit LanguagePreferences(site, langSvc, name) - -// Container class that delays loading of FSharp.Compiler.dll compiler types until they're actually needed -type internal FSharpCheckerContainer(checker) = - member this.FSharpChecker = checker - -/// LanguageService state. -type internal FSharpLanguageServiceTestable() as this = - static let colorizerGuid = new Guid("{A2976312-7D71-4BB4-A5F8-66A08EBF46C8}") // Guid for colorizwed user data on IVsTextBuffer - let mutable checkerContainerOpt : FSharpCheckerContainer option = None - let mutable artifacts : ProjectSitesAndFiles option = None - let mutable serviceProvider : System.IServiceProvider option = None - let mutable preferences : LanguagePreferences option = None - let mutable documentationBuilder : IDocumentationBuilder option = None - let mutable sourceFactory : (IVsTextLines -> IFSharpSource) option = None - let mutable dirtyForTypeCheckFiles : Set = Set.empty - let mutable isInitialized = false - let mutable unhooked = false - let getColorizer (view:IVsTextView) = - let buffer = Com.ThrowOnFailure1(view.GetBuffer()) - this.GetColorizer(buffer) - - let bgRequests = new FSharpLanguageServiceBackgroundRequests(getColorizer,(fun () -> this.FSharpChecker),(fun () -> this.ProjectSitesAndFiles),(fun () -> this.ServiceProvider),(fun () -> this.DocumentationBuilder)) - - member this.FSharpChecker = - if this.Unhooked then raise Error.UseOfUnhookedLanguageServiceState - if not this.IsInitialized then raise Error.UseOfUninitializedLanguageServiceState - checkerContainerOpt.Value.FSharpChecker - - member this.ServiceProvider = - if this.Unhooked then raise Error.UseOfUnhookedLanguageServiceState - if not this.IsInitialized then raise Error.UseOfUninitializedLanguageServiceState - serviceProvider.Value +open Microsoft.CodeAnalysis +open Microsoft.VisualStudio +open Microsoft.VisualStudio.LanguageServices +open Microsoft.VisualStudio.LanguageServices.Implementation.LanguageService +open Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem +open Microsoft.VisualStudio.LanguageServices.Implementation.DebuggerIntelliSense +open Microsoft.VisualStudio.LanguageServices.Implementation +open Microsoft.VisualStudio.Shell +open Microsoft.VisualStudio.Shell.Interop - member this.ProjectSitesAndFiles = - if not this.IsInitialized then raise Error.UseOfUninitializedLanguageServiceState - artifacts.Value - - member this.Preferences = - if this.Unhooked then raise Error.UseOfUnhookedLanguageServiceState - if not this.IsInitialized then raise Error.UseOfUninitializedLanguageServiceState - preferences.Value - - member this.SourceFactory = - if this.Unhooked then raise Error.UseOfUnhookedLanguageServiceState - if not this.IsInitialized then raise Error.UseOfUninitializedLanguageServiceState - sourceFactory.Value - - member this.IsInitialized = isInitialized - member this.Unhooked = unhooked - member this.DocumentationBuilder = documentationBuilder.Value - - /// Handle late intialization pieces - member this.Initialize (sp, dp, prefs, sourceFact) = - if this.Unhooked then raise Error.UseOfUnhookedLanguageServiceState - artifacts <- Some (ProjectSitesAndFiles()) - let checker = FSharpChecker.Create() - checker.BeforeBackgroundFileCheck.Add (fun filename -> UIThread.Run(fun () -> this.NotifyFileTypeCheckStateIsDirty(filename))) - checkerContainerOpt <- Some (FSharpCheckerContainer checker) - serviceProvider <- Some sp - isInitialized <- true - unhooked <- false - documentationBuilder <- Some dp - preferences <- Some prefs - sourceFactory <- Some sourceFact +// Workaround to access non-public settings persistence type. +// GetService( ) with this will work as long as the GUID matches the real type. +[] +type internal SVsSettingsPersistenceManager = class end - - member this.NotifyFileTypeCheckStateIsDirty(filename) = - dirtyForTypeCheckFiles <- dirtyForTypeCheckFiles.Add filename +[] +type internal FSharpLanguageService(package : FSharpPackage) = + inherit AbstractLanguageService(package) - /// Clear all language service caches and finalize all transient references to compiler objects - member this.ClearLanguageServiceRootCachesAndCollectAndFinalizeAllTransients() = - if this.Unhooked then raise Error.UseOfUnhookedLanguageServiceState - if this.IsInitialized then - this.FSharpChecker.ClearLanguageServiceRootCachesAndCollectAndFinalizeAllTransients() + override this.ContentTypeName = FSharpCommonConstants.FSharpContentTypeName + override this.LanguageName = FSharpCommonConstants.FSharpLanguageName + override this.RoslynLanguageName = FSharpCommonConstants.FSharpLanguageName - /// Unhook the object. These are the held resources that need to be disposed. - member this.Unhook() = - if this.Unhooked then raise Error.UseOfUnhookedLanguageServiceState - if this.IsInitialized then - // Dispose the preferences. - if this.Preferences <> null then this.Preferences.Dispose() - // Stop the background compile. - // here we refer to checkerContainerOpt directly to avoid triggering its creation - match checkerContainerOpt with - | Some container -> - let checker = container.FSharpChecker - checker.StopBackgroundCompile() - checker.ClearLanguageServiceRootCachesAndCollectAndFinalizeAllTransients() - | None -> () - - checkerContainerOpt <- None - artifacts <- None - preferences <- None - documentationBuilder <- None - unhooked <- true - sourceFactory <- None - serviceProvider <- None - - /// Respond to project settings changes - member this.OnProjectSettingsChanged(site:IProjectSite) = - // The project may have changed its references. These would be represented as 'dependency files' of each source file. Each source file will eventually start listening - // for changes to those dependencies, at which point we'll get OnDependencyFileCreateOrDelete notifications. Until then, though, we just 'make a note' that this project is out of date. - bgRequests.AddOutOfDateProjectFileName(site.ProjectFileName()) - for filename in site.SourceFilesOnDisk() do - let rdt = this.ServiceProvider.RunningDocumentTable - match this.ProjectSitesAndFiles.TryGetSourceOfFile(rdt,filename) with - | Some source -> - source.RecolorizeWholeFile() - source.RecordChangeToView() - | None -> () + override this.LanguageServiceId = new Guid(FSharpCommonConstants.languageServiceGuidString) + override this.DebuggerLanguageId = DebuggerEnvironment.GetLanguageID() - /// Respond to project being cleaned/rebuilt (any live type providers in the project should be refreshed) - member this.OnProjectCleaned(projectSite:IProjectSite) = - let checkOptions = ProjectSitesAndFiles.GetProjectOptionsForProjectSite(projectSite, "") - this.FSharpChecker.NotifyProjectCleaned(checkOptions) + override this.CreateContext(_,_,_,_,_) = raise(System.NotImplementedException()) - member this.OnActiveViewChanged(textView) = - bgRequests.OnActiveViewChanged(textView) + override this.SetupNewTextView(view) = + base.SetupNewTextView(view) + let workspace = this.Package.ComponentModel.GetService(); + let sp = new ServiceProvider(this.SystemServiceProvider.GetService()) - member this.BackgroundRequests = bgRequests - - /// Unittestable complement to LanguageServce.CreateSource - member this.CreateSource(buffer:IVsTextLines) : IFSharpSource = - - // Each time a source is created, also verify that the IProjectSite has been initialized to listen to changes to the project. - // We can't listen to OnProjectLoaded because the language service is not guaranteed to be loaded when this is called. + // Ensure that we have a project in the workspace for this document. + let (_, buffer) = view.GetBuffer() let filename = VsTextLines.GetFilename buffer - let rdt = this.ServiceProvider.RunningDocumentTable - let result = VsRunningDocumentTable.FindDocumentWithoutLocking(rdt,filename) - match result with - | Some(hier,_) -> - match hier with + let result = VsRunningDocumentTable.FindDocumentWithoutLocking(sp.RunningDocumentTable,filename) + match result with + | Some (hier, _) -> + match hier with | :? IProvideProjectSite as siteProvider -> let site = siteProvider.GetProjectSite() - site.AdviseProjectSiteChanges(KnownAdviseProjectSiteChangesCallbackOwners.LanguageService, - new AdviseProjectSiteChanges(fun () -> this.OnProjectSettingsChanged(site))) - site.AdviseProjectSiteCleaned(KnownAdviseProjectSiteChangesCallbackOwners.LanguageService, - new AdviseProjectSiteChanges(fun () -> this.OnProjectCleaned(site))) - | _ -> - // This can happen when the file is in a solution folder or in, say, a C# project. - () - | _ -> - // This can happen when renaming a file from a different language service into .fs or fsx. - // This naturally won't have an associated project. - () - - // Create the source and register file change callbacks there. - let source = this.SourceFactory(buffer) - this.ProjectSitesAndFiles.SetSource(buffer, source) - source + let projectFileName = site.ProjectFileName() + let projectId = workspace.ProjectTracker.GetOrCreateProjectIdForPath(projectFileName, projectFileName) + if obj.ReferenceEquals(workspace.ProjectTracker.GetProject(projectId), null) then + let projectSite = new FSharpProjectSite(hier, this.SystemServiceProvider, workspace, projectFileName); + projectSite.Initialize(hier, site) + | _ -> () + | _ -> () + +and [] + internal FSharpEditorFactory(package : FSharpPackage) = + inherit AbstractEditorFactory(package) + + override this.ContentTypeName = FSharpCommonConstants.FSharpContentTypeName + override this.GetFormattedTextChanges(_, _, _, _) = upcast Array.empty - // For each change in dependency files, notify the language service of the change and propagate the update - interface IDependencyFileChangeNotify with - member this.DependencyFileCreated projectSite = - // Invalidate the configuration if we notice any add for any DependencyFiles - let checkOptions = ProjectSitesAndFiles.GetProjectOptionsForProjectSite(projectSite, "") - this.FSharpChecker.InvalidateConfiguration(checkOptions) - - member this.DependencyFileChanged (filename) = - this.NotifyFileTypeCheckStateIsDirty filename - - - /// Do OnIdle processing for the whole language service. dirtyForTypeCheckFiles can be set by events - /// raised on the background compilation thread. - member this.OnIdle() = - for file in dirtyForTypeCheckFiles do - let rdt = this.ServiceProvider.RunningDocumentTable - match this.ProjectSitesAndFiles.TryGetSourceOfFile(rdt, file) with - | Some source -> source.RecordChangeToView() - | None -> () - dirtyForTypeCheckFiles <- Set.empty - - - /// Remove a colorizer. - member this.CloseColorizer(colorizer:FSharpColorizer) = - let buffer = colorizer.Buffer - let mutable guid = colorizerGuid - (buffer :?> IVsUserData).SetData(&guid, null) |> ErrorHandler.ThrowOnFailure |> ignore +and [] + internal FSharpCodePageEditorFactory(editorFactory: FSharpEditorFactory) = + inherit AbstractCodePageEditorFactory(editorFactory) - /// Get a colorizer for a particular buffer. - member this.GetColorizer(buffer:IVsTextLines) : FSharpColorizer = - let mutable guid = colorizerGuid - let mutable colorizerObj = null - - (buffer :?> IVsUserData).GetData(&guid, &colorizerObj) |> ignore - match colorizerObj with - | null -> - let scanner = - new FSharpScanner(fun source -> - // Note: in theory, the next few lines do not need to be recomputed every line. Instead we could just cache the tokenizer - // and only update it when e.g. the project system notifies us there is an important change (e.g. a file rename, etc). - // In practice we have been there, and always screwed up some non-unit-tested/testable corner-cases. - // So this is not ideal from a perf perspective, but it is easy to reason about the correctness. - let filename = VsTextLines.GetFilename buffer - let rdt = this.ServiceProvider.RunningDocumentTable - let defines = this.ProjectSitesAndFiles.GetDefinesForFile(rdt, filename) - let sourceTokenizer = FSharpSourceTokenizer(defines,filename) - sourceTokenizer.CreateLineTokenizer(source)) - - let colorizer = new FSharpColorizer(this.CloseColorizer, buffer, scanner) - (buffer :?> IVsUserData).SetData(&guid, colorizer) |> ErrorHandler.ThrowOnFailure |> ignore - colorizer - | _ -> colorizerObj :?> FSharpColorizer +and [] + internal FSharpPackage() = + inherit AbstractPackage() - /// Block until the background compile finishes. - // - // This is for unit testing only - member this.WaitForBackgroundCompile() = - this.FSharpChecker.WaitForBackgroundCompile() + override this.RoslynLanguageName = FSharpCommonConstants.FSharpLanguageName -module internal VsConstants = - let guidStdEditor = new Guid("9ADF33D0-8AAD-11D0-B606-00A0C922E851") - let guidCodeCloneProvider = new Guid("38fd587e-d4b7-4030-9a95-806ff0d5c2c6") + override this.Initialize() = + base.Initialize() + this.EstablishDefaultSettingsIfMissing() - let cmdidGotoDecl = 936u // "Go To Declaration" - let cmdidGotoRef = 1107u // "Go To Reference" + override this.CreateWorkspace() = this.ComponentModel.GetService() - let IDM_VS_EDITOR_CSCD_OUTLINING_MENU = 773u // "Outlining" - let ECMD_OUTLN_HIDE_SELECTION = 128u // "Hide Selection" - - let ECMD_OUTLN_TOGGLE_CURRENT = 129u // "Toggle Outlining Expansion" - - let ECMD_OUTLN_TOGGLE_ALL = 130u // "Toggle All Outlining" - let ECMD_OUTLN_STOP_HIDING_ALL = 131u // "Stop Outlining" - let ECMD_OUTLN_STOP_HIDING_CURRENT = 132u // "Stop Hiding Current" - -type private QueryStatusResult = - | NOTSUPPORTED = 0 - | SUPPORTED = 1 - | ENABLED = 2 - | LATCHED = 4 - | NINCHED = 8 - | INVISIBLE = 16 - -type internal FSharpViewFilter(mgr:CodeWindowManager,view:IVsTextView) = - inherit ViewFilter(mgr,view) - - override this.Dispose() = base.Dispose() - - member this.IsSupportedCommand(guidCmdGroup:byref,cmd:uint32) = - if guidCmdGroup = VsMenus.guidStandardCommandSet97 && (cmd = VsConstants.cmdidGotoDecl || cmd = VsConstants.cmdidGotoRef) then false - elif guidCmdGroup = VsConstants.guidCodeCloneProvider then false // disable commands for CodeClone package - else - // These are all the menu options in the "Outlining" cascading menu. We need to disable all the individual - // items to disable the cascading menu. QueryCommandStatus does not get called for the cascading menu itself. - assert((guidCmdGroup = VsConstants.guidStdEditor && cmd = VsConstants.IDM_VS_EDITOR_CSCD_OUTLINING_MENU) = false) - if guidCmdGroup = VsMenus.guidStandardCommandSet2K && (cmd = VsConstants.ECMD_OUTLN_HIDE_SELECTION || - cmd = VsConstants.ECMD_OUTLN_TOGGLE_CURRENT || - cmd = VsConstants.ECMD_OUTLN_TOGGLE_ALL || - cmd = VsConstants.ECMD_OUTLN_STOP_HIDING_ALL || - cmd = VsConstants.ECMD_OUTLN_STOP_HIDING_CURRENT) then false - else true - - override this.QueryCommandStatus(guidCmdGroup:byref,cmd:uint32) = - if this.IsSupportedCommand(&guidCmdGroup,cmd) then - base.QueryCommandStatus(&guidCmdGroup,cmd) - else - // Hide the menu item. Just returning QueryStatusResult.NOTSUPPORTED does not work - QueryStatusResult.INVISIBLE ||| QueryStatusResult.SUPPORTED |> int + override this.CreateLanguageService() = new FSharpLanguageService(this) + override this.CreateEditorFactories() = + // Disabling editor factories until fully implemented -[] -type internal FSharpLanguageService() as fls = - inherit LanguageService() - - let ls = FSharpLanguageServiceTestable() - let mutable rdtCookie = VSConstants.VSCOOKIE_NIL - - let thisAssembly = typeof.Assembly - let resources = lazy (new System.Resources.ResourceManager("VSPackage", thisAssembly)) - let GetString(name:string) = resources.Force().GetString(name, CultureInfo.CurrentUICulture) + // let editorFactory = new FSharpEditorFactory(this) + // let codePageEditorFactory = new FSharpCodePageEditorFactory(editorFactory) - let navigation = FSharpNavigationController() + [| + // editorFactory :> IVsEditorFactory; + // codePageEditorFactory :> IVsEditorFactory; + |] :> seq - let formatFilterList = lazy( - let fsFile = GetString("FSharpFile") - let fsInterfaceFile = GetString("FSharpInterfaceFile") - let fsxFile = GetString("FSXFile") - let fsScriptFile = GetString("FSharpScriptFile") - let result = sprintf "%s|*.fs\n%s|*.fsi\n%s|*.fsx\n%s|*.fsscript" - fsFile fsInterfaceFile fsxFile fsScriptFile - result) + override this.RegisterMiscellaneousFilesWorkspaceInformation(_) = () - // This array contains the definition of the colorable items provided by this - // language service. - let colorableItems = [| - // See e.g. the TokenColor type defined in Scanner.cs. Position 0 is implicit and always means "Plain Text". - // The next 5 items in this list MUST be these default items in this order: - new FSharpColorableItem("Keyword", lazy (GetString("Keyword")), COLORINDEX.CI_BLUE, COLORINDEX.CI_USERTEXT_BK) - new FSharpColorableItem("Comment", lazy (GetString("Comment")), COLORINDEX.CI_DARKGREEN, COLORINDEX.CI_USERTEXT_BK) - new FSharpColorableItem("Identifier", lazy (GetString("Identifier")), COLORINDEX.CI_USERTEXT_FG, COLORINDEX.CI_USERTEXT_BK) - new FSharpColorableItem("String", lazy (GetString("String")), COLORINDEX.CI_MAROON, COLORINDEX.CI_USERTEXT_BK) - new FSharpColorableItem("Number", lazy (GetString("Number")), COLORINDEX.CI_USERTEXT_FG, COLORINDEX.CI_USERTEXT_BK) - // User-defined color classes go here: - new FSharpColorableItem("Excluded Code", lazy (GetString("ExcludedCode")), COLORINDEX.CI_DARKGRAY, COLORINDEX.CI_USERTEXT_BK) - new FSharpColorableItem("Preprocessor Keyword", lazy (GetString("PreprocessorKeyword")), COLORINDEX.CI_BLUE, COLORINDEX.CI_USERTEXT_BK) - new FSharpColorableItem("Operator", lazy (GetString("Operator")), COLORINDEX.CI_USERTEXT_FG, COLORINDEX.CI_USERTEXT_BK) - |] - - let sourceFactory(buffer:IVsTextLines) = - let vsFileWatch = fls.GetService(typeof) :?> IVsFileChangeEx - Source.CreateSource(fls, buffer, fls.GetColorizer(buffer), vsFileWatch, ls :> IDependencyFileChangeNotify, (fun () -> ls.FSharpChecker)) - - let refreshUI() = - let uiShell = fls.Site.GetService() - if (uiShell <> null) then - uiShell.UpdateCommandUI(0) |> ignore - - - /// Initialize the language service - override fls.Initialize() = - if not ls.IsInitialized then - let preferences = new FSharpLanguagePreferences(fls.Site, (typeof).GUID, FSharpConstants.fsharpLanguageName) - preferences.Init() // Reads preferences from the VS registry. - preferences.MaxErrorMessages <- 1000 - - let serviceProvider = fls.Site - let docBuilder = XmlDocumentation.CreateDocumentationBuilder(serviceProvider.XmlService, serviceProvider.DTE) - - ls.Initialize(serviceProvider, docBuilder, preferences, sourceFactory) - - rdtCookie <- (Com.ThrowOnFailure1 (serviceProvider.RunningDocumentTable.AdviseRunningDocTableEvents (fls:>IVsRunningDocTableEvents))) - - - override fls.Dispose() = - try - try - if rdtCookie <> VSConstants.VSCOOKIE_NIL then - let rdt = fls.Site.RunningDocumentTable - Com.ThrowOnFailure0 (rdt.UnadviseRunningDocTableEvents rdtCookie) - finally - if ls.IsInitialized then - ls.Unhook() - finally - base.Dispose() - - member fls.Core = ls - - // ----------------------------------------------------------------------- - // Implement IVsLanguageInfo - - interface IVsLanguageInfo with - /// Allows a language to add adornments to a code editor. - member this.GetCodeWindowManager(codeWindow, mgr) = - fls.Initialize(); - let mutable buffer = null; - NativeMethods.ThrowOnFailure(codeWindow.GetBuffer(&buffer)) |> ignore; - // see if we already have a Source object. - let source = - match this.GetSource(buffer) with - | null -> - let s = ls.CreateSource(buffer) :?> ISource - this.sources.Add(s) |> ignore; - s - | s -> s - - mgr <- this.CreateCodeWindowManager(codeWindow, source); - NativeMethods.S_OK; - - /// Returns the colorizer. - member this.GetColorizer(buffer, result) = - result <- this.GetColorizer(buffer) - NativeMethods.S_OK - - /// Returns the file extensions belonging to this language. - member this.GetFileExtensions(extensions) = - extensions <- "" - NativeMethods.S_OK - - member this.GetLanguageName(name) = - name <- FSharpConstants.fsharpLanguageName - NativeMethods.S_OK - - // ----------------------------------------------------------------------- - // Implement LanguageService base methods from the C# code - - override fls.OnActiveViewChanged(textView) = - base.OnActiveViewChanged(textView) - ls.OnActiveViewChanged(textView) - if navigation.EnableNavBar || navigation.EnableRegions then - match fls.GetSource(textView) with - | null -> () - | source -> ls.BackgroundRequests.TriggerParseFile(textView, source) |> ignore - - - // Provides the index to the filter matching the extension of the file passed in. - override fls.CurFileExtensionFormat(filename) = - // These indexes match the "GetFormatFilterList" function - match Path.GetExtension(filename) with - | ".fs" -> 0 - | ".ml" -> 1 - | ".fsi" -> 2 - | ".mli" -> 3 - | ".fsx" -> 4 - | ".fsscript" -> 5 - | _ -> -1 - - /// Provides the list of available extensions for Save As. - /// The following default filter string is automatically added by Visual Studio: - /// "All Files (*.*)\n*.*\nText Files (*.txt)\n*.txt\n" - override fls.GetFormatFilterList() = - formatFilterList.Value - - // This seems to be called by codeWinMan.OnNewView(textView) to install a ViewFilter on the TextView. - override this.CreateViewFilter(mgr:CodeWindowManager,newView:IVsTextView) = new FSharpViewFilter(mgr,newView) :> ViewFilter - - override fls.GetLanguagePreferences() = ls.Preferences - - override fls.CreateBackgroundRequest(line, col, info, sourceText, snapshot, methodTipMiscellany, fname, reason, view, sink, source, timestamp, synchronous) = - ls.BackgroundRequests.CreateBackgroundRequest(line, col, info, sourceText, snapshot, methodTipMiscellany, fname,reason, view,sink, source, timestamp, synchronous) :> BackgroundRequest - - /// Handle an incoming request to analyze a file. - /// - /// Executed either on the UI thread (for req.IsSynchronous) or the background request thread. - override fls.ExecuteBackgroundRequest(req:BackgroundRequest) = - ls.BackgroundRequests.ExecuteBackgroundRequest(req :?> FSharpBackgroundRequest, req.Source :?> IFSharpSource) - - // Check if we can shortcut executing the background request and just fill in the latest - // cached scope for the active view from this.service.RecentFullTypeCheckResults. - // - // THIS MUST ONLY RETURN TRUE IF ---> ExecuteBackgroundRequest is equivalent to fetching a recent, - // perhaps out-of-date scope. - override fls.IsRecentScopeSufficientForBackgroundRequest(reason) = - ls.BackgroundRequests.IsRecentScopeSufficientForBackgroundRequest(reason) - - // This is called on the UI thread after fresh full typecheck results are available - override fls.OnParseFileOrCheckFileComplete( req:BackgroundRequest) = - if (req <> null && req.Source <> null && not req.Source.IsClosed) then - fls.SetUserContextDirty(req.FileName) - refreshUI() - ls.BackgroundRequests.OnParseFileOrCheckFileComplete(req, navigation.EnableRegions, fls.LastActiveTextView) - - override fls.GetColorizer(buffer) = - fls.Initialize() - ls.GetColorizer(buffer) :> Colorizer - - - override fls.CreateDropDownHelper(_view) = - if navigation.EnableNavBar then - (new FSharpNavigationBars(fls, fun () -> ls.BackgroundRequests.NavigationBarAndRegionInfo)) :> TypeAndMemberDropdownBars - else null - - /// Do OnIdle processing - override fls.OnIdle(periodic, mgr : IOleComponentManager) = - try - let r = base.OnIdle(periodic, mgr) - ls.OnIdle() - r - with e-> - Assert.Exception(e) - reraise() - - // ----------------------------------------------------------------------- - // Implement IVsLanguageDebugInfo - - /// This is used to return the expression evaluator language to the debugger - interface IVsLanguageDebugInfo with - // Returns the corresponding debugger back-end "language ID". - member fls.GetLanguageID(_buffer,_line, _col, langId) = - langId <- DebuggerEnvironment.GetLanguageID() - VSConstants.S_OK - - // Deprecated. Do not use. - member fls.GetLocationOfName(_name, pbstrMkDoc, _spans) = - pbstrMkDoc <- null - NativeMethods.E_NOTIMPL - - // Generates a name for the given location in the file. - member fls.GetNameOfLocation(_buffer, _line, _col, name, lineOffset) = - name <- null; - lineOffset <- 0; - NativeMethods.S_OK; - - // Generates proximity expressions, used to populate the "Autos" window - member fls.GetProximityExpressions(_buffer, _line, _col, _cLines, ppEnum) = - ppEnum <- null - NativeMethods.S_FALSE - - // Returns whether the location contains code that is mapped to another document, for example, client-side script code. - member fls.IsMappedLocation(_buffer, _line, _col) = - NativeMethods.S_FALSE - - // Disambiguates the given name, providing non-ambiguous names for all entities that "match" the name. - member fls.ResolveName(_name, _flags, ppNames) = - ppNames <- null - NativeMethods.E_NOTIMPL - - // Validates the given position as a place to set a breakpoint. - member fls.ValidateBreakpointLocation(buffer:IVsTextBuffer, line, col, pCodeSpan:TextSpan[]) = - let result = - if (pCodeSpan <> null) && (pCodeSpan.Length > 0) && (buffer :? IVsTextLines) then - let syncOk = - let view = fls.LastActiveTextView - view <> null && - let source = fls.GetSource(view) - source <> null && - ls.BackgroundRequests.TrySynchronizeParseFileInformation(view, source, millisecondsTimeout = 100) - let lineText = VsTextLines.LineText (buffer :?> IVsTextLines) line - let firstNonWhitespace = lineText.Length - (lineText.TrimStart [| ' '; '\t' |]).Length - let lastNonWhitespace = (lineText.TrimEnd [| ' '; '\t' |]).Length - // If the column is before the range of text then zap it to the text - // If the column is after the range of text then zap it to the _start_ of the text (like C#) - let attempt1, haveScope = - let col = if col > lastNonWhitespace || col < firstNonWhitespace then firstNonWhitespace else col - match ls.BackgroundRequests.ParseFileResults with - | Some parseResults -> - match parseResults.ValidateBreakpointLocation(Range.Pos.fromZ line col) with - | Some bpl -> Some (TextSpanOfRange bpl), true - | None -> None, true - | None -> - None, false - match attempt1 with - | Some r -> Some r - | None -> - if syncOk || haveScope then - None - else - // If we didn't sync OK AND we don't even have an ParseFileResults then just accept the whole line. - // This is unfortunate but necessary. - Some (TextSpan(iStartLine = line, iStartIndex = firstNonWhitespace, iEndLine = line, iEndIndex = lastNonWhitespace)) - else - None - - match result with - | Some span -> - pCodeSpan.[0] <- span - VSConstants.S_OK - | None -> - VSConstants.S_FALSE - - // ----------------------------------------------------------------------- - // Implement IVsProvideColorableItems - - interface IVsProvideColorableItems with - - member x.GetItemCount(count: int byref) = - count <- colorableItems.Length - VSConstants.S_OK - - member x.GetColorableItem(index, item: IVsColorableItem byref) = - if (index < 1) || (index > colorableItems.Length) then - VSConstants.E_INVALIDARG - else - item <- colorableItems.[index - 1] - VSConstants.S_OK - - /// Respond to changes to documents in the Running Document Table. - interface IVsRunningDocTableEvents with - member this.OnAfterAttributeChange(_docCookie, _grfAttribs) = VSConstants.S_OK - member this.OnAfterDocumentWindowHide(_docCookie, _frame) = VSConstants.S_OK - member this.OnAfterFirstDocumentLock(_docCookie,_dwRDTLockType,_dwReadLocks,_dwEditLocks) = VSConstants.S_OK - member this.OnAfterSave(_docCookie) = VSConstants.S_OK - member this.OnBeforeDocumentWindowShow(_docCookie,_isFirstShow,_frame) = VSConstants.S_OK - member this.OnBeforeLastDocumentUnlock(docCookie,_dwRDTLockType,dwReadLocksRemaining,dwEditLocksRemaining) = - let rdt = this.Site.RunningDocumentTable - let (_, _, _, _, file, _, _, unkdoc) = rdt.GetDocumentInfo docCookie // see here http://msdn.microsoft.com/en-us/library/microsoft.visualstudio.shell.interop.ivsrunningdocumenttable.getdocumentinfo(VS.80).aspx for info on the `GetDocumentInfo` results we're ignoring - try - if int dwReadLocksRemaining = 0 && int dwEditLocksRemaining = 0 then // check that this is there are no other read / edit locks - if SourceFile.IsCompilable file then - if IntPtr.Zero<>unkdoc then - match Marshal.GetObjectForIUnknown(unkdoc) with - | :? IVsTextLines as tl -> - ls.ProjectSitesAndFiles.UnsetSource(tl) - | _ -> () - finally - if IntPtr.Zero <> unkdoc then Marshal.Release(unkdoc)|>ignore - VSConstants.S_OK - + /// ISettingsManager only implemented for VS 14.0+ + /// In case custom VS profile settings for F# are not applied, explicitly set them here. + /// e.g. 'keep tabs' is the text editor default, but F# requires 'insert spaces'. + /// We specify our customizations in the General profile for VS, but we have found that in some cases, + /// those customizations are incorrectly ignored. So we take action if the setting has no current custom value. + member private this.EstablishDefaultSettingsIfMissing() = + #if !VS_VERSION_DEV12 + + let fsharpSpecificProfileSettings = [| + "TextEditor.F#.Insert Tabs", box false + "TextEditor.F#.Brace Completion", box true + "TextEditor.F#.Make URLs Hot", box false + "TextEditor.F#.Indent Style", box 1u |] + + match this.GetService(typeof) with + | :? Microsoft.VisualStudio.Settings.ISettingsManager as settingsManager -> + for settingName,defaultValue in fsharpSpecificProfileSettings do + match settingsManager.TryGetValue(settingName) with + | Microsoft.VisualStudio.Settings.GetValueResult.Missing, _ -> + settingsManager.SetValueAsync(settingName, defaultValue, false) |> ignore + | _ -> () + | _ -> () + + #endif \ No newline at end of file diff --git a/vsintegration/src/FSharp.LanguageService/FSharpPackage.fs b/vsintegration/src/FSharp.LanguageService/FSharpPackage.fs deleted file mode 100644 index fb61e0e854a..00000000000 --- a/vsintegration/src/FSharp.LanguageService/FSharpPackage.fs +++ /dev/null @@ -1,150 +0,0 @@ -// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -namespace Microsoft.VisualStudio.FSharp.LanguageService - -open System -open System.Configuration -open System.ComponentModel.Design -open System.Runtime.InteropServices -open Microsoft.VisualStudio -open Microsoft.VisualStudio.Shell -open Microsoft.VisualStudio.OLE.Interop - - -// Workaround to access non-public settings persistence type. -// GetService( ) with this will work as long as the GUID matches the real type. -[] -type internal SVsSettingsPersistenceManager = class end - -[] -type internal FSharpPackage() as self = - inherit Package() - - // In case the config file is incorrect, we silently recover and leave the feature enabled - let enableLanguageService = - try - "false" <> ConfigurationManager.AppSettings.[FSharpConstants.enableLanguageService] - with e -> - System.Diagnostics.Debug.Assert - (false, sprintf "Error while loading 'devenv.exe.config' configuration: %A" e) - true - - let mutable componentID = 0u - - let CreateIfEnabled container serviceType = - if enableLanguageService then - self.CreateService(container,serviceType) - else - null - - let callback = new ServiceCreatorCallback(CreateIfEnabled) - - let mutable mgr : IOleComponentManager = null - -#if !VS_VERSION_DEV12 - let fsharpSpecificProfileSettings = - [| "TextEditor.F#.Insert Tabs", box false - "TextEditor.F#.Brace Completion", box true - "TextEditor.F#.Make URLs Hot", box false - "TextEditor.F#.Indent Style", box 1u |] -#endif - - override self.Initialize() = - UIThread.CaptureSynchronizationContext() - self.EstablishDefaultSettingsIfMissing() - (self :> IServiceContainer).AddService(typeof, callback, true) - base.Initialize() - - /// In case custom VS profile settings for F# are not applied, explicitly set them here. - /// e.g. 'keep tabs' is the text editor default, but F# requires 'insert spaces'. - /// We specify our customizations in the General profile for VS, but we have found that in some cases - /// those customizations are incorrectly ignored. - member private this.EstablishDefaultSettingsIfMissing() = -#if VS_VERSION_DEV12 - () // ISettingsManager only implemented for VS 14.0+ -#else - match this.GetService(typeof) with - | :? Microsoft.VisualStudio.Settings.ISettingsManager as settingsManager -> - for settingName,defaultValue in fsharpSpecificProfileSettings do - // Only take action if the setting has no current custom value - // If cloud-synced settings have already been applied or the user has made an explicit change, do nothing - match settingsManager.TryGetValue(settingName) with - | Microsoft.VisualStudio.Settings.GetValueResult.Missing, _ -> - settingsManager.SetValueAsync(settingName, defaultValue, false) |> ignore - | _ -> () - | _ -> () -#endif - - member self.RegisterForIdleTime() = - mgr <- (self.GetService(typeof) :?> IOleComponentManager) - if (componentID = 0u && mgr <> null) then - let crinfo = Array.zeroCreate(1) - let mutable crinfo0 = crinfo.[0] - crinfo0.cbSize <- Marshal.SizeOf(typeof) |> uint32 - crinfo0.grfcrf <- uint32 (_OLECRF.olecrfNeedIdleTime ||| _OLECRF.olecrfNeedPeriodicIdleTime) - crinfo0.grfcadvf <- uint32 (_OLECADVF.olecadvfModal ||| _OLECADVF.olecadvfRedrawOff ||| _OLECADVF.olecadvfWarningsOff) - crinfo0.uIdleTimeInterval <- 1000u - crinfo.[0] <- crinfo0 - let componentID_out = ref componentID - let _hr = mgr.FRegisterComponent(self, crinfo, componentID_out) - componentID <- componentID_out.Value - () - - member self.CreateService(_container:IServiceContainer, serviceType:Type) = - match serviceType with - | x when x = typeof -> - let language = new FSharpLanguageService() - language.SetSite(self) - language.Initialize() - self.RegisterForIdleTime() - box language - | _ -> null - - override self.Dispose(disposing) = - try - if (componentID <> 0u) then - begin match self.GetService(typeof) with - | :? IOleComponentManager as mgr -> - mgr.FRevokeComponent(componentID) |> ignore - | _ -> () - end - componentID <- 0u - finally - base.Dispose(disposing) - - interface IOleComponent with - - override x.FContinueMessageLoop(_uReason:uint32, _pvLoopData:IntPtr, _pMsgPeeked:MSG[]) = - 1 - - override x.FDoIdle(grfidlef:uint32) = - // see e.g "C:\Program Files\Microsoft Visual Studio 2008 SDK\VisualStudioIntegration\Common\IDL\olecm.idl" for details - //Trace.Print("CurrentDirectoryDebug", (fun () -> sprintf "curdir='%s'\n" (System.IO.Directory.GetCurrentDirectory()))) // can be useful for watching how GetCurrentDirectory changes - match x.GetService(typeof) with - | :? FSharpLanguageService as pl -> - let periodic = (grfidlef &&& (uint32 _OLEIDLEF.oleidlefPeriodic)) <> 0u - let mutable r = pl.OnIdle(periodic, mgr) - if r = 0 && periodic && mgr.FContinueIdle() <> 0 then - r <- TaskReporterIdleRegistration.DoIdle(mgr) - r - | _ -> 0 - - override x.FPreTranslateMessage(_pMsg) = 0 - - override x.FQueryTerminate(_fPromptUser) = 1 - - override x.FReserved1(_dwReserved, _message, _wParam, _lParam) = 1 - - override x.HwndGetWindow(_dwWhich, _dwReserved) = 0n - - override x.OnActivationChange(_pic, _fSameComponent, _pcrinfo, _fHostIsActivating, _pchostinfo, _dwReserved) = () - - override x.OnAppActivate(_fActive, _dwOtherThreadID) = () - - override x.OnEnterState(_uStateID, _fEnter) = () - - override x.OnLoseActivation() = () - - override x.Terminate() = () - - diff --git a/vsintegration/src/FSharp.LanguageService/FSharpProjectSite.fs b/vsintegration/src/FSharp.LanguageService/FSharpProjectSite.fs new file mode 100644 index 00000000000..ff4c87ee762 --- /dev/null +++ b/vsintegration/src/FSharp.LanguageService/FSharpProjectSite.fs @@ -0,0 +1,83 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Microsoft.VisualStudio.FSharp.LanguageService + +open Microsoft.VisualStudio +open Microsoft.VisualStudio.TextManager.Interop +open Microsoft.VisualStudio.Shell +open Microsoft.VisualStudio.Shell.Interop +open Microsoft.VisualStudio.FSharp.LanguageService +open Microsoft.FSharp.Compiler.SourceCodeServices +open Microsoft.FSharp.Compiler.AbstractIL.Diagnostics +open Internal.Utilities.Collections +open Internal.Utilities.Debug +open System +open System.IO +open System.Diagnostics +open System.Collections.Generic +open Microsoft.CodeAnalysis +open Microsoft.VisualStudio.LanguageServices.Implementation +open Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem + +type internal FSharpProjectSite(hierarchy: IVsHierarchy, serviceProvider: System.IServiceProvider, visualStudioWorkspace: VisualStudioWorkspaceImpl, projectName: string) = + inherit AbstractProject(visualStudioWorkspace.ProjectTracker, null, projectName, hierarchy, "F#", serviceProvider, null, visualStudioWorkspace, null) + + let mutable checkOptions : FSharpProjectOptions option = None + + member internal this.CheckOptions = + match checkOptions with + | Some options -> options + | None -> failwith "Options haven't been computed yet." + + member internal this.Initialize(hier: IVsHierarchy, site : IProjectSite) = + this.ProjectTracker.AddProject(this) + + site.AdviseProjectSiteChanges(FSharpCommonConstants.FSharpLanguageServiceCallbackName, + new AdviseProjectSiteChanges(fun () -> this.OnProjectSettingsChanged(hier, site))) + + site.AdviseProjectSiteClosed(FSharpCommonConstants.FSharpLanguageServiceCallbackName, + new AdviseProjectSiteChanges(fun () -> this.Disconnect())) + + // Add files and references + for file in site.SourceFilesOnDisk() do this.AddDocument(hier, file) + for ref in this.GetReferences(site.CompilerFlags()) do this.AddReference(ref) + + // Capture the F# specific options that we'll pass to the type checker. + checkOptions <- Some(ProjectSitesAndFiles.GetProjectOptionsForProjectSite(site, site.ProjectFileName())) + + member this.GetReferences(flags : string[]) = + flags |> Array.choose(fun flag -> if flag.StartsWith("-r:") then Some(flag.Substring(3)) else None) + + member this.AddReference(filePath : string) = + this.AddMetadataReferenceAndTryConvertingToProjectReferenceIfPossible(filePath, new MetadataReferenceProperties(), VSConstants.S_FALSE) |> ignore + + member this.RemoveReference(filePath: string) = + this.RemoveMetadataReference(filePath) + + member internal this.AddDocument(hier: IVsHierarchy, file : string) = + let itemid = + match hier.ParseCanonicalName(file) with + | (VSConstants.S_OK, id) -> id + | _ -> uint32 VSConstants.VSITEMID.Nil + + let document = this.ProjectTracker.DocumentProvider.TryGetDocumentForFile(this, itemid, file, SourceCodeKind.Regular, fun x -> true) + this.AddDocument(document, true) + + member internal this.OnProjectSettingsChanged(hier: IVsHierarchy, site : IProjectSite) = + let sourceFiles = site.SourceFilesOnDisk() + + // Added files + for file in sourceFiles do if not(this.ContainsFile(file)) then this.AddDocument(hier, file) + // Removed files + let removedDocuments = this.GetCurrentDocuments() |> Seq.where(fun doc -> not(sourceFiles |> Seq.contains(doc.FilePath))) |> Seq.toList + for doc in removedDocuments do this.RemoveDocument(doc) + + let references = this.GetReferences(site.CompilerFlags()) + + // Added references + for ref in references do if not(this.HasMetadataReference(ref)) then this.AddReference(ref) + // Removed references + for ref in this.GetCurrentMetadataReferences() do if not(references |> Seq.contains(ref.FilePath)) then this.RemoveReference(ref.FilePath) + + // If the order of files changed, that'll be captured in the checkOptions. + checkOptions <- Some(ProjectSitesAndFiles.GetProjectOptionsForProjectSite(site, site.ProjectFileName())) \ No newline at end of file diff --git a/vsintegration/src/FSharp.LanguageService/IProjectSite.fs b/vsintegration/src/FSharp.LanguageService/IProjectSite.fs index d7b6e498b83..e0aff674664 100644 --- a/vsintegration/src/FSharp.LanguageService/IProjectSite.fs +++ b/vsintegration/src/FSharp.LanguageService/IProjectSite.fs @@ -21,6 +21,9 @@ type internal IProjectSite = /// 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 DescriptionOfProject : unit -> string diff --git a/vsintegration/src/FSharp.LanguageService/ProjectSitesAndFiles.fs b/vsintegration/src/FSharp.LanguageService/ProjectSitesAndFiles.fs index cbd0f3232d0..2a6b34872f3 100644 --- a/vsintegration/src/FSharp.LanguageService/ProjectSitesAndFiles.fs +++ b/vsintegration/src/FSharp.LanguageService/ProjectSitesAndFiles.fs @@ -32,6 +32,7 @@ type private ProjectSiteOfScriptFile(filename:string, checkOptions : FSharpProje override this.ErrorListTaskReporter() = None override this.AdviseProjectSiteChanges(_,_) = () override this.AdviseProjectSiteCleaned(_,_) = () + override this.AdviseProjectSiteClosed(_,_) = () override this.IsIncompleteTypeCheckEnvironment = checkOptions.IsIncompleteTypeCheckEnvironment override this.TargetFrameworkMoniker = "" override this.LoadTime = checkOptions.LoadTime @@ -62,6 +63,7 @@ type private ProjectSiteOfSingleFile(sourceFile) = override this.ErrorListTaskReporter() = None override this.AdviseProjectSiteChanges(_,_) = () override this.AdviseProjectSiteCleaned(_,_) = () + override this.AdviseProjectSiteClosed(_,_) = () override this.IsIncompleteTypeCheckEnvironment = true override this.TargetFrameworkMoniker = "" override this.LoadTime = new DateTime(2000,1,1) // any constant time is fine, orphan files do not interact with reloading based on update time diff --git a/vsintegration/src/FSharp.ProjectSystem.Base/Project/ProjectSystem.Base.csproj b/vsintegration/src/FSharp.ProjectSystem.Base/Project/ProjectSystem.Base.csproj index b6f732e1499..3e43b652632 100644 --- a/vsintegration/src/FSharp.ProjectSystem.Base/Project/ProjectSystem.Base.csproj +++ b/vsintegration/src/FSharp.ProjectSystem.Base/Project/ProjectSystem.Base.csproj @@ -17,7 +17,7 @@ {B700E38B-F8C0-4E49-B5EC-DB7B7AC0C4E7} $(DefineConstants);CODE_ANALYSIS;STAMP_OSS_VERSION true - v4.5 + v4.6 $(DefineConstants);FSHARP_CORE_4_5 $(DefineConstants);FX_ATLEAST_45 $(DefineConstants);FX_ATLEAST_40 @@ -26,7 +26,8 @@ $(DefineConstants);QUERIES_IN_FSLIB $(DefineConstants);PUT_TYPE_PROVIDERS_IN_FSCORE; $(DefineConstants);FX_ATLEAST_LINQ - + + @@ -88,7 +89,6 @@ - @@ -101,7 +101,6 @@ - @@ -119,7 +118,7 @@ - + @@ -192,6 +191,10 @@ {DED3BBD7-53F4-428A-8C9F-27968E768605} FSharp.Core + + {ee85aab7-cda0-4c4e-bda0-a64ccc413e3f} + FSharp.LanguageService + \ 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 378d82dc50f..b403d93d457 100644 --- a/vsintegration/src/FSharp.ProjectSystem.FSharp/Project.fs +++ b/vsintegration/src/FSharp.ProjectSystem.FSharp/Project.fs @@ -105,6 +105,7 @@ namespace Microsoft.VisualStudio.FSharp.ProjectSystem member ips.ProjectFileName() = inner.ProjectFileName() member ips.AdviseProjectSiteChanges(callbackOwnerKey,callback) = inner.AdviseProjectSiteChanges(callbackOwnerKey, callback) member ips.AdviseProjectSiteCleaned(callbackOwnerKey,callback) = inner.AdviseProjectSiteCleaned(callbackOwnerKey, callback) + member ips.AdviseProjectSiteClosed(callbackOwnerKey,callback) = inner.AdviseProjectSiteClosed(callbackOwnerKey, callback) member ips.ErrorListTaskProvider() = inner.ErrorListTaskProvider() member ips.ErrorListTaskReporter() = inner.ErrorListTaskReporter() member ips.TargetFrameworkMoniker = inner.TargetFrameworkMoniker @@ -472,6 +473,7 @@ See also ...\SetupAuthoring\FSharp\Registry\FSProjSys_Registration.wxs, e.g. let sourcesAndFlagsNotifier = new Notifier() let cleanNotifier = new Notifier() + let closeNotifier = new Notifier() [] static val mutable private imageOffset : int @@ -648,6 +650,7 @@ See also ...\SetupAuthoring\FSharp\Registry\FSProjSys_Registration.wxs, e.g. | _ -> () vsProject <- null accessor <- null + closeNotifier.Notify() base.Close() override x.Load(filename:string, location:string, name:string, flags:uint32, iidProject:byref, canceled:byref ) = @@ -1611,6 +1614,8 @@ See also ...\SetupAuthoring\FSharp\Registry\FSProjSys_Registration.wxs, e.g. sourcesAndFlagsNotifier.Advise(callbackOwnerKey,callback) member this.AdviseProjectSiteCleaned(callbackOwnerKey,callback) = cleanNotifier.Advise(callbackOwnerKey,callback) + member this.AdviseProjectSiteClosed(callbackOwnerKey,callback) = + closeNotifier.Advise(callbackOwnerKey,callback) member this.IsIncompleteTypeCheckEnvironment = false member this.TargetFrameworkMoniker = x.GetTargetFrameworkMoniker() member this.LoadTime = creationTime @@ -1640,6 +1645,7 @@ See also ...\SetupAuthoring\FSharp\Registry\FSProjSys_Registration.wxs, e.g. member ips.ErrorListTaskReporter() = taskReporter member this.AdviseProjectSiteChanges(_,_) = () member this.AdviseProjectSiteCleaned(_,_) = () + member this.AdviseProjectSiteClosed(_,_) = () member this.IsIncompleteTypeCheckEnvironment = false member this.TargetFrameworkMoniker = targetFrameworkMoniker member this.LoadTime = creationTime diff --git a/vsintegration/src/FSharp.ProjectSystem.FSharp/ProjectSystem.fsproj b/vsintegration/src/FSharp.ProjectSystem.FSharp/ProjectSystem.fsproj index 45d7cbe1691..e882258a34e 100644 --- a/vsintegration/src/FSharp.ProjectSystem.FSharp/ProjectSystem.fsproj +++ b/vsintegration/src/FSharp.ProjectSystem.FSharp/ProjectSystem.fsproj @@ -7,6 +7,7 @@ true $(MSBuildExtensionsPath)\Microsoft\VisualStudio\v$(VisualStudioVersion)\VSSDK 14.0 + v4.6 Debug diff --git a/vsintegration/src/FSharp.ProjectSystem.PropertyPages/DesignFramework/DesignerMessageBox.vb b/vsintegration/src/FSharp.ProjectSystem.PropertyPages/DesignFramework/DesignerMessageBox.vb index a0e67740fbc..9465b046c15 100644 --- a/vsintegration/src/FSharp.ProjectSystem.PropertyPages/DesignFramework/DesignerMessageBox.vb +++ b/vsintegration/src/FSharp.ProjectSystem.PropertyPages/DesignFramework/DesignerMessageBox.vb @@ -76,7 +76,7 @@ Namespace Microsoft.VisualStudio.Editors.DesignerFramework End If 'Pull out the original exception from target invocation exceptions (happen during serialization, etc.) - If TypeOf ex Is Reflection.TargetInvocationException Then + If TypeOf ex Is System.Reflection.TargetInvocationException Then ex = ex.InnerException End If diff --git a/vsintegration/src/FSharp.ProjectSystem.PropertyPages/FSharp.PropertiesPages.vbproj b/vsintegration/src/FSharp.ProjectSystem.PropertyPages/FSharp.PropertiesPages.vbproj index c691d52d833..e5f3d3f5254 100644 --- a/vsintegration/src/FSharp.ProjectSystem.PropertyPages/FSharp.PropertiesPages.vbproj +++ b/vsintegration/src/FSharp.ProjectSystem.PropertyPages/FSharp.PropertiesPages.vbproj @@ -28,7 +28,7 @@ 40026;42105;42107;42353 true - v4.5 + v4.6 $(DefineConstants),FSHARP_CORE_4_5=True $(DefineConstants),FX_ATLEAST_45=True $(DefineConstants),FX_ATLEAST_40=True diff --git a/vsintegration/src/FSharp.ProjectSystem.PropertyPages/PropertyPages/BuildPropPage.vb b/vsintegration/src/FSharp.ProjectSystem.PropertyPages/PropertyPages/BuildPropPage.vb index 51efaa35c58..160fefc8a6e 100644 --- a/vsintegration/src/FSharp.ProjectSystem.PropertyPages/PropertyPages/BuildPropPage.vb +++ b/vsintegration/src/FSharp.ProjectSystem.PropertyPages/PropertyPages/BuildPropPage.vb @@ -596,7 +596,7 @@ Namespace Microsoft.VisualStudio.Editors.PropertyPages Catch ex As InvalidCastException '// When all else fails assume dll (so they can edit it) OutputType = VSLangProj.prjOutputType.prjOutputTypeLibrary - Catch ex As Reflection.TargetInvocationException + Catch ex As System.Reflection.TargetInvocationException ' Property must be missing for this project flavor OutputType = VSLangProj.prjOutputType.prjOutputTypeLibrary End Try diff --git a/vsintegration/tests/Salsa/FSharpLanguageServiceTestable.fs b/vsintegration/tests/Salsa/FSharpLanguageServiceTestable.fs new file mode 100644 index 00000000000..5d6fe745bac --- /dev/null +++ b/vsintegration/tests/Salsa/FSharpLanguageServiceTestable.fs @@ -0,0 +1,223 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Salsa + +open System +open System.IO +open System.Collections.Generic +open System.Configuration +open System.Globalization +open System.Runtime.InteropServices +open Microsoft.VisualStudio +open Microsoft.VisualStudio.Shell +open Microsoft.VisualStudio.Shell.Interop +open Microsoft.VisualStudio.TextManager.Interop +open Microsoft.VisualStudio.Text +open Microsoft.VisualStudio.OLE.Interop +open Microsoft.FSharp.Compiler +open Microsoft.FSharp.Compiler.SourceCodeServices +open Microsoft.VisualStudio.FSharp.LanguageService + +type internal FSharpLanguageServiceTestable() as this = + static let colorizerGuid = new Guid("{A2976312-7D71-4BB4-A5F8-66A08EBF46C8}") // Guid for colorized user data on IVsTextBuffer + let mutable checkerContainerOpt : FSharpChecker option = None + let mutable artifacts : ProjectSitesAndFiles option = None + let mutable serviceProvider : System.IServiceProvider option = None + let mutable preferences : LanguagePreferences option = None + let mutable documentationBuilder : IDocumentationBuilder option = None + let mutable sourceFactory : (IVsTextLines -> IFSharpSource) option = None + let mutable dirtyForTypeCheckFiles : Set = Set.empty + let mutable isInitialized = false + let mutable unhooked = false + let getColorizer (view:IVsTextView) = + let buffer = Com.ThrowOnFailure1(view.GetBuffer()) + this.GetColorizer(buffer) + + let bgRequests = new FSharpLanguageServiceBackgroundRequests(getColorizer,(fun () -> this.FSharpChecker),(fun () -> this.ProjectSitesAndFiles),(fun () -> this.ServiceProvider),(fun () -> this.DocumentationBuilder)) + + member this.FSharpChecker = + if this.Unhooked then raise Error.UseOfUnhookedLanguageServiceState + if not this.IsInitialized then raise Error.UseOfUninitializedLanguageServiceState + checkerContainerOpt.Value + + member this.ServiceProvider = + if this.Unhooked then raise Error.UseOfUnhookedLanguageServiceState + if not this.IsInitialized then raise Error.UseOfUninitializedLanguageServiceState + serviceProvider.Value + + member this.ProjectSitesAndFiles = + if not this.IsInitialized then raise Error.UseOfUninitializedLanguageServiceState + artifacts.Value + + member this.Preferences = + if this.Unhooked then raise Error.UseOfUnhookedLanguageServiceState + if not this.IsInitialized then raise Error.UseOfUninitializedLanguageServiceState + preferences.Value + + member this.SourceFactory = + if this.Unhooked then raise Error.UseOfUnhookedLanguageServiceState + if not this.IsInitialized then raise Error.UseOfUninitializedLanguageServiceState + sourceFactory.Value + + member this.IsInitialized = isInitialized + member this.Unhooked = unhooked + member this.DocumentationBuilder = documentationBuilder.Value + + /// Handle late intialization pieces + member this.Initialize (sp, dp, prefs, sourceFact) = + if this.Unhooked then raise Error.UseOfUnhookedLanguageServiceState + artifacts <- Some (ProjectSitesAndFiles()) + let checker = FSharpChecker.Create() + checker.BeforeBackgroundFileCheck.Add (fun filename -> UIThread.Run(fun () -> this.NotifyFileTypeCheckStateIsDirty(filename))) + checkerContainerOpt <- Some (checker) + serviceProvider <- Some sp + isInitialized <- true + unhooked <- false + documentationBuilder <- Some dp + preferences <- Some prefs + sourceFactory <- Some sourceFact + + + member this.NotifyFileTypeCheckStateIsDirty(filename) = + dirtyForTypeCheckFiles <- dirtyForTypeCheckFiles.Add filename + + /// Clear all language service caches and finalize all transient references to compiler objects + member this.ClearLanguageServiceRootCachesAndCollectAndFinalizeAllTransients() = + if this.Unhooked then raise Error.UseOfUnhookedLanguageServiceState + if this.IsInitialized then + this.FSharpChecker.ClearLanguageServiceRootCachesAndCollectAndFinalizeAllTransients() + + /// Unhook the object. These are the held resources that need to be disposed. + member this.Unhook() = + if this.Unhooked then raise Error.UseOfUnhookedLanguageServiceState + if this.IsInitialized then + // Dispose the preferences. + if this.Preferences <> null then this.Preferences.Dispose() + // Stop the background compile. + // here we refer to checkerContainerOpt directly to avoid triggering its creation + match checkerContainerOpt with + | Some container -> + let checker = container + checker.StopBackgroundCompile() + checker.ClearLanguageServiceRootCachesAndCollectAndFinalizeAllTransients() + | None -> () + + checkerContainerOpt <- None + artifacts <- None + preferences <- None + documentationBuilder <- None + unhooked <- true + sourceFactory <- None + serviceProvider <- None + + /// Respond to project settings changes + member this.OnProjectSettingsChanged(site:IProjectSite) = + // The project may have changed its references. These would be represented as 'dependency files' of each source file. Each source file will eventually start listening + // for changes to those dependencies, at which point we'll get OnDependencyFileCreateOrDelete notifications. Until then, though, we just 'make a note' that this project is out of date. + bgRequests.AddOutOfDateProjectFileName(site.ProjectFileName()) + for filename in site.SourceFilesOnDisk() do + let rdt = this.ServiceProvider.RunningDocumentTable + match this.ProjectSitesAndFiles.TryGetSourceOfFile(rdt,filename) with + | Some source -> + source.RecolorizeWholeFile() + source.RecordChangeToView() + | None -> () + + /// Respond to project being cleaned/rebuilt (any live type providers in the project should be refreshed) + member this.OnProjectCleaned(projectSite:IProjectSite) = + let checkOptions = ProjectSitesAndFiles.GetProjectOptionsForProjectSite(projectSite, "") + this.FSharpChecker.NotifyProjectCleaned(checkOptions) + + member this.OnActiveViewChanged(textView) = + bgRequests.OnActiveViewChanged(textView) + + member this.BackgroundRequests = bgRequests + + /// Unittestable complement to LanguageServce.CreateSource + member this.CreateSource(buffer:IVsTextLines) : IFSharpSource = + + // Each time a source is created, also verify that the IProjectSite has been initialized to listen to changes to the project. + // We can't listen to OnProjectLoaded because the language service is not guaranteed to be loaded when this is called. + let filename = VsTextLines.GetFilename buffer + let rdt = this.ServiceProvider.RunningDocumentTable + let result = VsRunningDocumentTable.FindDocumentWithoutLocking(rdt,filename) + match result with + | Some(hier,_) -> + match hier with + | :? IProvideProjectSite as siteProvider -> + let site = siteProvider.GetProjectSite() + site.AdviseProjectSiteChanges(FSharpCommonConstants.FSharpLanguageServiceCallbackName, + new AdviseProjectSiteChanges(fun () -> this.OnProjectSettingsChanged(site))) + site.AdviseProjectSiteCleaned(FSharpCommonConstants.FSharpLanguageServiceCallbackName, + new AdviseProjectSiteChanges(fun () -> this.OnProjectCleaned(site))) + | _ -> + // This can happen when the file is in a solution folder or in, say, a C# project. + () + | _ -> + // This can happen when renaming a file from a different language service into .fs or fsx. + // This naturally won't have an associated project. + () + + // Create the source and register file change callbacks there. + let source = this.SourceFactory(buffer) + this.ProjectSitesAndFiles.SetSource(buffer, source) + source + + // For each change in dependency files, notify the language service of the change and propagate the update + interface IDependencyFileChangeNotify with + member this.DependencyFileCreated projectSite = + // Invalidate the configuration if we notice any add for any DependencyFiles + let checkOptions = ProjectSitesAndFiles.GetProjectOptionsForProjectSite(projectSite, "") + this.FSharpChecker.InvalidateConfiguration(checkOptions) + + member this.DependencyFileChanged (filename) = + this.NotifyFileTypeCheckStateIsDirty filename + + + /// Do OnIdle processing for the whole language service. dirtyForTypeCheckFiles can be set by events + /// raised on the background compilation thread. + member this.OnIdle() = + for file in dirtyForTypeCheckFiles do + let rdt = this.ServiceProvider.RunningDocumentTable + match this.ProjectSitesAndFiles.TryGetSourceOfFile(rdt, file) with + | Some source -> source.RecordChangeToView() + | None -> () + dirtyForTypeCheckFiles <- Set.empty + + + /// Remove a colorizer. + member this.CloseColorizer(colorizer:FSharpColorizer) = + let buffer = colorizer.Buffer + let mutable guid = colorizerGuid + (buffer :?> IVsUserData).SetData(&guid, null) |> ErrorHandler.ThrowOnFailure |> ignore + + /// Get a colorizer for a particular buffer. + member this.GetColorizer(buffer:IVsTextLines) : FSharpColorizer = + let mutable guid = colorizerGuid + let mutable colorizerObj = null + + (buffer :?> IVsUserData).GetData(&guid, &colorizerObj) |> ignore + match colorizerObj with + | null -> + let scanner = + new FSharpScanner(fun source -> + // Note: in theory, the next few lines do not need to be recomputed every line. Instead we could just cache the tokenizer + // and only update it when e.g. the project system notifies us there is an important change (e.g. a file rename, etc). + // In practice we have been there, and always screwed up some non-unit-tested/testable corner-cases. + // So this is not ideal from a perf perspective, but it is easy to reason about the correctness. + let filename = VsTextLines.GetFilename buffer + let rdt = this.ServiceProvider.RunningDocumentTable + let defines = this.ProjectSitesAndFiles.GetDefinesForFile(rdt, filename) + let sourceTokenizer = FSharpSourceTokenizer(defines,filename) + sourceTokenizer.CreateLineTokenizer(source)) + + let colorizer = new FSharpColorizer(this.CloseColorizer, buffer, scanner) + (buffer :?> IVsUserData).SetData(&guid, colorizer) |> ErrorHandler.ThrowOnFailure |> ignore + colorizer + | _ -> colorizerObj :?> FSharpColorizer + + /// Block until the background compile finishes. + // + // This is for unit testing only + member this.WaitForBackgroundCompile() = + this.FSharpChecker.WaitForBackgroundCompile() \ No newline at end of file diff --git a/vsintegration/tests/Salsa/VisualFSharp.Salsa.fsproj b/vsintegration/tests/Salsa/VisualFSharp.Salsa.fsproj index b77bc4791fd..32e441c2b39 100644 --- a/vsintegration/tests/Salsa/VisualFSharp.Salsa.fsproj +++ b/vsintegration/tests/Salsa/VisualFSharp.Salsa.fsproj @@ -6,6 +6,7 @@ FSharp true VisualFSharp.Salsa + v4.6 @@ -26,6 +27,7 @@ UnitTests.TestLib.Utils.fs + @@ -40,6 +42,7 @@ + diff --git a/vsintegration/tests/Salsa/salsa.fs b/vsintegration/tests/Salsa/salsa.fs index 1a50ad15b4d..32923a5e777 100644 --- a/vsintegration/tests/Salsa/salsa.fs +++ b/vsintegration/tests/Salsa/salsa.fs @@ -312,6 +312,7 @@ module internal Salsa = member this.ErrorListTaskReporter() = None member this.AdviseProjectSiteChanges(callbackOwnerKey,callback) = changeHandlers.[callbackOwnerKey] <- callback member this.AdviseProjectSiteCleaned(callbackOwnerKey,callback) = () // no unit testing support here + member this.AdviseProjectSiteClosed(callbackOwnerKey,callback) = () // no unit testing support here member this.IsIncompleteTypeCheckEnvironment = false member this.TargetFrameworkMoniker = "" member this.LoadTime = System.DateTime(2000,1,1) diff --git a/vsintegration/tests/unittests/VisualFSharp.Unittests.fsproj b/vsintegration/tests/unittests/VisualFSharp.Unittests.fsproj index 1b7609ba006..38fcc5afe42 100644 --- a/vsintegration/tests/unittests/VisualFSharp.Unittests.fsproj +++ b/vsintegration/tests/unittests/VisualFSharp.Unittests.fsproj @@ -6,6 +6,7 @@ FSharp true VisualFSharp.Unittests + v4.6