From 80f7e2492c878ee98f14e205cbc82d12bcbc468f Mon Sep 17 00:00:00 2001 From: vaskir Date: Sat, 26 Nov 2016 16:34:48 +0300 Subject: [PATCH 01/12] Implement FSharpQuickInfoProvider (by @OmarTawfik) --- .../src/FSharp.Editor/FSharp.Editor.fsproj | 8 ++ .../src/FSharp.Editor/QuickInfoProvider.fs | 95 +++++++++++++++++++ 2 files changed, 103 insertions(+) create mode 100644 vsintegration/src/FSharp.Editor/QuickInfoProvider.fs diff --git a/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj b/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj index f874ffcc9f2..cf9671f6610 100644 --- a/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj +++ b/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj @@ -68,6 +68,9 @@ BlockComment\CommentUncommentService.fs + + QuickInfo\QuickInfoProvider.fs + @@ -93,6 +96,8 @@ + + @@ -110,6 +115,9 @@ $(FSharpSourcesRoot)\..\packages\Microsoft.VisualStudio.Text.UI.Wpf.$(RoslynVSPackagesVersion)\lib\net45\Microsoft.VisualStudio.Text.UI.Wpf.dll + + $(FSharpSourcesRoot)\..\packages\Microsoft.VisualStudio.Language.Intellisense.$(RoslynVSPackagesVersion)\lib\net45\Microsoft.VisualStudio.Language.Intellisense.dll + $(FSharpSourcesRoot)\..\packages\Microsoft.VisualStudio.Text.Data.$(RoslynVSPackagesVersion)\lib\net45\Microsoft.VisualStudio.Text.Data.dll diff --git a/vsintegration/src/FSharp.Editor/QuickInfoProvider.fs b/vsintegration/src/FSharp.Editor/QuickInfoProvider.fs new file mode 100644 index 00000000000..c2fc974e344 --- /dev/null +++ b/vsintegration/src/FSharp.Editor/QuickInfoProvider.fs @@ -0,0 +1,95 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Microsoft.VisualStudio.FSharp.Editor + +open System +open System.Composition +open System.Collections.Concurrent +open System.Collections.Generic +open System.Collections.Immutable +open System.Threading +open System.Threading.Tasks +open System.Linq +open System.Runtime.CompilerServices +open System.Windows +open System.Windows.Controls +open System.Windows.Media + +open Microsoft.CodeAnalysis +open Microsoft.CodeAnalysis.Completion +open Microsoft.CodeAnalysis.Classification +open Microsoft.CodeAnalysis.Editor +open Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense.QuickInfo +open Microsoft.CodeAnalysis.Editor.Shared.Utilities +open Microsoft.CodeAnalysis.Formatting +open Microsoft.CodeAnalysis.Host.Mef +open Microsoft.CodeAnalysis.Options +open Microsoft.CodeAnalysis.Text + +open Microsoft.VisualStudio.FSharp.LanguageService +open Microsoft.VisualStudio.Text +open Microsoft.VisualStudio.Text.Tagging +open Microsoft.VisualStudio.Shell +open Microsoft.VisualStudio.Shell.Interop + +open Microsoft.FSharp.Compiler +open Microsoft.FSharp.Compiler.Parser +open Microsoft.FSharp.Compiler.Range +open Microsoft.FSharp.Compiler.SourceCodeServices + +// FSROSLYNTODO: with the merge of the below PR, the QuickInfo API should be changed +// to allow for a more flexible syntax for defining the content of the tooltip. +// The below interface should be discarded then or updated accourdingly. +// https://github.com/dotnet/roslyn/pull/13623 +type internal FSharpDeferredQuickInfoContent(content: string) = + interface IDeferredQuickInfoContent with + override this.Create() : FrameworkElement = + let label = new Label() + label.Content <- content + label.Foreground <- SolidColorBrush(Colors.White) + upcast label + +[] +[] +type internal FSharpQuickInfoProvider [] (serviceProvider: SVsServiceProvider) = + + let xmlMemberIndexService = serviceProvider.GetService(typeof) :?> IVsXMLMemberIndexService + let documentationBuilder = XmlDocumentation.CreateDocumentationBuilder(xmlMemberIndexService, serviceProvider.DTE) + + static member ProvideQuickInfo(sourceText: SourceText, position: int, options: FSharpProjectOptions, filePath: string, textVersionHash: int) = + async { + let! parseResults = FSharpChecker.Instance.ParseFileInProject(filePath, sourceText.ToString(), options) + let! checkFileAnswer = FSharpChecker.Instance.CheckFileInProject(parseResults, filePath, textVersionHash, sourceText.ToString(), options) + let checkFileResults = + match checkFileAnswer with + | FSharpCheckFileAnswer.Aborted -> failwith "Compilation isn't complete yet" + | FSharpCheckFileAnswer.Succeeded(results) -> results + + let textLine = sourceText.Lines.GetLineFromPosition(position) + let textLineNumber = textLine.LineNumber + 1 // Roslyn line numbers are zero-based + let qualifyingNames, partialName = QuickParse.GetPartialLongNameEx(textLine.ToString(), position - textLine.Start - 1) + return! checkFileResults.GetToolTipTextAlternate(textLineNumber, position, textLine.ToString(), qualifyingNames, tagOfToken(token.IDENT(partialName))) + } + + interface IQuickInfoProvider with + override this.GetItemAsync(document: Document, position: int, cancellationToken: CancellationToken): Task = + async { + match FSharpLanguageService.GetOptions(document.Project.Id) with + | Some(options) -> + let! sourceText = document.GetTextAsync(cancellationToken) |> Async.AwaitTask + let textSpan = TextSpan(0, sourceText.Length) + let defines = CompilerEnvironment.GetCompilationDefinesForEditing(document.Name, options.OtherOptions |> Seq.toList) + let tokens = FSharpColorizationService.GetColorizationData(document.Id, sourceText, textSpan, Some(document.FilePath), defines, cancellationToken) + + match tokens |> Seq.tryFind(fun t -> t.TextSpan.Contains(position)) with + | Some(token) -> + match token.ClassificationType with + | ClassificationTypeNames.Identifier -> + let! textVersion = document.GetTextVersionAsync(cancellationToken) |> Async.AwaitTask + let! toolTipElement = FSharpQuickInfoProvider.ProvideQuickInfo(sourceText, position, options, document.FilePath, textVersion.GetHashCode()) + let dataTipText = XmlDocumentation.BuildDataTipText(documentationBuilder, toolTipElement) + return QuickInfoItem(token.TextSpan, FSharpDeferredQuickInfoContent(dataTipText)) + | _ -> return Unchecked.defaultof<_> + | None -> return Unchecked.defaultof<_> + | None -> return Unchecked.defaultof<_> + } |> CommonRoslynHelpers.StartAsyncAsTask(cancellationToken) \ No newline at end of file From 4104e83ab8f4f655503ac4ff37f195f58928856b Mon Sep 17 00:00:00 2001 From: vaskir Date: Sat, 26 Nov 2016 19:02:35 +0300 Subject: [PATCH 02/12] extract tryClassifyAtPosition and reuse it in QuickInfoProvider --- .../src/FSharp.Editor/ColorizationService.fs | 178 ++------------- .../src/FSharp.Editor/CommonHelpers.fs | 211 ++++++++++++++++++ .../src/FSharp.Editor/CompletionProvider.fs | 10 +- .../src/FSharp.Editor/FSharp.Editor.fsproj | 1 + .../FSharp.Editor/GoToDefinitionService.fs | 104 ++++----- .../FSharp.Editor/LanguageDebugInfoService.fs | 2 +- .../src/FSharp.Editor/QuickInfoProvider.fs | 17 +- 7 files changed, 284 insertions(+), 239 deletions(-) create mode 100644 vsintegration/src/FSharp.Editor/CommonHelpers.fs diff --git a/vsintegration/src/FSharp.Editor/ColorizationService.fs b/vsintegration/src/FSharp.Editor/ColorizationService.fs index 931ec8023be..82506d4ef20 100644 --- a/vsintegration/src/FSharp.Editor/ColorizationService.fs +++ b/vsintegration/src/FSharp.Editor/ColorizationService.fs @@ -28,169 +28,22 @@ open Microsoft.VisualStudio.Utilities open Microsoft.FSharp.Compiler.SourceCodeServices -type private SourceLineData(lineStart: int, lexStateAtStartOfLine: FSharpTokenizerLexState, lexStateAtEndOfLine: FSharpTokenizerLexState, hashCode: int, classifiedSpans: IReadOnlyList) = - member val LineStart = lineStart - member val LexStateAtStartOfLine = lexStateAtStartOfLine - member val LexStateAtEndOfLine = lexStateAtEndOfLine - member val HashCode = hashCode - member val ClassifiedSpans = classifiedSpans - - member data.IsValid(textLine: TextLine) = - data.LineStart = textLine.Start && - let lineContents = textLine.Text.ToString(textLine.Span) - data.HashCode = lineContents.GetHashCode() - -type private SourceTextData(approxLines: int) = - let data = ResizeArray(approxLines) - let extendTo i = - if i >= data.Count then - data.Capacity <- i + 1 - for j in data.Count .. i do - data.Add(None) - member x.Item - with get (i:int) = extendTo i; data.[i] - and set (i:int) v = extendTo i; data.[i] <- v - - member x.ClearFrom(n) = - let mutable i = n - while i < data.Count && data.[i].IsSome do - data.[i] <- None - i <- i + 1 - - - [, FSharpCommonConstants.FSharpLanguageName)>] type internal FSharpColorizationService() = - - static let DataCache = ConditionalWeakTable() - static let Tokenizers = ConditionalWeakTable<(string list * string option), FSharpSourceTokenizer>() - - static let compilerTokenToRoslynToken(colorKind: FSharpTokenColorKind) : string = - match colorKind with - | FSharpTokenColorKind.Comment -> ClassificationTypeNames.Comment - | FSharpTokenColorKind.Identifier -> ClassificationTypeNames.Identifier - | FSharpTokenColorKind.Keyword -> ClassificationTypeNames.Keyword - | FSharpTokenColorKind.String -> ClassificationTypeNames.StringLiteral - | FSharpTokenColorKind.Text -> ClassificationTypeNames.Text - | FSharpTokenColorKind.UpperIdentifier -> ClassificationTypeNames.Identifier - | FSharpTokenColorKind.Number -> ClassificationTypeNames.NumericLiteral - | FSharpTokenColorKind.InactiveCode -> ClassificationTypeNames.ExcludedCode - | FSharpTokenColorKind.PreprocessorKeyword -> ClassificationTypeNames.PreprocessorKeyword - | FSharpTokenColorKind.Operator -> ClassificationTypeNames.Operator - | FSharpTokenColorKind.TypeName -> ClassificationTypeNames.ClassName - | FSharpTokenColorKind.Default | _ -> ClassificationTypeNames.Text - - static let scanSourceLine(sourceTokenizer: FSharpSourceTokenizer, textLine: TextLine, lineContents: string, lexState: FSharpTokenizerLexState) : SourceLineData = - - let colorMap = Array.create textLine.Span.Length ClassificationTypeNames.Text - let lineTokenizer = sourceTokenizer.CreateLineTokenizer(lineContents) - - let scanAndColorNextToken(lineTokenizer: FSharpLineTokenizer, lexState: Ref) : Option = - let tokenInfoOption, nextLexState = lineTokenizer.ScanToken(lexState.Value) - lexState.Value <- nextLexState - if tokenInfoOption.IsSome then - let classificationType = compilerTokenToRoslynToken(tokenInfoOption.Value.ColorClass) - for i = tokenInfoOption.Value.LeftColumn to tokenInfoOption.Value.RightColumn do - Array.set colorMap i classificationType - tokenInfoOption - - let previousLexState = ref lexState - let mutable tokenInfoOption = scanAndColorNextToken(lineTokenizer, previousLexState) - while tokenInfoOption.IsSome do - tokenInfoOption <- scanAndColorNextToken(lineTokenizer, previousLexState) - - let mutable startPosition = 0 - let mutable endPosition = startPosition - let classifiedSpans = new List() - - while startPosition < colorMap.Length do - let classificationType = colorMap.[startPosition] - endPosition <- startPosition - while endPosition < colorMap.Length && classificationType = colorMap.[endPosition] do - endPosition <- endPosition + 1 - let textSpan = new TextSpan(textLine.Start + startPosition, endPosition - startPosition) - classifiedSpans.Add(new ClassifiedSpan(classificationType, textSpan)) - startPosition <- endPosition - - SourceLineData(textLine.Start, lexState, previousLexState.Value, lineContents.GetHashCode(), classifiedSpans) - - static member GetColorizationData(documentKey: DocumentId, sourceText: SourceText, textSpan: TextSpan, fileName: Option, defines: string list, cancellationToken: CancellationToken) : List = - try - let sourceTokenizer = Tokenizers.GetValue ((defines, fileName), fun _ -> FSharpSourceTokenizer(defines, fileName)) - let lines = sourceText.Lines - // We keep incremental data per-document. When text changes we correlate text line-by-line (by hash codes of lines) - let sourceTextData = DataCache.GetValue(documentKey, fun key -> SourceTextData(lines.Count)) - - let startLine = lines.GetLineFromPosition(textSpan.Start).LineNumber - let endLine = lines.GetLineFromPosition(textSpan.End).LineNumber - - // Go backwards to find the last cached scanned line that is valid - let scanStartLine = - let mutable i = startLine - while i > 0 && (match sourceTextData.[i-1] with Some data -> not (data.IsValid(lines.[i])) | None -> true) do - i <- i - 1 - i - - // Rescan the lines if necessary and report the information - let result = new List() - let mutable lexState = if scanStartLine = 0 then 0L else sourceTextData.[scanStartLine - 1].Value.LexStateAtEndOfLine - - for i = scanStartLine to endLine do - cancellationToken.ThrowIfCancellationRequested() - let textLine = lines.[i] - let lineContents = textLine.Text.ToString(textLine.Span) - - let lineData = - // We can reuse the old data when - // 1. the line starts at the same overall position - // 2. the hash codes match - // 3. the start-of-line lex states are the same - match sourceTextData.[i] with - | Some data when data.IsValid(textLine) && data.LexStateAtStartOfLine = lexState -> - data - | _ -> - // Otherwise, we recompute - let newData = scanSourceLine(sourceTokenizer, textLine, lineContents, lexState) - sourceTextData.[i] <- Some newData - newData - - lexState <- lineData.LexStateAtEndOfLine - - if startLine <= i then - result.AddRange(lineData.ClassifiedSpans |> Seq.filter(fun token -> - textSpan.Contains(token.TextSpan.Start) || - textSpan.Contains(token.TextSpan.End - 1) || - (token.TextSpan.Start <= textSpan.Start && textSpan.End <= token.TextSpan.End))) - - // If necessary, invalidate all subsequent lines after endLine - if endLine < lines.Count - 1 then - match sourceTextData.[endLine+1] with - | Some data -> - if data.LexStateAtStartOfLine <> lexState then - sourceTextData.ClearFrom (endLine+1) - | None -> () - - result - - with ex -> - Assert.Exception(ex) - reraise() - interface IEditorClassificationService with - // Do not perform classification if we don't have project options (#defines matter) member this.AddLexicalClassifications(_: SourceText, _: TextSpan, _: List, _: CancellationToken) = () member this.AddSyntacticClassificationsAsync(document: Document, textSpan: TextSpan, result: List, cancellationToken: CancellationToken) = - document.GetTextAsync(cancellationToken).ContinueWith( - fun (sourceTextTask: Task) -> - match FSharpLanguageService.GetOptions(document.Project.Id) with - | Some(options) -> - let defines = CompilerEnvironment.GetCompilationDefinesForEditing(document.Name, options.OtherOptions |> Seq.toList) - if sourceTextTask.Status = TaskStatus.RanToCompletion then - result.AddRange(FSharpColorizationService.GetColorizationData(document.Id, sourceTextTask.Result, textSpan, Some(document.FilePath), defines, cancellationToken)) - | None -> () - , cancellationToken) + async { + let! sourceText = document.GetTextAsync(cancellationToken) |> Async.AwaitTask + match FSharpLanguageService.GetOptions(document.Project.Id) with + | Some(options) -> + let defines = CompilerEnvironment.GetCompilationDefinesForEditing(document.Name, options.OtherOptions |> Seq.toList) + result.AddRange(CommonHelpers.getColorizationData( + document.Id, sourceText, textSpan, Some(document.FilePath), defines, cancellationToken)) + | None -> () + } |> CommonRoslynHelpers.StartAsyncUnitAsTask(cancellationToken) member this.AddSemanticClassificationsAsync(document: Document, textSpan: TextSpan, result: List, cancellationToken: CancellationToken) = async { @@ -201,11 +54,14 @@ type internal FSharpColorizationService() = let! textVersion = document.GetTextVersionAsync(cancellationToken) |> Async.AwaitTask let! checkResultsAnswer = FSharpLanguageService.Checker.CheckFileInProject(parseResults, document.FilePath, textVersion.GetHashCode(), textSpan.ToString(), options) - let extraColorizationData = match checkResultsAnswer with - | FSharpCheckFileAnswer.Aborted -> failwith "Compilation isn't complete yet" - | FSharpCheckFileAnswer.Succeeded(results) -> results.GetExtraColorizationsAlternate() - |> Seq.map(fun (range, tokenColorKind) -> ClassifiedSpan(CommonRoslynHelpers.FSharpRangeToTextSpan(sourceText, range), compilerTokenToRoslynToken(tokenColorKind))) - |> Seq.toList + let extraColorizationData = + match checkResultsAnswer with + | FSharpCheckFileAnswer.Aborted -> failwith "Compilation isn't complete yet" + | FSharpCheckFileAnswer.Succeeded(results) -> results.GetExtraColorizationsAlternate() + |> Seq.map(fun (range, tokenColorKind) -> + ClassifiedSpan(CommonRoslynHelpers.FSharpRangeToTextSpan(sourceText, range), + CommonHelpers.compilerTokenToRoslynToken(tokenColorKind))) + |> Seq.toList result.AddRange(extraColorizationData) | None -> () diff --git a/vsintegration/src/FSharp.Editor/CommonHelpers.fs b/vsintegration/src/FSharp.Editor/CommonHelpers.fs new file mode 100644 index 00000000000..b1412601638 --- /dev/null +++ b/vsintegration/src/FSharp.Editor/CommonHelpers.fs @@ -0,0 +1,211 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Microsoft.VisualStudio.FSharp.Editor + +open System.Collections.Generic +open System.Threading +open System.Threading.Tasks +open System.Runtime.CompilerServices + +open Microsoft.CodeAnalysis +open Microsoft.CodeAnalysis.Classification +open Microsoft.CodeAnalysis.Text + +open Microsoft.VisualStudio.FSharp.LanguageService +open Microsoft.FSharp.Compiler.SourceCodeServices + +module CommonHelpers = + type private SourceLineData + ( + lineStart: int, + lexStateAtStartOfLine: FSharpTokenizerLexState, + lexStateAtEndOfLine: FSharpTokenizerLexState, + hashCode: int, + classifiedSpans: IReadOnlyList + ) = + member val LineStart = lineStart + member val LexStateAtStartOfLine = lexStateAtStartOfLine + member val LexStateAtEndOfLine = lexStateAtEndOfLine + member val HashCode = hashCode + member val ClassifiedSpans = classifiedSpans + + member data.IsValid(textLine: TextLine) = + data.LineStart = textLine.Start && + let lineContents = textLine.Text.ToString(textLine.Span) + data.HashCode = lineContents.GetHashCode() + + type private SourceTextData(approxLines: int) = + let data = ResizeArray(approxLines) + let extendTo i = + if i >= data.Count then + data.Capacity <- i + 1 + for j in data.Count .. i do + data.Add(None) + member x.Item + with get (i:int) = extendTo i; data.[i] + and set (i:int) v = extendTo i; data.[i] <- v + + member x.ClearFrom(n) = + let mutable i = n + while i < data.Count && data.[i].IsSome do + data.[i] <- None + i <- i + 1 + + let private dataCache = ConditionalWeakTable() + let private tokenizers = ConditionalWeakTable<(string list * string option), FSharpSourceTokenizer>() + + let internal compilerTokenToRoslynToken(colorKind: FSharpTokenColorKind) : string = + match colorKind with + | FSharpTokenColorKind.Comment -> ClassificationTypeNames.Comment + | FSharpTokenColorKind.Identifier -> ClassificationTypeNames.Identifier + | FSharpTokenColorKind.Keyword -> ClassificationTypeNames.Keyword + | FSharpTokenColorKind.String -> ClassificationTypeNames.StringLiteral + | FSharpTokenColorKind.Text -> ClassificationTypeNames.Text + | FSharpTokenColorKind.UpperIdentifier -> ClassificationTypeNames.Identifier + | FSharpTokenColorKind.Number -> ClassificationTypeNames.NumericLiteral + | FSharpTokenColorKind.InactiveCode -> ClassificationTypeNames.ExcludedCode + | FSharpTokenColorKind.PreprocessorKeyword -> ClassificationTypeNames.PreprocessorKeyword + | FSharpTokenColorKind.Operator -> ClassificationTypeNames.Operator + | FSharpTokenColorKind.TypeName -> ClassificationTypeNames.ClassName + | FSharpTokenColorKind.Default + | _ -> ClassificationTypeNames.Text + + let private scanSourceLine + ( + sourceTokenizer: FSharpSourceTokenizer, + textLine: TextLine, + lineContents: string, + lexState: FSharpTokenizerLexState + ) : SourceLineData = + + let colorMap = Array.create textLine.Span.Length ClassificationTypeNames.Text + let lineTokenizer = sourceTokenizer.CreateLineTokenizer(lineContents) + + let scanAndColorNextToken(lineTokenizer: FSharpLineTokenizer, lexState: Ref) : Option = + let tokenInfoOption, nextLexState = lineTokenizer.ScanToken(lexState.Value) + lexState.Value <- nextLexState + if tokenInfoOption.IsSome then + let classificationType = compilerTokenToRoslynToken(tokenInfoOption.Value.ColorClass) + for i = tokenInfoOption.Value.LeftColumn to tokenInfoOption.Value.RightColumn do + Array.set colorMap i classificationType + tokenInfoOption + + let previousLexState = ref lexState + let mutable tokenInfoOption = scanAndColorNextToken(lineTokenizer, previousLexState) + while tokenInfoOption.IsSome do + tokenInfoOption <- scanAndColorNextToken(lineTokenizer, previousLexState) + + let mutable startPosition = 0 + let mutable endPosition = startPosition + let classifiedSpans = new List() + + while startPosition < colorMap.Length do + let classificationType = colorMap.[startPosition] + endPosition <- startPosition + while endPosition < colorMap.Length && classificationType = colorMap.[endPosition] do + endPosition <- endPosition + 1 + let textSpan = new TextSpan(textLine.Start + startPosition, endPosition - startPosition) + classifiedSpans.Add(new ClassifiedSpan(classificationType, textSpan)) + startPosition <- endPosition + + SourceLineData(textLine.Start, lexState, previousLexState.Value, lineContents.GetHashCode(), classifiedSpans) + + let getColorizationData + ( + documentKey: DocumentId, + sourceText: SourceText, + textSpan: TextSpan, + fileName: string option, + defines: string list, + cancellationToken: CancellationToken + ) : List = + try + let sourceTokenizer = tokenizers.GetValue ((defines, fileName), fun _ -> FSharpSourceTokenizer(defines, fileName)) + let lines = sourceText.Lines + // We keep incremental data per-document. When text changes we correlate text line-by-line (by hash codes of lines) + let sourceTextData = dataCache.GetValue(documentKey, fun key -> SourceTextData(lines.Count)) + + let startLine = lines.GetLineFromPosition(textSpan.Start).LineNumber + let endLine = lines.GetLineFromPosition(textSpan.End).LineNumber + + // Go backwards to find the last cached scanned line that is valid + let scanStartLine = + let mutable i = startLine + while i > 0 && (match sourceTextData.[i-1] with Some data -> not (data.IsValid(lines.[i])) | None -> true) do + i <- i - 1 + i + + // Rescan the lines if necessary and report the information + let result = new List() + let mutable lexState = if scanStartLine = 0 then 0L else sourceTextData.[scanStartLine - 1].Value.LexStateAtEndOfLine + + for i = scanStartLine to endLine do + cancellationToken.ThrowIfCancellationRequested() + let textLine = lines.[i] + let lineContents = textLine.Text.ToString(textLine.Span) + + let lineData = + // We can reuse the old data when + // 1. the line starts at the same overall position + // 2. the hash codes match + // 3. the start-of-line lex states are the same + match sourceTextData.[i] with + | Some data when data.IsValid(textLine) && data.LexStateAtStartOfLine = lexState -> + data + | _ -> + // Otherwise, we recompute + let newData = scanSourceLine(sourceTokenizer, textLine, lineContents, lexState) + sourceTextData.[i] <- Some newData + newData + + lexState <- lineData.LexStateAtEndOfLine + + if startLine <= i then + result.AddRange(lineData.ClassifiedSpans |> Seq.filter(fun token -> + textSpan.Contains(token.TextSpan.Start) || + textSpan.Contains(token.TextSpan.End - 1) || + (token.TextSpan.Start <= textSpan.Start && textSpan.End <= token.TextSpan.End))) + + // If necessary, invalidate all subsequent lines after endLine + if endLine < lines.Count - 1 then + match sourceTextData.[endLine+1] with + | Some data -> + if data.LexStateAtStartOfLine <> lexState then + sourceTextData.ClearFrom (endLine+1) + | None -> () + + result + with ex -> + Assert.Exception(ex) + reraise() + + let tryClassifyAtPosition (documentKey, sourceText: SourceText, filePath, defines, position: int, cancellationToken) = + let textLine = sourceText.Lines.GetLineFromPosition(position) + let textLinePos = sourceText.Lines.GetLinePosition(position) + let textLineColumn = textLinePos.Character + + let classifiedSpanOption = + getColorizationData(documentKey, sourceText, textLine.Span, Some(filePath), defines, cancellationToken) + |> Seq.tryFind(fun classifiedSpan -> classifiedSpan.TextSpan.Contains(position)) + + match classifiedSpanOption with + | Some(classifiedSpan) -> + match classifiedSpan.ClassificationType with + | ClassificationTypeNames.ClassName + | ClassificationTypeNames.DelegateName + | ClassificationTypeNames.EnumName + | ClassificationTypeNames.InterfaceName + | ClassificationTypeNames.ModuleName + | ClassificationTypeNames.StructName + | ClassificationTypeNames.TypeParameterName + | ClassificationTypeNames.Identifier -> + match QuickParse.GetCompleteIdentifierIsland true (textLine.ToString()) textLineColumn with + | Some (islandIdentifier, islandColumn, isQuoted) -> + let qualifiers = if isQuoted then [islandIdentifier] else islandIdentifier.Split '.' |> Array.toList + Some (islandColumn, qualifiers) + | None -> None + | ClassificationTypeNames.Operator -> + let islandColumn = sourceText.Lines.GetLinePositionSpan(classifiedSpan.TextSpan).End.Character + Some (islandColumn, [""]) + | _ -> None + | _ -> None \ No newline at end of file diff --git a/vsintegration/src/FSharp.Editor/CompletionProvider.fs b/vsintegration/src/FSharp.Editor/CompletionProvider.fs index d83b2d08832..c9125021f27 100644 --- a/vsintegration/src/FSharp.Editor/CompletionProvider.fs +++ b/vsintegration/src/FSharp.Editor/CompletionProvider.fs @@ -43,7 +43,13 @@ type internal FSharpCompletionProvider(workspace: Workspace, serviceProvider: SV let xmlMemberIndexService = serviceProvider.GetService(typeof) :?> IVsXMLMemberIndexService let documentationBuilder = XmlDocumentation.CreateDocumentationBuilder(xmlMemberIndexService, serviceProvider.DTE) - static member ShouldTriggerCompletionAux(sourceText: SourceText, caretPosition: int, trigger: CompletionTriggerKind, getInfo: (unit -> DocumentId * string * string list)) = + static member ShouldTriggerCompletionAux + ( + sourceText: SourceText, + caretPosition: int, + trigger: CompletionTriggerKind, + getInfo: (unit -> DocumentId * string * string list) + ) = // Skip if we are at the start of a document if caretPosition = 0 then false @@ -64,7 +70,7 @@ type internal FSharpCompletionProvider(workspace: Workspace, serviceProvider: SV let triggerPosition = caretPosition - 1 let textLine = sourceText.Lines.GetLineFromPosition(triggerPosition) let classifiedSpanOption = - FSharpColorizationService.GetColorizationData(documentId, sourceText, textLine.Span, Some(filePath), defines, CancellationToken.None) + CommonHelpers.getColorizationData(documentId, sourceText, textLine.Span, Some(filePath), defines, CancellationToken.None) |> Seq.tryFind(fun classifiedSpan -> classifiedSpan.TextSpan.Contains(triggerPosition)) match classifiedSpanOption with diff --git a/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj b/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj index cf9671f6610..9d206f94cb9 100644 --- a/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj +++ b/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj @@ -31,6 +31,7 @@ + Classification\ColorizationService.fs diff --git a/vsintegration/src/FSharp.Editor/GoToDefinitionService.fs b/vsintegration/src/FSharp.Editor/GoToDefinitionService.fs index 79f019977e2..700693ea1bf 100644 --- a/vsintegration/src/FSharp.Editor/GoToDefinitionService.fs +++ b/vsintegration/src/FSharp.Editor/GoToDefinitionService.fs @@ -4,7 +4,6 @@ namespace Microsoft.VisualStudio.FSharp.Editor open System open System.Composition -open System.Collections.Concurrent open System.Collections.Generic open System.Collections.Immutable open System.Linq @@ -43,70 +42,45 @@ type internal FSharpNavigableItem(document: Document, textSpan: TextSpan) = [, FSharpCommonConstants.FSharpLanguageName)>] type internal FSharpGoToDefinitionService [] ([]presenters: IEnumerable) = - static member FindDefinition (documentKey: DocumentId, - sourceText: SourceText, - filePath: string, - position: int, - defines: string list, - options: FSharpProjectOptions, - textVersionHash: int, - cancellationToken: CancellationToken) - : Async> = - async { - - let textLine = sourceText.Lines.GetLineFromPosition(position) - let textLinePos = sourceText.Lines.GetLinePosition(position) - let fcsTextLineNumber = textLinePos.Line + 1 // Roslyn line numbers are zero-based, FSharp.Compiler.Service line numbers are 1-based - let textLineColumn = textLinePos.Character - let tryClassifyAtPosition (position: int) = - let classifiedSpanOption = - FSharpColorizationService.GetColorizationData(documentKey, sourceText, textLine.Span, Some(filePath), defines, cancellationToken) - |> Seq.tryFind(fun classifiedSpan -> classifiedSpan.TextSpan.Contains(position)) - - match classifiedSpanOption with - | Some(classifiedSpan) -> - match classifiedSpan.ClassificationType with - | ClassificationTypeNames.ClassName - | ClassificationTypeNames.DelegateName - | ClassificationTypeNames.EnumName - | ClassificationTypeNames.InterfaceName - | ClassificationTypeNames.ModuleName - | ClassificationTypeNames.StructName - | ClassificationTypeNames.TypeParameterName - | ClassificationTypeNames.Identifier -> - match QuickParse.GetCompleteIdentifierIsland true (textLine.ToString()) textLineColumn with - | Some (islandIdentifier, islandColumn, isQuoted) -> - let qualifiers = if isQuoted then [islandIdentifier] else islandIdentifier.Split '.' |> Array.toList - Some (islandColumn, qualifiers) - | None -> None - | ClassificationTypeNames.Operator -> - let islandColumn = sourceText.Lines.GetLinePositionSpan(classifiedSpan.TextSpan).End.Character - Some (islandColumn, [""]) - | _ -> None - | _ -> None - - // Tolerate being on the right of the identifier - let quickParseInfo = - match tryClassifyAtPosition position with - | None when textLineColumn > 0 -> tryClassifyAtPosition (position - 1) - | res -> res - - match quickParseInfo with - | Some (islandColumn, qualifiers) -> - let! parseResults = FSharpLanguageService.Checker.ParseFileInProject(filePath, sourceText.ToString(), options) - let! checkFileAnswer = FSharpLanguageService.Checker.CheckFileInProject(parseResults, filePath, textVersionHash, sourceText.ToString(), options) - let checkFileResults = - match checkFileAnswer with - | FSharpCheckFileAnswer.Aborted -> failwith "Compilation isn't complete yet" - | FSharpCheckFileAnswer.Succeeded(results) -> results - - let! declarations = checkFileResults.GetDeclarationLocationAlternate (fcsTextLineNumber, islandColumn, textLine.ToString(), qualifiers, false) - - match declarations with - | FSharpFindDeclResult.DeclFound(range) -> return Some(range) - | _ -> return None - | None -> return None - } + static member FindDefinition + ( + documentKey: DocumentId, + sourceText: SourceText, + filePath: string, + position: int, + defines: string list, + options: FSharpProjectOptions, + textVersionHash: int, + cancellationToken: CancellationToken + ) : Async> = + async { + let textLine = sourceText.Lines.GetLineFromPosition(position) + let textLinePos = sourceText.Lines.GetLinePosition(position) + let fcsTextLineNumber = textLinePos.Line + 1 // Roslyn line numbers are zero-based, FSharp.Compiler.Service line numbers are 1-based + let textLineColumn = textLinePos.Character + let tryClassifyAtPosition position = CommonHelpers.tryClassifyAtPosition(documentKey, sourceText, filePath, defines, position, cancellationToken) + // Tolerate being on the right of the identifier + let quickParseInfo = + match tryClassifyAtPosition position with + | None when textLineColumn > 0 -> tryClassifyAtPosition (position - 1) + | res -> res + + match quickParseInfo with + | Some (islandColumn, qualifiers) -> + let! parseResults = FSharpLanguageService.Checker.ParseFileInProject(filePath, sourceText.ToString(), options) + let! checkFileAnswer = FSharpLanguageService.Checker.CheckFileInProject(parseResults, filePath, textVersionHash, sourceText.ToString(), options) + let checkFileResults = + match checkFileAnswer with + | FSharpCheckFileAnswer.Aborted -> failwith "Compilation isn't complete yet" + | FSharpCheckFileAnswer.Succeeded(results) -> results + + let! declarations = checkFileResults.GetDeclarationLocationAlternate (fcsTextLineNumber, islandColumn, textLine.ToString(), qualifiers, false) + + match declarations with + | FSharpFindDeclResult.DeclFound(range) -> return Some(range) + | _ -> return None + | None -> return None + } // FSROSLYNTODO: Since we are not integrated with the Roslyn project system yet, the below call // document.Project.Solution.GetDocumentIdsWithFilePath() will only access files in the same project. diff --git a/vsintegration/src/FSharp.Editor/LanguageDebugInfoService.fs b/vsintegration/src/FSharp.Editor/LanguageDebugInfoService.fs index d7808258112..dab7714df06 100644 --- a/vsintegration/src/FSharp.Editor/LanguageDebugInfoService.fs +++ b/vsintegration/src/FSharp.Editor/LanguageDebugInfoService.fs @@ -71,7 +71,7 @@ type internal FSharpLanguageDebugInfoService() = let defines = CompilerEnvironment.GetCompilationDefinesForEditing(document.Name, options.OtherOptions |> Seq.toList) let! sourceText = document.GetTextAsync(cancellationToken) |> Async.AwaitTask let textSpan = TextSpan.FromBounds(0, sourceText.Length) - let tokens = FSharpColorizationService.GetColorizationData(document.Id, sourceText, textSpan, Some(document.Name), defines, cancellationToken) + let tokens = CommonHelpers.getColorizationData(document.Id, sourceText, textSpan, Some(document.Name), defines, cancellationToken) return match FSharpLanguageDebugInfoService.GetDataTipInformation(sourceText, position, tokens) with | None -> Unchecked.defaultof | Some(textSpan) -> new DebugDataTipInfo(textSpan, sourceText.GetSubText(textSpan).ToString()) diff --git a/vsintegration/src/FSharp.Editor/QuickInfoProvider.fs b/vsintegration/src/FSharp.Editor/QuickInfoProvider.fs index c2fc974e344..b02f71ce8bd 100644 --- a/vsintegration/src/FSharp.Editor/QuickInfoProvider.fs +++ b/vsintegration/src/FSharp.Editor/QuickInfoProvider.fs @@ -79,17 +79,14 @@ type internal FSharpQuickInfoProvider [] (serviceProvider: let! sourceText = document.GetTextAsync(cancellationToken) |> Async.AwaitTask let textSpan = TextSpan(0, sourceText.Length) let defines = CompilerEnvironment.GetCompilationDefinesForEditing(document.Name, options.OtherOptions |> Seq.toList) - let tokens = FSharpColorizationService.GetColorizationData(document.Id, sourceText, textSpan, Some(document.FilePath), defines, cancellationToken) + let classification = CommonHelpers.tryClassifyAtPosition(document.Id, sourceText, document.FilePath, defines, position, cancellationToken) - match tokens |> Seq.tryFind(fun t -> t.TextSpan.Contains(position)) with - | Some(token) -> - match token.ClassificationType with - | ClassificationTypeNames.Identifier -> - let! textVersion = document.GetTextVersionAsync(cancellationToken) |> Async.AwaitTask - let! toolTipElement = FSharpQuickInfoProvider.ProvideQuickInfo(sourceText, position, options, document.FilePath, textVersion.GetHashCode()) - let dataTipText = XmlDocumentation.BuildDataTipText(documentationBuilder, toolTipElement) - return QuickInfoItem(token.TextSpan, FSharpDeferredQuickInfoContent(dataTipText)) - | _ -> return Unchecked.defaultof<_> + match classification with + | Some(_column, _island) -> + let! textVersion = document.GetTextVersionAsync(cancellationToken) |> Async.AwaitTask + let! toolTipElement = FSharpQuickInfoProvider.ProvideQuickInfo(sourceText, position, options, document.FilePath, textVersion.GetHashCode()) + let dataTipText = XmlDocumentation.BuildDataTipText(documentationBuilder, toolTipElement) + return QuickInfoItem(textSpan, FSharpDeferredQuickInfoContent(dataTipText)) | None -> return Unchecked.defaultof<_> | None -> return Unchecked.defaultof<_> } |> CommonRoslynHelpers.StartAsyncAsTask(cancellationToken) \ No newline at end of file From 1138c8db2592a6e2bda45a24ecf87cfbb0cfabb5 Mon Sep 17 00:00:00 2001 From: vaskir Date: Sat, 26 Nov 2016 20:23:29 +0300 Subject: [PATCH 03/12] fix tests --- vsintegration/tests/unittests/ColorizationServiceTests.fs | 2 +- vsintegration/tests/unittests/LanguageDebugInfoServiceTests.fs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/vsintegration/tests/unittests/ColorizationServiceTests.fs b/vsintegration/tests/unittests/ColorizationServiceTests.fs index 7875f55cd71..6213835e3f8 100644 --- a/vsintegration/tests/unittests/ColorizationServiceTests.fs +++ b/vsintegration/tests/unittests/ColorizationServiceTests.fs @@ -18,7 +18,7 @@ type ColorizationServiceTests() = let textSpan = TextSpan(0, fileContents.Length) let fileName = if isScriptFile.IsSome && isScriptFile.Value then "test.fsx" else "test.fs" let documentId = DocumentId.CreateNewId(ProjectId.CreateNewId()) - let tokens = FSharpColorizationService.GetColorizationData(documentId, SourceText.From(fileContents), textSpan, Some(fileName), defines, CancellationToken.None) + let tokens = CommonHelpers.getColorizationData(documentId, SourceText.From(fileContents), textSpan, Some(fileName), defines, CancellationToken.None) let markerPosition = fileContents.IndexOf(marker) Assert.IsTrue(markerPosition >= 0, "Cannot find marker '{0}' in file contents", marker) (tokens, markerPosition) diff --git a/vsintegration/tests/unittests/LanguageDebugInfoServiceTests.fs b/vsintegration/tests/unittests/LanguageDebugInfoServiceTests.fs index 736c18dd4a4..b904a120fbb 100644 --- a/vsintegration/tests/unittests/LanguageDebugInfoServiceTests.fs +++ b/vsintegration/tests/unittests/LanguageDebugInfoServiceTests.fs @@ -54,7 +54,7 @@ let main argv = let sourceText = SourceText.From(code) let documentId = DocumentId.CreateNewId(ProjectId.CreateNewId()) - let tokens = FSharpColorizationService.GetColorizationData(documentId, sourceText, TextSpan.FromBounds(0, sourceText.Length), Some(fileName), defines, CancellationToken.None) + let tokens = CommonHelpers.getColorizationData(documentId, sourceText, TextSpan.FromBounds(0, sourceText.Length), Some(fileName), defines, CancellationToken.None) let actualDataTipSpanOption = FSharpLanguageDebugInfoService.GetDataTipInformation(sourceText, searchPosition, tokens) match actualDataTipSpanOption with From 6a088a3ed0657acf097de2dc0494408f79973ca4 Mon Sep 17 00:00:00 2001 From: vaskir Date: Sat, 26 Nov 2016 21:14:36 +0300 Subject: [PATCH 04/12] fix MEF composition --- src/fsharp/FSharp.Compiler/FSharp.Compiler.fsproj | 11 ----------- vsintegration/src/FSharp.Editor/QuickInfoProvider.fs | 5 +++-- 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/src/fsharp/FSharp.Compiler/FSharp.Compiler.fsproj b/src/fsharp/FSharp.Compiler/FSharp.Compiler.fsproj index 1ef7b864065..c5d0c2f4355 100644 --- a/src/fsharp/FSharp.Compiler/FSharp.Compiler.fsproj +++ b/src/fsharp/FSharp.Compiler/FSharp.Compiler.fsproj @@ -516,17 +516,6 @@ ..\..\..\packages\System.Reflection.Metadata.1.4.1-beta-24227-04\lib\portable-net45+win8\System.Reflection.Metadata.dll ..\..\..\packages\System.Collections.Immutable.1.2.0\lib\portable-net45+win8+wp8+wpa81\System.Collections.Immutable.dll ..\..\..\packages\System.ValueTuple.4.3.0\lib\netstandard1.0\System.ValueTuple.dlltrue - - - - - - - - - - - diff --git a/vsintegration/src/FSharp.Editor/QuickInfoProvider.fs b/vsintegration/src/FSharp.Editor/QuickInfoProvider.fs index b02f71ce8bd..6cf296dd38a 100644 --- a/vsintegration/src/FSharp.Editor/QuickInfoProvider.fs +++ b/vsintegration/src/FSharp.Editor/QuickInfoProvider.fs @@ -51,7 +51,8 @@ type internal FSharpDeferredQuickInfoContent(content: string) = [] [] -type internal FSharpQuickInfoProvider [] (serviceProvider: SVsServiceProvider) = +type internal FSharpQuickInfoProvider [] + ([)>] serviceProvider: IServiceProvider) = let xmlMemberIndexService = serviceProvider.GetService(typeof) :?> IVsXMLMemberIndexService let documentationBuilder = XmlDocumentation.CreateDocumentationBuilder(xmlMemberIndexService, serviceProvider.DTE) @@ -64,7 +65,7 @@ type internal FSharpQuickInfoProvider [] (serviceProvider: match checkFileAnswer with | FSharpCheckFileAnswer.Aborted -> failwith "Compilation isn't complete yet" | FSharpCheckFileAnswer.Succeeded(results) -> results - + let textLine = sourceText.Lines.GetLineFromPosition(position) let textLineNumber = textLine.LineNumber + 1 // Roslyn line numbers are zero-based let qualifyingNames, partialName = QuickParse.GetPartialLongNameEx(textLine.ToString(), position - textLine.Start - 1) From 4a48e945f88e7fb02b5193a66f71c21115ad892c Mon Sep 17 00:00:00 2001 From: vaskir Date: Sat, 26 Nov 2016 22:14:26 +0300 Subject: [PATCH 05/12] try to fix QuickInfoProvider --- src/fsharp/vs/service.fs | 2 +- vsintegration/src/FSharp.Editor/QuickInfoProvider.fs | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/fsharp/vs/service.fs b/src/fsharp/vs/service.fs index 247d8403bcf..fa47e06e95e 100755 --- a/src/fsharp/vs/service.fs +++ b/src/fsharp/vs/service.fs @@ -888,7 +888,7 @@ type TypeCheckInfo | None, _ -> [], None | Some(origLongIdent), Some _ -> origLongIdent, None | Some(origLongIdent), None -> - assert (not (isNil origLongIdent)) + System.Diagnostics.Debug.Assert(not (isNil origLongIdent), "origLongIdent is empty") // note: as above, this happens when we are called for "precise" resolution - (F1 keyword, data tip etc..) let plid, residue = List.frontAndBack origLongIdent plid, Some residue diff --git a/vsintegration/src/FSharp.Editor/QuickInfoProvider.fs b/vsintegration/src/FSharp.Editor/QuickInfoProvider.fs index 6cf296dd38a..7da6f03122d 100644 --- a/vsintegration/src/FSharp.Editor/QuickInfoProvider.fs +++ b/vsintegration/src/FSharp.Editor/QuickInfoProvider.fs @@ -68,7 +68,9 @@ type internal FSharpQuickInfoProvider [ Date: Sat, 26 Nov 2016 23:31:22 +0300 Subject: [PATCH 06/12] QuickInfoProvider (sort of) works --- .../src/FSharp.Editor/QuickInfoProvider.fs | 43 ++++++++++++++++--- 1 file changed, 36 insertions(+), 7 deletions(-) diff --git a/vsintegration/src/FSharp.Editor/QuickInfoProvider.fs b/vsintegration/src/FSharp.Editor/QuickInfoProvider.fs index 7da6f03122d..7a3999a478c 100644 --- a/vsintegration/src/FSharp.Editor/QuickInfoProvider.fs +++ b/vsintegration/src/FSharp.Editor/QuickInfoProvider.fs @@ -46,7 +46,7 @@ type internal FSharpDeferredQuickInfoContent(content: string) = override this.Create() : FrameworkElement = let label = new Label() label.Content <- content - label.Foreground <- SolidColorBrush(Colors.White) + label.Foreground <- SolidColorBrush(Colors.Black) upcast label [] @@ -57,7 +57,16 @@ type internal FSharpQuickInfoProvider [) :?> IVsXMLMemberIndexService let documentationBuilder = XmlDocumentation.CreateDocumentationBuilder(xmlMemberIndexService, serviceProvider.DTE) - static member ProvideQuickInfo(sourceText: SourceText, position: int, options: FSharpProjectOptions, filePath: string, textVersionHash: int) = + static member ProvideQuickInfo + ( + document: Document, + sourceText: SourceText, + position: int, + options: FSharpProjectOptions, + filePath: string, + textVersionHash: int, + cancellationToken: CancellationToken + ) = async { let! parseResults = FSharpChecker.Instance.ParseFileInProject(filePath, sourceText.ToString(), options) let! checkFileAnswer = FSharpChecker.Instance.CheckFileInProject(parseResults, filePath, textVersionHash, sourceText.ToString(), options) @@ -70,8 +79,23 @@ type internal FSharpQuickInfoProvider [ Seq.toList) + let tryClassifyAtPosition position = + CommonHelpers.tryClassifyAtPosition(document.Id, sourceText, filePath, defines, position, cancellationToken) + + let quickParseInfo = + match tryClassifyAtPosition position with + | None when textLineColumn > 0 -> tryClassifyAtPosition (position - 1) + | res -> res + + match quickParseInfo with + | Some (islandColumn, qualifiers) -> + //let tokenTag = QuickParse.CorrectIdentifierToken "" tokenTag + let! res = checkFileResults.GetToolTipTextAlternate( + textLineNumber, islandColumn, textLine.ToString(), qualifiers, 189) + return Some(res) + | None -> return None } interface IQuickInfoProvider with @@ -87,9 +111,14 @@ type internal FSharpQuickInfoProvider [ let! textVersion = document.GetTextVersionAsync(cancellationToken) |> Async.AwaitTask - let! toolTipElement = FSharpQuickInfoProvider.ProvideQuickInfo(sourceText, position, options, document.FilePath, textVersion.GetHashCode()) - let dataTipText = XmlDocumentation.BuildDataTipText(documentationBuilder, toolTipElement) - return QuickInfoItem(textSpan, FSharpDeferredQuickInfoContent(dataTipText)) + let! toolTipElement = + FSharpQuickInfoProvider.ProvideQuickInfo(document, sourceText, position, options, document.FilePath, + textVersion.GetHashCode(), cancellationToken) + match toolTipElement with + | Some toolTipElement -> + let dataTipText = XmlDocumentation.BuildDataTipText(documentationBuilder, toolTipElement) + return QuickInfoItem(textSpan, FSharpDeferredQuickInfoContent(dataTipText)) + | None -> return Unchecked.defaultof<_> | None -> return Unchecked.defaultof<_> | None -> return Unchecked.defaultof<_> } |> CommonRoslynHelpers.StartAsyncAsTask(cancellationToken) \ No newline at end of file From ec2f6055fba22d7c28af7801662771f42a99d75f Mon Sep 17 00:00:00 2001 From: vaskir Date: Sun, 27 Nov 2016 10:07:09 +0300 Subject: [PATCH 07/12] address code review --- .../src/FSharp.Editor/CommonHelpers.fs | 28 ++----------- .../src/FSharp.Editor/CompletionProvider.fs | 8 +--- .../FSharp.Editor/GoToDefinitionService.fs | 12 +----- .../src/FSharp.Editor/QuickInfoProvider.fs | 39 ++++++------------- 4 files changed, 16 insertions(+), 71 deletions(-) diff --git a/vsintegration/src/FSharp.Editor/CommonHelpers.fs b/vsintegration/src/FSharp.Editor/CommonHelpers.fs index b1412601638..6a3536d1cdb 100644 --- a/vsintegration/src/FSharp.Editor/CommonHelpers.fs +++ b/vsintegration/src/FSharp.Editor/CommonHelpers.fs @@ -15,14 +15,7 @@ open Microsoft.VisualStudio.FSharp.LanguageService open Microsoft.FSharp.Compiler.SourceCodeServices module CommonHelpers = - type private SourceLineData - ( - lineStart: int, - lexStateAtStartOfLine: FSharpTokenizerLexState, - lexStateAtEndOfLine: FSharpTokenizerLexState, - hashCode: int, - classifiedSpans: IReadOnlyList - ) = + type private SourceLineData(lineStart: int, lexStateAtStartOfLine: FSharpTokenizerLexState, lexStateAtEndOfLine: FSharpTokenizerLexState, hashCode: int, classifiedSpans: IReadOnlyList) = member val LineStart = lineStart member val LexStateAtStartOfLine = lexStateAtStartOfLine member val LexStateAtEndOfLine = lexStateAtEndOfLine @@ -70,14 +63,7 @@ module CommonHelpers = | FSharpTokenColorKind.Default | _ -> ClassificationTypeNames.Text - let private scanSourceLine - ( - sourceTokenizer: FSharpSourceTokenizer, - textLine: TextLine, - lineContents: string, - lexState: FSharpTokenizerLexState - ) : SourceLineData = - + let private scanSourceLine(sourceTokenizer: FSharpSourceTokenizer, textLine: TextLine, lineContents: string, lexState: FSharpTokenizerLexState) : SourceLineData = let colorMap = Array.create textLine.Span.Length ClassificationTypeNames.Text let lineTokenizer = sourceTokenizer.CreateLineTokenizer(lineContents) @@ -110,15 +96,7 @@ module CommonHelpers = SourceLineData(textLine.Start, lexState, previousLexState.Value, lineContents.GetHashCode(), classifiedSpans) - let getColorizationData - ( - documentKey: DocumentId, - sourceText: SourceText, - textSpan: TextSpan, - fileName: string option, - defines: string list, - cancellationToken: CancellationToken - ) : List = + let getColorizationData(documentKey: DocumentId, sourceText: SourceText, textSpan: TextSpan, fileName: string option, defines: string list, cancellationToken: CancellationToken) : List = try let sourceTokenizer = tokenizers.GetValue ((defines, fileName), fun _ -> FSharpSourceTokenizer(defines, fileName)) let lines = sourceText.Lines diff --git a/vsintegration/src/FSharp.Editor/CompletionProvider.fs b/vsintegration/src/FSharp.Editor/CompletionProvider.fs index c9125021f27..6d5c547a499 100644 --- a/vsintegration/src/FSharp.Editor/CompletionProvider.fs +++ b/vsintegration/src/FSharp.Editor/CompletionProvider.fs @@ -43,13 +43,7 @@ type internal FSharpCompletionProvider(workspace: Workspace, serviceProvider: SV let xmlMemberIndexService = serviceProvider.GetService(typeof) :?> IVsXMLMemberIndexService let documentationBuilder = XmlDocumentation.CreateDocumentationBuilder(xmlMemberIndexService, serviceProvider.DTE) - static member ShouldTriggerCompletionAux - ( - sourceText: SourceText, - caretPosition: int, - trigger: CompletionTriggerKind, - getInfo: (unit -> DocumentId * string * string list) - ) = + static member ShouldTriggerCompletionAux(sourceText: SourceText, caretPosition: int, trigger: CompletionTriggerKind, getInfo: (unit -> DocumentId * string * string list)) = // Skip if we are at the start of a document if caretPosition = 0 then false diff --git a/vsintegration/src/FSharp.Editor/GoToDefinitionService.fs b/vsintegration/src/FSharp.Editor/GoToDefinitionService.fs index 700693ea1bf..6c9e0b0e618 100644 --- a/vsintegration/src/FSharp.Editor/GoToDefinitionService.fs +++ b/vsintegration/src/FSharp.Editor/GoToDefinitionService.fs @@ -42,17 +42,7 @@ type internal FSharpNavigableItem(document: Document, textSpan: TextSpan) = [, FSharpCommonConstants.FSharpLanguageName)>] type internal FSharpGoToDefinitionService [] ([]presenters: IEnumerable) = - static member FindDefinition - ( - documentKey: DocumentId, - sourceText: SourceText, - filePath: string, - position: int, - defines: string list, - options: FSharpProjectOptions, - textVersionHash: int, - cancellationToken: CancellationToken - ) : Async> = + static member FindDefinition(documentKey: DocumentId, sourceText: SourceText, filePath: string, position: int, defines: string list, options: FSharpProjectOptions, textVersionHash: int, cancellationToken: CancellationToken) : Async> = async { let textLine = sourceText.Lines.GetLineFromPosition(position) let textLinePos = sourceText.Lines.GetLinePosition(position) diff --git a/vsintegration/src/FSharp.Editor/QuickInfoProvider.fs b/vsintegration/src/FSharp.Editor/QuickInfoProvider.fs index 7a3999a478c..e206de99e72 100644 --- a/vsintegration/src/FSharp.Editor/QuickInfoProvider.fs +++ b/vsintegration/src/FSharp.Editor/QuickInfoProvider.fs @@ -44,10 +44,7 @@ open Microsoft.FSharp.Compiler.SourceCodeServices type internal FSharpDeferredQuickInfoContent(content: string) = interface IDeferredQuickInfoContent with override this.Create() : FrameworkElement = - let label = new Label() - label.Content <- content - label.Foreground <- SolidColorBrush(Colors.Black) - upcast label + upcast new Label(Content = content, Foreground = SolidColorBrush(Colors.Black)) [] [] @@ -57,21 +54,11 @@ type internal FSharpQuickInfoProvider [) :?> IVsXMLMemberIndexService let documentationBuilder = XmlDocumentation.CreateDocumentationBuilder(xmlMemberIndexService, serviceProvider.DTE) - static member ProvideQuickInfo - ( - document: Document, - sourceText: SourceText, - position: int, - options: FSharpProjectOptions, - filePath: string, - textVersionHash: int, - cancellationToken: CancellationToken - ) = + static member ProvideQuickInfo(document: Document, sourceText: SourceText, position: int, options: FSharpProjectOptions, textVersionHash: int, cancellationToken: CancellationToken) = async { - let! parseResults = FSharpChecker.Instance.ParseFileInProject(filePath, sourceText.ToString(), options) - let! checkFileAnswer = FSharpChecker.Instance.CheckFileInProject(parseResults, filePath, textVersionHash, sourceText.ToString(), options) + let! _parseResults, checkResultsAnswer = FSharpChecker.Instance.ParseAndCheckFileInProject(document.FilePath, textVersionHash, sourceText.ToString(), options) let checkFileResults = - match checkFileAnswer with + match checkResultsAnswer with | FSharpCheckFileAnswer.Aborted -> failwith "Compilation isn't complete yet" | FSharpCheckFileAnswer.Succeeded(results) -> results @@ -82,7 +69,7 @@ type internal FSharpQuickInfoProvider [ Seq.toList) let tryClassifyAtPosition position = - CommonHelpers.tryClassifyAtPosition(document.Id, sourceText, filePath, defines, position, cancellationToken) + CommonHelpers.tryClassifyAtPosition(document.Id, sourceText, document.FilePath, defines, position, cancellationToken) let quickParseInfo = match tryClassifyAtPosition position with @@ -91,9 +78,7 @@ type internal FSharpQuickInfoProvider [ - //let tokenTag = QuickParse.CorrectIdentifierToken "" tokenTag - let! res = checkFileResults.GetToolTipTextAlternate( - textLineNumber, islandColumn, textLine.ToString(), qualifiers, 189) + let! res = checkFileResults.GetToolTipTextAlternate(textLineNumber, islandColumn, textLine.ToString(), qualifiers, FSharpTokenTag.IDENT) return Some(res) | None -> return None } @@ -109,16 +94,14 @@ type internal FSharpQuickInfoProvider [ + | Some _ -> let! textVersion = document.GetTextVersionAsync(cancellationToken) |> Async.AwaitTask - let! toolTipElement = - FSharpQuickInfoProvider.ProvideQuickInfo(document, sourceText, position, options, document.FilePath, - textVersion.GetHashCode(), cancellationToken) + let! toolTipElement = FSharpQuickInfoProvider.ProvideQuickInfo(document, sourceText, position, options, textVersion.GetHashCode(), cancellationToken) match toolTipElement with | Some toolTipElement -> let dataTipText = XmlDocumentation.BuildDataTipText(documentationBuilder, toolTipElement) return QuickInfoItem(textSpan, FSharpDeferredQuickInfoContent(dataTipText)) - | None -> return Unchecked.defaultof<_> - | None -> return Unchecked.defaultof<_> - | None -> return Unchecked.defaultof<_> + | None -> return null + | None -> return null + | None -> return null } |> CommonRoslynHelpers.StartAsyncAsTask(cancellationToken) \ No newline at end of file From 63bd19f6faf5d72b35743797b5a89175d4dd9e4b Mon Sep 17 00:00:00 2001 From: vaskir Date: Sun, 27 Nov 2016 10:36:20 +0300 Subject: [PATCH 08/12] fix QuickInfo text span --- vsintegration/src/FSharp.Editor/CommonHelpers.fs | 4 ++-- .../src/FSharp.Editor/GoToDefinitionService.fs | 2 +- vsintegration/src/FSharp.Editor/QuickInfoProvider.fs | 11 +++++------ 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/vsintegration/src/FSharp.Editor/CommonHelpers.fs b/vsintegration/src/FSharp.Editor/CommonHelpers.fs index 6a3536d1cdb..7eb0974a271 100644 --- a/vsintegration/src/FSharp.Editor/CommonHelpers.fs +++ b/vsintegration/src/FSharp.Editor/CommonHelpers.fs @@ -180,10 +180,10 @@ module CommonHelpers = match QuickParse.GetCompleteIdentifierIsland true (textLine.ToString()) textLineColumn with | Some (islandIdentifier, islandColumn, isQuoted) -> let qualifiers = if isQuoted then [islandIdentifier] else islandIdentifier.Split '.' |> Array.toList - Some (islandColumn, qualifiers) + Some (islandColumn, qualifiers, classifiedSpan.TextSpan) | None -> None | ClassificationTypeNames.Operator -> let islandColumn = sourceText.Lines.GetLinePositionSpan(classifiedSpan.TextSpan).End.Character - Some (islandColumn, [""]) + Some (islandColumn, [""], classifiedSpan.TextSpan) | _ -> None | _ -> None \ No newline at end of file diff --git a/vsintegration/src/FSharp.Editor/GoToDefinitionService.fs b/vsintegration/src/FSharp.Editor/GoToDefinitionService.fs index 6c9e0b0e618..0006b179eae 100644 --- a/vsintegration/src/FSharp.Editor/GoToDefinitionService.fs +++ b/vsintegration/src/FSharp.Editor/GoToDefinitionService.fs @@ -56,7 +56,7 @@ type internal FSharpGoToDefinitionService [] ([ res match quickParseInfo with - | Some (islandColumn, qualifiers) -> + | Some (islandColumn, qualifiers, _) -> let! parseResults = FSharpLanguageService.Checker.ParseFileInProject(filePath, sourceText.ToString(), options) let! checkFileAnswer = FSharpLanguageService.Checker.CheckFileInProject(parseResults, filePath, textVersionHash, sourceText.ToString(), options) let checkFileResults = diff --git a/vsintegration/src/FSharp.Editor/QuickInfoProvider.fs b/vsintegration/src/FSharp.Editor/QuickInfoProvider.fs index e206de99e72..581ce75d900 100644 --- a/vsintegration/src/FSharp.Editor/QuickInfoProvider.fs +++ b/vsintegration/src/FSharp.Editor/QuickInfoProvider.fs @@ -77,9 +77,9 @@ type internal FSharpQuickInfoProvider [ res match quickParseInfo with - | Some (islandColumn, qualifiers) -> + | Some (islandColumn, qualifiers, textSpan) -> let! res = checkFileResults.GetToolTipTextAlternate(textLineNumber, islandColumn, textLine.ToString(), qualifiers, FSharpTokenTag.IDENT) - return Some(res) + return Some(res, textSpan) | None -> return None } @@ -89,16 +89,15 @@ type internal FSharpQuickInfoProvider [ let! sourceText = document.GetTextAsync(cancellationToken) |> Async.AwaitTask - let textSpan = TextSpan(0, sourceText.Length) let defines = CompilerEnvironment.GetCompilationDefinesForEditing(document.Name, options.OtherOptions |> Seq.toList) let classification = CommonHelpers.tryClassifyAtPosition(document.Id, sourceText, document.FilePath, defines, position, cancellationToken) match classification with | Some _ -> let! textVersion = document.GetTextVersionAsync(cancellationToken) |> Async.AwaitTask - let! toolTipElement = FSharpQuickInfoProvider.ProvideQuickInfo(document, sourceText, position, options, textVersion.GetHashCode(), cancellationToken) - match toolTipElement with - | Some toolTipElement -> + let! quickInfoResult = FSharpQuickInfoProvider.ProvideQuickInfo(document, sourceText, position, options, textVersion.GetHashCode(), cancellationToken) + match quickInfoResult with + | Some(toolTipElement, textSpan) -> let dataTipText = XmlDocumentation.BuildDataTipText(documentationBuilder, toolTipElement) return QuickInfoItem(textSpan, FSharpDeferredQuickInfoContent(dataTipText)) | None -> return null From a8af9837f631870bfd2644abb5899263bf02de99 Mon Sep 17 00:00:00 2001 From: vaskir Date: Sun, 27 Nov 2016 10:48:59 +0300 Subject: [PATCH 09/12] don't show empty tooltip --- vsintegration/src/FSharp.Editor/QuickInfoProvider.fs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/vsintegration/src/FSharp.Editor/QuickInfoProvider.fs b/vsintegration/src/FSharp.Editor/QuickInfoProvider.fs index 581ce75d900..1533f8f674b 100644 --- a/vsintegration/src/FSharp.Editor/QuickInfoProvider.fs +++ b/vsintegration/src/FSharp.Editor/QuickInfoProvider.fs @@ -79,7 +79,10 @@ type internal FSharpQuickInfoProvider [ let! res = checkFileResults.GetToolTipTextAlternate(textLineNumber, islandColumn, textLine.ToString(), qualifiers, FSharpTokenTag.IDENT) - return Some(res, textSpan) + return + match res with + | FSharpToolTipText [] -> None + | _ -> Some(res, textSpan) | None -> return None } @@ -98,7 +101,7 @@ type internal FSharpQuickInfoProvider [ - let dataTipText = XmlDocumentation.BuildDataTipText(documentationBuilder, toolTipElement) + let dataTipText = XmlDocumentation.BuildDataTipText(documentationBuilder, toolTipElement) return QuickInfoItem(textSpan, FSharpDeferredQuickInfoContent(dataTipText)) | None -> return null | None -> return null From f0ebc6b34d00cabeda33f7e0b191497fc5641475 Mon Sep 17 00:00:00 2001 From: vaskir Date: Sun, 27 Nov 2016 12:19:05 +0300 Subject: [PATCH 10/12] add a QuickInfoProvider test --- .../src/FSharp.Editor/QuickInfoProvider.fs | 10 +- .../tests/unittests/QuickInfoProviderTests.fs | 103 ++++++++++++++++++ .../unittests/VisualFSharp.Unittests.fsproj | 3 + 3 files changed, 111 insertions(+), 5 deletions(-) create mode 100644 vsintegration/tests/unittests/QuickInfoProviderTests.fs diff --git a/vsintegration/src/FSharp.Editor/QuickInfoProvider.fs b/vsintegration/src/FSharp.Editor/QuickInfoProvider.fs index 1533f8f674b..156fb19d3cc 100644 --- a/vsintegration/src/FSharp.Editor/QuickInfoProvider.fs +++ b/vsintegration/src/FSharp.Editor/QuickInfoProvider.fs @@ -54,9 +54,9 @@ type internal FSharpQuickInfoProvider [) :?> IVsXMLMemberIndexService let documentationBuilder = XmlDocumentation.CreateDocumentationBuilder(xmlMemberIndexService, serviceProvider.DTE) - static member ProvideQuickInfo(document: Document, sourceText: SourceText, position: int, options: FSharpProjectOptions, textVersionHash: int, cancellationToken: CancellationToken) = + static member ProvideQuickInfo(documentId: DocumentId, sourceText: SourceText, filePath: string, position: int, options: FSharpProjectOptions, textVersionHash: int, cancellationToken: CancellationToken) = async { - let! _parseResults, checkResultsAnswer = FSharpChecker.Instance.ParseAndCheckFileInProject(document.FilePath, textVersionHash, sourceText.ToString(), options) + let! _parseResults, checkResultsAnswer = FSharpChecker.Instance.ParseAndCheckFileInProject(filePath, textVersionHash, sourceText.ToString(), options) let checkFileResults = match checkResultsAnswer with | FSharpCheckFileAnswer.Aborted -> failwith "Compilation isn't complete yet" @@ -67,9 +67,9 @@ type internal FSharpQuickInfoProvider [ Seq.toList) + let defines = CompilerEnvironment.GetCompilationDefinesForEditing(filePath, options.OtherOptions |> Seq.toList) let tryClassifyAtPosition position = - CommonHelpers.tryClassifyAtPosition(document.Id, sourceText, document.FilePath, defines, position, cancellationToken) + CommonHelpers.tryClassifyAtPosition(documentId, sourceText, filePath, defines, position, cancellationToken) let quickParseInfo = match tryClassifyAtPosition position with @@ -98,7 +98,7 @@ type internal FSharpQuickInfoProvider [ let! textVersion = document.GetTextVersionAsync(cancellationToken) |> Async.AwaitTask - let! quickInfoResult = FSharpQuickInfoProvider.ProvideQuickInfo(document, sourceText, position, options, textVersion.GetHashCode(), cancellationToken) + let! quickInfoResult = FSharpQuickInfoProvider.ProvideQuickInfo(document.Id, sourceText, document.FilePath, position, options, textVersion.GetHashCode(), cancellationToken) match quickInfoResult with | Some(toolTipElement, textSpan) -> let dataTipText = XmlDocumentation.BuildDataTipText(documentationBuilder, toolTipElement) diff --git a/vsintegration/tests/unittests/QuickInfoProviderTests.fs b/vsintegration/tests/unittests/QuickInfoProviderTests.fs new file mode 100644 index 00000000000..abe339bde75 --- /dev/null +++ b/vsintegration/tests/unittests/QuickInfoProviderTests.fs @@ -0,0 +1,103 @@ + +// To run the tests in this file: +// +// Technique 1: Compile VisualFSharp.Unittests.dll and run it as a set of unit tests +// +// Technique 2: +// +// Enable some tests in the #if EXE section at the end of the file, +// then compile this file as an EXE that has InternalsVisibleTo access into the +// appropriate DLLs. This can be the quickest way to get turnaround on updating the tests +// and capturing large amounts of structured output. +(* + cd Debug\net40\bin + .\fsc.exe --define:EXE -r:.\Microsoft.Build.Utilities.Core.dll -o VisualFSharp.Unittests.exe -g --optimize- -r .\FSharp.LanguageService.Compiler.dll -r .\FSharp.Editor.dll -r nunit.framework.dll ..\..\..\tests\service\FsUnit.fs ..\..\..\tests\service\Common.fs /delaysign /keyfile:..\..\..\src\fsharp\msft.pubkey ..\..\..\vsintegration\tests\unittests\CompletionProviderTests.fs + .\VisualFSharp.Unittests.exe +*) +// Technique 3: +// +// Use F# Interactive. This only works for FSharp.Compiler.Service.dll which has a public API + +// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +module Microsoft.VisualStudio.FSharp.Editor.Tests.Roslyn.QuickInfoProviderTests + +open System +open System.Threading + +open NUnit.Framework + +open Microsoft.CodeAnalysis +open Microsoft.CodeAnalysis.Text +open Microsoft.VisualStudio.FSharp.Editor + +//open Microsoft.VisualStudio.FSharp.Editor +//open Microsoft.VisualStudio.FSharp.LanguageService + +open Microsoft.FSharp.Compiler.SourceCodeServices +//open Microsoft.FSharp.Compiler.Range + +let filePath = "C:\\test.fs" + +let internal options = { + ProjectFileName = "C:\\test.fsproj" + ProjectFileNames = [| filePath |] + ReferencedProjects = [| |] + OtherOptions = [| |] + IsIncompleteTypeCheckEnvironment = true + UseScriptResolutionRules = false + LoadTime = DateTime.MaxValue + UnresolvedReferences = None +} + +let private normalizeLineEnds (s: string) = s.Replace("\r\n", "\n").Replace("\n\n", "\n") + +let private getQuickInfoText (FSharpToolTipText elements) : string = + let rec parseElement = function + | FSharpToolTipElement.None -> "" + | FSharpToolTipElement.Single(text, _) -> text + | FSharpToolTipElement.SingleParameter(text, _, _) -> text + | FSharpToolTipElement.Group(xs) -> xs |> List.map fst |> String.concat "\n" + | FSharpToolTipElement.CompositionError(error) -> error + elements |> List.map parseElement |> String.concat "\n" |> normalizeLineEnds + +[] +let ShouldShowQuickInfoAtCorrectPositions() = + let testCases = + [ "x", Some "val x : int\nFull name: Test.x" + "y", Some "val y : int\nFull name: Test.y" + "1", None + "2", None + "x +", Some "val x : int\nFull name: Test.x" + "System", Some "namespace System" + "Console", Some "type Console = + static member BackgroundColor : ConsoleColor with get, set + static member Beep : unit -> unit + 1 overload + static member BufferHeight : int with get, set + static member BufferWidth : int with get, set + static member CapsLock : bool + static member Clear : unit -> unit + static member CursorLeft : int with get, set + static member CursorSize : int with get, set + static member CursorTop : int with get, set + static member CursorVisible : bool with get, set + ... +Full name: System.Console" + "WriteLine", Some "System.Console.WriteLine(value: int) : unit" ] + + for (symbol: string, expected: string option) in testCases do + let expected = expected |> Option.map normalizeLineEnds + let fileContents = """ + let x = 1 + let y = 2 + System.Console.WriteLine(x + y) + """ + let caretPosition = fileContents.IndexOf(symbol) + 1 + let documentId = DocumentId.CreateNewId(ProjectId.CreateNewId()) + let getInfo() = documentId, filePath, [] + + let quickInfo = + FSharpQuickInfoProvider.ProvideQuickInfo(documentId, SourceText.From(fileContents), filePath, caretPosition, options, 0, CancellationToken.None) + |> Async.RunSynchronously + + let actual = quickInfo |> Option.map (fun (text, _) -> getQuickInfoText text) + Assert.AreEqual(expected, actual) \ No newline at end of file diff --git a/vsintegration/tests/unittests/VisualFSharp.Unittests.fsproj b/vsintegration/tests/unittests/VisualFSharp.Unittests.fsproj index 4b14380ef0b..fc3e055a305 100644 --- a/vsintegration/tests/unittests/VisualFSharp.Unittests.fsproj +++ b/vsintegration/tests/unittests/VisualFSharp.Unittests.fsproj @@ -108,6 +108,9 @@ Roslyn\GoToDefinition\GoToDefinitionServiceTests.fs + + Roslyn\QuickInfoProvider\QuickInfoProviderTests.fs + VisualFSharp.Unittests.dll.config {VisualStudioVersion} From 6a32c401ac88a35a03f83154f660b442066faf06 Mon Sep 17 00:00:00 2001 From: vaskir Date: Sun, 27 Nov 2016 12:33:49 +0300 Subject: [PATCH 11/12] QuickInfoProvider respects current theme colors --- .../src/FSharp.Editor/CommonHelpers.fs | 29 ++++++++++++++++++- .../src/FSharp.Editor/QuickInfoProvider.fs | 5 +++- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/vsintegration/src/FSharp.Editor/CommonHelpers.fs b/vsintegration/src/FSharp.Editor/CommonHelpers.fs index 7eb0974a271..8ebd6a41fb2 100644 --- a/vsintegration/src/FSharp.Editor/CommonHelpers.fs +++ b/vsintegration/src/FSharp.Editor/CommonHelpers.fs @@ -186,4 +186,31 @@ module CommonHelpers = let islandColumn = sourceText.Lines.GetLinePositionSpan(classifiedSpan.TextSpan).End.Character Some (islandColumn, [""], classifiedSpan.TextSpan) | _ -> None - | _ -> None \ No newline at end of file + | _ -> None + +open System.Reflection +open Microsoft.VisualStudio.Shell +open System.Windows.Controls +// A port of https://github.com/tomasr/viasfora/blob/master/Viasfora/Util/VsColors.cs +type VSColors() = + static let colorTypeOpt = + try + let vsShellAssembly = Assembly.Load("Microsoft.VisualStudio.Shell.11.0, Version=11.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a") + vsShellAssembly + |> Option.ofObj + |> Option.map (fun assembly -> + assembly.GetType("Microsoft.VisualStudio.PlatformUI.EnvironmentColors")) + with _ -> + None + + static let tryGetOrElse (key: string) (alternate: obj) = + colorTypeOpt + |> Option.map (fun colorType -> + let prop = colorType.GetProperty(key) + prop.GetValue(null, null)) + |> function Some x -> x | None -> alternate + + static member CommandShelfBackgroundGradientBrushKey = tryGetOrElse "CommandShelfBackgroundGradientBrushKey" VsBrushes.CommandBarGradientBeginKey + static member CommandBarTextActiveBrushKey = tryGetOrElse "CommandBarTextActiveBrushKey" VsBrushes.CommandBarTextActiveKey + static member ToolTipBrushKey = tryGetOrElse "ToolTipBrushKey" TextBlock.BackgroundProperty + static member ToolTipTextBrushKey = tryGetOrElse "ToolTipTextBrushKey" TextBlock.ForegroundProperty \ No newline at end of file diff --git a/vsintegration/src/FSharp.Editor/QuickInfoProvider.fs b/vsintegration/src/FSharp.Editor/QuickInfoProvider.fs index 156fb19d3cc..6f061d08432 100644 --- a/vsintegration/src/FSharp.Editor/QuickInfoProvider.fs +++ b/vsintegration/src/FSharp.Editor/QuickInfoProvider.fs @@ -44,7 +44,10 @@ open Microsoft.FSharp.Compiler.SourceCodeServices type internal FSharpDeferredQuickInfoContent(content: string) = interface IDeferredQuickInfoContent with override this.Create() : FrameworkElement = - upcast new Label(Content = content, Foreground = SolidColorBrush(Colors.Black)) + let label = Label(Content = content, Foreground = SolidColorBrush(Colors.Black)) + label.SetResourceReference(TextBlock.BackgroundProperty, VSColors.ToolTipBrushKey) + label.SetResourceReference(TextBlock.ForegroundProperty, VSColors.ToolTipTextBrushKey) + upcast label [] [] From d0f57cab89957f3def5d08c5ee7d786bbbd738fe Mon Sep 17 00:00:00 2001 From: vaskir Date: Sun, 27 Nov 2016 14:08:52 +0300 Subject: [PATCH 12/12] revert problem stuff into FSharp.Compiler.fsproj --- src/fsharp/FSharp.Compiler/FSharp.Compiler.fsproj | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/fsharp/FSharp.Compiler/FSharp.Compiler.fsproj b/src/fsharp/FSharp.Compiler/FSharp.Compiler.fsproj index c5d0c2f4355..1ef7b864065 100644 --- a/src/fsharp/FSharp.Compiler/FSharp.Compiler.fsproj +++ b/src/fsharp/FSharp.Compiler/FSharp.Compiler.fsproj @@ -516,6 +516,17 @@ ..\..\..\packages\System.Reflection.Metadata.1.4.1-beta-24227-04\lib\portable-net45+win8\System.Reflection.Metadata.dll ..\..\..\packages\System.Collections.Immutable.1.2.0\lib\portable-net45+win8+wp8+wpa81\System.Collections.Immutable.dll ..\..\..\packages\System.ValueTuple.4.3.0\lib\netstandard1.0\System.ValueTuple.dlltrue + + + + + + + + + + +