Skip to content

Handling defaults provided by IAsyncCompletionDefaultsSource #57685

Merged
genlu merged 23 commits intodotnet:mainfrom
genlu:DefaultItems
Dec 29, 2021
Merged

Handling defaults provided by IAsyncCompletionDefaultsSource #57685
genlu merged 23 commits intodotnet:mainfrom
genlu:DefaultItems

Conversation

@genlu
Copy link
Copy Markdown
Member

@genlu genlu commented Nov 10, 2021

IAsyncCompletionDefaultsSource returns the first token of whatever whole line prediction is displayed, the goal is to make selected item in completion more consistent with WLC prediction.

So, if you see something like:

image

The suggested default would be “Console”. If you accept Console and type ‘.’, the suggested default would be “WriteLine”.

The basic algorithm for using the suggested default is:

  1. Roslyn decides which item to select.
  2. If we have a good reason to select this item, indicated by it being preselected (e.g. this bug ), use that item instead of suggested default.
  3. Otherwise, compare the pattern matching result of the current selection (with the filter text) and compare it with the pattern matching result of the suggested defaults . If the suggested default is no worse than the current selected item (in a case-insensitive manner), use the suggested default. Otherwise use the original selected item.

Using the same example above, if user typed "C", roslyn might select "CancellationToken", but with suggested default, we will end up selecting "Console".

The handling logic is based on the original implementation in VS (see this), with the additional change to fix this bug and some general cleanup

@genlu genlu requested a review from a team as a code owner November 10, 2021 22:58
@ghost ghost added the Area-IDE label Nov 10, 2021
@dpugh
Copy link
Copy Markdown

dpugh commented Nov 11, 2021 via email

private readonly RecentItemsManager _recentItemsManager;
private readonly IGlobalOptionService _globalOptions;

public const string AggressiveDefaultsMatchingOptionName = "AggressiveDefaultsMatchingOption";
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

what does 'aggressive defaults' mean?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

From @dpugh:

With aggressive matching turned on, the default was use as long as what the user typed in the applicable to span was a prefix of the item

I believe the purpose of this option is to provide a way to experiment with different selection logic.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

the default was use as long as what the user typed in the applicable to span was a prefix of the item

i still don't know what this means. can we have a comment explaining this somewhere (ideally with examples).

@CyrusNajmabadi
Copy link
Copy Markdown
Contributor

Can you update the OP of thei PR to indicate what user perceivable change this has to the completion experience?

@genlu
Copy link
Copy Markdown
Member Author

genlu commented Nov 11, 2021

@CyrusNajmabadi Added some description of the change. You can also find more examples in the tests.

@genlu
Copy link
Copy Markdown
Member Author

genlu commented Nov 12, 2021

@dotnet/roslyn-ide Please review, thanks!

@genlu
Copy link
Copy Markdown
Member Author

genlu commented Nov 18, 2021

@CyrusNajmabadi could you please take a look?

highlightMatchingPortions,
completionHelper);
selectionHint,
centerSelection: true,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

what does 'centerSelection' mean?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

this was not answered.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

"Whether selected item should be displayed in the center of the list. Usually, this is true" from the comment

{
Contract.ThrowIfNull(document);
return (itemsWithPatternMatches, text) => completionService.FilterItems(document, itemsWithPatternMatches, text);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

i have absolutely no idea what is going on here.

}

private FilteredCompletionModel? HandleNormalFiltering(
private (int selectedItemIndex, UpdateSelectionHint selcetionHint, VSCompletionItem? uniqueItem)? HandleNormalFiltering(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

selcetionHint mispelled.

Also, what does UpdateSelectionHint even mean?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

was this PR comment handled?

for (var i = 0; (i < itemsWithMatch.Count); ++i)
{
var itemWithMatch = itemsWithMatch[i];
if (itemWithMatch.RoslynCompletionItem.FilterText == defaultText)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

why are we looking at filter-text? we should likely be looking at display text right (in other words, we're seeing that two things in the display match up, what is in teh buffer, and waht is shown the user. if these are the same, we should pick ours?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Makes sense, I changed it to DisplayText. FYI @dpugh

@CyrusNajmabadi
Copy link
Copy Markdown
Contributor

The complexity in ItemManager is really too much to handle at this point. We shoudl extract out a new type with clear responsibility as to what it's doing, and what the phases are taht it's doing that work in.

There should ideally be a very clear top level method that explains something like:

  1. here are the 5 steps we take here.
    1. step 1, do blah (a single method call)
    2. step 2, filter it down to blah
    3. step 3, select blah blah
    4. step 4, etc.

It really needs to be exceptionally simple and clear waht each phase is trying to accomplish, and what minimal data it needs to have access to to perform that. right now, i just see a ton of data/logic intermixed and i really have no idea if things are happening at the right layer or how the ordering of anything impacts the rest.

No change to the code other than extracting a helper class here.
This is a refactoring only commit
@genlu
Copy link
Copy Markdown
Member Author

genlu commented Nov 23, 2021

@CyrusNajmabadi Did some refactoring around ItemManager as you suggested, there's no other code change in the last two commits. Hopefully it's provide a bit more clarity here. I will do some additional clean-up and adding comments next.

@genlu
Copy link
Copy Markdown
Member Author

genlu commented Nov 24, 2021

@CyrusNajmabadi Based on your feedback, I have tuned it down to match case-sensitively for default items. FYI @dpugh

var associatedWithUnselectedExpander = false;
foreach (var itemFilter in item.Filters)
{
if (itemFilter is CompletionExpander)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

what is a completion-expander?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

It's this plus button at the beginning of the filter list

image

@CyrusNajmabadi
Copy link
Copy Markdown
Contributor

this is looking really good. so much easier to understand than before.

my top bit of feedback is: ground the algorithms with concrete examples of what sort of cases the user is running into, and why we're doing things. so cases that go "The item is 'Console' and the user has typed 'C'" are fantastic. They really help the reader get a grasp of what is being handled here and give much more of an intuition about why this behavior is desirable.

// Only use data.Snapshot in the theoretically possible but rare case when all items we are handling are from some non-Roslyn CompletionSource.
var snapshotForDocument = TryGetInitialTriggerLocation(_data, out var intialTriggerLocation)
? intialTriggerLocation.Snapshot
: _data.Snapshot;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

non-roslyn completionsource is a terrifying concept to me :) this is not on you, but in cases like this we really need to call out what those cases are. because i genuinely have no idea how we get into this state.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

You are not alone :)
I'm not certain about this either, therefore reluctant to make change here until understand this better.

}
EditorAsyncCompletionData.CompletionTriggerReason.InvokeAndCommitIfUnique => CompletionTriggerKind.InvokeAndCommitIfUnique,
EditorAsyncCompletionData.CompletionTriggerReason.Insertion => CompletionTriggerKind.Insertion,
EditorAsyncCompletionData.CompletionTriggerReason.Deletion or EditorAsyncCompletionData.CompletionTriggerReason.Backspace => CompletionTriggerKind.Deletion,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

so this seems to unify Delete/Backspace to just Delete (which is fine). but later on i see roslyn code checkking for backspace as well... which then seems weird to me.

// Decide the item to be selected for this completion session.
// The selection is mostly based on how well the item matches with the filter text, but we also need to
// take into consideration for things like CompletionTrigger, MatchPriority, MRU, etc.
var initialSelection = InitialTriggerReason == CompletionTriggerReason.Backspace || InitialTriggerReason == CompletionTriggerReason.Deletion
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

checking for Backspace and Delete seems weird to me given that it looked like your earlier helpers collapsed things down so we only see one of these.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Previously we have mixed usage of Roslyn's CompeltionTriggerKind and Editor's CompletionTriggerReason in the ItemManager, which is confusing. So I changed to use Editor's CompletionTriggerReason only in async completion layer, unless we are trying to interact with APIs in Roslyn's completion system (i.e. In Features layer)


// Use a dedicated pool to minimize potentially repeated large allocations,
// since the completion list could be long with import completion enabled.
var itemsToBeIncluded = s_listOfMatchResultPool.Allocate();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

much nicer :)

// Convert intial and update trigger reasons to corresponding Roslyn type so
// we can interact with Roslyn's completion system
var roslynInitialTriggerKind = Helpers.GetRoslynTriggerKind(InitialTriggerReason);
var roslynFilterReason = Helpers.GetFilterReason(UpdateTriggerReason);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

i feel like we should do this up front, and only expose the roslyn side of htings. is there a reason we ever want to work with the non-roslyn side?

if (_completionRules.DismissIfEmpty)
{
return null;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This applies to all initial trigger kinds, where the dismissal in HandleDeletionTrigger applies to deletion trigger only (and we keep all items in the list in that case, same as handling ctrl+j). It seems we might be able to rewrite this so the dismissal in HandleDeletionTrigger can happen earlier (i.e. handled by this line), but I'd need to think about it more and maybe do it in a follow up PR


// If the user has turned on some filtering states, and we filtered down to
// nothing, then we do want the UI to show that to them. That way the user
// can turn off filters they don't want and get the right set of items.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

this comment doesnt' seem to match the logic in the method. we seem to unilaterally bail out above, even if filters are set.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

we only bail when the update was triggered by insertion. There's a separate trigger reason for FilterChange

@genlu
Copy link
Copy Markdown
Member Author

genlu commented Dec 9, 2021

@CyrusNajmabadi Hey, is this good to merge now?

@genlu genlu merged commit a2d06f6 into dotnet:main Dec 29, 2021
@ghost ghost added this to the Next milestone Dec 29, 2021
@genlu genlu deleted the DefaultItems branch December 29, 2021 18:55
@Cosifne Cosifne modified the milestones: Next, 17.1.P3 Jan 5, 2022
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.

5 participants