Skip to content

UI Changes - Inline Parameter Name Hints#45800

Merged
akhera99 merged 22 commits intodotnet:features/parameter-hintsfrom
akhera99:fix-UI
Jul 21, 2020
Merged

UI Changes - Inline Parameter Name Hints#45800
akhera99 merged 22 commits intodotnet:features/parameter-hintsfrom
akhera99:fix-UI

Conversation

@akhera99
Copy link
Member

@akhera99 akhera99 commented Jul 8, 2020

Hello,

This is a PR for merging the UI changes I have made to the project. I am now putting the textblock within a border, so it has curved edges and looks to be more of a part of the editor. I also am now pulling from the Tools->Options->Fonts and Colors location in order to retrieve the font/font size of the editor so that the hints get updated from that. Also, there are now default colors for the hints in each theme which are also customizable.

The reason that this relies on the cache added in a separate PR is that when the format mapping changes, I now need to clear the cache so that they can be recreated with the changes made in the option page.

Here are samples from each IDE theme-

Light:
image

Blue:
image

Blue (Extra Contrast):
image

Dark:
image

Please let me know what you guys think!
@JoeRobich PTAL

@akhera99 akhera99 changed the base branch from master to features/parameter-hints July 8, 2020 21:08
@CyrusNajmabadi
Copy link
Contributor

i'm feeling too sick to review now. but i will say, on a visual level alone, taht i think that looks really nice :)

for those who don't know. We used some of the colors that Teams uses for chat bubbles to inform the design here. So as long as that product has appropriately tested things like contrast, we should be good on our end.

@akhera99 akhera99 marked this pull request as ready for review July 9, 2020 20:08
@akhera99 akhera99 requested a review from a team as a code owner July 9, 2020 20:08
@akhera99 akhera99 requested a review from a team July 9, 2020 20:08
@akhera99
Copy link
Member Author

akhera99 commented Jul 9, 2020

i'm feeling too sick to review now. but i will say, on a visual level alone, taht i think that looks really nice :)

for those who don't know. We used some of the colors that Teams uses for chat bubbles to inform the design here. So as long as that product has appropriately tested things like contrast, we should be good on our end.

To add to this, I used this website that Joey suggested which determines the ratio between the colors and made sure they meet the minimum for their respective themes.

I also went ahead and made the hint slightly smaller so that it would look more floaty.
image

@davidwengier
Copy link
Member

on a visual level alone, taht i think that looks really nice :)

I agree! This looks really good, and fits really well with how I would imagine using it, which is to most often be quickly toggling it on, and then off, for reference, but I think it also works for people who would want to leave it on permanently.

Comment on lines +17 to +20
[Export]
[Name(InlineParameterNameHintsTag.TagId)]
[BaseDefinition(PredefinedClassificationTypeNames.FormalLanguage)]
internal ClassificationTypeDefinition InlineParameterNameHints;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does this bit actually do? I had thought base definitions only mattered for which classification takes priority if a text span has multiple, and that wouldn't be applicable here.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is specifically for getting the IClassificationType in the Tagger. I need to essentially register the TagId I specified with the ClassificationTypeDefinition in order to do so.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That would be on me. I asked Ankita to copy all the attributes because I couldn't remember what BaseDefinition was used for.


namespace Microsoft.CodeAnalysis.Editor.InlineParameterNameHints
{
internal sealed class ClassificationTypeDefinitions
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Class name doesn't match file name; file name looks better I think?

_tagAggregator.TagsChanged += OnTagAggregatorTagsChanged;
}

private void OnClassificationFormatMappingChanged(object sender, EventArgs e)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This only runs on the UI thread?

Copy link
Member Author

@akhera99 akhera99 Jul 10, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The assembly online says this Microsoft.VisualStudio.Text.UI.Wpf.dll, so I believe so

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we have ways of asserting this. it woudl be good for us to do that assertion so that if this ever turns out to be a wrong assumption we can get a crash immediately instead of a hard to figure out race condition.

}

return tagsList;
return _cache;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We aren't filtering this to only the spans that were asked for? That can sometimes cause downstream surprises but if the editor is filtering them out right away then it's not too much of a concern.

Comment on lines +89 to +91
var fullSpan = new SnapshotSpan(snapshot, 0, snapshot.Length);
var requestSpans = new NormalizedSnapshotSpanCollection(fullSpan);
var dataTags = _tagAggregator.GetTags(requestSpans);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I recognize we're still investigating why this cache fixes the hang issue but if we merge this in let's file a tracking bug to not forget to remove this if we get distracted by other work. Making UI for all the tags on the UI thread even if they're not visible isn't great, but works for the short term.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jasonmalinowski is it possible the following is happening:

  1. we compute tags
  2. we report tags.
  3. editor calls into us to get tags and then displays them.
  4. displaying the tag causes the line to change (due to the adornment)
  5. we're listening to line changes and we kick off anotehr computation

rinse repeat?

Copy link
Member

