Skip to content

Commit c9c870d

Browse files
[main] VS: Use IViewElementFactory as intended (#15196)
Co-authored-by: majocha <[email protected]>
1 parent 1d819b9 commit c9c870d

File tree

3 files changed

+58
-81
lines changed

3 files changed

+58
-81
lines changed

vsintegration/src/FSharp.Editor/QuickInfo/Views.fs

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -55,14 +55,13 @@ module internal QuickInfoViewProvider =
5555
| TaggedText (TextTag.LineBreak, _) -> Some()
5656
| _ -> None
5757

58-
let wrapContent (elements: obj list) =
59-
ContainerElement(ContainerElementStyle.Wrapped, elements |> Seq.map box)
58+
let wrapContent (elements: obj seq) =
59+
ContainerElement(ContainerElementStyle.Wrapped, elements)
6060

61-
let stackContent (elements: obj list) =
62-
ContainerElement(ContainerElementStyle.Stacked, elements |> Seq.map box)
61+
let stackContent (elements: obj seq) =
62+
ContainerElement(ContainerElementStyle.Stacked, elements)
6363

64-
let encloseRuns runs =
65-
ClassifiedTextElement(runs |> List.rev) |> box
64+
let encloseRuns runs : obj = ClassifiedTextElement(runs |> List.rev)
6665

6766
let provideContent
6867
(
@@ -78,8 +77,8 @@ module internal QuickInfoViewProvider =
7877
match (text: TaggedText list) with
7978
| [] when runs |> List.isEmpty -> stackContent (stack |> List.rev)
8079
| [] -> stackContent (encloseRuns runs :: stack |> List.rev)
81-
// smaller gap instead of huge double line break
82-
| LineBreak :: rest when runs |> List.isEmpty -> loop rest [] (box (Separator false) :: stack)
80+
// smaller paragraph spacing instead of huge double line break
81+
| LineBreak :: rest when runs |> List.isEmpty -> loop rest [] (Paragraph :: stack)
8382
| LineBreak :: rest -> loop rest [] (encloseRuns runs :: stack)
8483
| :? NavigableTaggedText as item :: rest when navigation.IsTargetValid item.Range ->
8584
let classificationTag = layoutTagToClassificationTag item.Tag
@@ -93,17 +92,14 @@ module internal QuickInfoViewProvider =
9392
let run = ClassifiedTextRun(layoutTagToClassificationTag item.Tag, item.Text)
9493
loop rest (run :: runs) stack
9594

96-
loop text [] [] |> box
95+
loop text [] []
9796

9897
let innerElement =
9998
match imageId with
10099
| Some imageId -> wrapContent [ stackContent [ ImageElement(imageId) ]; encloseText description ]
101100
| None -> ContainerElement(ContainerElementStyle.Wrapped, encloseText description)
102101

103-
wrapContent [ stackContent [ innerElement; encloseText documentation ] ]
102+
wrapContent [ stackContent [ innerElement; encloseText documentation ]; CustomLinkStyle ]
104103

105104
let stackWithSeparators elements =
106-
elements
107-
|> List.map box
108-
|> List.intersperse (box (Separator true))
109-
|> stackContent
105+
elements |> List.map box |> List.intersperse Separator |> stackContent

vsintegration/src/FSharp.Editor/QuickInfo/WpfFactories.fs

Lines changed: 45 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -7,83 +7,64 @@ open System.Windows
77
open System.Windows.Controls
88

99
open Microsoft.VisualStudio.Text.Adornments
10-
open Microsoft.VisualStudio.Text.Editor
1110
open Microsoft.VisualStudio.Utilities
1211

13-
open Microsoft.VisualStudio.FSharp.Editor
14-
open Microsoft.VisualStudio.Text.Classification
12+
open Microsoft.VisualStudio.FSharp
1513

16-
type Separator =
17-
| Separator of visible: bool
18-
// preserve old behavior on mac
14+
type internal FSharpStyle =
15+
| Separator
16+
| Paragraph
17+
| CustomLinkStyle
18+
19+
// Render as strings for cross platform look.
1920
override this.ToString() =
2021
match this with
21-
| Separator true -> XmlDocumentation.separatorText
22-
| _ -> System.Environment.NewLine
22+
| Separator -> Editor.XmlDocumentation.separatorText
23+
| Paragraph -> System.Environment.NewLine
24+
| CustomLinkStyle -> ""
2325

26+
// Provide nicer look for the QuickInfo on Windows.
2427
[<Export(typeof<IViewElementFactory>)>]
25-
[<Name("ClassifiedTextElement to UIElement")>]
26-
[<TypeConversion(typeof<ClassifiedTextElement>, typeof<UIElement>)>]
27-
type WpfClassifiedTextElementFactory [<ImportingConstructor>]
28-
(
29-
classificationformatMapService: IClassificationFormatMapService,
30-
classificationTypeRegistry: IClassificationTypeRegistryService,
31-
settings: EditorOptions
32-
) =
33-
let resources = Microsoft.VisualStudio.FSharp.UIResources.NavStyles().Resources
34-
let formatMap = classificationformatMapService.GetClassificationFormatMap("tooltip")
35-
36-
interface IViewElementFactory with
37-
member _.CreateViewElement(_textView: ITextView, model: obj) =
38-
match model with
39-
| :? ClassifiedTextElement as text ->
40-
let tb = TextBlock()
41-
tb.FontSize <- formatMap.DefaultTextProperties.FontRenderingEmSize
42-
tb.FontFamily <- formatMap.DefaultTextProperties.Typeface.FontFamily
43-
tb.TextWrapping <- TextWrapping.Wrap
28+
[<Name("FSharpStyle to UIElement")>]
29+
[<TypeConversion(typeof<FSharpStyle>, typeof<UIElement>)>]
30+
type internal WpfFSharpStyleFactory [<ImportingConstructor>] (settings: Editor.EditorOptions) =
31+
let linkStyleUpdater () =
32+
let key =
33+
if settings.QuickInfo.DisplayLinks then
34+
$"{settings.QuickInfo.UnderlineStyle.ToString().ToLower()}_underline"
35+
else
36+
"no_underline"
4437

45-
for run in text.Runs do
46-
let ctype =
47-
classificationTypeRegistry.GetClassificationType(run.ClassificationTypeName)
38+
let style = UIResources.NavStyles().Resources[key] :?> Style
4839

49-
let props = formatMap.GetTextProperties(ctype)
50-
let inl = Documents.Run(run.Text, Foreground = props.ForegroundBrush)
40+
// Some assumptions are made here about the shape of QuickInfo visual tree rendered by VS.
41+
// If some future VS update were to render QuickInfo with different WPF elements
42+
// the links will still work, just without their custom styling.
43+
let rec styleLinks (element: DependencyObject) =
44+
match element with
45+
| :? TextBlock as t ->
46+
for run in t.Inlines do
47+
if run :? Documents.Hyperlink then
48+
run.Style <- style
49+
| :? Panel as p ->
50+
for e in p.Children do
51+
styleLinks e
52+
| _ -> ()
5153

52-
match run.NavigationAction |> Option.ofObj with
53-
| Some action ->
54-
let link =
55-
{ new Documents.Hyperlink(inl, ToolTip = run.Tooltip) with
56-
override _.OnClick() = action.Invoke()
57-
}
54+
// Return an invisible FrameworkElement which will traverse it's siblings
55+
// to find HyperLinks and update their style, when inserted into the visual tree.
56+
{ new FrameworkElement() with
57+
override this.OnVisualParentChanged _ = styleLinks this.Parent
58+
}
5859

59-
let key =
60-
match settings.QuickInfo.UnderlineStyle with
61-
| QuickInfoUnderlineStyle.Solid -> "solid_underline"
62-
| QuickInfoUnderlineStyle.Dash -> "dash_underline"
63-
| QuickInfoUnderlineStyle.Dot -> "dot_underline"
64-
65-
link.Style <- downcast resources[key]
66-
link.Foreground <- props.ForegroundBrush
67-
tb.Inlines.Add(link)
68-
| _ -> tb.Inlines.Add(inl)
69-
70-
box tb :?> _
71-
| _ ->
72-
failwith
73-
$"Invalid type conversion. Supported conversion is {typeof<ClassifiedTextElement>.Name} to {typeof<UIElement>.Name}."
74-
75-
[<Export(typeof<IViewElementFactory>)>]
76-
[<Name("Separator to UIElement")>]
77-
[<TypeConversion(typeof<Separator>, typeof<UIElement>)>]
78-
type WpfSeparatorFactory() =
7960
interface IViewElementFactory with
8061
member _.CreateViewElement(_, model: obj) =
8162
match model with
82-
| :? Separator as Separator visible ->
83-
if visible then
84-
Controls.Separator(Opacity = 0.3, Margin = Thickness(0, 8, 0, 8))
85-
else
86-
Controls.Separator(Opacity = 0)
63+
| :? FSharpStyle as fSharpStyle ->
64+
match fSharpStyle with
65+
| CustomLinkStyle -> linkStyleUpdater ()
66+
| Separator -> Controls.Separator(Opacity = 0.3, Margin = Thickness(0, 8, 0, 8))
67+
| Paragraph -> Controls.Separator(Opacity = 0)
8768
|> box
8869
:?> _
89-
| _ -> failwith $"Invalid type conversion. Supported conversion is {typeof<Separator>.Name} to {typeof<UIElement>.Name}."
70+
| _ -> failwith $"Invalid type conversion. Supported conversion is {typeof<FSharpStyle>.Name} to {typeof<UIElement>.Name}."

vsintegration/src/FSharp.UIResources/NavStyles.xaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
88
mc:Ignorable="d"
99
d:DesignHeight="450" d:DesignWidth="800">
1010
<UserControl.Resources>
11-
<SolidColorBrush x:Key="inherited_brush" Color="{Binding Path=Foreground.Color, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Hyperlink}}"/>
12-
<SolidColorBrush x:Key="inherited_semi_brush" Opacity="0.3" Color="{Binding Path=Foreground.Color, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Hyperlink}}"/>
11+
<SolidColorBrush x:Key="inherited_brush" Color="{Binding Path=Inlines.FirstInline.Foreground.Color, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Hyperlink}}"/>
12+
<SolidColorBrush x:Key="inherited_semi_brush" Opacity="0.3" Color="{Binding Path=Inlines.FirstInline.Foreground.Color, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Hyperlink}}"/>
1313
<DashStyle x:Key="dash_dashstyle" Dashes="5 5"/>
1414
<DashStyle x:Key="dot_dashstyle" Dashes="1 5"/>
1515
<Pen x:Key="dot_pen" DashStyle="{StaticResource dot_dashstyle}" Brush="{StaticResource inherited_brush}"/>
@@ -26,7 +26,7 @@
2626
<TextDecoration Location="Underline" PenOffset="1" Pen="{StaticResource dot_pen}"/>
2727
</TextDecorationCollection>
2828
<TextDecorationCollection x:Key="full_deco">
29-
<TextDecoration PenOffset="1" Pen="{StaticResource mouseover_pen}" />
29+
<TextDecoration Location="Underline" PenOffset="1" Pen="{StaticResource mouseover_pen}" />
3030
</TextDecorationCollection>
3131
<Style x:Key="hyperlink_mouse_over" TargetType="Hyperlink">
3232
<Style.Triggers>

0 commit comments

Comments
 (0)