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

Support for copilot-generated summaries in quick info. (On-the-fly docs) #12552

Open
wants to merge 14 commits into
base: main
Choose a base branch
from

Conversation

spebl
Copy link
Contributor

@spebl spebl commented Aug 8, 2024

This introduces a new setting C_Cpp.onTheFlyDocsEnabled to control the display of the option to show copilot-generated summaries in the hover tooltip.
When enabled, and also authenticated with the vscode-copilot extension, the hover tooltip will display an option to generate a summary of the symbol with copilot.
The setting is defaulted to default which will check the feature flag control to determine if the feature should be enabled, which allows for slow rollout and the ability to rollback should any issues arise.

Updating the vscode requirement to 1.90.0 to support using copilot features with "vscode.lm".

The IntelliSense client changes supporting this feature are included in a separate PR against that repository.

Some workarounds were needed to support icon rendering in hover markdown and updating hover content dynamically which currently requires the hover to be closed and reopened. Potential fixes from proposals and unreleased patches are linked where applicable.

All feedback is very welcome!

OTF_DOCS_VSCODE

@spebl spebl added the Feature: Hover (Quick Info) An issue related to Hover (Quick Info) label Aug 8, 2024
Copy link
Member

@benmcmorran benmcmorran left a comment

Choose a reason for hiding this comment

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

Left a few wording comments but mostly looks good to me ✨. Leaving official sign-off for cpptools owners.