@jasonmalinowski jasonmalinowski Jul 10, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@CyrusNajmabadi We figure it's something like that, yeah. Displaying the tag will cause the editor to have to lay out the text again but that won't create a new snapshot or make our async tagger reprocess it. We chatted with the editor team and they were aware of one potential issue; the editor calls for tags twice during the process of laying everything out; and if between those two times you are replacing text with an adornment (which the editor supports, but we're not doing here) and you changed the tags between those two calls you might end up with extra layouts or bit of a cycle until things stabilize. But we're not using that editor feature, and our async tagger correctly raises tags changed on the UI thread, and only then reports new tags, so it looks like everything is fine there.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The samples that the editor team have often use a cache of the adornment tags in general and we could bring that code into here; our original belief was because our async tagger wasn't doing the bad things the editor knew to avoid we thought we were good and didn't need a caching layer atop the async tagger's caching layer. @akhera99 is still investigating further and trying to narrow it down.

var dataTags = _tagAggregator.GetTags(spans);
var snapshot = spans[0].Snapshot;
foreach (var tag in dataTags)
if (_cache.Count == 0 || snapshot != _cacheSnapshot)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So if the file has no spans, then we'll keep requerying the aggregator over and over again? Any concerns about performance or cache impacts there? I'm not sure if the cache needs to better distinguish "empty" versus "not cached".

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good point. I will try to figure out a way to distinguish the cases.

@CyrusNajmabadi
Copy link
Contributor

I also went ahead and made the hint slightly smaller so that it would look more floaty.

That floaty look looks really good :)

VerticalAlignment = VerticalAlignment.Center,
FontStyle = FontStyles.Normal,
FontFamily = format.Typeface.FontFamily,
FontSize = format.FontRenderingEmSize - (0.25 * format.FontHintingEmSize),
Copy link
Contributor

@CyrusNajmabadi CyrusNajmabadi Jul 10, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

doc. also, can you post some pictures at:

  1. different font sizes in the editor
  2. different fonts in the editor
  3. different zoom sizes in teh editor. (for example, i run at 137%)
  4. different DPI levels for windows?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Font Size 7:
image

Font Size 14:
image

Font Size 19:
image

Font Size 24:
image

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comic Sans:
image

Elephant:
image

Pristina:
image

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

50%:
image

67%:
image

100%:
image

137%:
image

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DPI Level 100%:
image

DPI Level 125%:
image

DPI Level 150% (Recommended for my computer):
image

DPI Level 175%:
image

/// <summary>
/// _cache stores the parameter hint tags in a global location
/// </summary>
private readonly List<ITagSpan<IntraTextAdornmentTag>> _cache;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i'm still opposed to teh cache.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

regardless of the performance, I do think I will still need some sort of logic where the tagspans get stored globally because when the options change, I still need to clear that out and completely recreate them

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i would say you don't need to do that. intead, the way tagging works is you hook up "Event sources", i.e. things you're listening to taht should cause you to recompute tags. in your case, you'd have an event source listening for wahtever option this is to change, and then firing the appropraite "something changed" notification to retrigger tagging.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See ITaggerEventSource and TaggerEventSources. We'll likely need a new source here, but it should hopefully not be hard to hook up and make work.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, okay. I will look into it

<value>(external)</value>
</data>
<data name="Inline_Parameter_Name_Hints" xml:space="preserve">
<value>Inline Parameter Name Hints</value>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

where is this disdplayed. Capital Case Is A Bit Weird versus Most UI string should be title cased.

Copy link
Member Author

@akhera99 akhera99 Jul 10, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's being displayed on the Tools->Options->Fonts And Colors->Display Items

image

@CyrusNajmabadi
Copy link
Contributor

Done with pass.

@jasonmalinowski
Copy link
Member

So @akhera99 and I debugged into why that cache was preventing hangs. If we don't have a cache and re-create new FrameworkElement tag each time, what can happen is:

  1. The editor calls into GetTags. We return some FrameworkElements.
  2. The editor then adds those FrameworkElements to the editor surface. It subscribes to FrameworkElements.SizedChanges so that way if an adornment needs to change size, it can lay out the editor again.
  3. For some reason we get a spurious SizeChanged. This causes the editor to run layout again.
  4. The layout change calls GetTags again, which since we create FrameworkElements we're back to step 1.

This causes the dispatcher queue to always be filled as we're requeuing layouts over and over again.

The thing we don't understand yet is why the SizeChanged is being triggered; even if we do have the cache that probably means we're doing a bunch of extra layouts each time we add new adornments which isn't good even if we keep the cache in place.

Comment on lines +17 to +20
[Export]
[Name(InlineParameterNameHintsTag.TagId)]
[BaseDefinition(PredefinedClassificationTypeNames.FormalLanguage)]
internal ClassificationTypeDefinition InlineParameterNameHints;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That would be on me. I asked Ankita to copy all the attributes because I couldn't remember what BaseDefinition was used for.

</Color>
<Color Name="label name">
</Color>
<Color Name="inline parameter name hints">
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Each file is its own self contained world, so they need to be defined in both places. If we wanted to say these colors are not going to be scheme-able we could move this to the VS EditorColors.xml. But really, I should finish the job of moving every Roslyn color into these schemes.

VerticalAlignment = VerticalAlignment.Center,
FontStyle = FontStyles.Normal,
FontFamily = format.Typeface.FontFamily,
FontSize = format.FontRenderingEmSize - (0.25 * format.FontHintingEmSize),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to be setting both the height of the border and the height of the text? I would have imagined setting one was sufficient and the other would just resize accordingly.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I remember reading somewhere that, when encased in a border, you need to have the border set the height in order for the sizing of the text to change relative to that height.

Comment on lines +35 to +37
IClassificationFormatMapService classificationFormatMapService,
IClassificationTypeRegistryService classificationTypeRegistryService,
IThreadingContext threadingContext)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're not using these, right? We can just delete?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They're being used in the Tagger.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants