|
3 | 3 | namespace Microsoft.VisualStudio.FSharp.Editor |
4 | 4 |
|
5 | 5 | open System |
6 | | -open System.Composition |
7 | 6 | open System.Threading |
8 | 7 | open System.Threading.Tasks |
| 8 | +open System.Windows |
| 9 | +open System.Windows.Controls |
| 10 | +open System.ComponentModel.Composition |
9 | 11 |
|
10 | 12 | open Microsoft.CodeAnalysis |
| 13 | +open Microsoft.CodeAnalysis.Classification |
11 | 14 | open Microsoft.CodeAnalysis.Editor |
| 15 | +open Microsoft.CodeAnalysis.Editor.Shared.Utilities |
| 16 | +open Microsoft.CodeAnalysis.Editor.Shared.Extensions |
12 | 17 | open Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense.QuickInfo |
| 18 | +open Microsoft.CodeAnalysis.Navigation |
13 | 19 | open Microsoft.CodeAnalysis.Text |
14 | 20 |
|
15 | 21 | open Microsoft.VisualStudio.FSharp.LanguageService |
16 | 22 | open Microsoft.VisualStudio.Shell |
17 | 23 | open Microsoft.VisualStudio.Shell.Interop |
| 24 | +open Microsoft.VisualStudio.Utilities |
18 | 25 | open Microsoft.VisualStudio.Language.Intellisense |
19 | 26 |
|
20 | 27 | open Microsoft.FSharp.Compiler.SourceCodeServices |
| 28 | +open Microsoft.FSharp.Compiler.Range |
| 29 | +open Microsoft.FSharp.Compiler.CompileOps |
| 30 | + |
| 31 | +open CommonRoslynHelpers |
| 32 | + |
| 33 | +module internal FSharpQuickInfo = |
| 34 | + |
| 35 | + [<Literal>] |
| 36 | + let SessionCapturingProviderName = "Session Capturing Quick Info Source Provider" |
| 37 | + |
| 38 | + let mutable currentSession = None |
| 39 | + |
| 40 | + [<Export(typeof<IQuickInfoSourceProvider>)>] |
| 41 | + [<Name(SessionCapturingProviderName)>] |
| 42 | + [<Order(After = PredefinedQuickInfoProviderNames.Semantic)>] |
| 43 | + [<ContentType(FSharpCommonConstants.FSharpContentTypeName)>] |
| 44 | + type SourceProviderForCapturingSession() = |
| 45 | + interface IQuickInfoSourceProvider with |
| 46 | + member __.TryCreateQuickInfoSource _ = |
| 47 | + { new IQuickInfoSource with |
| 48 | + member __.AugmentQuickInfoSession(session,_,_) = currentSession <- Some session |
| 49 | + member __.Dispose() = () } |
| 50 | + |
| 51 | + let fragment(content, typemap: ClassificationTypeMap, thisDoc: Document) = |
| 52 | + |
| 53 | + let workspace = thisDoc.Project.Solution.Workspace |
| 54 | + let documentNavigationService = workspace.Services.GetService<IDocumentNavigationService>() |
| 55 | + let solution = workspace.CurrentSolution |
| 56 | + |
| 57 | + let documentId (range: range) = |
| 58 | + let filePath = System.IO.Path.GetFullPathSafe range.FileName |
| 59 | + let projectOf (id : DocumentId) = solution.GetDocument(id).Project |
| 60 | + |
| 61 | + //The same file may be present in many projects. We choose one from current or referenced project. |
| 62 | + let rec matchingDoc = function |
| 63 | + | [] -> None |
| 64 | + | id::_ when projectOf id = thisDoc.Project || IsScript thisDoc.FilePath -> Some id |
| 65 | + | id::tail -> |
| 66 | + if (projectOf id).GetDependentProjects() |> Seq.contains thisDoc.Project then Some id |
| 67 | + else matchingDoc tail |
| 68 | + solution.GetDocumentIdsWithFilePath(filePath) |> List.ofSeq |> matchingDoc |
| 69 | + |
| 70 | + let canGoTo range = |
| 71 | + range <> rangeStartup && documentId range |> Option.isSome |
| 72 | + |
| 73 | + let goTo range = |
| 74 | + asyncMaybe { |
| 75 | + let! id = documentId range |
| 76 | + let! src = solution.GetDocument(id).GetTextAsync() |
| 77 | + let! span = CommonRoslynHelpers.TryFSharpRangeToTextSpan(src, range) |
| 78 | + if documentNavigationService.TryNavigateToSpan(workspace, id, span) then |
| 79 | + let! session = currentSession |
| 80 | + session.Dismiss() |
| 81 | + } |> Async.Ignore |> Async.StartImmediate |
| 82 | + |
| 83 | + let formatMap = typemap.ClassificationFormatMapService.GetClassificationFormatMap("tooltip") |
| 84 | + |
| 85 | + let props = |
| 86 | + ClassificationTags.GetClassificationTypeName |
| 87 | + >> typemap.GetClassificationType |
| 88 | + >> formatMap.GetTextProperties |
| 89 | + |
| 90 | + let inlines = seq { |
| 91 | + for (tag, text, rangeOpt) in content do |
| 92 | + let run = |
| 93 | + match rangeOpt with |
| 94 | + | Some(range) when canGoTo range -> |
| 95 | + let h = Documents.Hyperlink(Documents.Run(text), ToolTip = range.FileName) |
| 96 | + h.Click.Add <| fun _ -> goTo range |
| 97 | + h :> Documents.Inline |
| 98 | + | _ -> |
| 99 | + Documents.Run(text) :> Documents.Inline |
| 100 | + DependencyObjectExtensions.SetTextProperties(run, props tag) |
| 101 | + yield run |
| 102 | + } |
| 103 | + |
| 104 | + let create() = |
| 105 | + let tb = TextBlock(TextWrapping = TextWrapping.Wrap, TextTrimming = TextTrimming.None) |
| 106 | + DependencyObjectExtensions.SetDefaultTextProperties(tb, formatMap) |
| 107 | + tb.Inlines.AddRange(inlines) |
| 108 | + if tb.Inlines.Count = 0 then tb.Visibility <- Visibility.Collapsed |
| 109 | + tb :> FrameworkElement |
| 110 | + |
| 111 | + { new IDeferredQuickInfoContent with member x.Create() = create() } |
| 112 | + |
| 113 | + let tooltip(symbolGlyph, mainDescription, documentation) = |
| 114 | + |
| 115 | + let empty = |
| 116 | + { new IDeferredQuickInfoContent with |
| 117 | + member x.Create() = TextBlock(Visibility = Visibility.Collapsed) :> FrameworkElement } |
| 118 | + |
| 119 | + let roslynQuickInfo = QuickInfoDisplayDeferredContent(symbolGlyph, null, mainDescription, documentation, empty, empty, empty, empty) |
| 120 | + |
| 121 | + let create() = |
| 122 | + let qi = roslynQuickInfo.Create() |
| 123 | + let style = Style(typeof<Documents.Hyperlink>) |
| 124 | + style.Setters.Add(Setter(Documents.Inline.TextDecorationsProperty, null)) |
| 125 | + let trigger = DataTrigger(Binding = Data.Binding("IsMouseOver", Source = qi), Value = true) |
| 126 | + trigger.Setters.Add(Setter(Documents.Inline.TextDecorationsProperty, TextDecorations.Underline)) |
| 127 | + style.Triggers.Add(trigger) |
| 128 | + qi.Resources.Add(typeof<Documents.Hyperlink>, style) |
| 129 | + qi |
| 130 | + |
| 131 | + { new IDeferredQuickInfoContent with member x.Create() = create() } |
21 | 132 |
|
22 | | -[<Shared>] |
23 | 133 | [<ExportQuickInfoProvider(PredefinedQuickInfoProviderNames.Semantic, FSharpCommonConstants.FSharpLanguageName)>] |
24 | 134 | type internal FSharpQuickInfoProvider |
25 | 135 | [<System.ComponentModel.Composition.ImportingConstructor>] |
@@ -64,21 +174,15 @@ type internal FSharpQuickInfoProvider |
64 | 174 | let documentation = Collections.Generic.List() |
65 | 175 | XmlDocumentation.BuildDataTipText( |
66 | 176 | documentationBuilder, |
67 | | - CommonRoslynHelpers.CollectTaggedText mainDescription, |
68 | | - CommonRoslynHelpers.CollectTaggedText documentation, |
| 177 | + CommonRoslynHelpers.CollectNavigableText mainDescription, |
| 178 | + CommonRoslynHelpers.CollectNavigableText documentation, |
69 | 179 | toolTipElement) |
70 | | - let empty = ClassifiableDeferredContent(Array.Empty<TaggedText>(), typeMap); |
71 | 180 | let content = |
72 | | - QuickInfoDisplayDeferredContent |
| 181 | + FSharpQuickInfo.tooltip |
73 | 182 | ( |
74 | | - symbolGlyph = SymbolGlyphDeferredContent(CommonRoslynHelpers.GetGlyphForSymbol(symbol, symbolKind), glyphService), |
75 | | - warningGlyph = null, |
76 | | - mainDescription = ClassifiableDeferredContent(mainDescription, typeMap), |
77 | | - documentation = ClassifiableDeferredContent(documentation, typeMap), |
78 | | - typeParameterMap = empty, |
79 | | - anonymousTypes = empty, |
80 | | - usageText = empty, |
81 | | - exceptionText = empty |
| 183 | + SymbolGlyphDeferredContent(CommonRoslynHelpers.GetGlyphForSymbol(symbol, symbolKind), glyphService), |
| 184 | + FSharpQuickInfo.fragment(mainDescription, typeMap, document), |
| 185 | + FSharpQuickInfo.fragment(documentation, typeMap, document) |
82 | 186 | ) |
83 | 187 | return QuickInfoItem(textSpan, content) |
84 | 188 | } |
|
0 commit comments