Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix a lot of IntelliSense issues #1799

Merged
merged 9 commits into from
May 17, 2022

Conversation

SeeminglyScience
Copy link
Collaborator

Changes:

(each section is it's own commit for easier review)

Mark completion request handler as Serial

This stops completion from being cancelled whenever a DidChangeTextDocument notification is sent. So previously, if you typed Get- very quickly, you'd get no results at all. It also lets completion cancel some other expensive requests like code lens.

Note: the one not fantastic down side of this is it makes "missing" code lenses more likely. By missing I mean:

image

I have not yet figured out a way to tell VSCode to just try again next time it's able. Also we were already getting this sometimes, but this change does make it at least a bit more likely.

Fix tasks never completing if cancelled quickly

We were exiting SynchronousTask.ExecuteSynchronously early if the cancellation was already requested. This was not setting our tcs state correctly, causing the caller to just fall off without warning. This made some things like finally and using disposals never happen.

Make alias discovery possible on idle

The use of Runspace.SessionStateProxy while the pipeline thread is busy (even if you are on the pipeline thread) results in an exception.

This also allows us to cancel GetAliasesAsync. This was really the main reason I was in there, but I also remembered a report from @ninmonkey about an exception here so two birds one stone.

Add space to completion trigger characters

Note: We should be on the look out for feedback about this change and let it sit in preview slightly longer than we'd normally like to. It's possible this may need to be opt in, but I think the entire intellisense experience is now optimized enough to that the behavior is ideal.

Adding space to completion triggers lets us automatically give completion results for parameter values. This also required building in some support for marking completion results as "Incomplete". This means that the client will not cache results when standard identifier characters are typed.

Mainly, this solves the scenario where you type space, get file completion results, and then type -. You expect to then get parameter names, but without marking "Incomplete", it will instead filter the file results.

Also fixed a scenario where you type $script: and on typing the : it would not update to available script scope variables.

Make completion requests cancellable

That way any completion results from custom argument completers can be cancelled if they take too long and the user keeps typing. TabExpansion2 is a callback-like function similar to prompt, partially for customizing tab completion but also for providing a command to get completion results.

In non-remote runspaces, we can also use it as a completion source that we can cancel by way of issuing a pipeline stop request.

@SeeminglyScience SeeminglyScience requested a review from a team May 12, 2022 19:23
@andyleejordan andyleejordan changed the title Fix a lot of intellisense issues Fix a lot of IntelliSense issues May 12, 2022
Comment on lines +106 to +113
// If PsesCompletionHandler is not marked as serial, then DidChangeTextDocument
// notifications will end up cancelling completion. So quickly typing `Get-`
// would result in no completions.
//
// This also lets completion requests interrupt time consuming background tasks
// like the references code lens.
.WithHandler<PsesCompletionHandler>(
new JsonRpcHandlerOptions() { RequestProcessType = RequestProcessType.Serial })
Copy link
Member

Choose a reason for hiding this comment

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

🥳

/// <returns>An array of CodeLenses.</returns>
CodeLens[] ProvideCodeLenses(ScriptFile scriptFile);
CodeLens[] ProvideCodeLenses(ScriptFile scriptFile, CancellationToken cancellationToken);
Copy link
Member

Choose a reason for hiding this comment

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

🥳

Comment on lines +118 to +120
// This async method is pretty dense with synchronous code
// so it's helpful to add some yields.
await Task.Yield();
Copy link
Member

Choose a reason for hiding this comment

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

👍

@@ -35,7 +35,7 @@ internal abstract class SynchronousTask<TResult> : ISynchronousTask
CancellationToken cancellationToken)
{
Logger = logger;
_taskCompletionSource = new TaskCompletionSource<TResult>();
_taskCompletionSource = new TaskCompletionSource<TResult>(TaskCreationOptions.RunContinuationsAsynchronously);
Copy link
Member

Choose a reason for hiding this comment

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

Can we comment exactly why we're doing this? (Like what did you hit that we have to set it.)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

You can kind of think of it like .ConfigureAwait(false) for completion sources. I didn't necessarily need to add it here, it was just a troubleshooting step, but we probably should be adding it everywhere. Related blog post

I can add a comment here but maybe I should just update all the other instances as well? If we go that route would you still prefer a comment over each one?

Copy link
Member

Choose a reason for hiding this comment

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

We don't need to do it in this PR, but thanks for the blog post. I'll make an issue for further investigation.

new PSCommand()
.AddCommand("Microsoft.PowerShell.Core\\Get-Command")
.AddParameter("ListImported", true)
.AddParameter("CommandType", CommandTypes.Alias),
Copy link
Member

Choose a reason for hiding this comment

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

Ah, makes sense.

Comment on lines +177 to +179
CommandHelpers.AliasMap aliases = await CommandHelpers.GetAliasesAsync(
_executionService,
cancellationToken).ConfigureAwait(false);
Copy link
Member

Choose a reason for hiding this comment

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

🥳 yay for things being cancellable!


// If the current runspace is not out of process, then we call TabExpansion2 so
// that we have the ability to issue pipeline stop requests on cancellation.
if (executionService is PsesInternalHost psesInternalHost
Copy link
Member

Choose a reason for hiding this comment

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

Oof, I would happily remove the IExecutionService interface as it makes us do things like this.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yeah either the sync task itself needs to reference IExecutionService or we need to strip it out. Or maybe there should be another interface that has state or w/e.

Maybe we hold off on tackling that until we get a source generator that handles the interface definitions for us? 🤷

Comment on lines +97 to +98
// If the current runspace is not out of process, then we call TabExpansion2 so
// that we have the ability to issue pipeline stop requests on cancellation.
Copy link
Member

Choose a reason for hiding this comment

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

🥳

if (completionMatch.ResultType is CompletionResultType.Variable
&& completionMatch.CompletionText.EndsWith(":"))
{
isIncomplete = true;
Copy link
Member

Choose a reason for hiding this comment

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

Nice work 🥳

This stops completion from being cancelled whenever a
DidChangeTextDocument notification is sent. Also lets completion cancel
some other expensive requests like code lens.
We were exiting SynchronousTask.ExecuteSynchronously early if the
cancellation was already requested. This was not setting our tcs state
correctly, causing the caller to just fall off without warning.
The use of `Runspace.SessionStateProxy` while the pipeline thread is busy
(even if you are **on** the pipeline thread) results in an exception.

This also allows us to cancel `GetAliasesAsync`.
- Cancellation tokens are now propagated to all code lens providers.
- Provider code that was heavy in synchronous code has some cancellation
  checks and `Task.Yield` calls to help keep thread pool threads more
  flexible.
Adding space to completion triggers lets us automatically give
completion results for parameter values. This also required building in
some support for marking completion results as "Incomplete". This means
that the client will not cache results when standard identifier
characters are typed.

Mainly, this solves the scenario where you type space, get file
completion results, and then type `-`. You expect to then get parameter
names, but without marking "Incomplete", it will instead filter the file
results.
That way any completion results from custom argument completers can be
cancelled if they take too long and the user keeps typing.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area-IntelliSense Issue-Enhancement A feature request (enhancement).
Projects
No open projects
Status: Done
Development

Successfully merging this pull request may close these issues.

2 participants