diff --git a/vsintegration/src/FSharp.Editor/Classification/ClassificationService.fs b/vsintegration/src/FSharp.Editor/Classification/ClassificationService.fs index a3844ae749e..bb16634ff28 100644 --- a/vsintegration/src/FSharp.Editor/Classification/ClassificationService.fs +++ b/vsintegration/src/FSharp.Editor/Classification/ClassificationService.fs @@ -28,7 +28,7 @@ open FSharp.Compiler.Tokenization #nowarn "57" -type SemanticClassificationData = SemanticClassificationView option +type SemanticClassificationData = SemanticClassificationView type SemanticClassificationLookup = IReadOnlyDictionary> [] @@ -68,10 +68,7 @@ type DocumentCache<'Value when 'Value : not struct>() = type internal FSharpClassificationService [] ( - checkerProvider: FSharpCheckerProvider, - projectInfoManager: FSharpProjectOptionsManager ) = - static let userOpName = "SemanticColorization" static let getLexicalClassifications(filePath: string, defines, text: SourceText, textSpan: TextSpan, ct) = let text = text.GetSubText(textSpan) @@ -117,23 +114,20 @@ type internal FSharpClassificationService | true, items -> addSemanticClassification sourceText targetSpan items outputResult | _ -> () - static let toSemanticClassificationLookup (data: SemanticClassificationData) = + static let toSemanticClassificationLookup (d: SemanticClassificationData) = let lookup = System.Collections.Generic.Dictionary>() - match data with - | None -> () - | Some d -> - let f (dataItem: SemanticClassificationItem) = - let items = - match lookup.TryGetValue dataItem.Range.StartLine with - | true, items -> items - | _ -> - let items = ResizeArray() - lookup.[dataItem.Range.StartLine] <- items - items - - items.Add dataItem - - d.ForEach(f) + let f (dataItem: SemanticClassificationItem) = + let items = + match lookup.TryGetValue dataItem.Range.StartLine with + | true, items -> items + | _ -> + let items = ResizeArray() + lookup.[dataItem.Range.StartLine] <- items + items + + items.Add dataItem + + d.ForEach(f) System.Collections.ObjectModel.ReadOnlyDictionary lookup :> IReadOnlyDictionary<_, _> @@ -147,8 +141,8 @@ type internal FSharpClassificationService async { use _logBlock = Logger.LogBlock(LogEditorFunctionId.Classification_Syntactic) - let defines = projectInfoManager.GetCompilationDefinesForEditingDocument(document) - let! sourceText = document.GetTextAsync(cancellationToken) |> Async.AwaitTask + let defines = document.GetFSharpQuickDefines() + let! sourceText = document.GetTextAsync(cancellationToken) |> Async.AwaitTask // For closed documents, only get classification for the text within the span. // This may be inaccurate for multi-line tokens such as string literals, but this is ok for now @@ -157,29 +151,29 @@ type internal FSharpClassificationService result.AddRange(getLexicalClassifications(document.FilePath, defines, sourceText, textSpan, cancellationToken)) else result.AddRange(Tokenizer.getClassifiedSpans(document.Id, sourceText, textSpan, Some(document.FilePath), defines, cancellationToken)) - } |> RoslynHelpers.StartAsyncUnitAsTask cancellationToken + } + |> RoslynHelpers.StartAsyncUnitAsTask cancellationToken - member _.AddSemanticClassificationsAsync(document: Document, textSpan: TextSpan, result: List, cancellationToken: CancellationToken) = - asyncMaybe { + member this.AddSemanticClassificationsAsync(document: Document, textSpan: TextSpan, result: List, cancellationToken: CancellationToken) = + async { use _logBlock = Logger.LogBlock(LogEditorFunctionId.Classification_Semantic) - let! _, _, projectOptions = projectInfoManager.TryGetOptionsForDocumentOrProject(document, cancellationToken, userOpName) - let! sourceText = document.GetTextAsync(cancellationToken) + let! sourceText = document.GetTextAsync(cancellationToken) |> Async.AwaitTask // If we are trying to get semantic classification for a document that is not open, get the results from the background and cache it. // We do this for find all references when it is populating results. // We cache it temporarily so we do not have to continously call into the checker and perform a background operation. if not (document.Project.Solution.Workspace.IsDocumentOpen document.Id) then - match! semanticClassificationCache.TryGetValueAsync document |> liftAsync with + match! semanticClassificationCache.TryGetValueAsync document with | ValueSome classificationDataLookup -> addSemanticClassificationByLookup sourceText textSpan classificationDataLookup result | _ -> - let! classificationData = checkerProvider.Checker.GetBackgroundSemanticClassificationForFile(document.FilePath, projectOptions, userOpName=userOpName) |> liftAsync + let! classificationData = document.GetFSharpSemanticClassificationAsync(nameof(FSharpClassificationService)) let classificationDataLookup = toSemanticClassificationLookup classificationData - do! semanticClassificationCache.SetAsync(document, classificationDataLookup) |> liftAsync + do! semanticClassificationCache.SetAsync(document, classificationDataLookup) addSemanticClassificationByLookup sourceText textSpan classificationDataLookup result else - let! _, _, checkResults = checkerProvider.Checker.ParseAndCheckDocument(document, projectOptions, allowStaleResults = false, userOpName=userOpName) + let! _, checkResults = document.GetFSharpParseAndCheckResultsAsync(nameof(IFSharpClassificationService)) let targetRange = RoslynHelpers.TextSpanToFSharpRange(document.FilePath, textSpan, sourceText) let classificationData = checkResults.GetSemanticClassification (Some targetRange) addSemanticClassification sourceText textSpan classificationData result diff --git a/vsintegration/src/FSharp.Editor/CodeFix/AddMissingFunKeyword.fs b/vsintegration/src/FSharp.Editor/CodeFix/AddMissingFunKeyword.fs index f188e25b9eb..14149d507f1 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/AddMissingFunKeyword.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/AddMissingFunKeyword.fs @@ -15,12 +15,9 @@ open FSharp.Compiler.CodeAnalysis type internal FSharpAddMissingFunKeywordCodeFixProvider [] ( - projectInfoManager: FSharpProjectOptionsManager ) = inherit CodeFixProvider() - static let userOpName = "AddMissingFunKeyword" - let fixableDiagnosticIds = set ["FS0010"] override _.FixableDiagnosticIds = Seq.toImmutableArray fixableDiagnosticIds @@ -34,8 +31,7 @@ type internal FSharpAddMissingFunKeywordCodeFixProvider // Only trigger when failing to parse `->`, which arises when `fun` is missing do! Option.guard (textOfError = "->") - let! parsingOptions, _ = projectInfoManager.TryGetOptionsForEditingDocumentOrProject(document, context.CancellationToken, userOpName) - let defines = CompilerEnvironment.GetCompilationDefinesForEditing parsingOptions + let! defines = document.GetFSharpCompilationDefinesAsync(nameof(FSharpAddMissingFunKeywordCodeFixProvider)) |> liftAsync let adjustedPosition = let rec loop ch pos = diff --git a/vsintegration/src/FSharp.Editor/CodeFix/AddMissingRecToMutuallyRecFunctions.fs b/vsintegration/src/FSharp.Editor/CodeFix/AddMissingRecToMutuallyRecFunctions.fs index 62c569dfaf0..55b73b1fee6 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/AddMissingRecToMutuallyRecFunctions.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/AddMissingRecToMutuallyRecFunctions.fs @@ -20,11 +20,9 @@ open Microsoft.CodeAnalysis.CodeActions type internal FSharpAddMissingRecToMutuallyRecFunctionsCodeFixProvider [] ( - projectInfoManager: FSharpProjectOptionsManager ) = inherit CodeFixProvider() - static let userOpName = "AddMissingRecToMutuallyRecFunctions" let fixableDiagnosticIds = set ["FS0576"] let createCodeFix (context: CodeFixContext, symbolName: string, titleFormat: string, textChange: TextChange, diagnostics: ImmutableArray) = @@ -45,9 +43,8 @@ type internal FSharpAddMissingRecToMutuallyRecFunctionsCodeFixProvider override _.RegisterCodeFixesAsync context = asyncMaybe { + let! defines = context.Document.GetFSharpCompilationDefinesAsync(nameof(FSharpAddMissingRecToMutuallyRecFunctionsCodeFixProvider)) |> liftAsync let! sourceText = context.Document.GetTextAsync(context.CancellationToken) - let! parsingOptions, _ = projectInfoManager.TryGetOptionsForEditingDocumentOrProject(context.Document, context.CancellationToken, userOpName) - let defines = CompilerEnvironment.GetCompilationDefinesForEditing parsingOptions let funcStartPos = let rec loop ch pos = diff --git a/vsintegration/src/FSharp.Editor/CodeFix/AddOpenCodeFixProvider.fs b/vsintegration/src/FSharp.Editor/CodeFix/AddOpenCodeFixProvider.fs index 3890eba616f..392773a637a 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/AddOpenCodeFixProvider.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/AddOpenCodeFixProvider.fs @@ -21,16 +21,12 @@ open FSharp.Compiler.Text type internal FSharpAddOpenCodeFixProvider [] ( - checkerProvider: FSharpCheckerProvider, - projectInfoManager: FSharpProjectOptionsManager, assemblyContentProvider: AssemblyContentProvider ) = inherit CodeFixProvider() - static let userOpName = "FSharpAddOpenCodeFixProvider" let fixableDiagnosticIds = ["FS0039"; "FS0043"] - let checker = checkerProvider.Checker let fixUnderscoresInMenuText (text: string) = text.Replace("_", "__") let qualifySymbolFix (context: CodeFixContext) (fullName, qualifier) = @@ -88,12 +84,12 @@ type internal FSharpAddOpenCodeFixProvider override _.RegisterCodeFixesAsync context : Task = asyncMaybe { let document = context.Document - let! parsingOptions, projectOptions = projectInfoManager.TryGetOptionsForEditingDocumentOrProject(document, context.CancellationToken, userOpName) - let! sourceText = context.Document.GetTextAsync(context.CancellationToken) - let! _, parsedInput, checkResults = checker.ParseAndCheckDocument(document, projectOptions, userOpName = userOpName) + + let! sourceText = document.GetTextAsync(context.CancellationToken) + let! parseResults, checkResults = document.GetFSharpParseAndCheckResultsAsync(nameof(FSharpAddOpenCodeFixProvider)) |> liftAsync let line = sourceText.Lines.GetLineFromPosition(context.Span.End) let linePos = sourceText.Lines.GetLinePosition(context.Span.End) - let defines = CompilerEnvironment.GetCompilationDefinesForEditing parsingOptions + let! defines = document.GetFSharpCompilationDefinesAsync(nameof(FSharpAddOpenCodeFixProvider)) |> liftAsync let! symbol = maybe { @@ -110,7 +106,7 @@ type internal FSharpAddOpenCodeFixProvider let endPos = Position.fromZ endLinePos.Line endLinePos.Character Range.mkRange context.Document.FilePath startPos endPos - let isAttribute = ParsedInput.GetEntityKind(unresolvedIdentRange.Start, parsedInput) = Some EntityKind.Attribute + let isAttribute = ParsedInput.GetEntityKind(unresolvedIdentRange.Start, parseResults.ParseTree) = Some EntityKind.Attribute let entities = assemblyContentProvider.GetAllEntitiesInProjectAndReferencedAssemblies checkResults @@ -126,7 +122,7 @@ type internal FSharpAddOpenCodeFixProvider s.CleanedIdents |> Array.replace (s.CleanedIdents.Length - 1) (lastIdent.Substring(0, lastIdent.Length - 9)) ]) - let longIdent = ParsedInput.GetLongIdentAt parsedInput unresolvedIdentRange.End + let longIdent = ParsedInput.GetLongIdentAt parseResults.ParseTree unresolvedIdentRange.End let! maybeUnresolvedIdents = longIdent @@ -138,10 +134,10 @@ type internal FSharpAddOpenCodeFixProvider |> List.toArray) let insertionPoint = - if document.FSharpOptions.CodeFixes.AlwaysPlaceOpensAtTopLevel then OpenStatementInsertionPoint.TopLevel + if document.Project.IsFSharpCodeFixesAlwaysPlaceOpensAtTopLevelEnabled then OpenStatementInsertionPoint.TopLevel else OpenStatementInsertionPoint.Nearest - let createEntity = ParsedInput.TryFindInsertionContext unresolvedIdentRange.StartLine parsedInput maybeUnresolvedIdents insertionPoint + let createEntity = ParsedInput.TryFindInsertionContext unresolvedIdentRange.StartLine parseResults.ParseTree maybeUnresolvedIdents insertionPoint return entities |> Seq.map createEntity |> Seq.concat |> Seq.toList |> addSuggestionsAsCodeFixes context } |> Async.Ignore diff --git a/vsintegration/src/FSharp.Editor/CodeFix/AddTypeAnnotationToObjectOfIndeterminateType.fs b/vsintegration/src/FSharp.Editor/CodeFix/AddTypeAnnotationToObjectOfIndeterminateType.fs index cbf1b645648..9f5c441cb83 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/AddTypeAnnotationToObjectOfIndeterminateType.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/AddTypeAnnotationToObjectOfIndeterminateType.fs @@ -20,13 +20,9 @@ open Microsoft.CodeAnalysis.CodeActions type internal FSharpAddTypeAnnotationToObjectOfIndeterminateTypeFixProvider [] ( - checkerProvider: FSharpCheckerProvider, - projectInfoManager: FSharpProjectOptionsManager ) = inherit CodeFixProvider() - static let userOpName = "AddTypeAnnotationToObjectOfIndeterminateType" - let fixableDiagnosticIds = set ["FS0072"; "FS3245"] override _.FixableDiagnosticIds = Seq.toImmutableArray fixableDiagnosticIds @@ -40,23 +36,21 @@ type internal FSharpAddTypeAnnotationToObjectOfIndeterminateTypeFixProvider let document = context.Document let position = context.Span.Start - let checker = checkerProvider.Checker - let! parsingOptions, projectOptions = projectInfoManager.TryGetOptionsForEditingDocumentOrProject(document, context.CancellationToken, userOpName) - let! sourceText = document.GetTextAsync () |> liftTaskAsync - let defines = CompilerEnvironment.GetCompilationDefinesForEditing parsingOptions + + let! sourceText = document.GetTextAsync(context.CancellationToken) let textLine = sourceText.Lines.GetLineFromPosition position let textLinePos = sourceText.Lines.GetLinePosition position let fcsTextLineNumber = Line.fromZ textLinePos.Line - let! _, _, checkFileResults = checker.ParseAndCheckDocument (document, projectOptions, userOpName=userOpName) - let! lexerSymbol = Tokenizer.getSymbolAtPosition (document.Id, sourceText, position, document.FilePath, defines, SymbolLookupKind.Greedy, false, false) + let! lexerSymbol = document.TryFindFSharpLexerSymbolAsync(position, SymbolLookupKind.Greedy, false, false, nameof(FSharpAddTypeAnnotationToObjectOfIndeterminateTypeFixProvider)) + + let! _, checkFileResults = document.GetFSharpParseAndCheckResultsAsync(nameof(FSharpAddTypeAnnotationToObjectOfIndeterminateTypeFixProvider)) |> liftAsync let decl = checkFileResults.GetDeclarationLocation (fcsTextLineNumber, lexerSymbol.Ident.idRange.EndColumn, textLine.ToString(), lexerSymbol.FullIsland, false) match decl with | FindDeclResult.DeclFound declRange when declRange.FileName = document.FilePath -> let! declSpan = RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, declRange) let declTextLine = sourceText.Lines.GetLineFromPosition declSpan.Start - let! declLexerSymbol = Tokenizer.getSymbolAtPosition (document.Id, sourceText, position, document.FilePath, defines, SymbolLookupKind.Greedy, false, false) - let! symbolUse = checkFileResults.GetSymbolUseAtLocation(declRange.StartLine, declRange.EndColumn, declTextLine.ToString(), declLexerSymbol.FullIsland) + let! symbolUse = checkFileResults.GetSymbolUseAtLocation(declRange.StartLine, declRange.EndColumn, declTextLine.ToString(), lexerSymbol.FullIsland) match symbolUse.Symbol with | :? FSharpMemberOrFunctionOrValue as mfv -> let typeString = mfv.FullType.FormatWithConstraints symbolUse.DisplayContext diff --git a/vsintegration/src/FSharp.Editor/CodeFix/ChangeRefCellDerefToNotExpression.fs b/vsintegration/src/FSharp.Editor/CodeFix/ChangeRefCellDerefToNotExpression.fs index b595cc21e55..f7f33d4c198 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/ChangeRefCellDerefToNotExpression.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/ChangeRefCellDerefToNotExpression.fs @@ -12,12 +12,9 @@ open Microsoft.CodeAnalysis.CodeFixes type internal FSharpChangeRefCellDerefToNotExpressionCodeFixProvider [] ( - checkerProvider: FSharpCheckerProvider, - projectInfoManager: FSharpProjectOptionsManager ) = inherit CodeFixProvider() - static let userOpName = "FSharpChangeRefCellDerefToNotExpressionCodeFix" let fixableDiagnosticIds = set ["FS0001"] override _.FixableDiagnosticIds = Seq.toImmutableArray fixableDiagnosticIds @@ -25,9 +22,8 @@ type internal FSharpChangeRefCellDerefToNotExpressionCodeFixProvider override this.RegisterCodeFixesAsync context : Task = asyncMaybe { let document = context.Document - let! parsingOptions, _ = projectInfoManager.TryGetOptionsForEditingDocumentOrProject(document, context.CancellationToken, userOpName) + let! parseResults = document.GetFSharpParseResultsAsync(nameof(FSharpChangeRefCellDerefToNotExpressionCodeFixProvider)) |> liftAsync let! sourceText = context.Document.GetTextAsync(context.CancellationToken) - let! parseResults = checkerProvider.Checker.ParseDocument(document, parsingOptions, userOpName=userOpName) let errorRange = RoslynHelpers.TextSpanToFSharpRange(document.FilePath, context.Span, sourceText) let! derefRange = parseResults.TryRangeOfRefCellDereferenceContainingPos errorRange.Start diff --git a/vsintegration/src/FSharp.Editor/CodeFix/ConvertCSharpLambdaToFSharpLambda.fs b/vsintegration/src/FSharp.Editor/CodeFix/ConvertCSharpLambdaToFSharpLambda.fs index 169588e1c84..824bc52e2a6 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/ConvertCSharpLambdaToFSharpLambda.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/ConvertCSharpLambdaToFSharpLambda.fs @@ -11,20 +11,16 @@ open Microsoft.CodeAnalysis.CodeFixes type internal FSharpConvertCSharpLambdaToFSharpLambdaCodeFixProvider [] ( - checkerProvider: FSharpCheckerProvider, - projectInfoManager: FSharpProjectOptionsManager ) = inherit CodeFixProvider() - static let userOpName = "ConvertCSharpLambdaToFSharpLambda" let fixableDiagnosticIds = set ["FS0039"; "FS0043"] override _.FixableDiagnosticIds = Seq.toImmutableArray fixableDiagnosticIds override _.RegisterCodeFixesAsync context = asyncMaybe { - let! parsingOptions, _ = projectInfoManager.TryGetOptionsForEditingDocumentOrProject(context.Document, context.CancellationToken, userOpName) - let! parseResults = checkerProvider.Checker.ParseDocument(context.Document, parsingOptions, userOpName) + let! parseResults = context.Document.GetFSharpParseResultsAsync(nameof(FSharpConvertCSharpLambdaToFSharpLambdaCodeFixProvider)) |> liftAsync let! sourceText = context.Document.GetTextAsync(context.CancellationToken) let errorRange = RoslynHelpers.TextSpanToFSharpRange(context.Document.FilePath, context.Span, sourceText) diff --git a/vsintegration/src/FSharp.Editor/CodeFix/ConvertToAnonymousRecord.fs b/vsintegration/src/FSharp.Editor/CodeFix/ConvertToAnonymousRecord.fs index 3710101ed4e..3250e7e33f5 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/ConvertToAnonymousRecord.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/ConvertToAnonymousRecord.fs @@ -14,13 +14,9 @@ open Microsoft.CodeAnalysis.CodeActions type internal FSharpConvertToAnonymousRecordCodeFixProvider [] ( - checkerProvider: FSharpCheckerProvider, - projectInfoManager: FSharpProjectOptionsManager ) = inherit CodeFixProvider() - static let userOpName = "ConvertToAnonymousRecord" - let fixableDiagnosticIds = set ["FS0039"] override _.FixableDiagnosticIds = Seq.toImmutableArray fixableDiagnosticIds @@ -28,8 +24,7 @@ type internal FSharpConvertToAnonymousRecordCodeFixProvider override _.RegisterCodeFixesAsync context : Task = asyncMaybe { let document = context.Document - let! parsingOptions, _ = projectInfoManager.TryGetOptionsForEditingDocumentOrProject(document, context.CancellationToken, userOpName) - let! parseResults = checkerProvider.Checker.ParseDocument(document, parsingOptions, userOpName=userOpName) + let! parseResults = document.GetFSharpParseResultsAsync(nameof(FSharpConvertToAnonymousRecordCodeFixProvider)) |> liftAsync let! sourceText = context.Document.GetTextAsync(context.CancellationToken) let errorRange = RoslynHelpers.TextSpanToFSharpRange(document.FilePath, context.Span, sourceText) diff --git a/vsintegration/src/FSharp.Editor/CodeFix/ImplementInterfaceCodeFixProvider.fs b/vsintegration/src/FSharp.Editor/CodeFix/ImplementInterfaceCodeFixProvider.fs index c21a1123380..93d2686c5c9 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/ImplementInterfaceCodeFixProvider.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/ImplementInterfaceCodeFixProvider.fs @@ -32,13 +32,9 @@ type internal InterfaceState = type internal FSharpImplementInterfaceCodeFixProvider [] ( - checkerProvider: FSharpCheckerProvider, - projectInfoManager: FSharpProjectOptionsManager ) = inherit CodeFixProvider() let fixableDiagnosticIds = ["FS0366"] - let checker = checkerProvider.Checker - static let userOpName = "ImplementInterfaceCodeFixProvider" let queryInterfaceState appendBracketAt (pos: pos) (tokens: Tokenizer.SavedTokenInfo[]) (ast: ParsedInput) = asyncMaybe { @@ -142,11 +138,11 @@ type internal FSharpImplementInterfaceCodeFixProvider override _.RegisterCodeFixesAsync context : Task = asyncMaybe { - let! parsingOptions, projectOptions = projectInfoManager.TryGetOptionsForEditingDocumentOrProject(context.Document, context.CancellationToken, userOpName) + let! parseResults, checkFileResults = context.Document.GetFSharpParseAndCheckResultsAsync(nameof(FSharpImplementInterfaceCodeFixProvider)) |> liftAsync let cancellationToken = context.CancellationToken let! sourceText = context.Document.GetTextAsync(cancellationToken) - let! _, parsedInput, checkFileResults = checker.ParseAndCheckDocument(context.Document, projectOptions, userOpName = userOpName) let textLine = sourceText.Lines.GetLineFromPosition context.Span.Start + let! _, _, parsingOptions, _ = context.Document.GetFSharpCompilationOptionsAsync(nameof(FSharpImplementInterfaceCodeFixProvider)) |> liftAsync let defines = CompilerEnvironment.GetCompilationDefinesForEditing parsingOptions // Notice that context.Span doesn't return reliable ranges to find tokens at exact positions. // That's why we tokenize the line and try to find the last successive identifier token @@ -172,7 +168,7 @@ type internal FSharpImplementInterfaceCodeFixProvider | '}' -> None | _ -> Some context.Span.End - let! interfaceState = queryInterfaceState appendBracketAt interfacePos tokens parsedInput + let! interfaceState = queryInterfaceState appendBracketAt interfacePos tokens parseResults.ParseTree let! symbol = Tokenizer.getSymbolAtPosition(context.Document.Id, sourceText, fixupPosition, context.Document.FilePath, defines, SymbolLookupKind.Greedy, false, false) let fcsTextLineNumber = textLine.LineNumber + 1 let lineContents = textLine.ToString() diff --git a/vsintegration/src/FSharp.Editor/CodeFix/MakeDeclarationMutable.fs b/vsintegration/src/FSharp.Editor/CodeFix/MakeDeclarationMutable.fs index 9aab9475320..e93e9f4db13 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/MakeDeclarationMutable.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/MakeDeclarationMutable.fs @@ -18,13 +18,9 @@ open FSharp.Compiler.Text type internal FSharpMakeDeclarationMutableFixProvider [] ( - checkerProvider: FSharpCheckerProvider, - projectInfoManager: FSharpProjectOptionsManager ) = inherit CodeFixProvider() - static let userOpName = "MakeDeclarationMutable" - let fixableDiagnosticIds = set ["FS0027"] override _.FixableDiagnosticIds = Seq.toImmutableArray fixableDiagnosticIds @@ -39,15 +35,13 @@ type internal FSharpMakeDeclarationMutableFixProvider let document = context.Document do! Option.guard (not(isSignatureFile document.FilePath)) let position = context.Span.Start - let checker = checkerProvider.Checker - let! parsingOptions, projectOptions = projectInfoManager.TryGetOptionsForEditingDocumentOrProject(document, CancellationToken.None, userOpName) + + let! lexerSymbol = document.TryFindFSharpLexerSymbolAsync(position, SymbolLookupKind.Greedy, false, false, nameof(FSharpMakeDeclarationMutableFixProvider)) let! sourceText = document.GetTextAsync () |> liftTaskAsync - let defines = CompilerEnvironment.GetCompilationDefinesForEditing parsingOptions let textLine = sourceText.Lines.GetLineFromPosition position let textLinePos = sourceText.Lines.GetLinePosition position let fcsTextLineNumber = Line.fromZ textLinePos.Line - let! parseFileResults, _, checkFileResults = checker.ParseAndCheckDocument (document, projectOptions, userOpName=userOpName) - let! lexerSymbol = Tokenizer.getSymbolAtPosition (document.Id, sourceText, position, document.FilePath, defines, SymbolLookupKind.Greedy, false, false) + let! parseFileResults, checkFileResults = document.GetFSharpParseAndCheckResultsAsync(nameof(FSharpMakeDeclarationMutableFixProvider)) |> liftAsync let decl = checkFileResults.GetDeclarationLocation (fcsTextLineNumber, lexerSymbol.Ident.idRange.EndColumn, textLine.ToString(), lexerSymbol.FullIsland, false) match decl with diff --git a/vsintegration/src/FSharp.Editor/CodeFix/MakeOuterBindingRecursive.fs b/vsintegration/src/FSharp.Editor/CodeFix/MakeOuterBindingRecursive.fs index 67b586688e5..842de538108 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/MakeOuterBindingRecursive.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/MakeOuterBindingRecursive.fs @@ -12,20 +12,16 @@ open Microsoft.CodeAnalysis.CodeFixes type internal FSharpMakeOuterBindingRecursiveCodeFixProvider [] ( - checkerProvider: FSharpCheckerProvider, - projectInfoManager: FSharpProjectOptionsManager ) = inherit CodeFixProvider() - static let userOpName = "MakeOuterBindingRecursive" let fixableDiagnosticIds = set ["FS0039"] override _.FixableDiagnosticIds = Seq.toImmutableArray fixableDiagnosticIds override _.RegisterCodeFixesAsync context = asyncMaybe { - let! parsingOptions, _ = projectInfoManager.TryGetOptionsForEditingDocumentOrProject(context.Document, context.CancellationToken, userOpName) - let! parseResults = checkerProvider.Checker.ParseDocument(context.Document, parsingOptions, userOpName) + let! parseResults = context.Document.GetFSharpParseResultsAsync(nameof(FSharpMakeOuterBindingRecursiveCodeFixProvider)) |> liftAsync let! sourceText = context.Document.GetTextAsync(context.CancellationToken) let diagnosticRange = RoslynHelpers.TextSpanToFSharpRange(context.Document.FilePath, context.Span, sourceText) diff --git a/vsintegration/src/FSharp.Editor/CodeFix/ProposeUppercaseLabel.fs b/vsintegration/src/FSharp.Editor/CodeFix/ProposeUppercaseLabel.fs index 71bfb049b51..65ebe4edb33 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/ProposeUppercaseLabel.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/ProposeUppercaseLabel.fs @@ -12,19 +12,16 @@ open FSharp.Compiler.Diagnostics type internal FSharpProposeUpperCaseLabelCodeFixProvider [] ( - checkerProvider: FSharpCheckerProvider, - projectInfoManager: FSharpProjectOptionsManager ) = inherit CodeFixProvider() let fixableDiagnosticIds = ["FS0053"] - static let userOpName = "ProposeUpperCaseLabel" override _.FixableDiagnosticIds = Seq.toImmutableArray fixableDiagnosticIds override _.RegisterCodeFixesAsync context : Task = asyncMaybe { let textChanger (originalText: string) = originalText.[0].ToString().ToUpper() + originalText.Substring(1) - let! solutionChanger, originalText = SymbolHelpers.changeAllSymbolReferences(context.Document, context.Span, textChanger, projectInfoManager, checkerProvider.Checker, userOpName) + let! solutionChanger, originalText = SymbolHelpers.changeAllSymbolReferences(context.Document, context.Span, textChanger) let title = CompilerDiagnostics.GetErrorMessage (FSharpDiagnosticKind.ReplaceWithSuggestion <| textChanger originalText) context.RegisterCodeFix( CodeAction.Create(title, solutionChanger, title), diff --git a/vsintegration/src/FSharp.Editor/CodeFix/RemoveReturnOrYield.fs b/vsintegration/src/FSharp.Editor/CodeFix/RemoveReturnOrYield.fs index 59e6ff102c1..863a9343e72 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/RemoveReturnOrYield.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/RemoveReturnOrYield.fs @@ -11,20 +11,16 @@ open Microsoft.CodeAnalysis.CodeFixes type internal FSharpRemoveReturnOrYieldCodeFixProvider [] ( - checkerProvider: FSharpCheckerProvider, - projectInfoManager: FSharpProjectOptionsManager ) = inherit CodeFixProvider() - static let userOpName = "RemoveReturnOrYield" let fixableDiagnosticIds = set ["FS0748"; "FS0747"] override _.FixableDiagnosticIds = Seq.toImmutableArray fixableDiagnosticIds override _.RegisterCodeFixesAsync context = asyncMaybe { - let! parsingOptions, _ = projectInfoManager.TryGetOptionsForEditingDocumentOrProject(context.Document, context.CancellationToken, userOpName) - let! parseResults = checkerProvider.Checker.ParseDocument(context.Document, parsingOptions, userOpName=userOpName) + let! parseResults = context.Document.GetFSharpParseResultsAsync(nameof(FSharpRemoveReturnOrYieldCodeFixProvider)) |> liftAsync let! sourceText = context.Document.GetTextAsync(context.CancellationToken) let errorRange = RoslynHelpers.TextSpanToFSharpRange(context.Document.FilePath, context.Span, sourceText) diff --git a/vsintegration/src/FSharp.Editor/CodeFix/RemoveUnusedBinding.fs b/vsintegration/src/FSharp.Editor/CodeFix/RemoveUnusedBinding.fs index 35e489e8675..fe0a4200e47 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/RemoveUnusedBinding.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/RemoveUnusedBinding.fs @@ -13,12 +13,10 @@ open Microsoft.CodeAnalysis.CodeFixes type internal FSharpRemoveUnusedBindingCodeFixProvider [] ( - checkerProvider: FSharpCheckerProvider, - projectInfoManager: FSharpProjectOptionsManager ) = inherit CodeFixProvider() - static let userOpName = "RemoveUnusedBinding" + let fixableDiagnosticIds = set ["FS1182"] override _.FixableDiagnosticIds = Seq.toImmutableArray fixableDiagnosticIds @@ -26,13 +24,12 @@ type internal FSharpRemoveUnusedBindingCodeFixProvider override _.RegisterCodeFixesAsync context : Task = asyncMaybe { // Don't show code fixes for unused values, even if they are compiler-generated. - do! Option.guard context.Document.FSharpOptions.CodeFixes.UnusedDeclarations + do! Option.guard context.Document.Project.IsFSharpCodeFixesUnusedDeclarationsEnabled let document = context.Document let! sourceText = document.GetTextAsync(context.CancellationToken) - let! parsingOptions, _ = projectInfoManager.TryGetOptionsForEditingDocumentOrProject(document, context.CancellationToken, userOpName) - let! parseResults = checkerProvider.Checker.ParseDocument(document, parsingOptions, userOpName=userOpName) + let! parseResults = context.Document.GetFSharpParseResultsAsync(nameof(FSharpRemoveUnusedBindingCodeFixProvider)) |> liftAsync let diagnostics = context.Diagnostics diff --git a/vsintegration/src/FSharp.Editor/CodeFix/RemoveUnusedOpens.fs b/vsintegration/src/FSharp.Editor/CodeFix/RemoveUnusedOpens.fs index 1cc40bfa47b..9bc06368210 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/RemoveUnusedOpens.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/RemoveUnusedOpens.fs @@ -15,11 +15,9 @@ open FSharp.Compiler.Text type internal FSharpRemoveUnusedOpensCodeFixProvider [] ( - checkerProvider: FSharpCheckerProvider, - projectInfoManager: FSharpProjectOptionsManager ) = inherit CodeFixProvider() - let userOpName = "FSharpRemoveUnusedOpensCodeFixProvider" + let fixableDiagnosticIds = [FSharpIDEDiagnosticIds.RemoveUnnecessaryImportsDiagnosticId] override _.FixableDiagnosticIds = Seq.toImmutableArray fixableDiagnosticIds @@ -28,9 +26,7 @@ type internal FSharpRemoveUnusedOpensCodeFixProvider asyncMaybe { let document = context.Document let! sourceText = document.GetTextAsync() - let checker = checkerProvider.Checker - let! _, projectOptions = projectInfoManager.TryGetOptionsForEditingDocumentOrProject(document, context.CancellationToken, userOpName) - let! unusedOpens = UnusedOpensDiagnosticAnalyzer.GetUnusedOpenRanges(document, projectOptions, checker) + let! unusedOpens = UnusedOpensDiagnosticAnalyzer.GetUnusedOpenRanges(document) let changes = unusedOpens |> List.map (fun m -> diff --git a/vsintegration/src/FSharp.Editor/CodeFix/RenameParamToMatchSignature.fs b/vsintegration/src/FSharp.Editor/CodeFix/RenameParamToMatchSignature.fs index c1671a31c95..64632f5bbf4 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/RenameParamToMatchSignature.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/RenameParamToMatchSignature.fs @@ -17,12 +17,10 @@ open FSharp.Compiler.Tokenization.FSharpKeywords type internal FSharpRenameParamToMatchSignature [] ( - checkerProvider: FSharpCheckerProvider, - projectInfoManager: FSharpProjectOptionsManager ) = inherit CodeFixProvider() - static let userOpName = "RenameParamToMatchSignature" + let fixableDiagnosticIds = ["FS3218"] @@ -44,7 +42,7 @@ type internal FSharpRenameParamToMatchSignature let document = context.Document let! cancellationToken = Async.CancellationToken |> liftAsync let! sourceText = document.GetTextAsync(cancellationToken) - let! symbolUses = getSymbolUsesOfSymbolAtLocationInDocument (document, context.Span.Start, projectInfoManager, checkerProvider.Checker, userOpName) + let! symbolUses = getSymbolUsesOfSymbolAtLocationInDocument (document, context.Span.Start) let changes = [| for symbolUse in symbolUses do match RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, symbolUse.Range) with diff --git a/vsintegration/src/FSharp.Editor/CodeFix/RenameUnusedValue.fs b/vsintegration/src/FSharp.Editor/CodeFix/RenameUnusedValue.fs index 96ecbcd4351..9f44097b25b 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/RenameUnusedValue.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/RenameUnusedValue.fs @@ -18,21 +18,18 @@ open FSharp.Compiler.Syntax type internal FSharpRenameUnusedValueCodeFixProvider [] ( - checkerProvider: FSharpCheckerProvider, - projectInfoManager: FSharpProjectOptionsManager ) = inherit CodeFixProvider() - static let userOpName = "RenameUnusedValueCodeFix" + let fixableDiagnosticIds = set ["FS1182"] - let checker = checkerProvider.Checker override _.FixableDiagnosticIds = Seq.toImmutableArray fixableDiagnosticIds override _.RegisterCodeFixesAsync context : Task = asyncMaybe { // Don't show code fixes for unused values, even if they are compiler-generated. - do! Option.guard context.Document.FSharpOptions.CodeFixes.UnusedDeclarations + do! Option.guard context.Document.Project.IsFSharpCodeFixesUnusedDeclarationsEnabled let document = context.Document let! sourceText = document.GetTextAsync(context.CancellationToken) @@ -41,12 +38,10 @@ type internal FSharpRenameUnusedValueCodeFixProvider // We have to use the additional check for backtickes because `IsOperatorOrBacktickedName` operates on display names // where backtickes are replaced with parens. if not (PrettyNaming.IsOperatorOrBacktickedName ident) && not (ident.StartsWith "``") then - let! parsingOptions, projectOptions = projectInfoManager.TryGetOptionsForEditingDocumentOrProject(document, context.CancellationToken, userOpName) - let! _, _, checkResults = checker.ParseAndCheckDocument(document, projectOptions, userOpName=userOpName) + let! lexerSymbol = document.TryFindFSharpLexerSymbolAsync(context.Span.Start, SymbolLookupKind.Greedy, false, false, nameof(FSharpRenameUnusedValueCodeFixProvider)) let m = RoslynHelpers.TextSpanToFSharpRange(document.FilePath, context.Span, sourceText) - let defines = CompilerEnvironment.GetCompilationDefinesForEditing parsingOptions - let! lexerSymbol = Tokenizer.getSymbolAtPosition (document.Id, sourceText, context.Span.Start, document.FilePath, defines, SymbolLookupKind.Greedy, false, false) let lineText = (sourceText.Lines.GetLineFromPosition context.Span.Start).ToString() + let! _, checkResults = document.GetFSharpParseAndCheckResultsAsync(nameof(FSharpRenameUnusedValueCodeFixProvider)) |> liftAsync let! symbolUse = checkResults.GetSymbolUseAtLocation(m.StartLine, m.EndColumn, lineText, lexerSymbol.FullIsland) let symbolName = symbolUse.Symbol.DisplayName diff --git a/vsintegration/src/FSharp.Editor/CodeFix/ReplaceWithSuggestion.fs b/vsintegration/src/FSharp.Editor/CodeFix/ReplaceWithSuggestion.fs index fdbd65bd228..8fb23402339 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/ReplaceWithSuggestion.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/ReplaceWithSuggestion.fs @@ -17,15 +17,11 @@ open FSharp.Compiler.Tokenization type internal FSharpReplaceWithSuggestionCodeFixProvider [] ( - checkerProvider: FSharpCheckerProvider, - projectInfoManager: FSharpProjectOptionsManager, settings: EditorOptions ) = inherit CodeFixProvider() - static let userOpName = "ReplaceWithSuggestionCodeFix" let fixableDiagnosticIds = set ["FS0039"; "FS1129"; "FS0495"] - let checker = checkerProvider.Checker override _.FixableDiagnosticIds = Seq.toImmutableArray fixableDiagnosticIds @@ -34,8 +30,7 @@ type internal FSharpReplaceWithSuggestionCodeFixProvider do! Option.guard settings.CodeFixes.SuggestNamesForErrors let document = context.Document - let! _, projectOptions = projectInfoManager.TryGetOptionsForEditingDocumentOrProject(document, context.CancellationToken, userOpName) - let! parseFileResults, _, checkFileResults = checker.ParseAndCheckDocument(document, projectOptions, userOpName=userOpName) + let! parseFileResults, checkFileResults = document.GetFSharpParseAndCheckResultsAsync(nameof(FSharpReplaceWithSuggestionCodeFixProvider)) |> liftAsync // This is all needed to get a declaration list let! sourceText = document.GetTextAsync(context.CancellationToken) diff --git a/vsintegration/src/FSharp.Editor/CodeFix/UseMutationWhenValueIsMutable.fs b/vsintegration/src/FSharp.Editor/CodeFix/UseMutationWhenValueIsMutable.fs index 44a1fa5682d..ecf9c59708b 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/UseMutationWhenValueIsMutable.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/UseMutationWhenValueIsMutable.fs @@ -19,13 +19,9 @@ open FSharp.Compiler.Text type internal FSharpUseMutationWhenValueIsMutableFixProvider [] ( - checkerProvider: FSharpCheckerProvider, - projectInfoManager: FSharpProjectOptionsManager ) = inherit CodeFixProvider() - static let userOpName = "UseMutationWhenValueIsMutable" - let fixableDiagnosticIds = set ["FS0020"] override _.FixableDiagnosticIds = Seq.toImmutableArray fixableDiagnosticIds @@ -39,10 +35,8 @@ type internal FSharpUseMutationWhenValueIsMutableFixProvider let document = context.Document do! Option.guard (not(isSignatureFile document.FilePath)) - let checker = checkerProvider.Checker - let! parsingOptions, projectOptions = projectInfoManager.TryGetOptionsForEditingDocumentOrProject(document, CancellationToken.None, userOpName) - let! sourceText = document.GetTextAsync () |> liftTaskAsync - let defines = CompilerEnvironment.GetCompilationDefinesForEditing parsingOptions + + let! sourceText = document.GetTextAsync(context.CancellationToken) let adjustedPosition = let rec loop ch pos = @@ -56,8 +50,8 @@ type internal FSharpUseMutationWhenValueIsMutableFixProvider let textLine = sourceText.Lines.GetLineFromPosition adjustedPosition let textLinePos = sourceText.Lines.GetLinePosition adjustedPosition let fcsTextLineNumber = Line.fromZ textLinePos.Line - let! _, _, checkFileResults = checker.ParseAndCheckDocument (document, projectOptions, userOpName=userOpName) - let! lexerSymbol = Tokenizer.getSymbolAtPosition (document.Id, sourceText, adjustedPosition, document.FilePath, defines, SymbolLookupKind.Greedy, false, false) + let! lexerSymbol = document.TryFindFSharpLexerSymbolAsync(adjustedPosition, SymbolLookupKind.Greedy, false, false, nameof(FSharpUseMutationWhenValueIsMutableFixProvider)) + let! _, checkFileResults = document.GetFSharpParseAndCheckResultsAsync(nameof(FSharpUseMutationWhenValueIsMutableFixProvider)) |> liftAsync let! symbolUse = checkFileResults.GetSymbolUseAtLocation(fcsTextLineNumber, lexerSymbol.Ident.idRange.EndColumn, textLine.ToString(), lexerSymbol.FullIsland) match symbolUse.Symbol with diff --git a/vsintegration/src/FSharp.Editor/CodeLens/CodeLensProvider.fs b/vsintegration/src/FSharp.Editor/CodeLens/CodeLensProvider.fs index 096ac474d16..8d3be2fcf35 100644 --- a/vsintegration/src/FSharp.Editor/CodeLens/CodeLensProvider.fs +++ b/vsintegration/src/FSharp.Editor/CodeLens/CodeLensProvider.fs @@ -23,8 +23,7 @@ type internal CodeLensProvider ( [)>] serviceProvider: IServiceProvider, textDocumentFactory: ITextDocumentFactoryService, - checkerProvider: FSharpCheckerProvider, - projectInfoManager: FSharpProjectOptionsManager, + metadataAsSource: FSharpMetadataAsSourceService, typeMap : FSharpClassificationTypeMap Lazy, settings: EditorOptions ) = @@ -50,7 +49,7 @@ type internal CodeLensProvider ) let tagger = CodeLensGeneralTagger(wpfView, buffer) - let service = FSharpCodeLensService(serviceProvider, workspace, documentId, buffer, checkerProvider, projectInfoManager, componentModel.GetService(), typeMap, tagger, settings) + let service = FSharpCodeLensService(serviceProvider, workspace, documentId, buffer, metadataAsSource, componentModel.GetService(), typeMap, tagger, settings) let provider = (wpfView, (tagger, service)) wpfView.Closed.Add (fun _ -> taggers.Remove provider |> ignore) taggers.Add((wpfView, (tagger, service))) @@ -69,7 +68,7 @@ type internal CodeLensProvider | _ -> None |> Option.get ) - let service = FSharpCodeLensService(serviceProvider, workspace, documentId, buffer, checkerProvider, projectInfoManager, componentModel.GetService(), typeMap, LineLensDisplayService(wpfView, buffer), settings) + let service = FSharpCodeLensService(serviceProvider, workspace, documentId, buffer, metadataAsSource, componentModel.GetService(), typeMap, LineLensDisplayService(wpfView, buffer), settings) let provider = (wpfView, service) wpfView.Closed.Add (fun _ -> lineLensProvider.Remove provider |> ignore) lineLensProvider.Add(provider) diff --git a/vsintegration/src/FSharp.Editor/CodeLens/FSharpCodeLensService.fs b/vsintegration/src/FSharp.Editor/CodeLens/FSharpCodeLensService.fs index a9b1fc84584..ac322ac733f 100644 --- a/vsintegration/src/FSharp.Editor/CodeLens/FSharpCodeLensService.fs +++ b/vsintegration/src/FSharp.Editor/CodeLens/FSharpCodeLensService.fs @@ -44,8 +44,7 @@ type internal FSharpCodeLensService workspace: Workspace, documentId: Lazy, buffer: ITextBuffer, - checkerProvider: FSharpCheckerProvider, - projectInfoManager: FSharpProjectOptionsManager, + metadataAsSource: FSharpMetadataAsSourceService, classificationFormatMapService: IClassificationFormatMapService, typeMap: Lazy, codeLens : CodeLensDisplayService, @@ -53,8 +52,6 @@ type internal FSharpCodeLensService ) as self = let lineLens = codeLens - let userOpName = "FSharpCodeLensService" - let checker = checkerProvider.Checker let visit pos parseTree = SyntaxTraversal.Traverse(pos, parseTree, { new SyntaxVisitorBase<_>() with @@ -157,8 +154,8 @@ type internal FSharpCodeLensService logInfof "Rechecking code due to buffer edit!" #endif let! document = workspace.CurrentSolution.GetDocument(documentId.Value) |> Option.ofObj - let! _, options = projectInfoManager.TryGetOptionsForEditingDocumentOrProject(document, bufferChangedCts.Token, userOpName) - let! _, parsedInput, checkFileResults = checker.ParseAndCheckDocument(document, options, "LineLens", allowStaleResults=true) + let! parseFileResults, checkFileResults = document.GetFSharpParseAndCheckResultsAsync(nameof(FSharpUseMutationWhenValueIsMutableFixProvider)) |> liftAsync + let parsedInput = parseFileResults.ParseTree #if DEBUG logInfof "Getting uses of all symbols!" #endif @@ -194,7 +191,7 @@ type internal FSharpCodeLensService let taggedText = ResizeArray() typeLayout |> Seq.iter taggedText.Add let statusBar = StatusBar(serviceProvider.GetService()) - let navigation = QuickInfoNavigation(statusBar, checkerProvider, projectInfoManager, document, realPosition) + let navigation = QuickInfoNavigation(statusBar, metadataAsSource, document, realPosition) // Because the data is available notify that this line should be updated, displaying the results return Some (taggedText, navigation) | None -> diff --git a/vsintegration/src/FSharp.Editor/Commands/HelpContextService.fs b/vsintegration/src/FSharp.Editor/Commands/HelpContextService.fs index 1f77bba3428..3b1afa222a2 100644 --- a/vsintegration/src/FSharp.Editor/Commands/HelpContextService.fs +++ b/vsintegration/src/FSharp.Editor/Commands/HelpContextService.fs @@ -20,14 +20,11 @@ open Microsoft.CodeAnalysis type internal FSharpHelpContextService [] ( - checkerProvider: FSharpCheckerProvider, - projectInfoManager: FSharpProjectOptionsManager ) = - static let userOpName = "ImplementInterfaceCodeFix" - static member GetHelpTerm(checker: FSharpChecker, document: Document, options, span: TextSpan, tokens: List, perfOptions) : Async = + static member GetHelpTerm(document: Document, span: TextSpan, tokens: List) : Async = asyncMaybe { - let! _, _, check = checker.ParseAndCheckDocument(document, options, perfOptions, userOpName) + let! _, check = document.GetFSharpParseAndCheckResultsAsync(nameof(FSharpHelpContextService)) |> liftAsync let! sourceText = document.GetTextAsync() |> liftTaskAsync let textLines = sourceText.Lines let lineInfo = textLines.GetLineFromPosition(span.Start) @@ -101,13 +98,11 @@ type internal FSharpHelpContextService member this.GetHelpTermAsync(document, textSpan, cancellationToken) = asyncMaybe { - let! _parsingOptions, projectOptions = projectInfoManager.TryGetOptionsForEditingDocumentOrProject(document, cancellationToken, userOpName) let! sourceText = document.GetTextAsync(cancellationToken) - let defines = projectInfoManager.GetCompilationDefinesForEditingDocument(document) + let defines = document.GetFSharpQuickDefines() let textLine = sourceText.Lines.GetLineFromPosition(textSpan.Start) let classifiedSpans = Tokenizer.getClassifiedSpans(document.Id, sourceText, textLine.Span, Some document.Name, defines, cancellationToken) - let perfOptions = document.FSharpOptions.LanguageServicePerformance - return! FSharpHelpContextService.GetHelpTerm(checkerProvider.Checker, document, projectOptions, textSpan, classifiedSpans, perfOptions) + return! FSharpHelpContextService.GetHelpTerm(document, textSpan, classifiedSpans) } |> Async.map (Option.defaultValue "") |> RoslynHelpers.StartAsyncAsTask cancellationToken diff --git a/vsintegration/src/FSharp.Editor/Commands/XmlDocCommandService.fs b/vsintegration/src/FSharp.Editor/Commands/XmlDocCommandService.fs index 4dd22f0ffa1..0771f84d4d8 100644 --- a/vsintegration/src/FSharp.Editor/Commands/XmlDocCommandService.fs +++ b/vsintegration/src/FSharp.Editor/Commands/XmlDocCommandService.fs @@ -20,16 +20,10 @@ open FSharp.Compiler.EditorServices type internal XmlDocCommandFilter ( wpfTextView: IWpfTextView, - filePath: string, - checkerProvider: FSharpCheckerProvider, - projectInfoManager: FSharpProjectOptionsManager, + filePath: string, workspace: VisualStudioWorkspace ) = - static let userOpName = "XmlDocCommand" - - let checker = checkerProvider.Checker - let document = // There may be multiple documents with the same file path. // However, for the purpose of generating XmlDoc comments, it is ok to keep only the first document. @@ -67,10 +61,9 @@ type internal XmlDocCommandFilter // XmlDocable line #1 are 1-based, editor is 0-based let curLineNum = wpfTextView.Caret.Position.BufferPosition.GetContainingLine().LineNumber + 1 let! document = document.Value - let! parsingOptions, _options = projectInfoManager.TryGetOptionsForEditingDocumentOrProject(document, CancellationToken.None, userOpName) let! cancellationToken = Async.CancellationToken |> liftAsync let! sourceText = document.GetTextAsync(cancellationToken) - let! parseResults = checker.ParseDocument(document, parsingOptions, userOpName) + let! parseResults = document.GetFSharpParseResultsAsync(nameof(XmlDocCommandFilter)) |> liftAsync let xmlDocables = XmlDocParser.GetXmlDocables (sourceText.ToFSharpSourceText(), parseResults.ParseTree) let xmlDocablesBelowThisLine = // +1 because looking below current line for e.g. a 'member' @@ -118,11 +111,11 @@ type internal XmlDocCommandFilter [] type internal XmlDocCommandFilterProvider [] - (checkerProvider: FSharpCheckerProvider, - projectInfoManager: FSharpProjectOptionsManager, + ( workspace: VisualStudioWorkspace, textDocumentFactoryService: ITextDocumentFactoryService, - editorFactory: IVsEditorAdaptersFactoryService) = + editorFactory: IVsEditorAdaptersFactoryService + ) = interface IWpfTextViewCreationListener with member _.TextViewCreated(textView) = match editorFactory.GetViewAdapter(textView) with @@ -130,6 +123,6 @@ type internal XmlDocCommandFilterProvider | textViewAdapter -> match textDocumentFactoryService.TryGetTextDocument(textView.TextBuffer) with | true, doc -> - let commandFilter = XmlDocCommandFilter(textView, doc.FilePath, checkerProvider, projectInfoManager, workspace) + let commandFilter = XmlDocCommandFilter(textView, doc.FilePath, workspace) commandFilter.AttachToViewAdapter textViewAdapter | _ -> () \ No newline at end of file diff --git a/vsintegration/src/FSharp.Editor/Common/Extensions.fs b/vsintegration/src/FSharp.Editor/Common/Extensions.fs index 825a010f387..f6f07117b52 100644 --- a/vsintegration/src/FSharp.Editor/Common/Extensions.fs +++ b/vsintegration/src/FSharp.Editor/Common/Extensions.fs @@ -42,6 +42,7 @@ type Project with member this.IsFSharpMiscellaneous = this.Name = FSharpConstants.FSharpMiscellaneousFilesName member this.IsFSharpMetadata = this.Name.StartsWith(FSharpConstants.FSharpMetadataName) member this.IsFSharpMiscellaneousOrMetadata = this.IsFSharpMiscellaneous || this.IsFSharpMetadata + member this.IsFSharp = this.Language = LanguageNames.FSharp type Document with member this.TryGetLanguageService<'T when 'T :> ILanguageService>() = diff --git a/vsintegration/src/FSharp.Editor/Completion/CompletionProvider.fs b/vsintegration/src/FSharp.Editor/Completion/CompletionProvider.fs index f99d696c62e..8cf74d0d665 100644 --- a/vsintegration/src/FSharp.Editor/Completion/CompletionProvider.fs +++ b/vsintegration/src/FSharp.Editor/Completion/CompletionProvider.fs @@ -28,14 +28,11 @@ type internal FSharpCompletionProvider ( workspace: Workspace, serviceProvider: SVsServiceProvider, - checkerProvider: FSharpCheckerProvider, - projectInfoManager: FSharpProjectOptionsManager, assemblyContentProvider: AssemblyContentProvider ) = inherit CompletionProvider() - static let userOpName = "CompletionProvider" // Save the backing data in a cache, we need to save for at least the length of the completion session // See https://github.com/Microsoft/visualfsharp/issues/4714 static let mutable declarationItems: DeclarationListItem[] = [||] @@ -59,8 +56,6 @@ type internal FSharpCompletionProvider sortText = sprintf "%06d" (1000000 + n)) .AddProperty(KeywordDescription, description)) - let checker = checkerProvider.Checker - let settings: EditorOptions = workspace.Services.GetService() let documentationBuilder = XmlDocumentation.CreateDocumentationBuilder(serviceProvider.XMLMemberIndexService) @@ -107,12 +102,11 @@ type internal FSharpCompletionProvider (triggerChar = '.' || (intelliSenseOptions.ShowAfterCharIsTyped && CompletionUtils.isStartingNewWord(sourceText, triggerPosition))) - static member ProvideCompletionsAsyncAux(checker: FSharpChecker, document: Document, caretPosition: int, options: FSharpProjectOptions, - getAllSymbols: FSharpCheckFileResults -> AssemblySymbol list, languageServicePerformanceOptions: LanguageServicePerformanceOptions, intellisenseOptions: IntelliSenseOptions) = + static member ProvideCompletionsAsyncAux(document: Document, caretPosition: int, getAllSymbols: FSharpCheckFileResults -> AssemblySymbol list, intellisenseOptions: IntelliSenseOptions) = asyncMaybe { - let! parseResults, _, checkFileResults = checker.ParseAndCheckDocument(document, options, languageServicePerformanceOptions, userOpName = userOpName) - let! sourceText = document.GetTextAsync() |> liftTaskAsync + let! parseResults, checkFileResults = document.GetFSharpParseAndCheckResultsAsync("ProvideCompletionsAsyncAux") |> liftAsync + let! sourceText = document.GetTextAsync() let textLines = sourceText.Lines let caretLinePos = textLines.GetLinePosition(caretPosition) let caretLine = textLines.GetLineFromPosition(caretPosition) @@ -211,7 +205,7 @@ type internal FSharpCompletionProvider let getInfo() = let documentId = workspace.GetDocumentIdInCurrentContext(sourceText.Container) let document = workspace.CurrentSolution.GetDocument(documentId) - let defines = projectInfoManager.GetCompilationDefinesForEditingDocument(document) + let defines = document.GetFSharpQuickDefines() (documentId, document.FilePath, defines) FSharpCompletionProvider.ShouldTriggerCompletionAux(sourceText, caretPosition, trigger.Kind, getInfo, settings.IntelliSense) @@ -221,16 +215,14 @@ type internal FSharpCompletionProvider use _logBlock = Logger.LogBlockMessage context.Document.Name LogEditorFunctionId.Completion_ProvideCompletionsAsync let document = context.Document let! sourceText = context.Document.GetTextAsync(context.CancellationToken) - let defines = projectInfoManager.GetCompilationDefinesForEditingDocument(document) + let defines = document.GetFSharpQuickDefines() do! Option.guard (CompletionUtils.shouldProvideCompletion(document.Id, document.FilePath, defines, sourceText, context.Position)) - let! _parsingOptions, projectOptions = projectInfoManager.TryGetOptionsForEditingDocumentOrProject(document, context.CancellationToken, userOpName) let getAllSymbols(fileCheckResults: FSharpCheckFileResults) = if settings.IntelliSense.IncludeSymbolsFromUnopenedNamespacesOrModules then assemblyContentProvider.GetAllEntitiesInProjectAndReferencedAssemblies(fileCheckResults) else [] let! results = - FSharpCompletionProvider.ProvideCompletionsAsyncAux(checker, context.Document, context.Position, projectOptions, - getAllSymbols, settings.LanguageServicePerformance, settings.IntelliSense) + FSharpCompletionProvider.ProvideCompletionsAsyncAux(context.Document, context.Position, getAllSymbols, settings.IntelliSense) context.AddItems(results) } |> Async.Ignore |> RoslynHelpers.StartAsyncUnitAsTask context.CancellationToken @@ -291,8 +283,7 @@ type internal FSharpCompletionProvider let! sourceText = document.GetTextAsync(cancellationToken) let textWithItemCommitted = sourceText.WithChanges(TextChange(item.Span, nameInCode)) let line = sourceText.Lines.GetLineFromPosition(item.Span.Start) - let! parsingOptions, _options = projectInfoManager.TryGetOptionsForEditingDocumentOrProject(document, cancellationToken, userOpName) - let! parseResults = checker.ParseDocument(document, parsingOptions, userOpName) + let! parseResults = document.GetFSharpParseResultsAsync(nameof(FSharpCompletionProvider)) |> liftAsync let fullNameIdents = fullName |> Option.map (fun x -> x.Split '.') |> Option.defaultValue [||] let insertionPoint = diff --git a/vsintegration/src/FSharp.Editor/Completion/CompletionService.fs b/vsintegration/src/FSharp.Editor/Completion/CompletionService.fs index e2c03429d55..ccdadf504f7 100644 --- a/vsintegration/src/FSharp.Editor/Completion/CompletionService.fs +++ b/vsintegration/src/FSharp.Editor/Completion/CompletionService.fs @@ -17,16 +17,16 @@ type internal FSharpCompletionService ( workspace: Workspace, serviceProvider: SVsServiceProvider, - checkerProvider: FSharpCheckerProvider, - projectInfoManager: FSharpProjectOptionsManager, assemblyContentProvider: AssemblyContentProvider, settings: EditorOptions ) = inherit CompletionServiceWithProviders(workspace) + let projectInfoManager = workspace.Services.GetRequiredService().FSharpProjectOptionsManager + let builtInProviders = ImmutableArray.Create( - FSharpCompletionProvider(workspace, serviceProvider, checkerProvider, projectInfoManager, assemblyContentProvider), + FSharpCompletionProvider(workspace, serviceProvider, assemblyContentProvider), FSharpCommonCompletionProvider.Create(HashDirectiveCompletionProvider.Create(workspace, projectInfoManager))) override _.Language = FSharpConstants.FSharpLanguageName @@ -56,13 +56,11 @@ type internal FSharpCompletionServiceFactory [] ( serviceProvider: SVsServiceProvider, - checkerProvider: FSharpCheckerProvider, - projectInfoManager: FSharpProjectOptionsManager, assemblyContentProvider: AssemblyContentProvider, settings: EditorOptions ) = interface ILanguageServiceFactory with member _.CreateLanguageService(hostLanguageServices: HostLanguageServices) : ILanguageService = - upcast new FSharpCompletionService(hostLanguageServices.WorkspaceServices.Workspace, serviceProvider, checkerProvider, projectInfoManager, assemblyContentProvider, settings) + upcast new FSharpCompletionService(hostLanguageServices.WorkspaceServices.Workspace, serviceProvider, assemblyContentProvider, settings) diff --git a/vsintegration/src/FSharp.Editor/Completion/SignatureHelp.fs b/vsintegration/src/FSharp.Editor/Completion/SignatureHelp.fs index 96ff3b2d7a2..8bc06631418 100644 --- a/vsintegration/src/FSharp.Editor/Completion/SignatureHelp.fs +++ b/vsintegration/src/FSharp.Editor/Completion/SignatureHelp.fs @@ -53,12 +53,9 @@ type SignatureHelpData = type internal FSharpSignatureHelpProvider [] ( - serviceProvider: SVsServiceProvider, - checkerProvider: FSharpCheckerProvider, - projectInfoManager: FSharpProjectOptionsManager + serviceProvider: SVsServiceProvider ) = - static let userOpName = "SignatureHelpProvider" let documentationBuilder = XmlDocumentation.CreateDocumentationBuilder(serviceProvider.XMLMemberIndexService) static let oneColAfter (lp: LinePosition) = LinePosition(lp.Line,lp.Character+1) @@ -500,23 +497,20 @@ type internal FSharpSignatureHelpProvider ( document: Document, defines: string list, - checker: FSharpChecker, documentationBuilder: IDocumentationBuilder, caretPosition: int, - options: FSharpProjectOptions, triggerTypedChar: char option, possibleCurrentSignatureHelpSessionKind: CurrentSignatureHelpSessionKind option ) = asyncMaybe { + let! parseResults, checkFileResults = document.GetFSharpParseAndCheckResultsAsync("ProvideSignatureHelp") |> liftAsync + let! sourceText = document.GetTextAsync() |> liftTaskAsync let textLines = sourceText.Lines - let perfOptions = document.FSharpOptions.LanguageServicePerformance let caretLinePos = textLines.GetLinePosition(caretPosition) let caretLineColumn = caretLinePos.Character - let! parseResults, _, checkFileResults = checker.ParseAndCheckDocument(document, options, perfOptions, userOpName = userOpName) - let adjustedColumnInSource = let rec loop ch pos = if Char.IsWhiteSpace(ch) then @@ -575,9 +569,7 @@ type internal FSharpSignatureHelpProvider member _.GetItemsAsync(document, position, triggerInfo, cancellationToken) = asyncMaybe { - let! _, projectOptions = projectInfoManager.TryGetOptionsForEditingDocumentOrProject(document, cancellationToken, userOpName) - let defines = projectInfoManager.GetCompilationDefinesForEditingDocument(document) - let checker = checkerProvider.Checker + let defines = document.GetFSharpQuickDefines() let triggerTypedChar = if triggerInfo.TriggerCharacter.HasValue && triggerInfo.TriggerReason = FSharpSignatureHelpTriggerReason.TypeCharCommand then @@ -590,10 +582,8 @@ type internal FSharpSignatureHelpProvider FSharpSignatureHelpProvider.ProvideSignatureHelp( document, defines, - checker, documentationBuilder, position, - projectOptions, triggerTypedChar, possibleCurrentSignatureHelpSessionKind) match signatureHelpDataOpt with diff --git a/vsintegration/src/FSharp.Editor/Debugging/BreakpointResolutionService.fs b/vsintegration/src/FSharp.Editor/Debugging/BreakpointResolutionService.fs index 77f3081d879..f0fe54af81d 100644 --- a/vsintegration/src/FSharp.Editor/Debugging/BreakpointResolutionService.fs +++ b/vsintegration/src/FSharp.Editor/Debugging/BreakpointResolutionService.fs @@ -21,12 +21,9 @@ open FSharp.Compiler.Text.Position type internal FSharpBreakpointResolutionService [] ( - checkerProvider: FSharpCheckerProvider, - projectInfoManager: FSharpProjectOptionsManager ) = - static let userOpName = "BreakpointResolution" - static member GetBreakpointLocation(checker: FSharpChecker, document: Document, textSpan: TextSpan, parsingOptions: FSharpParsingOptions) = + static member GetBreakpointLocation(document: Document, textSpan: TextSpan) = async { let! ct = Async.CancellationToken @@ -40,7 +37,7 @@ type internal FSharpBreakpointResolutionService else let textLineColumn = textLinePos.Character let fcsTextLineNumber = Line.fromZ textLinePos.Line // Roslyn line numbers are zero-based, FSharp.Compiler.Service line numbers are 1-based - let! parseResults = checker.ParseDocument(document, parsingOptions, userOpName) + let! parseResults = document.GetFSharpParseResultsAsync(nameof(FSharpBreakpointResolutionService)) |> liftAsync match parseResults with | Some parseResults -> return parseResults.ValidateBreakpointLocation(mkPos fcsTextLineNumber textLineColumn) | _ -> return None @@ -49,8 +46,7 @@ type internal FSharpBreakpointResolutionService interface IFSharpBreakpointResolutionService with member this.ResolveBreakpointAsync(document: Document, textSpan: TextSpan, cancellationToken: CancellationToken): Task = asyncMaybe { - let! parsingOptions, _options = projectInfoManager.TryGetOptionsForEditingDocumentOrProject(document, cancellationToken, userOpName) - let! range = FSharpBreakpointResolutionService.GetBreakpointLocation(checkerProvider.Checker, document, textSpan, parsingOptions) + let! range = FSharpBreakpointResolutionService.GetBreakpointLocation(document, textSpan) let! sourceText = document.GetTextAsync(cancellationToken) let! span = RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, range) return FSharpBreakpointResolutionResult.CreateSpanResult(document, span) diff --git a/vsintegration/src/FSharp.Editor/Debugging/LanguageDebugInfoService.fs b/vsintegration/src/FSharp.Editor/Debugging/LanguageDebugInfoService.fs index f633b7d54d5..d2b30fc01bb 100644 --- a/vsintegration/src/FSharp.Editor/Debugging/LanguageDebugInfoService.fs +++ b/vsintegration/src/FSharp.Editor/Debugging/LanguageDebugInfoService.fs @@ -15,7 +15,7 @@ open Microsoft.CodeAnalysis.Text open Microsoft.CodeAnalysis.ExternalAccess.FSharp.Editor.Implementation.Debugging [)>] -type internal FSharpLanguageDebugInfoService [](projectInfoManager: FSharpProjectOptionsManager) = +type internal FSharpLanguageDebugInfoService []() = static member GetDataTipInformation(sourceText: SourceText, position: int, tokens: List): TextSpan option = let tokenIndex = tokens |> Seq.tryFindIndex(fun t -> t.TextSpan.Contains(position)) @@ -49,7 +49,7 @@ type internal FSharpLanguageDebugInfoService [](projectInf member this.GetDataTipInfoAsync(document: Document, position: int, cancellationToken: CancellationToken): Task = async { - let defines = projectInfoManager.GetCompilationDefinesForEditingDocument(document) + let defines = document.GetFSharpQuickDefines() let! cancellationToken = Async.CancellationToken let! sourceText = document.GetTextAsync(cancellationToken) |> Async.AwaitTask let textSpan = TextSpan.FromBounds(0, sourceText.Length) diff --git a/vsintegration/src/FSharp.Editor/Diagnostics/DocumentDiagnosticAnalyzer.fs b/vsintegration/src/FSharp.Editor/Diagnostics/DocumentDiagnosticAnalyzer.fs index dd26fbf1026..4f8e55f23fd 100644 --- a/vsintegration/src/FSharp.Editor/Diagnostics/DocumentDiagnosticAnalyzer.fs +++ b/vsintegration/src/FSharp.Editor/Diagnostics/DocumentDiagnosticAnalyzer.fs @@ -25,12 +25,8 @@ type internal DiagnosticsType = type internal FSharpDocumentDiagnosticAnalyzer [] ( - checkerProvider: FSharpCheckerProvider, - projectInfoManager: FSharpProjectOptionsManager ) = - static let userOpName = "DocumentDiagnosticAnalyzer" - static let errorInfoEqualityComparer = { new IEqualityComparer with member _.Equals (x, y) = @@ -56,14 +52,11 @@ type internal FSharpDocumentDiagnosticAnalyzer hash } - static member GetDiagnostics(checker: FSharpChecker, document: Document, parsingOptions: FSharpParsingOptions, options: FSharpProjectOptions, diagnosticType: DiagnosticsType) = + static member GetDiagnostics(document: Document, diagnosticType: DiagnosticsType) = async { let! ct = Async.CancellationToken - let! parseResults = checker.ParseDocument(document, parsingOptions, userOpName) - match parseResults with - | None -> return ImmutableArray.Empty - | Some parseResults -> + let! parseResults = document.GetFSharpParseResultsAsync("GetDiagnostics") let! sourceText = document.GetTextAsync(ct) |> Async.AwaitTask let filePath = document.FilePath @@ -72,14 +65,11 @@ type internal FSharpDocumentDiagnosticAnalyzer async { match diagnosticType with | DiagnosticsType.Semantic -> - let! checkResultsAnswer = checker.CheckDocument(document, parseResults, options, userOpName) - match checkResultsAnswer with - | FSharpCheckFileAnswer.Aborted -> return [||] - | FSharpCheckFileAnswer.Succeeded results -> - // In order to eleminate duplicates, we should not return parse errors here because they are returned by `AnalyzeSyntaxAsync` method. - let allErrors = HashSet(results.Diagnostics, errorInfoEqualityComparer) - allErrors.ExceptWith(parseResults.Diagnostics) - return Seq.toArray allErrors + let! _, checkResults = document.GetFSharpParseAndCheckResultsAsync("GetDiagnostics") + // In order to eleminate duplicates, we should not return parse errors here because they are returned by `AnalyzeSyntaxAsync` method. + let allErrors = HashSet(checkResults.Diagnostics, errorInfoEqualityComparer) + allErrors.ExceptWith(parseResults.Diagnostics) + return Seq.toArray allErrors | DiagnosticsType.Syntax -> return parseResults.Diagnostics } @@ -119,9 +109,8 @@ type internal FSharpDocumentDiagnosticAnalyzer else asyncMaybe { - let! parsingOptions, projectOptions = projectInfoManager.TryGetOptionsForEditingDocumentOrProject(document, cancellationToken, userOpName) return! - FSharpDocumentDiagnosticAnalyzer.GetDiagnostics(checkerProvider.Checker, document, parsingOptions, projectOptions, DiagnosticsType.Syntax) + FSharpDocumentDiagnosticAnalyzer.GetDiagnostics(document, DiagnosticsType.Syntax) |> liftAsync } |> Async.map (Option.defaultValue ImmutableArray.Empty) @@ -132,9 +121,8 @@ type internal FSharpDocumentDiagnosticAnalyzer else asyncMaybe { - let! parsingOptions, _, projectOptions = projectInfoManager.TryGetOptionsForDocumentOrProject(document, cancellationToken, userOpName) return! - FSharpDocumentDiagnosticAnalyzer.GetDiagnostics(checkerProvider.Checker, document, parsingOptions, projectOptions, DiagnosticsType.Semantic) + FSharpDocumentDiagnosticAnalyzer.GetDiagnostics(document, DiagnosticsType.Semantic) |> liftAsync } |> Async.map (Option.defaultValue ImmutableArray.Empty) diff --git a/vsintegration/src/FSharp.Editor/Diagnostics/SimplifyNameDiagnosticAnalyzer.fs b/vsintegration/src/FSharp.Editor/Diagnostics/SimplifyNameDiagnosticAnalyzer.fs index 64007b19de8..0c853a404cf 100644 --- a/vsintegration/src/FSharp.Editor/Diagnostics/SimplifyNameDiagnosticAnalyzer.fs +++ b/vsintegration/src/FSharp.Editor/Diagnostics/SimplifyNameDiagnosticAnalyzer.fs @@ -20,8 +20,6 @@ type private PerDocumentSavedData = { Hash: int; Diagnostics: ImmutableArray] ( - checkerProvider: FSharpCheckerProvider, - projectInfoManager: FSharpProjectOptionsManager ) = static let userOpName = "SimplifyNameDiagnosticAnalyzer" @@ -38,9 +36,8 @@ type internal SimplifyNameDiagnosticAnalyzer else asyncMaybe { - do! Option.guard document.FSharpOptions.CodeFixes.SimplifyName + do! Option.guard document.Project.IsFSharpCodeFixesSimplifyNameEnabled do Trace.TraceInformation("{0:n3} (start) SimplifyName", DateTime.Now.TimeOfDay.TotalSeconds) - let! _parsingOptions, projectOptions = projectInfoManager.TryGetOptionsForEditingDocumentOrProject(document, cancellationToken, userOpName) let! textVersion = document.GetTextVersionAsync(cancellationToken) let textVersionHash = textVersion.GetHashCode() let! _ = guard.WaitAsync(cancellationToken) |> Async.AwaitTask |> liftAsync @@ -50,8 +47,7 @@ type internal SimplifyNameDiagnosticAnalyzer | :? PerDocumentSavedData as data when data.Hash = textVersionHash -> return data.Diagnostics | _ -> let! sourceText = document.GetTextAsync() - let checker = checkerProvider.Checker - let! _, _, checkResults = checker.ParseAndCheckDocument(document, projectOptions, userOpName=userOpName) + let! _, checkResults = document.GetFSharpParseAndCheckResultsAsync(nameof(SimplifyNameDiagnosticAnalyzer)) |> liftAsync let! result = SimplifyNames.getSimplifiableNames(checkResults, fun lineNumber -> sourceText.Lines.[Line.toZ lineNumber].ToString()) |> liftAsync let mutable diag = ResizeArray() for r in result do diff --git a/vsintegration/src/FSharp.Editor/Diagnostics/UnusedDeclarationsAnalyzer.fs b/vsintegration/src/FSharp.Editor/Diagnostics/UnusedDeclarationsAnalyzer.fs index 094796c9e36..c94b193e6aa 100644 --- a/vsintegration/src/FSharp.Editor/Diagnostics/UnusedDeclarationsAnalyzer.fs +++ b/vsintegration/src/FSharp.Editor/Diagnostics/UnusedDeclarationsAnalyzer.fs @@ -15,11 +15,7 @@ open Microsoft.CodeAnalysis.ExternalAccess.FSharp.Diagnostics type internal UnusedDeclarationsAnalyzer [] ( - checkerProvider: FSharpCheckerProvider, - projectInfoManager: FSharpProjectOptionsManager ) = - - static let userOpName = "UnusedDeclarationsAnalyzer" interface IFSharpUnusedDeclarationsDiagnosticAnalyzer with @@ -28,19 +24,16 @@ type internal UnusedDeclarationsAnalyzer else asyncMaybe { - do! Option.guard document.FSharpOptions.CodeFixes.UnusedDeclarations + do! Option.guard document.Project.IsFSharpCodeFixesUnusedDeclarationsEnabled do Trace.TraceInformation("{0:n3} (start) UnusedDeclarationsAnalyzer", DateTime.Now.TimeOfDay.TotalSeconds) - match! projectInfoManager.TryGetOptionsForEditingDocumentOrProject(document, cancellationToken, userOpName) with - | (_parsingOptions, projectOptions) -> - let! sourceText = document.GetTextAsync() - let checker = checkerProvider.Checker - let! _, _, checkResults = checker.ParseAndCheckDocument(document, projectOptions, userOpName = userOpName) - let! unusedRanges = UnusedDeclarations.getUnusedDeclarations( checkResults, (isScriptFile document.FilePath)) |> liftAsync - return - unusedRanges - |> Seq.map (fun m -> Diagnostic.Create(descriptor, RoslynHelpers.RangeToLocation(m, sourceText, document.FilePath))) - |> Seq.toImmutableArray + let! _, checkResults = document.GetFSharpParseAndCheckResultsAsync(nameof(UnusedDeclarationsAnalyzer)) |> liftAsync + let! unusedRanges = UnusedDeclarations.getUnusedDeclarations( checkResults, (isScriptFile document.FilePath)) |> liftAsync + let! sourceText = document.GetTextAsync() + return + unusedRanges + |> Seq.map (fun m -> Diagnostic.Create(descriptor, RoslynHelpers.RangeToLocation(m, sourceText, document.FilePath))) + |> Seq.toImmutableArray } |> Async.map (Option.defaultValue ImmutableArray.Empty) |> RoslynHelpers.StartAsyncAsTask cancellationToken diff --git a/vsintegration/src/FSharp.Editor/Diagnostics/UnusedOpensDiagnosticAnalyzer.fs b/vsintegration/src/FSharp.Editor/Diagnostics/UnusedOpensDiagnosticAnalyzer.fs index c63341ecd0a..9c75bab971e 100644 --- a/vsintegration/src/FSharp.Editor/Diagnostics/UnusedOpensDiagnosticAnalyzer.fs +++ b/vsintegration/src/FSharp.Editor/Diagnostics/UnusedOpensDiagnosticAnalyzer.fs @@ -20,17 +20,13 @@ open Microsoft.CodeAnalysis.ExternalAccess.FSharp.Diagnostics type internal UnusedOpensDiagnosticAnalyzer [] ( - checkerProvider: FSharpCheckerProvider, - projectInfoManager: FSharpProjectOptionsManager ) = - static let userOpName = "UnusedOpensAnalyzer" - - static member GetUnusedOpenRanges(document: Document, options, checker: FSharpChecker) : Async> = + static member GetUnusedOpenRanges(document: Document) : Async> = asyncMaybe { - do! Option.guard document.FSharpOptions.CodeFixes.UnusedOpens + do! Option.guard document.Project.IsFSharpCodeFixesUnusedOpensEnabled let! sourceText = document.GetTextAsync() - let! _, _, checkResults = checker.ParseAndCheckDocument(document, options, userOpName = userOpName) + let! _, checkResults = document.GetFSharpParseAndCheckResultsAsync(nameof(UnusedOpensDiagnosticAnalyzer)) |> liftAsync let! unusedOpens = UnusedOpens.getUnusedOpens(checkResults, fun lineNumber -> sourceText.Lines.[Line.toZ lineNumber].ToString()) |> liftAsync return unusedOpens } @@ -43,10 +39,8 @@ type internal UnusedOpensDiagnosticAnalyzer asyncMaybe { do Trace.TraceInformation("{0:n3} (start) UnusedOpensAnalyzer", DateTime.Now.TimeOfDay.TotalSeconds) - let! _parsingOptions, projectOptions = projectInfoManager.TryGetOptionsForEditingDocumentOrProject(document, cancellationToken, userOpName) let! sourceText = document.GetTextAsync() - let checker = checkerProvider.Checker - let! unusedOpens = UnusedOpensDiagnosticAnalyzer.GetUnusedOpenRanges(document, projectOptions, checker) + let! unusedOpens = UnusedOpensDiagnosticAnalyzer.GetUnusedOpenRanges(document) return unusedOpens diff --git a/vsintegration/src/FSharp.Editor/DocumentHighlights/DocumentHighlightsService.fs b/vsintegration/src/FSharp.Editor/DocumentHighlights/DocumentHighlightsService.fs index c2309227658..207c1dd1e5c 100644 --- a/vsintegration/src/FSharp.Editor/DocumentHighlights/DocumentHighlightsService.fs +++ b/vsintegration/src/FSharp.Editor/DocumentHighlights/DocumentHighlightsService.fs @@ -21,9 +21,7 @@ type internal FSharpHighlightSpan = override this.ToString() = sprintf "%+A" this [)>] -type internal FSharpDocumentHighlightsService [] (checkerProvider: FSharpCheckerProvider, projectInfoManager: FSharpProjectOptionsManager) = - - static let userOpName = "DocumentHighlights" +type internal FSharpDocumentHighlightsService [] () = /// Fix invalid spans if they appear to have redundant suffix and prefix. static let fixInvalidSymbolSpans (sourceText: SourceText) (lastIdent: string) (spans: FSharpHighlightSpan []) = @@ -51,16 +49,17 @@ type internal FSharpDocumentHighlightsService [] (checkerP |> Seq.distinctBy (fun span -> span.TextSpan.Start) |> Seq.toArray - static member GetDocumentHighlights(checker: FSharpChecker, document: Document, position: int, - defines: string list, options: FSharpProjectOptions, languageServicePerformanceOptions: LanguageServicePerformanceOptions) : Async = + static member GetDocumentHighlights(document: Document, position: int) : Async = asyncMaybe { - let! sourceText = document.GetTextAsync() |> liftTaskAsync - let filePath = document.FilePath + let! symbol = document.TryFindFSharpLexerSymbolAsync(position, SymbolLookupKind.Greedy, false, false, nameof(FSharpDocumentHighlightsService.GetDocumentHighlights)) + + let! ct = Async.CancellationToken |> liftAsync + let! sourceText = document.GetTextAsync(ct) let textLine = sourceText.Lines.GetLineFromPosition(position) let textLinePos = sourceText.Lines.GetLinePosition(position) let fcsTextLineNumber = Line.fromZ textLinePos.Line - let! symbol = Tokenizer.getSymbolAtPosition(document.Id, sourceText, position, filePath, defines, SymbolLookupKind.Greedy, false, false) - let! _, _, checkFileResults = checker.ParseAndCheckDocument(document, options, languageServicePerformanceOptions, userOpName = userOpName) + + let! _, checkFileResults = document.GetFSharpParseAndCheckResultsAsync(nameof(FSharpDocumentHighlightsService)) |> liftAsync let! symbolUse = checkFileResults.GetSymbolUseAtLocation(fcsTextLineNumber, symbol.Ident.idRange.EndColumn, textLine.ToString(), symbol.FullIsland) let symbolUses = checkFileResults.GetUsesOfSymbolInFile(symbolUse.Symbol) return @@ -76,10 +75,7 @@ type internal FSharpDocumentHighlightsService [] (checkerP interface IFSharpDocumentHighlightsService with member _.GetDocumentHighlightsAsync(document, position, _documentsToSearch, cancellationToken) : Task> = asyncMaybe { - let! parsingOptions, projectOptions = projectInfoManager.TryGetOptionsForEditingDocumentOrProject(document, cancellationToken, userOpName) - let defines = CompilerEnvironment.GetCompilationDefinesForEditing parsingOptions - let perfOptions = document.FSharpOptions.LanguageServicePerformance - let! spans = FSharpDocumentHighlightsService.GetDocumentHighlights(checkerProvider.Checker, document, position, defines, projectOptions, perfOptions) + let! spans = FSharpDocumentHighlightsService.GetDocumentHighlights(document, position) let highlightSpans = spans |> Array.map (fun span -> diff --git a/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj b/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj index 39f449db8bc..aff4b4c25d8 100644 --- a/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj +++ b/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj @@ -42,16 +42,15 @@ - - + diff --git a/vsintegration/src/FSharp.Editor/Formatting/BraceMatchingService.fs b/vsintegration/src/FSharp.Editor/Formatting/BraceMatchingService.fs index 531e8c22e4e..edd3920d7c0 100644 --- a/vsintegration/src/FSharp.Editor/Formatting/BraceMatchingService.fs +++ b/vsintegration/src/FSharp.Editor/Formatting/BraceMatchingService.fs @@ -12,11 +12,7 @@ open Microsoft.CodeAnalysis.ExternalAccess.FSharp.Editor type internal FSharpBraceMatchingService [] ( - checkerProvider: FSharpCheckerProvider, - projectInfoManager: FSharpProjectOptionsManager ) = - - static let userOpName = "BraceMatching" static member GetBraceMatchingResult(checker: FSharpChecker, sourceText: SourceText, fileName, parsingOptions: FSharpParsingOptions, position: int, userOpName: string, [] forFormatting: bool) = async { @@ -36,9 +32,9 @@ type internal FSharpBraceMatchingService interface IFSharpBraceMatcher with member this.FindBracesAsync(document, position, cancellationToken) = asyncMaybe { - let! parsingOptions, _options = projectInfoManager.TryGetOptionsForEditingDocumentOrProject(document, cancellationToken, userOpName) + let! checker, _, parsingOptions, _ = document.GetFSharpCompilationOptionsAsync(nameof(FSharpBraceMatchingService)) |> liftAsync let! sourceText = document.GetTextAsync(cancellationToken) - let! (left, right) = FSharpBraceMatchingService.GetBraceMatchingResult(checkerProvider.Checker, sourceText, document.Name, parsingOptions, position, userOpName) + let! (left, right) = FSharpBraceMatchingService.GetBraceMatchingResult(checker, sourceText, document.Name, parsingOptions, position, nameof(FSharpBraceMatchingService)) let! leftSpan = RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, left) let! rightSpan = RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, right) return FSharpBraceMatchingResult(leftSpan, rightSpan) diff --git a/vsintegration/src/FSharp.Editor/Formatting/EditorFormattingService.fs b/vsintegration/src/FSharp.Editor/Formatting/EditorFormattingService.fs index 04f21aa306d..b35d122d04b 100644 --- a/vsintegration/src/FSharp.Editor/Formatting/EditorFormattingService.fs +++ b/vsintegration/src/FSharp.Editor/Formatting/EditorFormattingService.fs @@ -20,8 +20,6 @@ open System.Windows.Forms type internal FSharpEditorFormattingService [] ( - checkerProvider: FSharpCheckerProvider, - projectInfoManager: FSharpProjectOptionsManager, settings: EditorOptions ) = @@ -153,8 +151,8 @@ type internal FSharpEditorFormattingService let! sourceText = document.GetTextAsync(cancellationToken) |> Async.AwaitTask let! options = document.GetOptionsAsync(cancellationToken) |> Async.AwaitTask let indentStyle = options.GetOption(FormattingOptions.SmartIndent, FSharpConstants.FSharpLanguageName) - let parsingOptions = projectInfoManager.TryGetQuickParsingOptionsForEditingDocumentOrProject(document) - let! textChange = FSharpEditorFormattingService.GetFormattingChanges(document.Id, sourceText, document.FilePath, checkerProvider.Checker, indentStyle, parsingOptions, position) + let parsingOptions = document.GetFSharpQuickParsingOptions() + let! textChange = FSharpEditorFormattingService.GetFormattingChanges(document.Id, sourceText, document.FilePath, document.GetFSharpChecker(), indentStyle, parsingOptions, position) return textChange |> Option.toList |> toIList } @@ -164,7 +162,7 @@ type internal FSharpEditorFormattingService let! options = document.GetOptionsAsync(cancellationToken) |> Async.AwaitTask let tabSize = options.GetOption(FormattingOptions.TabSize, FSharpConstants.FSharpLanguageName) - let parsingOptions = projectInfoManager.TryGetQuickParsingOptionsForEditingDocumentOrProject(document) + let parsingOptions = document.GetFSharpQuickParsingOptions() let! textChanges = FSharpEditorFormattingService.GetPasteChanges(document.Id, sourceText, document.FilePath, settings.Formatting, tabSize, parsingOptions, currentClipboard, span) return textChanges |> Option.defaultValue Seq.empty |> toIList } diff --git a/vsintegration/src/FSharp.Editor/Formatting/IndentationService.fs b/vsintegration/src/FSharp.Editor/Formatting/IndentationService.fs index c70c38b991f..b540e06caf4 100644 --- a/vsintegration/src/FSharp.Editor/Formatting/IndentationService.fs +++ b/vsintegration/src/FSharp.Editor/Formatting/IndentationService.fs @@ -21,7 +21,7 @@ open FSharp.Compiler.Tokenization [)>] type internal FSharpIndentationService [] - (projectInfoManager: FSharpProjectOptionsManager) = + () = static member IsSmartIndentEnabled (options: Microsoft.CodeAnalysis.Options.OptionSet) = options.GetOption(FormattingOptions.SmartIndent, FSharpConstants.FSharpLanguageName) = FormattingOptions.IndentStyle.Smart @@ -101,7 +101,7 @@ type internal FSharpIndentationService let! options = document.GetOptionsAsync(cancellationToken) |> Async.AwaitTask let tabSize = options.GetOption(FormattingOptions.TabSize, FSharpConstants.FSharpLanguageName) let indentStyle = options.GetOption(FormattingOptions.SmartIndent, FSharpConstants.FSharpLanguageName) - let parsingOptions = projectInfoManager.TryGetQuickParsingOptionsForEditingDocumentOrProject(document) + let parsingOptions = document.GetFSharpQuickParsingOptions() let indent = FSharpIndentationService.GetDesiredIndentation(document.Id, sourceText, document.FilePath, lineNumber, tabSize, indentStyle, parsingOptions) return match indent with diff --git a/vsintegration/src/FSharp.Editor/InlineRename/InlineRenameService.fs b/vsintegration/src/FSharp.Editor/InlineRename/InlineRenameService.fs index d08d365f0c6..51a9efe9951 100644 --- a/vsintegration/src/FSharp.Editor/InlineRename/InlineRenameService.fs +++ b/vsintegration/src/FSharp.Editor/InlineRename/InlineRenameService.fs @@ -71,8 +71,6 @@ type internal InlineRenameLocationSet(locations: FSharpInlineRenameLocation [], type internal InlineRenameInfo ( - checker: FSharpChecker, - projectInfoManager: FSharpProjectOptionsManager, document: Document, triggerSpan: TextSpan, lexerSymbol: LexerSymbol, @@ -81,15 +79,13 @@ type internal InlineRenameInfo checkFileResults: FSharpCheckFileResults ) = - static let userOpName = "InlineRename" - let getDocumentText (document: Document) cancellationToken = match document.TryGetText() with | true, text -> text | _ -> document.GetTextAsync(cancellationToken).Result - let symbolUses = - SymbolHelpers.getSymbolUsesInSolution(symbolUse.Symbol, declLoc, checkFileResults, projectInfoManager, checker, document.Project.Solution, userOpName) + let symbolUses = + SymbolHelpers.getSymbolUsesInSolution(symbolUse.Symbol, declLoc, checkFileResults, document.Project.Solution) |> Async.cache interface IFSharpInlineRenameInfo with @@ -144,35 +140,31 @@ type internal InlineRenameInfo type internal InlineRenameService [] ( - projectInfoManager: FSharpProjectOptionsManager, - checkerProvider: FSharpCheckerProvider ) = - static let userOpName = "InlineRename" - static member GetInlineRenameInfo(checker: FSharpChecker, projectInfoManager: FSharpProjectOptionsManager, document: Document, sourceText: SourceText, position: int, - defines: string list, options: FSharpProjectOptions) : Async = + static member GetInlineRenameInfo(document: Document, position: int) : Async = asyncMaybe { + let! ct = Async.CancellationToken |> liftAsync + let! sourceText = document.GetTextAsync(ct) let textLine = sourceText.Lines.GetLineFromPosition(position) let textLinePos = sourceText.Lines.GetLinePosition(position) let fcsTextLineNumber = Line.fromZ textLinePos.Line - let! symbol = Tokenizer.getSymbolAtPosition(document.Id, sourceText, position, document.FilePath, defines, SymbolLookupKind.Greedy, false, false) - let! _, _, checkFileResults = checker.ParseAndCheckDocument(document, options, userOpName = userOpName) + let! symbol = document.TryFindFSharpLexerSymbolAsync(position, SymbolLookupKind.Greedy, false, false, nameof(InlineRenameService)) + + let! _, checkFileResults = document.GetFSharpParseAndCheckResultsAsync(nameof(InlineRenameService)) |> liftAsync let! symbolUse = checkFileResults.GetSymbolUseAtLocation(fcsTextLineNumber, symbol.Ident.idRange.EndColumn, textLine.Text.ToString(), symbol.FullIsland) let! declLoc = symbolUse.GetDeclarationLocation(document) let! span = RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, symbolUse.Range) let triggerSpan = Tokenizer.fixupSpan(sourceText, span) - return InlineRenameInfo(checker, projectInfoManager, document, triggerSpan, symbol, symbolUse, declLoc, checkFileResults) :> IFSharpInlineRenameInfo + return InlineRenameInfo(document, triggerSpan, symbol, symbolUse, declLoc, checkFileResults) :> IFSharpInlineRenameInfo } interface IFSharpEditorInlineRenameService with member _.GetRenameInfoAsync(document: Document, position: int, cancellationToken: CancellationToken) : Task = asyncMaybe { - let! parsingOptions, projectOptions = projectInfoManager.TryGetOptionsForEditingDocumentOrProject(document, cancellationToken, userOpName) - let! sourceText = document.GetTextAsync(cancellationToken) - let defines = CompilerEnvironment.GetCompilationDefinesForEditing parsingOptions - return! InlineRenameService.GetInlineRenameInfo(checkerProvider.Checker, projectInfoManager, document, sourceText, position, defines, projectOptions) + return! InlineRenameService.GetInlineRenameInfo(document, position) } |> Async.map (Option.defaultValue FailureInlineRenameInfo.Instance) |> RoslynHelpers.StartAsyncAsTask(cancellationToken) diff --git a/vsintegration/src/FSharp.Editor/LanguageService/FSharpCheckerExtensions.fs b/vsintegration/src/FSharp.Editor/LanguageService/FSharpCheckerExtensions.fs deleted file mode 100644 index 44e2b4b9684..00000000000 --- a/vsintegration/src/FSharp.Editor/LanguageService/FSharpCheckerExtensions.fs +++ /dev/null @@ -1,98 +0,0 @@ -[] -module internal Microsoft.VisualStudio.FSharp.Editor.FSharpCheckerExtensions - -open System - -open Microsoft.CodeAnalysis -open Microsoft.CodeAnalysis.Text - -open FSharp.Compiler.CodeAnalysis - -type FSharpChecker with - member checker.ParseDocument(document: Document, parsingOptions: FSharpParsingOptions, userOpName: string) = - asyncMaybe { - let! ct = Async.CancellationToken |> liftAsync - - let! sourceText = document.GetTextAsync(ct) |> liftTaskAsync - - let! fileParseResults = checker.ParseFile(document.FilePath, sourceText.ToFSharpSourceText(), parsingOptions, userOpName=userOpName) |> liftAsync - return fileParseResults - } - - member checker.CheckDocument(document: Document, parseResults: FSharpParseFileResults, options: FSharpProjectOptions, userOpName: string) = - async { - let! ct = Async.CancellationToken - - let! sourceText = document.GetTextAsync(ct) |> Async.AwaitTask - let! textVersion = document.GetTextVersionAsync(ct) |> Async.AwaitTask - - let filePath = document.FilePath - let textVersionHash = textVersion.GetHashCode() - - return! checker.CheckFileInProject(parseResults, filePath, textVersionHash, sourceText.ToFSharpSourceText(), options,userOpName=userOpName) - } - - member checker.ParseAndCheckDocument(document: Document, options: FSharpProjectOptions, languageServicePerformanceOptions: LanguageServicePerformanceOptions, userOpName: string) = - async { - let! ct = Async.CancellationToken - - let! sourceText = document.GetTextAsync(ct) |> Async.AwaitTask - let! textVersion = document.GetTextVersionAsync(ct) |> Async.AwaitTask - - let filePath = document.FilePath - let textVersionHash = textVersion.GetHashCode() - - let parseAndCheckFile = - async { - let! (parseResults, checkFileAnswer) = checker.ParseAndCheckFileInProject(filePath, textVersionHash, sourceText.ToFSharpSourceText(), options, userOpName=userOpName) - return - match checkFileAnswer with - | FSharpCheckFileAnswer.Aborted -> - None - | FSharpCheckFileAnswer.Succeeded(checkFileResults) -> - Some (parseResults, checkFileResults) - } - - let tryGetFreshResultsWithTimeout() = - async { - let! worker = Async.StartChild(async { try return! parseAndCheckFile with | _ -> return None }, millisecondsTimeout=languageServicePerformanceOptions.TimeUntilStaleCompletion) - try - return! worker - with :? TimeoutException -> - return None // worker is cancelled at this point, we cannot return it and wait its completion anymore - } - - let bindParsedInput(results: (FSharpParseFileResults * FSharpCheckFileResults) option) = - match results with - | Some(parseResults, checkResults) -> - Some (parseResults, parseResults.ParseTree, checkResults) - | None -> None - - if languageServicePerformanceOptions.AllowStaleCompletionResults then - let! freshResults = tryGetFreshResultsWithTimeout() - - let! results = - match freshResults with - | Some x -> async.Return (Some x) - | None -> - async { - match checker.TryGetRecentCheckResultsForFile(filePath, options, userOpName=userOpName) with - | Some (parseResults, checkFileResults, _) -> - return Some (parseResults, checkFileResults) - | None -> - return! parseAndCheckFile - } - return bindParsedInput results - else - let! results = parseAndCheckFile - return bindParsedInput results - } - - member checker.ParseAndCheckDocument(document: Document, options: FSharpProjectOptions, userOpName: string, ?allowStaleResults: bool) = - async { - let perfOpts = - match allowStaleResults with - | Some b -> { document.FSharpOptions.LanguageServicePerformance with AllowStaleCompletionResults = b } - | _ -> document.FSharpOptions.LanguageServicePerformance - return! checker.ParseAndCheckDocument(document, options, perfOpts, userOpName=userOpName) - } diff --git a/vsintegration/src/FSharp.Editor/LanguageService/FSharpCheckerProvider.fs b/vsintegration/src/FSharp.Editor/LanguageService/FSharpCheckerProvider.fs deleted file mode 100644 index ae2f2ca3bf4..00000000000 --- a/vsintegration/src/FSharp.Editor/LanguageService/FSharpCheckerProvider.fs +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. - -namespace Microsoft.VisualStudio.FSharp.Editor - -open System -open System.ComponentModel.Composition -open System.Diagnostics -open FSharp.Compiler.CodeAnalysis -open FSharp.Compiler.CodeAnalysis -open FSharp.NativeInterop -open Microsoft.CodeAnalysis -open Microsoft.VisualStudio -open Microsoft.VisualStudio.FSharp.Editor -open Microsoft.VisualStudio.LanguageServices -open Microsoft.VisualStudio.LanguageServices.ProjectSystem -open Microsoft.CodeAnalysis.ExternalAccess.FSharp.Diagnostics - -#nowarn "9" // NativePtr.toNativeInt - -// Exposes FSharpChecker as MEF export -[); Composition.Shared>] -type internal FSharpCheckerProvider - [] - ( - [)>] workspace: VisualStudioWorkspace, - projectContextFactory: IWorkspaceProjectContextFactory, - settings: EditorOptions - ) = - - let metadataAsSource = FSharpMetadataAsSourceService(projectContextFactory) - - let tryGetMetadataSnapshot (path, timeStamp) = - try - let md = Microsoft.CodeAnalysis.ExternalAccess.FSharp.LanguageServices.FSharpVisualStudioWorkspaceExtensions.GetMetadata(workspace, path, timeStamp) - let amd = (md :?> AssemblyMetadata) - let mmd = amd.GetModules().[0] - let mmr = mmd.GetMetadataReader() - - // "lifetime is timed to Metadata you got from the GetMetadata(...). As long as you hold it strongly, raw - // memory we got from metadata reader will be alive. Once you are done, just let everything go and - // let finalizer handle resource rather than calling Dispose from Metadata directly. It is shared metadata. - // You shouldn't dispose it directly." - - let objToHold = box md - - // We don't expect any ilread WeakByteFile to be created when working in Visual Studio - // Debug.Assert((FSharp.Compiler.AbstractIL.ILBinaryReader.GetStatistics().weakByteFileCount = 0), "Expected weakByteFileCount to be zero when using F# in Visual Studio. Was there a problem reading a .NET binary?") - - Some (objToHold, NativePtr.toNativeInt mmr.MetadataPointer, mmr.MetadataLength) - with ex -> - // We catch all and let the backup routines in the F# compiler find the error - Assert.Exception(ex) - None - - let checker = - lazy - let checker = - FSharpChecker.Create( - projectCacheSize = settings.LanguageServicePerformance.ProjectCheckCacheSize, - keepAllBackgroundResolutions = false, - // Enabling this would mean that if devenv.exe goes above 2.3GB we do a one-off downsize of the F# Compiler Service caches - (* , MaxMemory = 2300 *) - legacyReferenceResolver=LegacyMSBuildReferenceResolver.getResolver(), - tryGetMetadataSnapshot = tryGetMetadataSnapshot, - keepAllBackgroundSymbolUses = false, - enableBackgroundItemKeyStoreAndSemanticClassification = true, - enablePartialTypeChecking = true) - checker - - member this.Checker = checker.Value - - member _.MetadataAsSource = metadataAsSource - diff --git a/vsintegration/src/FSharp.Editor/LanguageService/FSharpProjectOptionsManager.fs b/vsintegration/src/FSharp.Editor/LanguageService/FSharpProjectOptionsManager.fs index aa5d35b0815..3700d639e1e 100644 --- a/vsintegration/src/FSharp.Editor/LanguageService/FSharpProjectOptionsManager.fs +++ b/vsintegration/src/FSharp.Editor/LanguageService/FSharpProjectOptionsManager.fs @@ -6,21 +6,13 @@ open System open System.Collections.Generic open System.Collections.Concurrent open System.Collections.Immutable -open System.ComponentModel.Composition open System.IO open System.Linq open Microsoft.CodeAnalysis open FSharp.Compiler open FSharp.Compiler.CodeAnalysis -open Microsoft.VisualStudio open Microsoft.VisualStudio.FSharp.Editor -open Microsoft.VisualStudio.LanguageServices -open Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem -open Microsoft.VisualStudio.Shell open System.Threading -open Microsoft.VisualStudio.Shell.Interop -open Microsoft.VisualStudio.LanguageServices.Implementation.TaskList -open Microsoft.CodeAnalysis.ExternalAccess.FSharp.LanguageServices open Microsoft.VisualStudio.FSharp.Interactive.Session open System.Runtime.CompilerServices @@ -75,7 +67,7 @@ module private FSharpProjectOptionsHelpers = let p2 = newProject.Solution.GetProject(p2.ProjectId) doesProjectIdDiffer || ( - if p1.Language = LanguageNames.FSharp then + if p1.IsFSharp then p1.Version <> p2.Version else let v1 = p1.GetDependentVersionAsync(ct).Result @@ -84,9 +76,9 @@ module private FSharpProjectOptionsHelpers = ) ) - let isProjectInvalidated (oldProject: Project) (newProject: Project) (settings: EditorOptions) ct = + let isProjectInvalidated (oldProject: Project) (newProject: Project) ct = let hasProjectVersionChanged = hasProjectVersionChanged oldProject newProject - if settings.LanguageServicePerformance.EnableInMemoryCrossProjectReferences then + if newProject.AreFSharpInMemoryCrossProjectReferencesEnabled then hasProjectVersionChanged || hasDependentVersionChanged oldProject newProject ct else hasProjectVersionChanged @@ -99,11 +91,11 @@ type private FSharpProjectOptionsMessage = | ClearSingleFileOptionsCache of DocumentId [] -type private FSharpProjectOptionsReactor (workspace: Workspace, settings: EditorOptions, _serviceProvider, checkerProvider: FSharpCheckerProvider) = +type private FSharpProjectOptionsReactor (checker: FSharpChecker) = let cancellationTokenSource = new CancellationTokenSource() - // Hack to store command line options from HandleCommandLineChanges - let cpsCommandLineOptions = ConcurrentDictionary() + // Store command line options + let commandLineOptions = ConcurrentDictionary() let legacyProjectSites = ConcurrentDictionary() @@ -171,7 +163,7 @@ type private FSharpProjectOptionsReactor (workspace: Workspace, settings: Editor let! sourceText = document.GetTextAsync(ct) |> Async.AwaitTask let! scriptProjectOptions, _ = - checkerProvider.Checker.GetProjectOptionsFromScript(document.FilePath, + checker.GetProjectOptionsFromScript(document.FilePath, sourceText.ToFSharpSourceText(), SessionsProperties.fsiPreview, assumeDotNetFramework=not SessionsProperties.fsiUseNetCore, @@ -209,7 +201,7 @@ type private FSharpProjectOptionsReactor (workspace: Workspace, settings: Editor Stamp = Some(int64 (fileStamp.GetHashCode())) } - let parsingOptions, _ = checkerProvider.Checker.GetParsingOptionsFromProjectOptions(projectOptions) + let parsingOptions, _ = checker.GetParsingOptionsFromProjectOptions(projectOptions) singleFileCache.[document.Id] <- (fileStamp, parsingOptions, projectOptions) @@ -225,8 +217,8 @@ type private FSharpProjectOptionsReactor (workspace: Workspace, settings: Editor let tryGetProjectSite (project: Project) = // Cps - if cpsCommandLineOptions.ContainsKey project.Id then - Some (mapCpsProjectToSite(project, cpsCommandLineOptions)) + if commandLineOptions.ContainsKey project.Id then + Some (mapCpsProjectToSite(project, commandLineOptions)) else // Legacy match legacyProjectSites.TryGetValue project.Id with @@ -245,7 +237,7 @@ type private FSharpProjectOptionsReactor (workspace: Workspace, settings: Editor let referencedProjects = ResizeArray() - if settings.LanguageServicePerformance.EnableInMemoryCrossProjectReferences then + if project.AreFSharpInMemoryCrossProjectReferencesEnabled then for projectReference in project.ProjectReferences do let referencedProject = project.Solution.GetProject(projectReference.ProjectId) if referencedProject.Language = FSharpConstants.FSharpLanguageName then @@ -303,7 +295,7 @@ type private FSharpProjectOptionsReactor (workspace: Workspace, settings: Editor return None else // Clear any caches that need clearing and invalidate the project. - let currentSolution = workspace.CurrentSolution + let currentSolution = project.Solution.Workspace.CurrentSolution let projectsToClearCache = cache |> Seq.filter (fun pair -> not (currentSolution.ContainsProject pair.Key)) @@ -316,7 +308,7 @@ type private FSharpProjectOptionsReactor (workspace: Workspace, settings: Editor |> Seq.map (fun pair -> let _, _, projectOptions = pair.Value projectOptions) - checkerProvider.Checker.ClearCache(options, userOpName = "tryComputeOptions") + checker.ClearCache(options, userOpName = "tryComputeOptions") lastSuccessfulCompilations.ToArray() |> Array.iter (fun pair -> @@ -324,16 +316,16 @@ type private FSharpProjectOptionsReactor (workspace: Workspace, settings: Editor lastSuccessfulCompilations.TryRemove(pair.Key) |> ignore ) - checkerProvider.Checker.InvalidateConfiguration(projectOptions, userOpName = "tryComputeOptions") + checker.InvalidateConfiguration(projectOptions, userOpName = "tryComputeOptions") - let parsingOptions, _ = checkerProvider.Checker.GetParsingOptionsFromProjectOptions(projectOptions) + let parsingOptions, _ = checker.GetParsingOptionsFromProjectOptions(projectOptions) cache.[projectId] <- (project, parsingOptions, projectOptions) return Some(parsingOptions, projectOptions) | true, (oldProject, parsingOptions, projectOptions) -> - if isProjectInvalidated oldProject project settings ct then + if isProjectInvalidated oldProject project ct then cache.TryRemove(projectId) |> ignore return! tryComputeOptions project ct else @@ -401,7 +393,7 @@ type private FSharpProjectOptionsReactor (workspace: Workspace, settings: Editor match cache.TryRemove(projectId) with | true, (_, _, projectOptions) -> lastSuccessfulCompilations.TryRemove(projectId) |> ignore - checkerProvider.Checker.ClearCache([projectOptions]) + checker.ClearCache([projectOptions]) | _ -> () legacyProjectSites.TryRemove(projectId) |> ignore @@ -409,7 +401,7 @@ type private FSharpProjectOptionsReactor (workspace: Workspace, settings: Editor match singleFileCache.TryRemove(documentId) with | true, (_, _, projectOptions) -> lastSuccessfulCompilations.TryRemove(documentId.ProjectId) |> ignore - checkerProvider.Checker.ClearCache([projectOptions]) + checker.ClearCache([projectOptions]) | _ -> () } @@ -428,8 +420,8 @@ type private FSharpProjectOptionsReactor (workspace: Workspace, settings: Editor member _.ClearSingleFileOptionsCache(documentId) = agent.Post(FSharpProjectOptionsMessage.ClearSingleFileOptionsCache(documentId)) - member _.SetCpsCommandLineOptions(projectId, sourcePaths, options) = - cpsCommandLineOptions.[projectId] <- (sourcePaths, options) + member _.SetCommandLineOptions(projectId, sourcePaths, options) = + commandLineOptions.[projectId] <- (sourcePaths, options) member _.SetLegacyProjectSite (projectId, projectSite) = legacyProjectSites.[projectId] <- projectSite @@ -440,7 +432,7 @@ type private FSharpProjectOptionsReactor (workspace: Workspace, settings: Editor | _ -> None member _.ClearAllCaches() = - cpsCommandLineOptions.Clear() + commandLineOptions.Clear() legacyProjectSites.Clear() cache.Clear() singleFileCache.Clear() @@ -452,26 +444,14 @@ type private FSharpProjectOptionsReactor (workspace: Workspace, settings: Editor cancellationTokenSource.Dispose() (agent :> IDisposable).Dispose() -/// Exposes FCS FSharpProjectOptions information management as MEF component. -// -// This service allows analyzers to get an appropriate FSharpProjectOptions value for a project or single file. -// It also allows a 'cheaper' route to get the project options relevant to parsing (e.g. the #define values). -// The main entrypoints are TryGetOptionsForDocumentOrProject and TryGetOptionsForEditingDocumentOrProject. -[); Composition.Shared>] +/// Manages mappings of Roslyn workspace Projects/Documents to FCS. type internal FSharpProjectOptionsManager - [] ( - checkerProvider: FSharpCheckerProvider, - [)>] workspace: VisualStudioWorkspace, - [)>] serviceProvider: System.IServiceProvider, - settings: EditorOptions + checker: FSharpChecker, + workspace: Workspace ) = - let projectDisplayNameOf projectFileName = - if String.IsNullOrWhiteSpace projectFileName then projectFileName - else Path.GetFileNameWithoutExtension projectFileName - - let reactor = new FSharpProjectOptionsReactor(workspace, settings, serviceProvider, checkerProvider) + let reactor = new FSharpProjectOptionsReactor(checker) do // We need to listen to this event for lifecycle purposes. @@ -484,7 +464,7 @@ type internal FSharpProjectOptionsManager workspace.DocumentClosed.Add(fun args -> let doc = args.Document let proj = doc.Project - if proj.Language = LanguageNames.FSharp && proj.IsFSharpMiscellaneousOrMetadata then + if proj.IsFSharp && proj.IsFSharpMiscellaneousOrMetadata then reactor.ClearSingleFileOptionsCache(doc.Id) ) @@ -535,32 +515,10 @@ type internal FSharpProjectOptionsManager | Some (_, parsingOptions, _) -> parsingOptions | _ -> { FSharpParsingOptions.Default with IsInteractive = CompilerEnvironment.IsScriptFile document.Name } + member this.SetCommandLineOptions(projectId, sourcePaths, options: ImmutableArray) = + reactor.SetCommandLineOptions(projectId, sourcePaths, options.ToArray()) + member this.ClearAllCaches() = reactor.ClearAllCaches() - [] - /// This handles commandline change notifications from the Dotnet Project-system - /// Prior to VS 15.7 path contained path to project file, post 15.7 contains target binpath - /// binpath is more accurate because a project file can have multiple in memory projects based on configuration - member _.HandleCommandLineChanges(path:string, sources:ImmutableArray, _references:ImmutableArray, options:ImmutableArray) = - use _logBlock = Logger.LogBlock(LogEditorFunctionId.LanguageService_HandleCommandLineArgs) - - let projectId = - match Microsoft.CodeAnalysis.ExternalAccess.FSharp.LanguageServices.FSharpVisualStudioWorkspaceExtensions.TryGetProjectIdByBinPath(workspace, path) with - | true, projectId -> projectId - | false, _ -> Microsoft.CodeAnalysis.ExternalAccess.FSharp.LanguageServices.FSharpVisualStudioWorkspaceExtensions.GetOrCreateProjectIdForPath(workspace, path, projectDisplayNameOf path) - let path = Microsoft.CodeAnalysis.ExternalAccess.FSharp.LanguageServices.FSharpVisualStudioWorkspaceExtensions.GetProjectFilePath(workspace, projectId) - - let getFullPath p = - let p' = - if Path.IsPathRooted(p) || path = null then p - else Path.Combine(Path.GetDirectoryName(path), p) - Path.GetFullPathSafe(p') - - let sourcePaths = sources |> Seq.map(fun s -> getFullPath s.Path) |> Seq.toArray - - reactor.SetCpsCommandLineOptions(projectId, sourcePaths, options.ToArray()) - - member _.Checker = checkerProvider.Checker - - member _.MetadataAsSource = checkerProvider.MetadataAsSource + member _.Checker = checker diff --git a/vsintegration/src/FSharp.Editor/LanguageService/LanguageService.fs b/vsintegration/src/FSharp.Editor/LanguageService/LanguageService.fs index 5b981fb3903..6a921b01cb7 100644 --- a/vsintegration/src/FSharp.Editor/LanguageService/LanguageService.fs +++ b/vsintegration/src/FSharp.Editor/LanguageService/LanguageService.fs @@ -6,10 +6,13 @@ open System open System.ComponentModel.Design open System.Runtime.InteropServices open System.Threading +open System.IO +open System.Collections.Immutable open Microsoft.CodeAnalysis open Microsoft.CodeAnalysis.Options open FSharp.Compiler open FSharp.Compiler.CodeAnalysis +open FSharp.NativeInterop open Microsoft.VisualStudio open Microsoft.VisualStudio.Editor open Microsoft.VisualStudio.FSharp.Editor @@ -21,13 +24,16 @@ open Microsoft.VisualStudio.Shell open Microsoft.VisualStudio.Shell.Interop open Microsoft.VisualStudio.Text.Outlining open Microsoft.CodeAnalysis.ExternalAccess.FSharp -open Microsoft.CodeAnalysis.ExternalAccess.FSharp.Diagnostics +open Microsoft.CodeAnalysis.Host +open Microsoft.CodeAnalysis.Host.Mef + +#nowarn "9" // NativePtr.toNativeInt // Used to expose FSharpChecker/ProjectInfo manager to diagnostic providers // Diagnostic providers can be executed in environment that does not use MEF so they can rely only // on services exposed by the workspace -type internal FSharpCheckerWorkspaceService = - inherit Microsoft.CodeAnalysis.Host.IWorkspaceService +type internal IFSharpWorkspaceService = + inherit IWorkspaceService abstract Checker: FSharpChecker abstract FSharpProjectOptionsManager: FSharpProjectOptionsManager @@ -42,28 +48,96 @@ type internal RoamingProfileStorageLocation(keyName: string) = let substituteLanguageName = if languageName = FSharpConstants.FSharpLanguageName then "FSharp" else languageName unsubstitutedKeyName.Replace("%LANGUAGE%", substituteLanguageName) -[] -[, Microsoft.CodeAnalysis.Host.Mef.ServiceLayer.Default)>] -type internal FSharpCheckerWorkspaceServiceFactory - [] +[] +[, ServiceLayer.Default)>] +type internal FSharpWorkspaceServiceFactory + [] ( - checkerProvider: FSharpCheckerProvider, - projectInfoManager: FSharpProjectOptionsManager ) = - interface Microsoft.CodeAnalysis.Host.Mef.IWorkspaceServiceFactory with - member _.CreateService(_workspaceServices) = - upcast { new FSharpCheckerWorkspaceService with - member _.Checker = checkerProvider.Checker - member _.FSharpProjectOptionsManager = projectInfoManager } + + // We have a lock just in case if multi-threads try to create a new IFSharpWorkspaceService - + // but we only want to have a single instance of the FSharpChecker regardless if there are multiple instances of IFSharpWorkspaceService. + // In VS, we only ever have a single IFSharpWorkspaceService, but for testing we may have mutliple; we still only want a + // single FSharpChecker instance shared across them. + static let gate = obj() + + // We only ever want to have a single FSharpChecker. + static let mutable checkerSingleton = None + + interface IWorkspaceServiceFactory with + member _.CreateService(workspaceServices) = + + let workspace = workspaceServices.Workspace + + let tryGetMetadataSnapshot (path, timeStamp) = + match workspace with + | :? VisualStudioWorkspace as workspace -> + try + let md = Microsoft.CodeAnalysis.ExternalAccess.FSharp.LanguageServices.FSharpVisualStudioWorkspaceExtensions.GetMetadata(workspace, path, timeStamp) + let amd = (md :?> AssemblyMetadata) + let mmd = amd.GetModules().[0] + let mmr = mmd.GetMetadataReader() + + // "lifetime is timed to Metadata you got from the GetMetadata(...). As long as you hold it strongly, raw + // memory we got from metadata reader will be alive. Once you are done, just let everything go and + // let finalizer handle resource rather than calling Dispose from Metadata directly. It is shared metadata. + // You shouldn't dispose it directly." + + let objToHold = box md + + // We don't expect any ilread WeakByteFile to be created when working in Visual Studio + // Debug.Assert((FSharp.Compiler.AbstractIL.ILBinaryReader.GetStatistics().weakByteFileCount = 0), "Expected weakByteFileCount to be zero when using F# in Visual Studio. Was there a problem reading a .NET binary?") + + Some (objToHold, NativePtr.toNativeInt mmr.MetadataPointer, mmr.MetadataLength) + with ex -> + // We catch all and let the backup routines in the F# compiler find the error + Assert.Exception(ex) + None + | _ -> + None + + lock gate (fun () -> + match checkerSingleton with + | Some _ -> () + | _ -> + let checker = + lazy + let checker = + FSharpChecker.Create( + projectCacheSize = 5000, // We do not care how big the cache is. VS will actually tell FCS to clear caches, so this is fine. + keepAllBackgroundResolutions = false, + legacyReferenceResolver=LegacyMSBuildReferenceResolver.getResolver(), + tryGetMetadataSnapshot = tryGetMetadataSnapshot, + keepAllBackgroundSymbolUses = false, + enableBackgroundItemKeyStoreAndSemanticClassification = true, + enablePartialTypeChecking = true) + checker + checkerSingleton <- Some checker + ) + + let optionsManager = + lazy + match checkerSingleton with + | Some checker -> + FSharpProjectOptionsManager(checker.Value, workspaceServices.Workspace) + | _ -> + failwith "Checker not set." + + { new IFSharpWorkspaceService with + member _.Checker = + match checkerSingleton with + | Some checker -> checker.Value + | _ -> failwith "Checker not set." + member _.FSharpProjectOptionsManager = optionsManager.Value } :> _ [] -type private FSharpSolutionEvents(projectManager: FSharpProjectOptionsManager) = +type private FSharpSolutionEvents(projectManager: FSharpProjectOptionsManager, metadataAsSource: FSharpMetadataAsSourceService) = interface IVsSolutionEvents with member _.OnAfterCloseSolution(_) = projectManager.Checker.ClearLanguageServiceRootCachesAndCollectAndFinalizeAllTransients() - projectManager.MetadataAsSource.ClearGeneratedFiles() + metadataAsSource.ClearGeneratedFiles() projectManager.ClearAllCaches() VSConstants.S_OK @@ -176,10 +250,15 @@ type internal FSharpPackage() as this = // FSI-LINKAGE-POINT: private method GetDialogPage forces fsi options to be loaded let _fsiPropertyPage = this.GetDialogPage(typeof) - let projectInfoManager = this.ComponentModel.DefaultExportProvider.GetExport().Value + + + let workspace = this.ComponentModel.GetService() + let _ = this.ComponentModel.DefaultExportProvider.GetExport() + let optionsManager = workspace.Services.GetService().FSharpProjectOptionsManager + let metadataAsSource = this.ComponentModel.DefaultExportProvider.GetExport().Value let solution = this.GetServiceAsync(typeof).Result let solution = solution :?> IVsSolution - let solutionEvents = FSharpSolutionEvents(projectInfoManager) + let solutionEvents = FSharpSolutionEvents(optionsManager, metadataAsSource) let rdt = this.GetServiceAsync(typeof).Result let rdt = rdt :?> IVsRunningDocumentTable @@ -187,16 +266,15 @@ type internal FSharpPackage() as this = solution.AdviseSolutionEvents(solutionEvents) |> ignore let projectContextFactory = this.ComponentModel.GetService() - let workspace = this.ComponentModel.GetService() let miscFilesWorkspace = this.ComponentModel.GetService() let _singleFileWorkspaceMap = new SingleFileWorkspaceMap( workspace, miscFilesWorkspace, - projectInfoManager, + optionsManager, projectContextFactory, rdt) - let _legacyProjectWorkspaceMap = new LegacyProjectWorkspaceMap(solution, projectInfoManager, projectContextFactory) + let _legacyProjectWorkspaceMap = new LegacyProjectWorkspaceMap(solution, optionsManager, projectContextFactory) () let awaiter = this.JoinableTaskFactory.SwitchToMainThreadAsync().GetAwaiter() if awaiter.IsCompleted then @@ -252,3 +330,39 @@ type internal FSharpLanguageService(package : FSharpPackage) = if not (isNull outliningManager) then let settings = this.Workspace.Services.GetService() outliningManager.Enabled <- settings.Advanced.IsOutliningEnabled + +[] +[)>] +type internal HackCpsCommandLineChanges + [] + ( + [)>] workspace: VisualStudioWorkspace + ) = + + static let projectDisplayNameOf projectFileName = + if String.IsNullOrWhiteSpace projectFileName then projectFileName + else Path.GetFileNameWithoutExtension projectFileName + + [] + /// This handles commandline change notifications from the Dotnet Project-system + /// Prior to VS 15.7 path contained path to project file, post 15.7 contains target binpath + /// binpath is more accurate because a project file can have multiple in memory projects based on configuration + member _.HandleCommandLineChanges(path:string, sources:ImmutableArray, _references:ImmutableArray, options:ImmutableArray) = + use _logBlock = Logger.LogBlock(LogEditorFunctionId.LanguageService_HandleCommandLineArgs) + + let projectId = + match Microsoft.CodeAnalysis.ExternalAccess.FSharp.LanguageServices.FSharpVisualStudioWorkspaceExtensions.TryGetProjectIdByBinPath(workspace, path) with + | true, projectId -> projectId + | false, _ -> Microsoft.CodeAnalysis.ExternalAccess.FSharp.LanguageServices.FSharpVisualStudioWorkspaceExtensions.GetOrCreateProjectIdForPath(workspace, path, projectDisplayNameOf path) + let path = Microsoft.CodeAnalysis.ExternalAccess.FSharp.LanguageServices.FSharpVisualStudioWorkspaceExtensions.GetProjectFilePath(workspace, projectId) + + let getFullPath p = + let p' = + if Path.IsPathRooted(p) || path = null then p + else Path.Combine(Path.GetDirectoryName(path), p) + Path.GetFullPathSafe(p') + + let sourcePaths = sources |> Seq.map(fun s -> getFullPath s.Path) |> Seq.toArray + + let workspaceService = workspace.Services.GetRequiredService() + workspaceService.FSharpProjectOptionsManager.SetCommandLineOptions(projectId, sourcePaths, options) diff --git a/vsintegration/src/FSharp.Editor/LanguageService/MetadataAsSource.fs b/vsintegration/src/FSharp.Editor/LanguageService/MetadataAsSource.fs index 82d39f38c25..1121c0c8609 100644 --- a/vsintegration/src/FSharp.Editor/LanguageService/MetadataAsSource.fs +++ b/vsintegration/src/FSharp.Editor/LanguageService/MetadataAsSource.fs @@ -11,6 +11,7 @@ open System.Linq open System.Text open System.Runtime.InteropServices open System.Reflection.PortableExecutable +open System.ComponentModel.Composition open Microsoft.CodeAnalysis open Microsoft.CodeAnalysis.FindSymbols @@ -91,7 +92,8 @@ module internal MetadataAsSource = None [] -type internal FSharpMetadataAsSourceService(projectContextFactory: IWorkspaceProjectContextFactory) = +[); Composition.Shared>] +type internal FSharpMetadataAsSourceService [] (projectContextFactory: IWorkspaceProjectContextFactory) = let serviceProvider = ServiceProvider.GlobalProvider let projs = System.Collections.Concurrent.ConcurrentDictionary() diff --git a/vsintegration/src/FSharp.Editor/LanguageService/SymbolHelpers.fs b/vsintegration/src/FSharp.Editor/LanguageService/SymbolHelpers.fs index 3ed760c5bae..5a16321171a 100644 --- a/vsintegration/src/FSharp.Editor/LanguageService/SymbolHelpers.fs +++ b/vsintegration/src/FSharp.Editor/LanguageService/SymbolHelpers.fs @@ -18,51 +18,30 @@ open Microsoft.VisualStudio.FSharp.Editor.Symbols module internal SymbolHelpers = /// Used for local code fixes in a document, e.g. to rename local parameters - let getSymbolUsesOfSymbolAtLocationInDocument (document: Document, position: int, projectInfoManager: FSharpProjectOptionsManager, checker: FSharpChecker, userOpName) = + let getSymbolUsesOfSymbolAtLocationInDocument (document: Document, position: int) = asyncMaybe { + let userOpName = "getSymbolUsesOfSymbolAtLocationInDocument" + let! _, checkFileResults = document.GetFSharpParseAndCheckResultsAsync(userOpName) |> liftAsync + let! defines = document.GetFSharpCompilationDefinesAsync(userOpName) |> liftAsync + let! cancellationToken = Async.CancellationToken |> liftAsync let! sourceText = document.GetTextAsync(cancellationToken) let textLine = sourceText.Lines.GetLineFromPosition(position) let textLinePos = sourceText.Lines.GetLinePosition(position) let fcsTextLineNumber = Line.fromZ textLinePos.Line - let! parsingOptions, projectOptions = projectInfoManager.TryGetOptionsForEditingDocumentOrProject(document, cancellationToken, userOpName) - let defines = CompilerEnvironment.GetCompilationDefinesForEditing parsingOptions let! symbol = Tokenizer.getSymbolAtPosition(document.Id, sourceText, position, document.FilePath, defines, SymbolLookupKind.Greedy, false, false) - let settings = document.FSharpOptions - let! _, _, checkFileResults = checker.ParseAndCheckDocument(document, projectOptions, settings.LanguageServicePerformance, userOpName = userOpName) let! symbolUse = checkFileResults.GetSymbolUseAtLocation(fcsTextLineNumber, symbol.Ident.idRange.EndColumn, textLine.ToString(), symbol.FullIsland) let! ct = Async.CancellationToken |> liftAsync let symbolUses = checkFileResults.GetUsesOfSymbolInFile(symbolUse.Symbol, cancellationToken=ct) return symbolUses } - let getSymbolUsesInProjects (symbol: FSharpSymbol, projectInfoManager: FSharpProjectOptionsManager, checker: FSharpChecker, projects: Project list, onFound: Document -> TextSpan -> range -> Async, userOpName) = + let getSymbolUsesInProjects (symbol: FSharpSymbol, projects: Project list, onFound: Document -> TextSpan -> range -> Async) = projects - |> Seq.map (fun project -> - async { - match! projectInfoManager.TryGetOptionsByProject(project, CancellationToken.None) with - | Some (_parsingOptions, projectOptions) -> - for filePath in projectOptions.SourceFiles do - let! symbolUses = checker.FindBackgroundReferencesInFile(filePath, projectOptions, symbol, canInvalidateProject = false, userOpName = userOpName) - let documentOpt = project.Solution.TryGetDocumentFromPath(filePath, project.Id) - match documentOpt with - | Some document -> - let! ct = Async.CancellationToken - let! sourceText = document.GetTextAsync ct |> Async.AwaitTask - for symbolUse in symbolUses do - match RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, symbolUse) with - | Some textSpan -> - do! onFound document textSpan symbolUse - | _ -> - () - | _ -> - () - | _ -> () - }) + |> Seq.map (fun project -> project.FindFSharpReferencesAsync(symbol, onFound, "getSymbolUsesInProjects")) |> Async.Sequential - let getSymbolUsesInSolution (symbol: FSharpSymbol, declLoc: SymbolDeclarationLocation, checkFileResults: FSharpCheckFileResults, - projectInfoManager: FSharpProjectOptionsManager, checker: FSharpChecker, solution: Solution, userOpName) = + let getSymbolUsesInSolution (symbol: FSharpSymbol, declLoc: SymbolDeclarationLocation, checkFileResults: FSharpCheckFileResults, solution: Solution) = async { let toDict (symbolUseRanges: range seq) = let groups = @@ -94,7 +73,7 @@ module internal SymbolHelpers = fun _ _ symbolUseRange -> async { symbolUseRanges.Add symbolUseRange } - let! _ = getSymbolUsesInProjects (symbol, projectInfoManager, checker, projects, onFound, userOpName) + let! _ = getSymbolUsesInProjects (symbol, projects, onFound) // Distinct these down because each TFM will produce a new 'project'. // Unless guarded by a #if define, symbols with the same range will be added N times @@ -112,21 +91,22 @@ module internal SymbolHelpers = // A better approach is to use something like createTextChangeCodeFix below, with a delayed function to compute a set of changes to be applied // simultaneously. But that doesn't work for this case, as we want a set of changes to apply acrosss the whole solution. - let changeAllSymbolReferences (document: Document, symbolSpan: TextSpan, textChanger: string -> string, projectInfoManager: FSharpProjectOptionsManager, checker: FSharpChecker, userOpName) + let changeAllSymbolReferences (document: Document, symbolSpan: TextSpan, textChanger: string -> string) : Async<(Func> * OriginalText) option> = asyncMaybe { + let userOpName = "changeAllSymbolReferences" do! Option.guard (symbolSpan.Length > 0) let! cancellationToken = liftAsync Async.CancellationToken let! sourceText = document.GetTextAsync(cancellationToken) let originalText = sourceText.ToString(symbolSpan) do! Option.guard (originalText.Length > 0) - let! parsingOptions, projectOptions = projectInfoManager.TryGetOptionsForEditingDocumentOrProject(document, cancellationToken, userOpName) - let defines = CompilerEnvironment.GetCompilationDefinesForEditing parsingOptions - let! symbol = Tokenizer.getSymbolAtPosition(document.Id, sourceText, symbolSpan.Start, document.FilePath, defines, SymbolLookupKind.Greedy, false, false) - let! _, _, checkFileResults = checker.ParseAndCheckDocument(document, projectOptions, userOpName = userOpName) + + let! symbol = document.TryFindFSharpLexerSymbolAsync(symbolSpan.Start, SymbolLookupKind.Greedy, false, false, userOpName) let textLine = sourceText.Lines.GetLineFromPosition(symbolSpan.Start) let textLinePos = sourceText.Lines.GetLinePosition(symbolSpan.Start) let fcsTextLineNumber = Line.fromZ textLinePos.Line + + let! _, checkFileResults = document.GetFSharpParseAndCheckResultsAsync(userOpName) |> liftAsync let! symbolUse = checkFileResults.GetSymbolUseAtLocation(fcsTextLineNumber, symbol.Ident.idRange.EndColumn, textLine.ToString(), symbol.FullIsland) let! declLoc = symbolUse.GetDeclarationLocation(document) let newText = textChanger originalText @@ -135,7 +115,7 @@ module internal SymbolHelpers = Func<_,_>(fun (cancellationToken: CancellationToken) -> async { let! symbolUsesByDocumentId = - getSymbolUsesInSolution(symbolUse.Symbol, declLoc, checkFileResults, projectInfoManager, checker, document.Project.Solution, userOpName) + getSymbolUsesInSolution(symbolUse.Symbol, declLoc, checkFileResults, document.Project.Solution) let mutable solution = document.Project.Solution diff --git a/vsintegration/src/FSharp.Editor/LanguageService/WorkspaceExtensions.fs b/vsintegration/src/FSharp.Editor/LanguageService/WorkspaceExtensions.fs new file mode 100644 index 00000000000..11a265d3b04 --- /dev/null +++ b/vsintegration/src/FSharp.Editor/LanguageService/WorkspaceExtensions.fs @@ -0,0 +1,217 @@ +[] +module internal Microsoft.VisualStudio.FSharp.Editor.WorkspaceExtensions + +open System +open System.Runtime.CompilerServices +open System.Threading +open Microsoft.CodeAnalysis +open FSharp.Compiler +open FSharp.Compiler.CodeAnalysis + +[] +module private CheckerExtensions = + + type FSharpChecker with + /// Parse the source text from the Roslyn document. + member checker.ParseDocument(document: Document, parsingOptions: FSharpParsingOptions, userOpName: string) = + async { + let! ct = Async.CancellationToken + let! sourceText = document.GetTextAsync(ct) |> Async.AwaitTask + + return! checker.ParseFile(document.FilePath, sourceText.ToFSharpSourceText(), parsingOptions, userOpName=userOpName) + } + + /// Parse and check the source text from the Roslyn document with possible stale results. + member checker.ParseAndCheckDocumentWithPossibleStaleResults(document: Document, options: FSharpProjectOptions, allowStaleResults: bool, userOpName: string) = + async { + let! ct = Async.CancellationToken + + let! sourceText = document.GetTextAsync(ct) |> Async.AwaitTask + let! textVersion = document.GetTextVersionAsync(ct) |> Async.AwaitTask + + let filePath = document.FilePath + let textVersionHash = textVersion.GetHashCode() + + let parseAndCheckFile = + async { + let! (parseResults, checkFileAnswer) = checker.ParseAndCheckFileInProject(filePath, textVersionHash, sourceText.ToFSharpSourceText(), options, userOpName=userOpName) + return + match checkFileAnswer with + | FSharpCheckFileAnswer.Aborted -> + None + | FSharpCheckFileAnswer.Succeeded(checkFileResults) -> + Some (parseResults, checkFileResults) + } + + let tryGetFreshResultsWithTimeout() = + async { + let! worker = Async.StartChild(async { try return! parseAndCheckFile with | _ -> return None }, millisecondsTimeout=document.Project.FSharpTimeUntilStaleCompletion) + try + return! worker + with :? TimeoutException -> + return None // worker is cancelled at this point, we cannot return it and wait its completion anymore + } + + let bindParsedInput(results: (FSharpParseFileResults * FSharpCheckFileResults) option) = + match results with + | Some(parseResults, checkResults) -> + Some (parseResults, parseResults.ParseTree, checkResults) + | None -> None + + if allowStaleResults then + let! freshResults = tryGetFreshResultsWithTimeout() + + let! results = + match freshResults with + | Some x -> async.Return (Some x) + | None -> + async { + match checker.TryGetRecentCheckResultsForFile(filePath, options, userOpName=userOpName) with + | Some (parseResults, checkFileResults, _) -> + return Some (parseResults, checkFileResults) + | None -> + return! parseAndCheckFile + } + return bindParsedInput results + else + let! results = parseAndCheckFile + return bindParsedInput results + } + + /// Parse and check the source text from the Roslyn document. + member checker.ParseAndCheckDocument(document: Document, options: FSharpProjectOptions, userOpName: string, ?allowStaleResults: bool) = + async { + let allowStaleResults = + match allowStaleResults with + | Some b -> b + | _ -> document.Project.IsFSharpStaleCompletionResultsEnabled + return! checker.ParseAndCheckDocumentWithPossibleStaleResults(document, options, allowStaleResults, userOpName=userOpName) + } + +[] +module private ProjectCache = + + /// This is a cache to maintain FSharpParsingOptions and FSharpProjectOptions per Roslyn Project. + /// The Roslyn Project is held weakly meaning when it is cleaned up by the GC, the FSharParsingOptions and FSharpProjectOptions will be cleaned up by the GC. + /// At some point, this will be the main caching mechanism for FCS projects instead of FCS itself. + let Projects = ConditionalWeakTable() + +type Solution with + + /// Get the instance of IFSharpWorkspaceService. + member private this.GetFSharpWorkspaceService() = + this.Workspace.Services.GetRequiredService() + +type Document with + + /// Get the FSharpParsingOptions and FSharpProjectOptions from the F# project that is associated with the given F# document. + member this.GetFSharpCompilationOptionsAsync(userOpName) = + async { + if this.Project.IsFSharp then + match ProjectCache.Projects.TryGetValue(this.Project) with + | true, result -> return result + | _ -> + let service = this.Project.Solution.GetFSharpWorkspaceService() + let projectOptionsManager = service.FSharpProjectOptionsManager + let! ct = Async.CancellationToken + match! projectOptionsManager.TryGetOptionsForDocumentOrProject(this, ct, userOpName) with + | None -> return raise(System.OperationCanceledException("FSharp project options not found.")) + | Some(parsingOptions, _, projectOptions) -> + let result = (service.Checker, projectOptionsManager, parsingOptions, projectOptions) + return ProjectCache.Projects.GetValue(this.Project, Runtime.CompilerServices.ConditionalWeakTable<_,_>.CreateValueCallback(fun _ -> result)) + else + return raise(System.OperationCanceledException("Document is not a FSharp document.")) + } + + /// Get the compilation defines from F# project that is associated with the given F# document. + member this.GetFSharpCompilationDefinesAsync(userOpName) = + async { + let! _, _, parsingOptions, _ = this.GetFSharpCompilationOptionsAsync(userOpName) + return CompilerEnvironment.GetCompilationDefinesForEditing parsingOptions + } + + /// Get the instance of the FSharpChecker from the workspace by the given F# document. + member this.GetFSharpChecker() = + let workspaceService = this.Project.Solution.GetFSharpWorkspaceService() + workspaceService.Checker + + /// A non-async call that quickly gets FSharpParsingOptions of the given F# document. + /// This tries to get the FSharpParsingOptions by looking at an internal cache; if it doesn't exist in the cache it will create an inaccurate but usable form of the FSharpParsingOptions. + member this.GetFSharpQuickParsingOptions() = + let workspaceService = this.Project.Solution.GetFSharpWorkspaceService() + workspaceService.FSharpProjectOptionsManager.TryGetQuickParsingOptionsForEditingDocumentOrProject(this) + + /// A non-async call that quickly gets the defines of the given F# document. + /// This tries to get the defines by looking at an internal cache; if it doesn't exist in the cache it will create an inaccurate but usable form of the defines. + member this.GetFSharpQuickDefines() = + let workspaceService = this.Project.Solution.GetFSharpWorkspaceService() + workspaceService.FSharpProjectOptionsManager.GetCompilationDefinesForEditingDocument(this) + + /// Parses the given F# document. + member this.GetFSharpParseResultsAsync(userOpName) = + async { + let! checker, _, parsingOptions, _ = this.GetFSharpCompilationOptionsAsync(userOpName) + return! checker.ParseDocument(this, parsingOptions, userOpName) + } + + /// Parses and checks the given F# document. + member this.GetFSharpParseAndCheckResultsAsync(userOpName) = + async { + let! checker, _, _, projectOptions = this.GetFSharpCompilationOptionsAsync(userOpName) + match! checker.ParseAndCheckDocument(this, projectOptions, userOpName, allowStaleResults = false) with + | Some(parseResults, _, checkResults) -> + return (parseResults, checkResults) + | _ -> + return raise(System.OperationCanceledException("Unable to get FSharp parse and check results.")) + } + + /// Get the semantic classifications of the given F# document. + member this.GetFSharpSemanticClassificationAsync(userOpName) = + async { + let! checker, _, _, projectOptions = this.GetFSharpCompilationOptionsAsync(userOpName) + match! checker.GetBackgroundSemanticClassificationForFile(this.FilePath, projectOptions) with + | Some results -> return results + | _ -> return raise(System.OperationCanceledException("Unable to get FSharp semantic classification.")) + } + + /// Find F# references in the given F# document. + member this.FindFSharpReferencesAsync(symbol, onFound, userOpName) = + async { + let! checker, _, _, projectOptions = this.GetFSharpCompilationOptionsAsync(userOpName) + let! symbolUses = checker.FindBackgroundReferencesInFile(this.FilePath, projectOptions, symbol, canInvalidateProject = false) + let! ct = Async.CancellationToken + let! sourceText = this.GetTextAsync ct |> Async.AwaitTask + for symbolUse in symbolUses do + match RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, symbolUse) with + | Some textSpan -> + do! onFound textSpan symbolUse + | _ -> + () + } + + /// Try to find a F# lexer/token symbol of the given F# document and position. + member this.TryFindFSharpLexerSymbolAsync(position, lookupKind, wholeActivePattern, allowStringToken, userOpName) = + async { + let! defines = this.GetFSharpCompilationDefinesAsync(userOpName) + let! ct = Async.CancellationToken + let! sourceText = this.GetTextAsync(ct) |> Async.AwaitTask + return Tokenizer.getSymbolAtPosition(this.Id, sourceText, position, this.FilePath, defines, lookupKind, wholeActivePattern, allowStringToken) + } + + /// This is only used for testing purposes. It sets the ProjectCache.Projects with the given FSharpProjectOptions and F# document's project. + member this.SetFSharpProjectOptionsForTesting(projectOptions: FSharpProjectOptions) = + let workspaceService = this.Project.Solution.GetFSharpWorkspaceService() + let parsingOptions, _, _ = + workspaceService.FSharpProjectOptionsManager.TryGetOptionsForDocumentOrProject(this, CancellationToken.None, nameof(this.SetFSharpProjectOptionsForTesting)) + |> Async.RunSynchronously + |> Option.get + ProjectCache.Projects.Add(this.Project, (workspaceService.Checker, workspaceService.FSharpProjectOptionsManager, parsingOptions, projectOptions)) + +type Project with + + /// Find F# references in the given project. + member this.FindFSharpReferencesAsync(symbol, onFound, userOpName) = + async { + for doc in this.Documents do + do! doc.FindFSharpReferencesAsync(symbol, (fun textSpan range -> onFound doc textSpan range), userOpName) + } diff --git a/vsintegration/src/FSharp.Editor/Navigation/FindUsagesService.fs b/vsintegration/src/FSharp.Editor/Navigation/FindUsagesService.fs index 53a0e68bd25..2819eb65d38 100644 --- a/vsintegration/src/FSharp.Editor/Navigation/FindUsagesService.fs +++ b/vsintegration/src/FSharp.Editor/Navigation/FindUsagesService.fs @@ -20,11 +20,7 @@ open Microsoft.CodeAnalysis.Text type internal FSharpFindUsagesService [] ( - checkerProvider: FSharpCheckerProvider, - projectInfoManager: FSharpProjectOptionsManager ) = - - static let userOpName = "FindUsages" // File can be included in more than one project, hence single `range` may results with multiple `Document`s. let rangeToDocumentSpans (solution: Solution, range: range) = @@ -48,17 +44,14 @@ type internal FSharpFindUsagesService return spans |> Array.choose id |> Array.toList } - let findReferencedSymbolsAsync(document: Document, position: int, context: IFSharpFindUsagesContext, allReferences: bool, userOpName: string) : Async = + let findReferencedSymbolsAsync(document: Document, position: int, context: IFSharpFindUsagesContext, allReferences: bool) : Async = asyncMaybe { let! sourceText = document.GetTextAsync(context.CancellationToken) |> Async.AwaitTask |> liftAsync - let checker = checkerProvider.Checker - let! parsingOptions, _, projectOptions = projectInfoManager.TryGetOptionsForDocumentOrProject(document, context.CancellationToken, userOpName) - let! _, _, checkFileResults = checker.ParseAndCheckDocument(document, projectOptions, userOpName = userOpName) let textLine = sourceText.Lines.GetLineFromPosition(position).ToString() let lineNumber = sourceText.Lines.GetLinePosition(position).Line + 1 - let defines = CompilerEnvironment.GetCompilationDefinesForEditing parsingOptions + let! symbol = document.TryFindFSharpLexerSymbolAsync(position, SymbolLookupKind.Greedy, false, false, "findReferencedSymbolsAsync") - let! symbol = Tokenizer.getSymbolAtPosition(document.Id, sourceText, position, document.FilePath, defines, SymbolLookupKind.Greedy, false, false) + let! _, checkFileResults = document.GetFSharpParseAndCheckResultsAsync(nameof(FSharpFindUsagesService)) |> liftAsync let! symbolUse = checkFileResults.GetSymbolUseAtLocation(lineNumber, symbol.Ident.idRange.EndColumn, textLine, symbol.FullIsland) let declaration = checkFileResults.GetDeclarationLocation (lineNumber, symbol.Ident.idRange.EndColumn, textLine, symbol.FullIsland, false) let tags = FSharpGlyphTags.GetTags(Tokenizer.GetGlyphForSymbol (symbolUse.Symbol, symbol.Kind)) @@ -129,15 +122,15 @@ type internal FSharpFindUsagesService // In order to find all its usages we have to check all F# projects. | _ -> Seq.toList document.Project.Solution.Projects - let! _ = SymbolHelpers.getSymbolUsesInProjects (symbolUse.Symbol, projectInfoManager, checker, projectsToCheck, onFound, userOpName) |> liftAsync + let! _ = SymbolHelpers.getSymbolUsesInProjects (symbolUse.Symbol, projectsToCheck, onFound) |> liftAsync () } |> Async.Ignore interface IFSharpFindUsagesService with member _.FindReferencesAsync(document, position, context) = - findReferencedSymbolsAsync(document, position, context, true, userOpName) + findReferencedSymbolsAsync(document, position, context, true) |> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken) member _.FindImplementationsAsync(document, position, context) = - findReferencedSymbolsAsync(document, position, context, false, userOpName) + findReferencedSymbolsAsync(document, position, context, false) |> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken) \ No newline at end of file diff --git a/vsintegration/src/FSharp.Editor/Navigation/GoToDefinition.fs b/vsintegration/src/FSharp.Editor/Navigation/GoToDefinition.fs index 240207ad780..90474acfd3c 100644 --- a/vsintegration/src/FSharp.Editor/Navigation/GoToDefinition.fs +++ b/vsintegration/src/FSharp.Editor/Navigation/GoToDefinition.fs @@ -151,10 +151,7 @@ type internal FSharpGoToDefinitionResult = | NavigableItem of FSharpNavigableItem | ExternalAssembly of FSharpSymbolUse * MetadataReference seq -type internal GoToDefinition(checkerProvider: FSharpCheckerProvider, projectInfoManager: FSharpProjectOptionsManager) = - let userOpName = "GoToDefinition" - let checker = checkerProvider.Checker - let metadataAsSourceService = checkerProvider.MetadataAsSource +type internal GoToDefinition(metadataAsSource: FSharpMetadataAsSourceService) = /// Use an origin document to provide the solution & workspace used to /// find the corresponding textSpan and INavigableItem for the range @@ -176,18 +173,16 @@ type internal GoToDefinition(checkerProvider: FSharpCheckerProvider, projectInfo /// Helper function that is used to determine the navigation strategy to apply, can be tuned towards signatures or implementation files. member private _.FindSymbolHelper (originDocument: Document, originRange: range, sourceText: SourceText, preferSignature: bool) = asyncMaybe { - let! parsingOptions, projectOptions = projectInfoManager.TryGetOptionsForEditingDocumentOrProject(originDocument, CancellationToken.None, userOpName) - let defines = CompilerEnvironment.GetCompilationDefinesForEditing parsingOptions + let userOpName = "FindSymbolHelper" let! originTextSpan = RoslynHelpers.TryFSharpRangeToTextSpan (sourceText, originRange) let position = originTextSpan.Start - let! lexerSymbol = Tokenizer.getSymbolAtPosition (originDocument.Id, sourceText, position, originDocument.FilePath, defines, SymbolLookupKind.Greedy, false, false) - + let! lexerSymbol = originDocument.TryFindFSharpLexerSymbolAsync(position, SymbolLookupKind.Greedy, false, false, userOpName) let textLinePos = sourceText.Lines.GetLinePosition position let fcsTextLineNumber = Line.fromZ textLinePos.Line let lineText = (sourceText.Lines.GetLineFromPosition position).ToString() - - let! _, _, checkFileResults = checker.ParseAndCheckDocument (originDocument, projectOptions, userOpName=userOpName) let idRange = lexerSymbol.Ident.idRange + + let! _, checkFileResults = originDocument.GetFSharpParseAndCheckResultsAsync(nameof(GoToDefinition)) |> liftAsync let! fsSymbolUse = checkFileResults.GetSymbolUseAtLocation (fcsTextLineNumber, idRange.EndColumn, lineText, lexerSymbol.FullIsland) let symbol = fsSymbolUse.Symbol // if the tooltip was spawned in an implementation file and we have a range targeting @@ -198,8 +193,7 @@ type internal GoToDefinition(checkerProvider: FSharpCheckerProvider, projectInfo if not (File.Exists fsfilePath) then return! None else let! implDoc = originDocument.Project.Solution.TryGetDocumentFromPath fsfilePath let! implSourceText = implDoc.GetTextAsync () - let! _parsingOptions, projectOptions = projectInfoManager.TryGetOptionsForEditingDocumentOrProject(implDoc, CancellationToken.None, userOpName) - let! _, _, checkFileResults = checker.ParseAndCheckDocument (implDoc, projectOptions, userOpName=userOpName) + let! _, checkFileResults = implDoc.GetFSharpParseAndCheckResultsAsync(userOpName) |> liftAsync let symbolUses = checkFileResults.GetUsesOfSymbolInFile symbol let! implSymbol = symbolUses |> Array.tryHead let! implTextSpan = RoslynHelpers.TryFSharpRangeToTextSpan (implSourceText, implSymbol.Range) @@ -212,13 +206,13 @@ type internal GoToDefinition(checkerProvider: FSharpCheckerProvider, projectInfo /// if the symbol is defined in the given file, return its declaration location, otherwise use the targetSymbol to find the first /// instance of its presence in the provided source file. The first case is needed to return proper declaration location for /// recursive type definitions, where the first its usage may not be the declaration. - member _.FindSymbolDeclarationInDocument(targetSymbolUse: FSharpSymbolUse, document: Document, options: FSharpProjectOptions) = + member _.FindSymbolDeclarationInDocument(targetSymbolUse: FSharpSymbolUse, document: Document) = asyncMaybe { let filePath = document.FilePath match targetSymbolUse.Symbol.DeclarationLocation with | Some decl when decl.FileName = filePath -> return decl | _ -> - let! _, _, checkFileResults = checker.ParseAndCheckDocument(document, options, userOpName) + let! _, checkFileResults = document.GetFSharpParseAndCheckResultsAsync("FindSymbolDeclarationInDocument") |> liftAsync let symbolUses = checkFileResults.GetUsesOfSymbolInFile targetSymbolUse.Symbol let! implSymbol = symbolUses |> Array.tryHead return implSymbol.Range @@ -226,9 +220,8 @@ type internal GoToDefinition(checkerProvider: FSharpCheckerProvider, projectInfo member private this.FindDefinitionAtPosition(originDocument: Document, position: int, cancellationToken: CancellationToken) = asyncMaybe { - let! parsingOptions, projectOptions = projectInfoManager.TryGetOptionsForEditingDocumentOrProject(originDocument, CancellationToken.None, userOpName) - let! sourceText = originDocument.GetTextAsync(cancellationToken) |> liftTaskAsync - let defines = CompilerEnvironment.GetCompilationDefinesForEditing parsingOptions + let userOpName = "FindDefinitionAtPosition" + let! sourceText = originDocument.GetTextAsync(cancellationToken) let textLine = sourceText.Lines.GetLineFromPosition position let textLinePos = sourceText.Lines.GetLinePosition position let textLineString = textLine.ToString() @@ -236,12 +229,11 @@ type internal GoToDefinition(checkerProvider: FSharpCheckerProvider, projectInfo let lineText = (sourceText.Lines.GetLineFromPosition position).ToString() let preferSignature = isSignatureFile originDocument.FilePath - - let! _, _, checkFileResults = checker.ParseAndCheckDocument (originDocument, projectOptions, userOpName=userOpName) - let! lexerSymbol = Tokenizer.getSymbolAtPosition (originDocument.Id, sourceText, position,originDocument.FilePath, defines, SymbolLookupKind.Greedy, false, false) + let! lexerSymbol = originDocument.TryFindFSharpLexerSymbolAsync(position, SymbolLookupKind.Greedy, false, false, userOpName) let idRange = lexerSymbol.Ident.idRange + let! _, checkFileResults = originDocument.GetFSharpParseAndCheckResultsAsync(userOpName) |> liftAsync let declarations = checkFileResults.GetDeclarationLocation (fcsTextLineNumber, lexerSymbol.Ident.idRange.EndColumn, textLineString, lexerSymbol.FullIsland, preferSignature) let! targetSymbolUse = checkFileResults.GetSymbolUseAtLocation (fcsTextLineNumber, idRange.EndColumn, lineText, lexerSymbol.FullIsland) @@ -285,7 +277,7 @@ type internal GoToDefinition(checkerProvider: FSharpCheckerProvider, projectInfo if not (File.Exists implFilePath) then return! None else let! implDocument = originDocument.Project.Solution.TryGetDocumentFromPath implFilePath - let! targetRange = this.FindSymbolDeclarationInDocument(targetSymbolUse, implDocument, projectOptions) + let! targetRange = this.FindSymbolDeclarationInDocument(targetSymbolUse, implDocument) let! implSourceText = implDocument.GetTextAsync(cancellationToken) |> liftTaskAsync let! implTextSpan = RoslynHelpers.TryFSharpRangeToTextSpan (implSourceText, targetRange) let navItem = FSharpGoToDefinitionNavigableItem (implDocument, implTextSpan) @@ -320,10 +312,8 @@ type internal GoToDefinition(checkerProvider: FSharpCheckerProvider, projectInfo else sigDocument.FilePath let! implDocument = originDocument.Project.Solution.TryGetDocumentFromPath implFilePath - - let! _, projectOptions = projectInfoManager.TryGetOptionsForEditingDocumentOrProject(implDocument, cancellationToken, userOpName) - let! targetRange = this.FindSymbolDeclarationInDocument(targetSymbolUse, implDocument, projectOptions) + let! targetRange = this.FindSymbolDeclarationInDocument(targetSymbolUse, implDocument) let! implSourceText = implDocument.GetTextAsync () |> liftTaskAsync let! implTextSpan = RoslynHelpers.TryFSharpRangeToTextSpan (implSourceText, targetRange) @@ -420,13 +410,12 @@ type internal GoToDefinition(checkerProvider: FSharpCheckerProvider, projectInfo AssemblyIdentity(targetSymbolUse.Symbol.Assembly.QualifiedName), fileName, metadataReferences) - let tmpShownDocOpt = metadataAsSourceService.ShowDocument(tmpProjInfo, tmpDocInfo.FilePath, SourceText.From(text.ToString())) + let tmpShownDocOpt = metadataAsSource.ShowDocument(tmpProjInfo, tmpDocInfo.FilePath, SourceText.From(text.ToString())) match tmpShownDocOpt with | Some tmpShownDoc -> let goToAsync = asyncMaybe { - let! _, _, projectOptions = projectInfoManager.TryGetOptionsForDocumentOrProject (tmpShownDoc, cancellationToken, userOpName) - let! _, _, checkResults = checker.ParseAndCheckDocument(tmpShownDoc, projectOptions, userOpName) + let! _, checkResults = tmpShownDoc.GetFSharpParseAndCheckResultsAsync("NavigateToExternalDeclaration") |> liftAsync let! r = let rec areTypesEqual (ty1: FSharpType) (ty2: FSharpType) = let ty1 = ty1.StripAbbreviations() diff --git a/vsintegration/src/FSharp.Editor/Navigation/GoToDefinitionService.fs b/vsintegration/src/FSharp.Editor/Navigation/GoToDefinitionService.fs index d74b8049dfa..6ed309bf573 100644 --- a/vsintegration/src/FSharp.Editor/Navigation/GoToDefinitionService.fs +++ b/vsintegration/src/FSharp.Editor/Navigation/GoToDefinitionService.fs @@ -18,11 +18,10 @@ open Microsoft.VisualStudio.Shell.Interop type internal FSharpGoToDefinitionService [] ( - checkerProvider: FSharpCheckerProvider, - projectInfoManager: FSharpProjectOptionsManager + metadataAsSource: FSharpMetadataAsSourceService ) = - let gtd = GoToDefinition(checkerProvider, projectInfoManager) + let gtd = GoToDefinition(metadataAsSource) let statusBar = StatusBar(ServiceProvider.GlobalProvider.GetService()) interface IFSharpGoToDefinitionService with diff --git a/vsintegration/src/FSharp.Editor/Navigation/NavigableSymbolsService.fs b/vsintegration/src/FSharp.Editor/Navigation/NavigableSymbolsService.fs index 6bc1822438c..d1af75a598a 100644 --- a/vsintegration/src/FSharp.Editor/Navigation/NavigableSymbolsService.fs +++ b/vsintegration/src/FSharp.Editor/Navigation/NavigableSymbolsService.fs @@ -28,10 +28,10 @@ type internal FSharpNavigableSymbol(item: FSharpNavigableItem, span: SnapshotSpa member _.SymbolSpan = span -type internal FSharpNavigableSymbolSource(checkerProvider: FSharpCheckerProvider, projectInfoManager: FSharpProjectOptionsManager, serviceProvider: IServiceProvider) = +type internal FSharpNavigableSymbolSource(metadataAsSource, serviceProvider: IServiceProvider) = let mutable disposed = false - let gtd = GoToDefinition(checkerProvider, projectInfoManager) + let gtd = GoToDefinition(metadataAsSource) let statusBar = StatusBar(serviceProvider.GetService()) interface INavigableSymbolSource with @@ -107,10 +107,9 @@ type internal FSharpNavigableSymbolService [] ( [)>] serviceProvider: IServiceProvider, - checkerProvider: FSharpCheckerProvider, - projectInfoManager: FSharpProjectOptionsManager + metadataAsSource: FSharpMetadataAsSourceService ) = interface INavigableSymbolSourceProvider with member _.TryCreateNavigableSymbolSource(_: ITextView, _: ITextBuffer) = - new FSharpNavigableSymbolSource(checkerProvider, projectInfoManager, serviceProvider) :> INavigableSymbolSource \ No newline at end of file + new FSharpNavigableSymbolSource(metadataAsSource, serviceProvider) :> INavigableSymbolSource \ No newline at end of file diff --git a/vsintegration/src/FSharp.Editor/Navigation/NavigateToSearchService.fs b/vsintegration/src/FSharp.Editor/Navigation/NavigateToSearchService.fs index 821dab6624c..6fd2bf231cf 100644 --- a/vsintegration/src/FSharp.Editor/Navigation/NavigateToSearchService.fs +++ b/vsintegration/src/FSharp.Editor/Navigation/NavigateToSearchService.fs @@ -169,49 +169,43 @@ module private Utils = type internal FSharpNavigateToSearchService [] ( - checkerProvider: FSharpCheckerProvider, - projectInfoManager: FSharpProjectOptionsManager ) = - let userOpName = "FSharpNavigateToSearchService" let kindsProvided = ImmutableHashSet.Create(FSharpNavigateToItemKind.Module, FSharpNavigateToItemKind.Class, FSharpNavigateToItemKind.Field, FSharpNavigateToItemKind.Property, FSharpNavigateToItemKind.Method, FSharpNavigateToItemKind.Enum, FSharpNavigateToItemKind.EnumItem) :> IImmutableSet // Save the backing navigation data in a memory cache held in a sliding window let itemsByDocumentId = new MemoryCache("FSharp.Editor.FSharpNavigateToSearchService") - let GetNavigableItems(document: Document, parsingOptions: FSharpParsingOptions, kinds: IImmutableSet) = + let GetNavigableItems(document: Document, kinds: IImmutableSet) = async { let! cancellationToken = Async.CancellationToken - let! parseResults = checkerProvider.Checker.ParseDocument(document, parsingOptions, userOpName) - match parseResults with - | None -> return [||] - | Some parseResults -> - let! sourceText = document.GetTextAsync(cancellationToken) |> Async.AwaitTask - let navItems parsedInput = - NavigateTo.GetNavigableItems parsedInput - |> Array.filter (fun i -> kinds.Contains(navigateToItemKindToRoslynKind i.Kind)) - - let items = parseResults.ParseTree |> navItems - let navigableItems = - [| - for item in items do - match RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, item.Range) with - | None -> () - | Some sourceSpan -> - let glyph = navigateToItemKindToGlyph item.Kind - let kind = navigateToItemKindToRoslynKind item.Kind - let additionalInfo = containerToString item.Container document - let _name = - if isSignatureFile document.FilePath then - item.Name + " (signature)" - else - item.Name - yield NavigableItem(document, sourceSpan, glyph, item.Name, kind, additionalInfo) - |] - return navigableItems + let! parseResults = document.GetFSharpParseResultsAsync(nameof(FSharpNavigateToSearchService)) + let! sourceText = document.GetTextAsync(cancellationToken) |> Async.AwaitTask + let navItems parsedInput = + NavigateTo.GetNavigableItems parsedInput + |> Array.filter (fun i -> kinds.Contains(navigateToItemKindToRoslynKind i.Kind)) + + let items = parseResults.ParseTree |> navItems + let navigableItems = + [| + for item in items do + match RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, item.Range) with + | None -> () + | Some sourceSpan -> + let glyph = navigateToItemKindToGlyph item.Kind + let kind = navigateToItemKindToRoslynKind item.Kind + let additionalInfo = containerToString item.Container document + let _name = + if isSignatureFile document.FilePath then + item.Name + " (signature)" + else + item.Name + yield NavigableItem(document, sourceSpan, glyph, item.Name, kind, additionalInfo) + |] + return navigableItems } - let getCachedIndexedNavigableItems(document: Document, parsingOptions: FSharpParsingOptions, kinds: IImmutableSet) = + let getCachedIndexedNavigableItems(document: Document, kinds: IImmutableSet) = async { let! cancellationToken = Async.CancellationToken let! textVersion = document.GetTextVersionAsync(cancellationToken) |> Async.AwaitTask @@ -220,7 +214,7 @@ type internal FSharpNavigateToSearchService match itemsByDocumentId.Get(key) with | :? PerDocumentSavedData as data when data.Hash = textVersionHash -> return data.Items | _ -> - let! items = GetNavigableItems(document, parsingOptions, kinds) + let! items = GetNavigableItems(document, kinds) let indexedItems = Index.build items let data = { Hash= textVersionHash; Items = indexedItems } let cacheItem = CacheItem(key, data) @@ -239,10 +233,9 @@ type internal FSharpNavigateToSearchService interface IFSharpNavigateToSearchService with member _.SearchProjectAsync(project, _priorityDocuments, searchPattern, kinds, cancellationToken) : Task> = asyncMaybe { - let! parsingOptions, _options = projectInfoManager.TryGetOptionsByProject(project, cancellationToken) let! items = project.Documents - |> Seq.map (fun document -> getCachedIndexedNavigableItems(document, parsingOptions, kinds)) + |> Seq.map (fun document -> getCachedIndexedNavigableItems(document, kinds)) |> Async.Parallel |> liftAsync @@ -271,8 +264,7 @@ type internal FSharpNavigateToSearchService member _.SearchDocumentAsync(document, searchPattern, kinds, cancellationToken) : Task> = asyncMaybe { - let! parsingOptions, _, _ = projectInfoManager.TryGetOptionsForDocumentOrProject(document, cancellationToken, userOpName) - let! items = getCachedIndexedNavigableItems(document, parsingOptions, kinds) |> liftAsync + let! items = getCachedIndexedNavigableItems(document, kinds) |> liftAsync return items.Find(searchPattern) } |> Async.map (Option.defaultValue [||]) diff --git a/vsintegration/src/FSharp.Editor/Navigation/NavigationBarItemService.fs b/vsintegration/src/FSharp.Editor/Navigation/NavigationBarItemService.fs index c83fb363a2b..d09647a2396 100644 --- a/vsintegration/src/FSharp.Editor/Navigation/NavigationBarItemService.fs +++ b/vsintegration/src/FSharp.Editor/Navigation/NavigationBarItemService.fs @@ -17,20 +17,16 @@ type internal NavigationBarSymbolItem(text, glyph, spans, childItems) = type internal FSharpNavigationBarItemService [] ( - checkerProvider: FSharpCheckerProvider, - projectInfoManager: FSharpProjectOptionsManager ) = - static let userOpName = "NavigationBarItem" static let emptyResult: IList = upcast [||] interface IFSharpNavigationBarItemService with member _.GetItemsAsync(document, cancellationToken) : Task> = asyncMaybe { - let! parsingOptions, _options = projectInfoManager.TryGetOptionsForEditingDocumentOrProject(document, cancellationToken, userOpName) - let! sourceText = document.GetTextAsync(cancellationToken) - let! parseResults = checkerProvider.Checker.ParseDocument(document, parsingOptions, userOpName=userOpName) + let! parseResults = document.GetFSharpParseResultsAsync(nameof(FSharpNavigationBarItemService)) |> liftAsync let navItems = Navigation.getNavigation parseResults.ParseTree + let! sourceText = document.GetTextAsync(cancellationToken) let rangeToTextSpan range = RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, range) return navItems.Declarations diff --git a/vsintegration/src/FSharp.Editor/Options/EditorOptions.fs b/vsintegration/src/FSharp.Editor/Options/EditorOptions.fs index 9261bdc104e..578b675ae14 100644 --- a/vsintegration/src/FSharp.Editor/Options/EditorOptions.fs +++ b/vsintegration/src/FSharp.Editor/Options/EditorOptions.fs @@ -5,6 +5,7 @@ open System.ComponentModel.Composition open System.Runtime.InteropServices open Microsoft.VisualStudio.Shell open Microsoft.VisualStudio.FSharp.UIResources +open Microsoft.CodeAnalysis module DefaultTuning = /// How long is the per-document data saved before it is eligible for eviction from the cache? 10 seconds. @@ -100,7 +101,7 @@ type FormattingOptions = type EditorOptions [] ( - [)>] serviceProvider: IServiceProvider + [)>] serviceProvider: IServiceProvider ) = let store = SettingsStore(serviceProvider) @@ -128,13 +129,6 @@ type EditorOptions member _.LoadSettings() = store.LoadSettings() member _.SaveSettings(settings) = store.SaveSettings(settings) - -[] -module internal WorkspaceSettingFromDocumentExtension = - type Microsoft.CodeAnalysis.Document with - member this.FSharpOptions = - this.Project.Solution.Workspace.Services.GetService() : EditorOptions - module internal OptionsUI = open OptionsUIHelpers @@ -194,3 +188,56 @@ module internal OptionsUI = inherit AbstractOptionPage() override _.CreateView() = upcast FormattingOptionsControl() + +[] +module EditorOptionsExtensions = + + type Project with + + member this.AreFSharpInMemoryCrossProjectReferencesEnabled = + let editorOptions = this.Solution.Workspace.Services.GetService() + match box editorOptions with + | null -> true + | _ -> editorOptions.LanguageServicePerformance.EnableInMemoryCrossProjectReferences + + member this.IsFSharpCodeFixesAlwaysPlaceOpensAtTopLevelEnabled = + let editorOptions = this.Solution.Workspace.Services.GetService() + match box editorOptions with + | null -> false + | _ -> editorOptions.CodeFixes.AlwaysPlaceOpensAtTopLevel + + member this.IsFSharpCodeFixesUnusedDeclarationsEnabled = + let editorOptions = this.Solution.Workspace.Services.GetService() + match box editorOptions with + | null -> false + | _ -> editorOptions.CodeFixes.UnusedDeclarations + + member this.IsFSharpStaleCompletionResultsEnabled = + let editorOptions = this.Solution.Workspace.Services.GetService() + match box editorOptions with + | null -> false + | _ -> editorOptions.LanguageServicePerformance.AllowStaleCompletionResults + + member this.FSharpTimeUntilStaleCompletion = + let editorOptions = this.Solution.Workspace.Services.GetService() + match box editorOptions with + | null -> 0 + | _ -> editorOptions.LanguageServicePerformance.TimeUntilStaleCompletion + + member this.IsFSharpCodeFixesSimplifyNameEnabled = + let editorOptions = this.Solution.Workspace.Services.GetService() + match box editorOptions with + | null -> false + | _ -> editorOptions.CodeFixes.SimplifyName + + member this.IsFSharpCodeFixesUnusedOpensEnabled = + let editorOptions = this.Solution.Workspace.Services.GetService() + match box editorOptions with + | null -> false + | _ -> editorOptions.CodeFixes.UnusedOpens + + member this.IsFSharpBlockStructureEnabled = + let editorOptions = this.Solution.Workspace.Services.GetService() + match box editorOptions with + | null -> false + | _ -> editorOptions.Advanced.IsBlockStructureEnabled \ No newline at end of file diff --git a/vsintegration/src/FSharp.Editor/QuickInfo/Navigation.fs b/vsintegration/src/FSharp.Editor/QuickInfo/Navigation.fs index e596239cc23..0ea6a4a3a06 100644 --- a/vsintegration/src/FSharp.Editor/QuickInfo/Navigation.fs +++ b/vsintegration/src/FSharp.Editor/QuickInfo/Navigation.fs @@ -14,8 +14,7 @@ open Microsoft.VisualStudio.Shell.Interop type internal QuickInfoNavigation ( statusBar: StatusBar, - checkerProvider: FSharpCheckerProvider, - projectInfoManager: FSharpProjectOptionsManager, + metadataAsSource: FSharpMetadataAsSourceService, initialDoc: Document, thisSymbolUseRange: range ) = @@ -43,7 +42,7 @@ type internal QuickInfoNavigation let! targetDoc = solution.TryGetDocumentFromFSharpRange (range, initialDoc.Project.Id) let! targetSource = targetDoc.GetTextAsync() let! targetTextSpan = RoslynHelpers.TryFSharpRangeToTextSpan (targetSource, range) - let gtd = GoToDefinition(checkerProvider, projectInfoManager) + let gtd = GoToDefinition(metadataAsSource) // To ensure proper navigation decsions, we need to check the type of document the navigation call // is originating from and the target we're provided by default: diff --git a/vsintegration/src/FSharp.Editor/QuickInfo/QuickInfoProvider.fs b/vsintegration/src/FSharp.Editor/QuickInfo/QuickInfoProvider.fs index 0d383f76136..a4b37b7c908 100644 --- a/vsintegration/src/FSharp.Editor/QuickInfo/QuickInfoProvider.fs +++ b/vsintegration/src/FSharp.Editor/QuickInfo/QuickInfoProvider.fs @@ -39,8 +39,6 @@ module internal FSharpQuickInfo = // therefore we should include these doccoms in our design time quick info let getQuickInfoFromRange ( - checker: FSharpChecker, - projectInfoManager: FSharpProjectOptionsManager, document: Document, declRange: range, cancellationToken: CancellationToken @@ -48,6 +46,7 @@ module internal FSharpQuickInfo = : Async = asyncMaybe { + let userOpName = "getQuickInfoFromRange" let solution = document.Project.Solution // ascertain the location of the target declaration in the signature file let! extDocId = solution.GetDocumentIdsWithFilePath declRange.FileName |> Seq.tryHead @@ -57,10 +56,8 @@ module internal FSharpQuickInfo = let extLineText = (extSourceText.Lines.GetLineFromPosition extSpan.Start).ToString() // project options need to be retrieved because the signature file could be in another project - let! extParsingOptions, extProjectOptions = projectInfoManager.TryGetOptionsByProject(extDocument.Project, cancellationToken) - let extDefines = CompilerEnvironment.GetCompilationDefinesForEditing extParsingOptions - let! extLexerSymbol = Tokenizer.getSymbolAtPosition(extDocId, extSourceText, extSpan.Start, declRange.FileName, extDefines, SymbolLookupKind.Greedy, true, true) - let! _, _, extCheckFileResults = checker.ParseAndCheckDocument(extDocument, extProjectOptions, allowStaleResults=true, userOpName = userOpName) + let! extLexerSymbol = extDocument.TryFindFSharpLexerSymbolAsync(extSpan.Start, SymbolLookupKind.Greedy, true, true, userOpName) + let! _, extCheckFileResults = extDocument.GetFSharpParseAndCheckResultsAsync(userOpName) |> liftAsync let extQuickInfoText = extCheckFileResults.GetToolTip @@ -83,8 +80,6 @@ module internal FSharpQuickInfo = /// Get QuickInfo combined from doccom of Signature and definition let getQuickInfo ( - checker: FSharpChecker, - projectInfoManager: FSharpProjectOptionsManager, document: Document, position: int, cancellationToken: CancellationToken @@ -92,12 +87,11 @@ module internal FSharpQuickInfo = : Async<(range * QuickInfo option * QuickInfo option) option> = asyncMaybe { + let userOpName = "getQuickInfo" + let! lexerSymbol = document.TryFindFSharpLexerSymbolAsync(position, SymbolLookupKind.Greedy, true, true, userOpName) + let! _, checkFileResults = document.GetFSharpParseAndCheckResultsAsync(userOpName) |> liftAsync let! sourceText = document.GetTextAsync cancellationToken - let! parsingOptions, projectOptions = projectInfoManager.TryGetOptionsForEditingDocumentOrProject(document, cancellationToken, userOpName) - let defines = CompilerEnvironment.GetCompilationDefinesForEditing parsingOptions - let! lexerSymbol = Tokenizer.getSymbolAtPosition(document.Id, sourceText, position, document.FilePath, defines, SymbolLookupKind.Greedy, true, true) let idRange = lexerSymbol.Ident.idRange - let! _, _, checkFileResults = checker.ParseAndCheckDocument(document, projectOptions, allowStaleResults = true, userOpName = userOpName) let textLinePos = sourceText.Lines.GetLinePosition position let fcsTextLineNumber = Line.fromZ textLinePos.Line let lineText = (sourceText.Lines.GetLineFromPosition position).ToString() @@ -144,7 +138,7 @@ module internal FSharpQuickInfo = match findSigDeclarationResult with | FindDeclResult.DeclFound declRange when isSignatureFile declRange.FileName -> asyncMaybe { - let! sigQuickInfo = getQuickInfoFromRange(checker, projectInfoManager, document, declRange, cancellationToken) + let! sigQuickInfo = getQuickInfoFromRange(document, declRange, cancellationToken) // if the target was declared in a signature file, and the current file // is not the corresponding module implementation file for that signature, @@ -157,7 +151,7 @@ module internal FSharpQuickInfo = | FindDeclResult.ExternalDecl _ -> return symbolUse.Range, Some sigQuickInfo, None | FindDeclResult.DeclFound declRange -> - let! implQuickInfo = getQuickInfoFromRange(checker, projectInfoManager, document, declRange, cancellationToken) + let! implQuickInfo = getQuickInfoFromRange(document, declRange, cancellationToken) return symbolUse.Range, Some sigQuickInfo, Some { implQuickInfo with Span = targetQuickInfo.Span } } | _ -> async.Return None @@ -170,23 +164,21 @@ type internal FSharpAsyncQuickInfoSource ( statusBar: StatusBar, xmlMemberIndexService: IVsXMLMemberIndexService, - checkerProvider:FSharpCheckerProvider, - projectInfoManager:FSharpProjectOptionsManager, + metadataAsSource: FSharpMetadataAsSourceService, textBuffer:ITextBuffer, _settings: EditorOptions ) = // test helper - static member ProvideQuickInfo(checker:FSharpChecker, document: Document, position:int, parsingOptions:FSharpParsingOptions, options:FSharpProjectOptions, languageServicePerformanceOptions: LanguageServicePerformanceOptions) = + static member ProvideQuickInfo(document: Document, position:int) = asyncMaybe { - let! _, _, checkFileResults = checker.ParseAndCheckDocument(document, options, languageServicePerformanceOptions, userOpName=FSharpQuickInfo.userOpName) let! sourceText = document.GetTextAsync() - let filePath = document.FilePath let textLine = sourceText.Lines.GetLineFromPosition position let textLineNumber = textLine.LineNumber + 1 // Roslyn line numbers are zero-based let textLineString = textLine.ToString() - let defines = CompilerEnvironment.GetCompilationDefinesForEditing parsingOptions - let! symbol = Tokenizer.getSymbolAtPosition (document.Id, sourceText, position, filePath, defines, SymbolLookupKind.Precise, true, true) + let! symbol = document.TryFindFSharpLexerSymbolAsync(position, SymbolLookupKind.Precise, true, true, nameof(FSharpAsyncQuickInfoSource)) + + let! _, checkFileResults = document.GetFSharpParseAndCheckResultsAsync(nameof(FSharpAsyncQuickInfoSource)) |> liftAsync let res = checkFileResults.GetToolTip (textLineNumber, symbol.Ident.idRange.EndColumn, textLineString, symbol.FullIsland, FSharpTokenTag.IDENT) match res with | ToolTipText [] @@ -219,7 +211,7 @@ type internal FSharpAsyncQuickInfoSource let triggerPoint = triggerPoint.GetValueOrDefault() asyncMaybe { let document = textBuffer.CurrentSnapshot.GetOpenDocumentInCurrentContextWithChanges() - let! symbolUseRange, sigQuickInfo, targetQuickInfo = FSharpQuickInfo.getQuickInfo(checkerProvider.Checker, projectInfoManager, document, triggerPoint.Position, cancellationToken) + let! symbolUseRange, sigQuickInfo, targetQuickInfo = FSharpQuickInfo.getQuickInfo(document, triggerPoint.Position, cancellationToken) let getTrackingSpan (span:TextSpan) = textBuffer.CurrentSnapshot.CreateTrackingSpan(span.Start, span.Length, SpanTrackingMode.EdgeInclusive) @@ -230,7 +222,7 @@ type internal FSharpAsyncQuickInfoSource | None, Some quickInfo -> let mainDescription, docs = FSharpAsyncQuickInfoSource.BuildSingleQuickInfoItem documentationBuilder quickInfo let imageId = Tokenizer.GetImageIdForSymbol(quickInfo.Symbol, quickInfo.SymbolKind) - let navigation = QuickInfoNavigation(statusBar, checkerProvider, projectInfoManager, document, symbolUseRange) + let navigation = QuickInfoNavigation(statusBar, metadataAsSource, document, symbolUseRange) let content = QuickInfoViewProvider.provideContent(imageId, mainDescription, docs, navigation) let span = getTrackingSpan quickInfo.Span return QuickInfoItem(span, content) @@ -260,7 +252,7 @@ type internal FSharpAsyncQuickInfoSource ] |> ResizeArray let docs = RoslynHelpers.joinWithLineBreaks [documentation; typeParameterMap; usage; exceptions] let imageId = Tokenizer.GetImageIdForSymbol(targetQuickInfo.Symbol, targetQuickInfo.SymbolKind) - let navigation = QuickInfoNavigation(statusBar, checkerProvider, projectInfoManager, document, symbolUseRange) + let navigation = QuickInfoNavigation(statusBar, metadataAsSource, document, symbolUseRange) let content = QuickInfoViewProvider.provideContent(imageId, mainDescription, docs, navigation) let span = getTrackingSpan targetQuickInfo.Span return QuickInfoItem(span, content) @@ -275,8 +267,7 @@ type internal FSharpAsyncQuickInfoSourceProvider [] ( [)>] serviceProvider: IServiceProvider, - checkerProvider:FSharpCheckerProvider, - projectInfoManager:FSharpProjectOptionsManager, + metadataAsSource: FSharpMetadataAsSourceService, settings: EditorOptions ) = @@ -286,4 +277,4 @@ type internal FSharpAsyncQuickInfoSourceProvider // It is safe to do it here (see #4713) let statusBar = StatusBar(serviceProvider.GetService()) let xmlMemberIndexService = serviceProvider.XMLMemberIndexService - new FSharpAsyncQuickInfoSource(statusBar, xmlMemberIndexService, checkerProvider, projectInfoManager, textBuffer, settings) :> IAsyncQuickInfoSource + new FSharpAsyncQuickInfoSource(statusBar, xmlMemberIndexService, metadataAsSource, textBuffer, settings) :> IAsyncQuickInfoSource diff --git a/vsintegration/src/FSharp.Editor/Refactor/AddExplicitTypeToParameter.fs b/vsintegration/src/FSharp.Editor/Refactor/AddExplicitTypeToParameter.fs index f2c73274dd4..6eea90f188d 100644 --- a/vsintegration/src/FSharp.Editor/Refactor/AddExplicitTypeToParameter.fs +++ b/vsintegration/src/FSharp.Editor/Refactor/AddExplicitTypeToParameter.fs @@ -20,26 +20,20 @@ open Microsoft.CodeAnalysis.CodeActions type internal FSharpAddExplicitTypeToParameterRefactoring [] ( - checkerProvider: FSharpCheckerProvider, - projectInfoManager: FSharpProjectOptionsManager ) = inherit CodeRefactoringProvider() - static let userOpName = "AddExplicitTypeToParameter" - override _.ComputeRefactoringsAsync context = asyncMaybe { let document = context.Document let position = context.Span.Start - let checker = checkerProvider.Checker - let! parsingOptions, projectOptions = projectInfoManager.TryGetOptionsForEditingDocumentOrProject(document, CancellationToken.None, userOpName) let! sourceText = document.GetTextAsync () |> liftTaskAsync - let defines = CompilerEnvironment.GetCompilationDefinesForEditing parsingOptions let textLine = sourceText.Lines.GetLineFromPosition position let textLinePos = sourceText.Lines.GetLinePosition position let fcsTextLineNumber = Line.fromZ textLinePos.Line - let! parseFileResults, _, checkFileResults = checker.ParseAndCheckDocument (document, projectOptions, userOpName=userOpName) - let! lexerSymbol = Tokenizer.getSymbolAtPosition (document.Id, sourceText, position, document.FilePath, defines, SymbolLookupKind.Greedy, false, false) + let! lexerSymbol = document.TryFindFSharpLexerSymbolAsync(position, SymbolLookupKind.Greedy, false, false, nameof(FSharpAddExplicitTypeToParameterRefactoring)) + + let! parseFileResults, checkFileResults = document.GetFSharpParseAndCheckResultsAsync(nameof(FSharpAddExplicitTypeToParameterRefactoring)) |> liftAsync let! symbolUse = checkFileResults.GetSymbolUseAtLocation(fcsTextLineNumber, lexerSymbol.Ident.idRange.EndColumn, textLine.ToString(), lexerSymbol.FullIsland) let isValidParameterWithoutTypeAnnotation (funcOrValue: FSharpMemberOrFunctionOrValue) (symbolUse: FSharpSymbolUse) = diff --git a/vsintegration/src/FSharp.Editor/Refactor/ChangeDerefToValueRefactoring.fs b/vsintegration/src/FSharp.Editor/Refactor/ChangeDerefToValueRefactoring.fs index 2b51b4c3444..ab0a748276d 100644 --- a/vsintegration/src/FSharp.Editor/Refactor/ChangeDerefToValueRefactoring.fs +++ b/vsintegration/src/FSharp.Editor/Refactor/ChangeDerefToValueRefactoring.fs @@ -20,19 +20,14 @@ open Microsoft.CodeAnalysis.CodeActions type internal FSharpChangeDerefToValueRefactoring [] ( - checkerProvider: FSharpCheckerProvider, - projectInfoManager: FSharpProjectOptionsManager ) = inherit CodeRefactoringProvider() - static let userOpName = "ChangeDerefToValue" - override _.ComputeRefactoringsAsync context = asyncMaybe { let document = context.Document - let! parsingOptions, _ = projectInfoManager.TryGetOptionsForEditingDocumentOrProject(document, context.CancellationToken, userOpName) let! sourceText = context.Document.GetTextAsync(context.CancellationToken) - let! parseResults = checkerProvider.Checker.ParseDocument(document, parsingOptions, userOpName=userOpName) + let! parseResults = document.GetFSharpParseResultsAsync(nameof(FSharpChangeDerefToValueRefactoring)) |> liftAsync let selectionRange = RoslynHelpers.TextSpanToFSharpRange(document.FilePath, context.Span, sourceText) let! derefRange = parseResults.TryRangeOfRefCellDereferenceContainingPos selectionRange.Start diff --git a/vsintegration/src/FSharp.Editor/Refactor/ChangeTypeofWithNameToNameofExpression.fs b/vsintegration/src/FSharp.Editor/Refactor/ChangeTypeofWithNameToNameofExpression.fs index e1ef1dcf8af..5fd447fcc24 100644 --- a/vsintegration/src/FSharp.Editor/Refactor/ChangeTypeofWithNameToNameofExpression.fs +++ b/vsintegration/src/FSharp.Editor/Refactor/ChangeTypeofWithNameToNameofExpression.fs @@ -20,19 +20,14 @@ open Microsoft.CodeAnalysis.CodeActions type internal FSharpChangeTypeofWithNameToNameofExpressionRefactoring [] ( - checkerProvider: FSharpCheckerProvider, - projectInfoManager: FSharpProjectOptionsManager ) = inherit CodeRefactoringProvider() - static let userOpName = "ChangeTypeofWithNameToNameofExpression" - override _.ComputeRefactoringsAsync context = asyncMaybe { let document = context.Document - let! parsingOptions, _ = projectInfoManager.TryGetOptionsForEditingDocumentOrProject(document, context.CancellationToken, userOpName) let! sourceText = context.Document.GetTextAsync(context.CancellationToken) - let! parseResults = checkerProvider.Checker.ParseDocument(document, parsingOptions, userOpName=userOpName) + let! parseResults = document.GetFSharpParseResultsAsync(nameof(FSharpChangeTypeofWithNameToNameofExpressionRefactoring)) |> liftAsync let selectionRange = RoslynHelpers.TextSpanToFSharpRange(document.FilePath, context.Span, sourceText) let! namedTypeOfResults = parseResults.TryRangeOfTypeofWithNameAndTypeExpr(selectionRange.Start) diff --git a/vsintegration/src/FSharp.Editor/Structure/BlockStructureService.fs b/vsintegration/src/FSharp.Editor/Structure/BlockStructureService.fs index 2a4a7a58da6..8211b8f4bc2 100644 --- a/vsintegration/src/FSharp.Editor/Structure/BlockStructureService.fs +++ b/vsintegration/src/FSharp.Editor/Structure/BlockStructureService.fs @@ -141,18 +141,15 @@ module internal BlockStructure = open BlockStructure [)>] -type internal FSharpBlockStructureService [] (checkerProvider: FSharpCheckerProvider, projectInfoManager: FSharpProjectOptionsManager) = - - static let userOpName = "FSharpBlockStructure" +type internal FSharpBlockStructureService [] () = interface IFSharpBlockStructureService with member _.GetBlockStructureAsync(document, cancellationToken) : Task = asyncMaybe { - let! parsingOptions, _options = projectInfoManager.TryGetOptionsForEditingDocumentOrProject(document, cancellationToken, userOpName) let! sourceText = document.GetTextAsync(cancellationToken) - let! parseResults = checkerProvider.Checker.ParseDocument(document, parsingOptions, userOpName) - return createBlockSpans document.FSharpOptions.Advanced.IsBlockStructureEnabled sourceText parseResults.ParseTree |> Seq.toImmutableArray + let! parseResults = document.GetFSharpParseResultsAsync(nameof(FSharpBlockStructureService)) |> liftAsync + return createBlockSpans document.Project.IsFSharpBlockStructureEnabled sourceText parseResults.ParseTree |> Seq.toImmutableArray } |> Async.map (Option.defaultValue ImmutableArray<_>.Empty) |> Async.map FSharpBlockStructure diff --git a/vsintegration/tests/GetTypesVS.UnitTests/GetTypesVS.UnitTests.fsproj b/vsintegration/tests/GetTypesVS.UnitTests/GetTypesVS.UnitTests.fsproj index 911e5e9a944..4fa4549e645 100644 --- a/vsintegration/tests/GetTypesVS.UnitTests/GetTypesVS.UnitTests.fsproj +++ b/vsintegration/tests/GetTypesVS.UnitTests/GetTypesVS.UnitTests.fsproj @@ -23,6 +23,7 @@ + diff --git a/vsintegration/tests/UnitTests/BreakpointResolutionService.fs b/vsintegration/tests/UnitTests/BreakpointResolutionService.fs index e9437195d8e..09b5e105857 100644 --- a/vsintegration/tests/UnitTests/BreakpointResolutionService.fs +++ b/vsintegration/tests/UnitTests/BreakpointResolutionService.fs @@ -75,9 +75,7 @@ let main argv = let document, sourceText = RoslynTestHelpers.CreateDocument(fileName, code) let searchSpan = TextSpan.FromBounds(searchPosition, searchPosition + searchToken.Length) - // let document = Microsoft.CodeAnalysis.DocumentInfo.Create(Microsoft.CodeAnalysis.DocumentId() - let parsingOptions, _ = checker.GetParsingOptionsFromProjectOptions projectOptions - let actualResolutionOption = FSharpBreakpointResolutionService.GetBreakpointLocation(checker, document, searchSpan, parsingOptions) |> Async.RunSynchronously + let actualResolutionOption = FSharpBreakpointResolutionService.GetBreakpointLocation(document, searchSpan) |> Async.RunSynchronously match actualResolutionOption with | None -> Assert.IsTrue(expectedResolution.IsNone, "BreakpointResolutionService failed to resolve breakpoint position") diff --git a/vsintegration/tests/UnitTests/CompletionProviderTests.fs b/vsintegration/tests/UnitTests/CompletionProviderTests.fs index 8e2c70cfb47..9003031df00 100644 --- a/vsintegration/tests/UnitTests/CompletionProviderTests.fs +++ b/vsintegration/tests/UnitTests/CompletionProviderTests.fs @@ -56,7 +56,7 @@ let VerifyCompletionListWithOptions(fileContents: string, marker: string, expect let caretPosition = fileContents.IndexOf(marker) + marker.Length let document, _ = RoslynTestHelpers.CreateDocument(filePath, fileContents) let results = - FSharpCompletionProvider.ProvideCompletionsAsyncAux(checker, document, caretPosition, projectOptions opts, (fun _ -> []), LanguageServicePerformanceOptions.Default, IntelliSenseOptions.Default) + FSharpCompletionProvider.ProvideCompletionsAsyncAux(document, caretPosition, (fun _ -> []), IntelliSenseOptions.Default) |> Async.RunSynchronously |> Option.defaultValue (ResizeArray()) |> Seq.map(fun result -> result.DisplayText) @@ -104,11 +104,10 @@ let VerifyCompletionList(fileContents, marker, expected, unexpected) = let VerifyCompletionListExactly(fileContents: string, marker: string, expected: string list) = - let projectOptions = { projectOptions [| |] with ProjectId = Some(Guid.NewGuid().ToString()) } let caretPosition = fileContents.IndexOf(marker) + marker.Length let document, _ = RoslynTestHelpers.CreateDocument(filePath, fileContents) let actual = - FSharpCompletionProvider.ProvideCompletionsAsyncAux(checker, document, caretPosition, projectOptions, (fun _ -> []), LanguageServicePerformanceOptions.Default, IntelliSenseOptions.Default) + FSharpCompletionProvider.ProvideCompletionsAsyncAux(document, caretPosition, (fun _ -> []), IntelliSenseOptions.Default) |> Async.RunSynchronously |> Option.defaultValue (ResizeArray()) |> Seq.toList diff --git a/vsintegration/tests/UnitTests/DocumentDiagnosticAnalyzerTests.fs b/vsintegration/tests/UnitTests/DocumentDiagnosticAnalyzerTests.fs index 25e6f9f4060..2f3b76b2965 100644 --- a/vsintegration/tests/UnitTests/DocumentDiagnosticAnalyzerTests.fs +++ b/vsintegration/tests/UnitTests/DocumentDiagnosticAnalyzerTests.fs @@ -40,11 +40,9 @@ type DocumentDiagnosticAnalyzerTests() = let getDiagnostics (fileContents: string) = async { - let projectOptions = { projectOptions with ProjectId = Some(Guid.NewGuid().ToString()) } let document, _ = RoslynTestHelpers.CreateDocument(filePath, fileContents) - let parsingOptions, _ = checker.GetParsingOptionsFromProjectOptions projectOptions - let! syntacticDiagnostics = FSharpDocumentDiagnosticAnalyzer.GetDiagnostics(checker, document, parsingOptions, projectOptions, DiagnosticsType.Syntax) - let! semanticDiagnostics = FSharpDocumentDiagnosticAnalyzer.GetDiagnostics(checker, document, parsingOptions, projectOptions, DiagnosticsType.Semantic) + let! syntacticDiagnostics = FSharpDocumentDiagnosticAnalyzer.GetDiagnostics(document, DiagnosticsType.Syntax) + let! semanticDiagnostics = FSharpDocumentDiagnosticAnalyzer.GetDiagnostics(document, DiagnosticsType.Semantic) return syntacticDiagnostics.AddRange(semanticDiagnostics) } |> Async.RunSynchronously diff --git a/vsintegration/tests/UnitTests/DocumentHighlightsServiceTests.fs b/vsintegration/tests/UnitTests/DocumentHighlightsServiceTests.fs index baf54d9ee50..f7074122dd5 100644 --- a/vsintegration/tests/UnitTests/DocumentHighlightsServiceTests.fs +++ b/vsintegration/tests/UnitTests/DocumentHighlightsServiceTests.fs @@ -52,9 +52,8 @@ let internal projectOptions = { } let private getSpans (sourceText: SourceText) (caretPosition: int) = - let projectOptions = { projectOptions with ProjectId = Some(Guid.NewGuid().ToString()) } let document = RoslynTestHelpers.CreateDocument(filePath, sourceText) - FSharpDocumentHighlightsService.GetDocumentHighlights(checker, document, caretPosition, [], projectOptions, LanguageServicePerformanceOptions.Default) + FSharpDocumentHighlightsService.GetDocumentHighlights(document, caretPosition) |> Async.RunSynchronously |> Option.defaultValue [||] diff --git a/vsintegration/tests/UnitTests/FsxCompletionProviderTests.fs b/vsintegration/tests/UnitTests/FsxCompletionProviderTests.fs index e9c7188d064..7441639316f 100644 --- a/vsintegration/tests/UnitTests/FsxCompletionProviderTests.fs +++ b/vsintegration/tests/UnitTests/FsxCompletionProviderTests.fs @@ -55,12 +55,11 @@ type Worker () = } member _.VerifyCompletionListExactly(fileContents: string, marker: string, expected: List) = - let projectOptions = { projectOptions with ProjectId = Some(Guid.NewGuid().ToString()) } let caretPosition = fileContents.IndexOf(marker) + marker.Length - let document, _ = RoslynTestHelpers.CreateDocument(filePath, fileContents) + let document = RoslynTestHelpers.CreateDocument(filePath, SourceText.From(fileContents), options = projectOptions) let expected = expected |> Seq.toList let actual = - let x = FSharpCompletionProvider.ProvideCompletionsAsyncAux(checker, document, caretPosition, projectOptions, (fun _ -> []), LanguageServicePerformanceOptions.Default, IntelliSenseOptions.Default) + let x = FSharpCompletionProvider.ProvideCompletionsAsyncAux(document, caretPosition, (fun _ -> []), IntelliSenseOptions.Default) |> Async.RunSynchronously x |> Option.defaultValue (ResizeArray()) |> Seq.toList diff --git a/vsintegration/tests/UnitTests/GoToDefinitionServiceTests.fs b/vsintegration/tests/UnitTests/GoToDefinitionServiceTests.fs index f77a3e6e9cf..49527630ff6 100644 --- a/vsintegration/tests/UnitTests/GoToDefinitionServiceTests.fs +++ b/vsintegration/tests/UnitTests/GoToDefinitionServiceTests.fs @@ -41,19 +41,17 @@ module GoToDefinitionServiceTests = let private findDefinition ( - checker: FSharpChecker, document: Document, sourceText: SourceText, position: int, - defines: string list, - options: FSharpProjectOptions + defines: string list ) : range option = maybe { let textLine = sourceText.Lines.GetLineFromPosition position let textLinePos = sourceText.Lines.GetLinePosition position let fcsTextLineNumber = Line.fromZ textLinePos.Line let! lexerSymbol = Tokenizer.getSymbolAtPosition(document.Id, sourceText, position, document.FilePath, defines, SymbolLookupKind.Greedy, false, false) - let! _, _, checkFileResults = checker.ParseAndCheckDocument (document, options, LanguageServicePerformanceOptions.Default, userOpName=userOpName) |> Async.RunSynchronously + let _, checkFileResults = document.GetFSharpParseAndCheckResultsAsync(nameof(userOpName)) |> Async.RunSynchronously let declarations = checkFileResults.GetDeclarationLocation (fcsTextLineNumber, lexerSymbol.Ident.idRange.EndColumn, textLine.ToString(), lexerSymbol.FullIsland, false) @@ -80,13 +78,12 @@ module GoToDefinitionServiceTests = let GoToDefinitionTest (fileContents: string, caretMarker: string, expected) = let filePath = Path.GetTempFileName() + ".fs" - let options = makeOptions filePath [| |] File.WriteAllText(filePath, fileContents) let caretPosition = fileContents.IndexOf(caretMarker) + caretMarker.Length - 1 // inside the marker let document, sourceText = RoslynTestHelpers.CreateDocument(filePath, fileContents) let actual = - findDefinition(checker, document, sourceText, caretPosition, [], options) + findDefinition(document, sourceText, caretPosition, []) |> Option.map (fun range -> (range.StartLine, range.EndLine, range.StartColumn, range.EndColumn)) if actual <> expected then diff --git a/vsintegration/tests/UnitTests/QuickInfoTests.fs b/vsintegration/tests/UnitTests/QuickInfoTests.fs index 688ca2624ee..b53905f57df 100644 --- a/vsintegration/tests/UnitTests/QuickInfoTests.fs +++ b/vsintegration/tests/UnitTests/QuickInfoTests.fs @@ -16,7 +16,7 @@ let internal GetQuickInfo (project:FSharpProject) (fileName:string) (caretPositi async { let code = File.ReadAllText(fileName) let document, _ = RoslynTestHelpers.CreateDocument(fileName, code) - return! FSharpAsyncQuickInfoSource.ProvideQuickInfo(checker, document, caretPosition, FSharpParsingOptions.Default, project.Options, LanguageServicePerformanceOptions.Default) + return! FSharpAsyncQuickInfoSource.ProvideQuickInfo(document, caretPosition) } |> Async.RunSynchronously let GetQuickInfoText (project:FSharpProject) (fileName:string) (caretPosition:int) = diff --git a/vsintegration/tests/UnitTests/SemanticColorizationServiceTests.fs b/vsintegration/tests/UnitTests/SemanticColorizationServiceTests.fs index 3846e2b6308..78d0732493b 100644 --- a/vsintegration/tests/UnitTests/SemanticColorizationServiceTests.fs +++ b/vsintegration/tests/UnitTests/SemanticColorizationServiceTests.fs @@ -32,10 +32,9 @@ type SemanticClassificationServiceTests() = let perfOptions = { LanguageServicePerformanceOptions.Default with AllowStaleCompletionResults = false } let getRanges (source: string) : SemanticClassificationItem list = - let projectOptions = { projectOptions with ProjectId = Some(Guid.NewGuid().ToString()) } asyncMaybe { let document, _ = RoslynTestHelpers.CreateDocument(filePath, source) - let! _, _, checkFileResults = checker.ParseAndCheckDocument(document, projectOptions, perfOptions, "") + let! _, checkFileResults = document.GetFSharpParseAndCheckResultsAsync("SemanticClassificationServiceTests") |> liftAsync return checkFileResults.GetSemanticClassification(None) } |> Async.RunSynchronously diff --git a/vsintegration/tests/UnitTests/SignatureHelpProviderTests.fs b/vsintegration/tests/UnitTests/SignatureHelpProviderTests.fs index df7ff193736..77b8038e98c 100644 --- a/vsintegration/tests/UnitTests/SignatureHelpProviderTests.fs +++ b/vsintegration/tests/UnitTests/SignatureHelpProviderTests.fs @@ -53,14 +53,11 @@ let GetSignatureHelp (project:FSharpProject) (fileName:string) (caretPosition:in let textLines = sourceText.Lines let caretLinePos = textLines.GetLinePosition(caretPosition) let caretLineColumn = caretLinePos.Character - let perfOptions = LanguageServicePerformanceOptions.Default - let document = RoslynTestHelpers.CreateDocument(fileName, sourceText) - let parseResults, _, checkFileResults = - let x = - checker.ParseAndCheckDocument(document, project.Options, perfOptions, "TestSignatureHelpProvider") - |> Async.RunSynchronously - x.Value + let document = RoslynTestHelpers.CreateDocument(fileName, sourceText, options = project.Options) + let parseResults, checkFileResults = + document.GetFSharpParseAndCheckResultsAsync("GetSignatureHelp") + |> Async.RunSynchronously let paramInfoLocations = parseResults.FindParameterLocations(Position.fromZ caretLinePos.Line caretLineColumn).Value let triggered = @@ -103,17 +100,11 @@ let assertSignatureHelpForMethodCalls (fileContents: string) (marker: string) (e let textLines = sourceText.Lines let caretLinePos = textLines.GetLinePosition(caretPosition) let caretLineColumn = caretLinePos.Character - let perfOptions = LanguageServicePerformanceOptions.Default - let document = RoslynTestHelpers.CreateDocument(filePath, sourceText) - let parseResults, _, checkFileResults = - let x = - checker.ParseAndCheckDocument(document, projectOptions, perfOptions, "TestSignatureHelpProvider") - |> Async.RunSynchronously - - if x.IsNone then - Assert.Fail("Could not parse and check document.") - x.Value + let document = RoslynTestHelpers.CreateDocument(filePath, sourceText, options = projectOptions) + let parseResults, checkFileResults = + document.GetFSharpParseAndCheckResultsAsync("assertSignatureHelpForMethodCalls") + |> Async.RunSynchronously let actual = let paramInfoLocations = parseResults.FindParameterLocations(Position.fromZ caretLinePos.Line caretLineColumn) @@ -142,16 +133,10 @@ let assertSignatureHelpForMethodCalls (fileContents: string) (marker: string) (e let assertSignatureHelpForFunctionApplication (fileContents: string) (marker: string) expectedArgumentCount expectedArgumentIndex = let caretPosition = fileContents.LastIndexOf(marker) + marker.Length let document, sourceText = RoslynTestHelpers.CreateDocument(filePath, fileContents) - let perfOptions = LanguageServicePerformanceOptions.Default - let parseResults, _, checkFileResults = - let x = - checker.ParseAndCheckDocument(document, projectOptions, perfOptions, "TestSignatureHelpProvider") - |> Async.RunSynchronously - - if x.IsNone then - Assert.Fail("Could not parse and check document.") - x.Value + let parseResults, checkFileResults = + document.GetFSharpParseAndCheckResultsAsync("assertSignatureHelpForFunctionApplication") + |> Async.RunSynchronously let adjustedColumnInSource = let rec loop ch pos = @@ -429,16 +414,10 @@ M.f let caretPosition = fileContents.IndexOf(marker) + marker.Length let document, sourceText = RoslynTestHelpers.CreateDocument(filePath, fileContents) - let perfOptions = LanguageServicePerformanceOptions.Default - let parseResults, _, checkFileResults = - let x = - checker.ParseAndCheckDocument(document, projectOptions, perfOptions, "TestSignatureHelpProvider") - |> Async.RunSynchronously - - if x.IsNone then - Assert.Fail("Could not parse and check document.") - x.Value + let parseResults, checkFileResults = + document.GetFSharpParseAndCheckResultsAsync("function application in single pipeline with no additional args") + |> Async.RunSynchronously let adjustedColumnInSource = let rec loop ch pos = diff --git a/vsintegration/tests/UnitTests/Tests.RoslynHelpers.fs b/vsintegration/tests/UnitTests/Tests.RoslynHelpers.fs index 6ccdbacc715..7481753ec30 100644 --- a/vsintegration/tests/UnitTests/Tests.RoslynHelpers.fs +++ b/vsintegration/tests/UnitTests/Tests.RoslynHelpers.fs @@ -1,37 +1,250 @@ -namespace Microsoft.VisualStudio.FSharp.Editor.Tests.Roslyn +namespace rec Microsoft.VisualStudio.FSharp.Editor.Tests.Roslyn open System open System.IO +open System.Reflection +open System.Linq +open System.Composition.Hosting +open System.Collections.Generic +open System.Collections.Immutable +open Microsoft.VisualStudio.Composition open Microsoft.CodeAnalysis +open Microsoft.CodeAnalysis.Host open Microsoft.CodeAnalysis.Text +open Microsoft.VisualStudio.FSharp.Editor +open Microsoft.CodeAnalysis.Host.Mef +open Microsoft.VisualStudio.LanguageServices +open Microsoft.VisualStudio.Shell + +[] +module MefHelpers = + + let getAssemblies() = + let self = Assembly.GetExecutingAssembly() + let here = AppContext.BaseDirectory + + let imports = [| + "Microsoft.CodeAnalysis.Workspaces.dll" + "Microsoft.VisualStudio.Shell.15.0.dll" + "FSharp.Editor.dll" + |] + + let resolvedImports = imports.Select(fun name -> Path.Combine(here, name)).ToList() + let missingDlls = resolvedImports.Where(fun path -> not(File.Exists(path))).ToList() + if (missingDlls.Any()) then + failwith "Missing imports" + + let loadedImports = resolvedImports.Select(fun p -> Assembly.LoadFrom(p)).ToList() + + let result = loadedImports.ToDictionary(fun k -> Path.GetFileNameWithoutExtension(k.Location)) + result.Values + |> Seq.append [|self|] + |> Seq.append MefHostServices.DefaultAssemblies + |> Array.ofSeq + + let createExportProvider() = + let resolver = Resolver.DefaultInstance + let catalog = + let asms = getAssemblies() + let partDiscovery = PartDiscovery.Combine(new AttributedPartDiscoveryV1(resolver), new AttributedPartDiscovery(resolver, isNonPublicSupported = true)); + let parts = partDiscovery.CreatePartsAsync(asms).Result + let catalog = ComposableCatalog.Create(resolver) + catalog.AddParts(parts) + + let configuration = CompositionConfiguration.Create(catalog.WithCompositionService()) + let runtimeComposition = RuntimeComposition.CreateRuntimeComposition(configuration) + let exportProviderFactory = runtimeComposition.CreateExportProviderFactory() + exportProviderFactory.CreateExportProvider() + +type TestWorkspaceServiceMetadata(serviceType: string, layer: string) = + + member _.ServiceType = serviceType + member _.Layer = layer + + new(data: IDictionary) = + let serviceType = + match data.TryGetValue("ServiceType") with + | true, result -> result :?> string + | _ -> Unchecked.defaultof<_> + + let layer = + match data.TryGetValue("Layer") with + | true, result -> result :?> string + | _ -> Unchecked.defaultof<_> + TestWorkspaceServiceMetadata(serviceType, layer) + + new(serviceType: Type, layer: string) = + TestWorkspaceServiceMetadata(serviceType.AssemblyQualifiedName, layer) + +type TestLanguageServiceMetadata(language: string, serviceType: string, layer: string, data: IDictionary) = + + member _.Language = language + member _.ServiceType = serviceType + member _.Layer = layer + member _.Data = data + + new(data: IDictionary) = + let language = + match data.TryGetValue("Language") with + | true, result -> result :?> string + | _ -> Unchecked.defaultof<_> + + let serviceType = + match data.TryGetValue("ServiceType") with + | true, result -> result :?> string + | _ -> Unchecked.defaultof<_> + + let layer = + match data.TryGetValue("Layer") with + | true, result -> result :?> string + | _ -> Unchecked.defaultof<_> + TestLanguageServiceMetadata(language, serviceType, layer, data) + +type TestHostLanguageServices(workspaceServices: HostWorkspaceServices, language: string, exportProvider: ExportProvider) as this = + inherit HostLanguageServices() + + let services1 = + exportProvider.GetExports() + |> Seq.filter (fun x -> x.Metadata.Language = language) + + let factories1 = + exportProvider.GetExports() + |> Seq.filter (fun x -> x.Metadata.Language = language) + |> Seq.map (fun x -> + Lazy<_, _>((fun () -> x.Value.CreateLanguageService(this)), x.Metadata) + ) + + let otherServices1 = Seq.append factories1 services1 + + let otherServicesMap1 = + otherServices1 + |> Seq.map (fun x -> + KeyValuePair(x.Metadata.ServiceType, x) + ) + |> Seq.distinctBy (fun x -> x.Key) + |> System.Collections.Concurrent.ConcurrentDictionary + + override this.WorkspaceServices = workspaceServices + + override this.Language = language + + override this.GetService<'T when 'T :> ILanguageService>() : 'T = + match otherServicesMap1.TryGetValue(typeof<'T>.AssemblyQualifiedName) with + | true, otherService -> + otherService.Value :?> 'T + | _ -> + try + exportProvider.GetExport<'T>().Value + with + | _ -> + Unchecked.defaultof<'T> + +type TestHostWorkspaceServices(hostServices: HostServices, workspace: Workspace) as this = + inherit HostWorkspaceServices() + + let exportProvider = createExportProvider() + + let services1 = + exportProvider.GetExports() + + let factories1 = + exportProvider.GetExports() + |> Seq.map (fun x -> + Lazy<_, _>((fun () -> x.Value.CreateService(this)), x.Metadata) + ) + + let otherServices1 = + Seq.append factories1 services1 + + let otherServicesMap1 = + otherServices1 + |> Seq.map (fun x -> + KeyValuePair(x.Metadata.ServiceType, x) + ) + |> Seq.distinctBy (fun x -> x.Key) + |> System.Collections.Concurrent.ConcurrentDictionary + + let langServices = TestHostLanguageServices(this, LanguageNames.FSharp, exportProvider) + + override _.Workspace = workspace + + override this.GetService<'T when 'T :> IWorkspaceService>() : 'T = + let ty = typeof<'T> + match otherServicesMap1.TryGetValue(ty.AssemblyQualifiedName) with + | true, otherService -> + otherService.Value :?> 'T + | _ -> + try + exportProvider.GetExport<'T>().Value + with + | _ -> + Unchecked.defaultof<'T> + + override _.FindLanguageServices(filter) = Seq.empty + + override _.GetLanguageServices(languageName) = + match languageName with + | LanguageNames.FSharp -> + langServices :> HostLanguageServices + | _ -> + raise(NotSupportedException(sprintf "Language '%s' not supported in FSharp VS tests." languageName)) + + override _.HostServices = hostServices + +type TestHostServices() = + inherit HostServices() + + override this.CreateWorkspaceServices(workspace) = + TestHostWorkspaceServices(this, workspace) :> HostWorkspaceServices [] type RoslynTestHelpers private () = - static member CreateDocument (filePath, text: SourceText) = + static member CreateDocument (filePath, text: SourceText, ?options: FSharp.Compiler.CodeAnalysis.FSharpProjectOptions) = let isScript = String.Equals(Path.GetExtension(filePath), ".fsx", StringComparison.OrdinalIgnoreCase) - let workspace = new AdhocWorkspace() - let projInfo = - let projId = ProjectId.CreateNewId() - ProjectInfo.Create( - projId, - VersionStamp.Create(DateTime.UtcNow), - "test.fsproj", - "test.dll", - LanguageNames.CSharp - ) - let proj = workspace.AddProject(projInfo) + + let workspace = new AdhocWorkspace(TestHostServices()) + + let projId = ProjectId.CreateNewId() + let docId = DocumentId.CreateNewId(projId) let docInfo = - let docId = DocumentId.CreateNewId(proj.Id) DocumentInfo.Create( docId, - filePath, + filePath, loader=TextLoader.From(text.Container, VersionStamp.Create(DateTime.UtcNow)), filePath=filePath, sourceCodeKind= if isScript then SourceCodeKind.Script else SourceCodeKind.Regular) - workspace.AddDocument(docInfo) + let projFilePath = "C:\\test.fsproj" + let projInfo = + ProjectInfo.Create( + projId, + VersionStamp.Create(DateTime.UtcNow), + projFilePath, + "test.dll", + LanguageNames.FSharp, + documents = [docInfo], + filePath = projFilePath + ) + + let solutionInfo = SolutionInfo.Create(SolutionId.CreateNewId(), VersionStamp.Create(DateTime.UtcNow), "test.sln", [projInfo]) + + let solution = workspace.AddSolution(solutionInfo) + + let workspaceService = workspace.Services.GetService() + + let document = solution.GetProject(projId).GetDocument(docId) + + match options with + | Some options -> + let options = { options with ProjectId = Some(Guid.NewGuid().ToString()) } + workspaceService.FSharpProjectOptionsManager.SetCommandLineOptions(projId, options.SourceFiles, options.OtherOptions |> ImmutableArray.CreateRange) + document.SetFSharpProjectOptionsForTesting(options) + | _ -> + workspaceService.FSharpProjectOptionsManager.SetCommandLineOptions(projId, [|filePath|], ImmutableArray.Empty) + + document static member CreateDocument (filePath, code: string) = let text = SourceText.From(code) diff --git a/vsintegration/tests/UnitTests/VisualFSharp.UnitTests.fsproj b/vsintegration/tests/UnitTests/VisualFSharp.UnitTests.fsproj index b0ce865f554..baa2c21ae98 100644 --- a/vsintegration/tests/UnitTests/VisualFSharp.UnitTests.fsproj +++ b/vsintegration/tests/UnitTests/VisualFSharp.UnitTests.fsproj @@ -196,6 +196,8 @@ + +