Extension/package.json Outdated Show resolved Hide resolved
Extension/package.nls.json Outdated Show resolved Hide resolved
Extension/src/LanguageServer/extension.ts Outdated Show resolved Hide resolved
Extension/src/LanguageServer/extension.ts Outdated Show resolved Hide resolved
Extension/src/LanguageServer/extension.ts Outdated Show resolved Hide resolved
);
} catch (err) {
if (err instanceof vscode.LanguageModelError) {
console.log(err.message, err.code, err.cause);
Copy link
Member

Choose a reason for hiding this comment

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

Not sure what standard practice is in cpptools, but should we be sending some error telemetry here too?

Extension/src/LanguageServer/settings.ts Outdated Show resolved Hide resolved
Extension/src/LanguageServer/settings.ts Outdated Show resolved Hide resolved
Extension/src/LanguageServer/settings.ts Outdated Show resolved Hide resolved
Extension/package.json Outdated Show resolved Hide resolved
@sean-mcmanus
Copy link
Collaborator

@spebl Did you want this in our next 1.22.x or what release?

@spebl
Copy link
Contributor Author

spebl commented Aug 8, 2024

@spebl Did you want this in our next 1.22.x or what release?

Not currently targeting a specific release, no need to block anything on this going in. I first wanted to get feedback on this approach, along with the upgrading of the vscode version. I'm taking a look now at how we can best keep support for the older versions while also using the new language model apis when available.

// This uses several workarounds for interacting with the hover feature.
// A proposal for dynamic hover content would help, such as the one here (https://github.com/microsoft/vscode/issues/195394)
async function onCopilotHover(): Promise<void> {
if (!vscode.window.activeTextEditor) { return; }
Copy link
Member

Choose a reason for hiding this comment

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

Does our linter allow this? I expected that it would want:

if (!vscode.window.activeTextEditor) {
    return;
}

Copy link
Collaborator

Choose a reason for hiding this comment

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

@bobbrow Our formatter recently changed and allows this currently. Maybe there's some setting we need to change to alter it

Copy link
Member

Choose a reason for hiding this comment

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

I can look into it later. I'm not a fan of this style.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I meant to update this to match the rest of the formatting, I'll update it now. I don't have any strong preference for one or the other.

@@ -1536,7 +1556,7 @@ export class DefaultClient implements Client {
resetDatabase: resetDatabase,
edgeMessagesDirectory: path.join(util.getExtensionFilePath("bin"), "messages", getLocaleId()),
localizedStrings: localizedStrings,
settings: this.getAllSettings()
settings: await this.getAllSettings()
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm almost certain we avoided asyncs early in client creation code, as a fix for issues that occurs due to race conditions at startup. I don't recall the exact details at the moment. But the call to createLanguageClient is not awaited (it's called from a constructor, and constructors aren't async). So, with your change, it's possible for the client to get constructed and presumably calls to this.languageClient to occur before that variable is properly set. I think that's why there are no awaits there until after languageClient is set and everything has been hooked up.

Would it be possible to remove this async/await and deliver the setting value without awaiting to see if the flight is enabled? Then, gate the functionality later based on whether the flight is enabled?

@@ -89,7 +89,17 @@ export function createProtocolFilter(): Middleware {
provideHover: async (document, position, token, next: (document: any, position: any, token: any) => any) => clients.ActiveClient.enqueue(async () => {
const me: Client = clients.getClientFor(document.uri);
if (me.TrackedDocuments.has(document.uri.toString())) {
return next(document, position, token);
// Currently needed to support icons.
Copy link
Collaborator

Choose a reason for hiding this comment

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

This makes me nervous. It looks like you're potentially hijacking any hover response, if it happens to contain an array that has a markdown string in it.

I think it would be cleaner if we switched to using a HoverProvider instead of hacking around the LSP hover request. (Then you'd be more easily able to do pre/post processing of the request in TypeScript code. That may also help avoid that async/await to check if the flight is enabled, as that could happen in the a HoverProvider)

Copy link
Collaborator

Choose a reason for hiding this comment

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

Also, I think having a hover provider would allow everything to be done in the same hover request, without needing to add additional custom LSP messages. Instead, you could add additional fields to a primary hover request message, and do any additional work immediately before or after forwarding that to the native process using sendRequest, like the other provider classes.

public async showCopilotHover(content: string): Promise<ShowCopilotHoverResult> {
const params: ShowCopilotHoverParams = {content: content};
await this.ready;
return this.languageClient.sendRequest(ShowCopilotHoverRequest, params);
Copy link
Collaborator

@Colengms Colengms Aug 15, 2024

Choose a reason for hiding this comment

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

It's common for multiple hover requests to be issued and get backlogged as the user moves the mouse around. As each one is generated, the prior one is cancelled. I think the cancellation token needs to be plumbed up here, all the way to these calls to sendRequest, in order for the lsp_manager on the native side to handle cancellation.

Cancellation will now cause an exception to be thrown from sendRequest, which you could catch, check for the specific cancellation error code, and handle appropriately (i.e., to throw a CancellationError exception, if that is what calling code expects).

@benmcmorran
Copy link
Member

Capturing notes from a quick discussion I had with @Colengms. He's going to go ahead and move from LSP-based hover to a HoverProvider which should make it easier for us to opt-in to icons and avoid async work during settings initialization. We also discussed a potential alternative using multiple HoverProviders where a provider that actually displays Copilot results simply blocks until the user clicks the Copilot link from the first provider. FYI @spebl

@@ -3286,6 +3286,17 @@
"default": false,
"markdownDescription": "%c_cpp.configuration.addNodeAddonIncludePaths.markdownDescription%",
"scope": "application"
},
"C_Cpp.copilotHover": {
Copy link
Member

Choose a reason for hiding this comment

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

This is being defined in the "Miscellaneous" section of settings. Just checking to make sure that's what you intended.

image

await vscode.window.showTextDocument(vscode.window.activeTextEditor.document, { preserveFocus: false, selection: new vscode.Selection(hoverPosition, hoverPosition) });

// Same workaround as above to force the editor to update it's content.
await clients.ActiveClient.showCopilotHover('$(loading~spin)');
Copy link
Collaborator

Choose a reason for hiding this comment

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

It would be nice to have a way to do this other than wiggling the mouse.

VS Code generally merges results from multiple providers. I suspect if there are multiple HoverProviders and only one returns results (yet) it will show only one result. If that result had the link in it, and that link 'unblocked' a secondary HoverProvider from returning an AI result, I suspect VS Code would then populate a new 'section' of the tooltip with the additional provider result. That is just a guess, I haven't tried it. :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I finally got a chance to try this out, and it almost works perfectly, but unfortunately while the provider is blocking waiting for the signal, VS Code reserves hover space for it and displays "Loading..." until it's finally unblocked and the Promise resolves.

If you have any ideas on a way to not show the "Loading..." message, I think everything else should work. So far everything I've tried has hit either that same issue or the issue of the Hover content not updating after being rendered.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Maybe we could put in a request to VS Code, to make this more perfect? :) Since this is AI related, they might consider it a priority. Hypothetically, if there were N hover providers, does it make sense for VS Code to show N Loading... messages? Seems like just one would suffice, and additional results could appear for additional providers, once they provide results. That might be easy for them to fix. I'd suggest opening an issue against VS Code and seeing what they think. https://github.com/microsoft/vscode/issues

@bobbrow bobbrow added this to the 1.22 milestone Aug 23, 2024
await vscode.commands.executeCommand('editor.action.showHover', { focus: 'noAutoFocus' });

// Move back and show the correct hover.
await clients.ActiveClient.showCopilotHover('$(loading~spin)');
Copy link
Collaborator

Choose a reason for hiding this comment

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

The active document and active client could change after returning from an await. I think this could be leading to bugs I'm seeing when switching between documents and hovering.

// This uses several workarounds for interacting with the hover feature.
// A proposal for dynamic hover content would help, such as the one here (https://github.com/microsoft/vscode/issues/195394)
async function onCopilotHover(): Promise<void> {
if (!vscode.window.activeTextEditor) { return; }
Copy link
Collaborator

Choose a reason for hiding this comment

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

Hover is supposed to work on a document that is not the active editor.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I see the hover disappear when Generate Copilot summary is clicked and there is no summary result.


// Workaround to force the editor to update it's content, needs to be called from another location first.
await vscode.commands.executeCommand('cursorMove', { to: 'right' });
await vscode.commands.executeCommand('editor.action.showHover', { focus: 'noAutoFocus' });
Copy link
Collaborator

@sean-mcmanus sean-mcmanus Sep 14, 2024

Choose a reason for hiding this comment

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

If the user changes the document and cursor, it can trigger a different hover provider -- I got GitLens hover to be invoked and the line changed to some random line. It seems like it should save the original document or something.

const hoverPosition = new vscode.Position(copilotHoverResult.hoverPos.line, copilotHoverResult.hoverPos.character);

// Make sure the editor has focus.
await vscode.window.showTextDocument(vscode.window.activeTextEditor.document, { preserveFocus: false, selection: new vscode.Selection(hoverPosition, hoverPosition) });
Copy link
Collaborator

Choose a reason for hiding this comment

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

If the user switches active documents, it'll move the selection to an incorrect line (i.e. the line number of the previously active document), so they'll be at some random spot in the code which may not even have C++ hover, which is why I was getting only hover from GitLens in my other comment because it moved my cursor to hover over a space that had a GitLens inlay hint on it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Feature: Hover (Quick Info) An issue related to Hover (Quick Info)
Projects
Status: Pull Request
Development

Successfully merging this pull request may close these issues.

5 participants