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 40 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
0eac8bf
initial on-the-fly docs implementation for vscode
spebl Aug 5, 2024
93d7bb0
merge from main
spebl Aug 5, 2024
2667ec5
add localization support
spebl Aug 6, 2024
fb310c8
add support for icons in hover
spebl Aug 7, 2024
433f22d
add support for feature flag control
spebl Aug 7, 2024
7ec62d9
add setting and strings
spebl Aug 7, 2024
7a41841
fix settings when copilot models load later and fix showing copilot w…
spebl Aug 8, 2024
db42258
'otf docs' -> 'copilot hover' in naming conventions and strings. fixe…
spebl Aug 14, 2024
c73a589
fix spacing
spebl Aug 14, 2024
aa6779c
merge from main
spebl Aug 14, 2024
28390f1
reset vscode version
spebl Aug 14, 2024
3d5b7db
merge from main
spebl Aug 26, 2024
bd1eb43
merge from main
spebl Sep 3, 2024
d9c74c3
merge from main
spebl Sep 10, 2024
c31c4ab
merge from main
spebl Sep 12, 2024
9742465
fix formatting
spebl Sep 24, 2024
3bb4f1f
merge from main
spebl Sep 24, 2024
1cf0785
merge from main
spebl Sep 25, 2024
6076ca9
swap to using new copilot hover provider as to not interfere with exi…
spebl Sep 28, 2024
fd7095d
Add RAI disclaimers
benmcmorran Oct 9, 2024
9e1a547
merge from vscode
spebl Oct 10, 2024
d6a340f
merge from upstream
spebl Oct 10, 2024
f8cdde6
Copilot summary should now only show up when there is already hover c…
spebl Oct 11, 2024
713f430
Merge branch 'main' into dev/spebl/otfdocs
spebl Oct 11, 2024
10fb3cf
move AI inaccuracy warning to hover tooltip, add sparkle to load, che…
spebl Oct 11, 2024
3a0b8d8
prompt user to reload workspace when changing copilot hover option
spebl Oct 11, 2024
1be1fcc
merge from upstream
spebl Oct 11, 2024
ceb9f8e
merge from main
spebl Oct 18, 2024
9376135
merge from main
spebl Oct 21, 2024
ba5f149
merge from main
spebl Oct 22, 2024
8707994
Merge branch 'main' into dev/spebl/otfdocs
spebl Oct 22, 2024
1b37fd1
track and pass cancellation token + fix copilot hover showing up befo…
spebl Oct 24, 2024
f8fc500
Merge branch 'dev/spebl/otfdocs' of https://github.com/Microsoft/vsco…
spebl Oct 24, 2024
223d52b
better handling of intentional cancellation by user
spebl Oct 28, 2024
c055ce2
ensure tokens and cancellation are all in sync between copilot hover …
spebl Oct 28, 2024
3af533d
Merge branch 'main' into dev/spebl/otfdocs
spebl Oct 29, 2024
ad991c6
Merge branch 'main' into dev/spebl/otfdocs
spebl Oct 30, 2024
76000c7
swap to use same params for copilot hover info as text document posit…
spebl Nov 13, 2024
0fc52ed
Merge branch 'main' of https://github.com/Microsoft/vscode-cpptools i…
spebl Nov 13, 2024
1788813
Merge branch 'dev/spebl/otfdocs' of https://github.com/Microsoft/vsco…
spebl Nov 13, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions Extension/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3286,6 +3286,17 @@
"default": false,
"markdownDescription": "%c_cpp.configuration.addNodeAddonIncludePaths.markdownDescription%",
"scope": "application"
},
"C_Cpp.copilotHover": {
sean-mcmanus marked this conversation as resolved.
Show resolved Hide resolved
"type": "string",
"enum": [
"default",
"enabled",
"disabled"
],
"default": "default",
"markdownDescription": "%c_cpp.configuration.copilotHover.markdownDescription%",
"scope": "window"
}
}
}
Expand Down
6 changes: 6 additions & 0 deletions Extension/package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -775,6 +775,12 @@
"Markdown text between `` should not be translated or localized (they represent literal text) and the capitalization, spacing, and punctuation (including the ``) should not be altered."
]
},
"c_cpp.configuration.copilotHover.markdownDescription": {
"message": "If `enabled`, the hover tooltip will display an option to generate a summary of the symbol with Copilot. If `disabled`, the option will not be displayed.",
"comment": [
"Markdown text between `` should not be translated or localized (they represent literal text) and the capitalization, spacing, and punctuation (including the ``) should not be altered."
]
},
"c_cpp.configuration.renameRequiresIdentifier.markdownDescription": {
"message": "If `true`, 'Rename Symbol' will require a valid C/C++ identifier.",
"comment": [
Expand Down
45 changes: 42 additions & 3 deletions Extension/src/LanguageServer/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -541,6 +541,21 @@ interface GetIncludesResult {
includedFiles: string[];
}

interface ShowCopilotHoverParams
{
content: string;
}

interface ShowCopilotHoverResult
{
hoverPos: Position;
}

interface GetCopilotHoverInfoResult
{
content: string;
}

// Requests
const PreInitializationRequest: RequestType<void, string, void> = new RequestType<void, string, void>('cpptools/preinitialize');
const InitializationRequest: RequestType<CppInitializationParams, void, void> = new RequestType<CppInitializationParams, void, void>('cpptools/initialize');
Expand All @@ -560,6 +575,8 @@ const GoToDirectiveInGroupRequest: RequestType<GoToDirectiveInGroupParams, Posit
const GenerateDoxygenCommentRequest: RequestType<GenerateDoxygenCommentParams, GenerateDoxygenCommentResult | undefined, void> = new RequestType<GenerateDoxygenCommentParams, GenerateDoxygenCommentResult, void>('cpptools/generateDoxygenComment');
const ChangeCppPropertiesRequest: RequestType<CppPropertiesParams, void, void> = new RequestType<CppPropertiesParams, void, void>('cpptools/didChangeCppProperties');
const IncludesRequest: RequestType<GetIncludesParams, GetIncludesResult, void> = new RequestType<GetIncludesParams, GetIncludesResult, void>('cpptools/getIncludes');
const GetCopilotHoverInfoRequest: RequestType<void, GetCopilotHoverInfoResult, void> = new RequestType<void, GetCopilotHoverInfoResult, void>('cpptools/getCopilotHoverInfo');
const ShowCopilotHoverRequest: RequestType<ShowCopilotHoverParams, ShowCopilotHoverResult, void> = new RequestType<ShowCopilotHoverParams, ShowCopilotHoverResult, void>('cpptools/showCopilotHover');

// Notifications to the server
const DidOpenNotification: NotificationType<DidOpenTextDocumentParams> = new NotificationType<DidOpenTextDocumentParams>('textDocument/didOpen');
Expand Down Expand Up @@ -790,6 +807,8 @@ export interface Client {
setShowConfigureIntelliSenseButton(show: boolean): void;
addTrustedCompiler(path: string): Promise<void>;
getIncludes(maxDepth: number): Promise<GetIncludesResult>;
showCopilotHover(content: string): Promise<ShowCopilotHoverResult>;
getCopilotHoverInfo(): Promise<GetCopilotHoverInfoResult>;
}

export function createClient(workspaceFolder?: vscode.WorkspaceFolder): Client {
Expand Down Expand Up @@ -1441,7 +1460,7 @@ export class DefaultClient implements Client {
return workspaceFolderSettingsParams;
}

private getAllSettings(): SettingsParams {
private async getAllSettings(): Promise<SettingsParams> {
const workspaceSettings: CppSettings = new CppSettings();
const workspaceOtherSettings: OtherSettings = new OtherSettings();
const workspaceFolderSettingsParams: WorkspaceFolderSettingsParams[] = this.getAllWorkspaceFolderSettings();
Expand All @@ -1467,6 +1486,7 @@ export class DefaultClient implements Client {
codeAnalysisMaxConcurrentThreads: workspaceSettings.codeAnalysisMaxConcurrentThreads,
codeAnalysisMaxMemory: workspaceSettings.codeAnalysisMaxMemory,
codeAnalysisUpdateDelay: workspaceSettings.codeAnalysisUpdateDelay,
copilotHover: await workspaceSettings.copilotHover,
workspaceFolderSettings: workspaceFolderSettingsParams
};
}
Expand Down Expand Up @@ -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()
spebl marked this conversation as resolved.
Show resolved Hide resolved
};

this.loggingLevel = util.getNumericLoggingLevel(cppInitializationParams.settings.loggingLevel);
Expand Down Expand Up @@ -1577,6 +1597,12 @@ export class DefaultClient implements Client {
// We manually restart the language server so tell the LanguageClient not to do it automatically for us.
return { action: CloseAction.DoNotRestart, message };
}
},
markdown: {
isTrusted: true
// TODO: support for icons in markdown is not yet in the released version of vscode-languageclient.
// Based on PR (https://github.com/microsoft/vscode-languageserver-node/pull/1504)
//supportThemeIcons: true
}

// TODO: should I set the output channel? Does this sort output between servers?
Expand All @@ -1602,7 +1628,7 @@ export class DefaultClient implements Client {
public async sendDidChangeSettings(): Promise<void> {
// Send settings json to native side
await this.ready;
await this.languageClient.sendNotification(DidChangeSettingsNotification, this.getAllSettings());
await this.languageClient.sendNotification(DidChangeSettingsNotification, await this.getAllSettings());
}

public async onDidChangeSettings(_event: vscode.ConfigurationChangeEvent): Promise<Record<string, string>> {
Expand Down Expand Up @@ -3972,6 +3998,17 @@ export class DefaultClient implements Client {
compilerDefaults = await this.requestCompiler(path);
DebugConfigurationProvider.ClearDetectedBuildTasks();
}

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

@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).

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'm hoping that since we're no longer going back and forth from the lsp server, this shouldn't be an issue. I've added exception catching around the one call this still does make to the native side. Let me know if there is any additional cancellation I should be tracking still.

Copy link
Contributor

Choose a reason for hiding this comment

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

It looks like this sendRequest was moved to getRequestInfo and doesn't have a cancellation token passed to it. I think you'll want to capture the cancellation token from the original(?) hover request (or, maybe the subsequent hover request caused by showCopilotContent?), and pass that to sendRequest to communicate that cancellation to the native process. Even if you're not explicitly checking for cancellation (!request.is_active()) in your LSP message handler, this would allow the LSP manager itself to discard requests in queue that have already been cancelled and to discard the response for an operation already cancelled.

}

public async getCopilotHoverInfo(): Promise<GetCopilotHoverInfoResult> {
await this.ready;
return this.languageClient.sendRequest(GetCopilotHoverInfoRequest, null);
Copy link
Contributor

Choose a reason for hiding this comment

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

Similar feedback to the other call to sendRequest. Assuming this happens in the context of a hover operation, could you pass the associated cancellation token? For some of our calls to sendRequest that are triggered by commands, we don't get a token from VS Code, so don't pass it along. But we should pass along cancellation tokens for any user operation that has one associated with 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.

We don't directly have the token at call time, but we're relying on other stashed hover values so I've added similar logic for the cancellation token. Also, thanks for the heads up on the multiple send request calls added, this one isn't used anymore so I was able to remove it.

Copy link
Contributor

Choose a reason for hiding this comment

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

Could you clarify? I would expect a hover-related operation to be conceptually cancelable/dismissible by the user. (It looks like you currently have a bug where, if a user has moved on to another task, a slow CopilotHover request can result in the cursor being repositioned, potentially even when the user is mid-typing?).

I think it's important that this feature handle cancellation. The cpptools_getCopilotHoverInfo handler on the native side will acquire a (potentially new) TU, which may impose a hefty perf impact. Even if the TU were already existing, if racing with edit, it may trigger a preparse. There could be other messages in queue, delaying delivery of yours, or piling up after yours.

For a lot of users, IntelliSense is very slow. I have a project at home in which a TU can take more than a minute to perform a preparse. If racing with edits, we don't want to waste time fully completing an operation whose result won't be used. As soon as there are subsequent edits in queue, the preparse it performs potentially be entirely wasted.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sorry, I think my response was extra confusing since this code location doesn't make sense anymore (was removed).

For the other case where we're still calling to the native layer, we're not in the Copilot Hover's call stack directly, so we have to store the last hover cancellation token to use it later when called through the "onCopilotHover" command.

It looks like there's a bug with which token I was holding, so I'm fixing that now to make sure we have the most up-to-date hover cancellation token when checking it and passing it through to the native layer.

I agree that cancellation is important, trying to handle it from the command callback function has been a little more complicated since we're currently invoking multiple follow up hovers. Hopefully we can remove that need in a future update with better platform support, which would really help streamline the cancellation.

Copy link
Contributor

Choose a reason for hiding this comment

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

In my last response, I was specifically looking at calls to our languageClient.sendRequest, trying to ensure they all had tokens passed to them. It looks like one of your last pushes addressed that. I may have been looking at stale code.

One possible issue: In onCopilotHover, it looks like you're passing in a token from a new (not otherwise referenced) token source:

        chatResponse = await model.sendRequest(
            messages,
            {},
            new vscode.CancellationTokenSource().token
        );

I'm guessing maybe you had to pass a token (undefined wasn't accepted?), and didn't have a valid one?

trying to handle it from the command callback function has been a little more complicated since we're currently invoking multiple follow up hovers

Are those follow-ups all in the context of the same hover operation? If so, could the original hover operation's token be used for all of them?

Normally, I'd suggest passing an argument when programmatically invoking the command, but it looks like you're embedding the command as text into the hover text. Note that you can still pass an encoded argument to the command using syntax like so:

[Find my new Compiler](command:C_Cpp.RescanCompilers?%22walkthrough%22)",

Follow the call into onRescanCompilers for an example of how to extract it. I don't think you'd be able to pass through an object reference, but you could pass through a key that you could associate a value with in a map.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks! I'll push out my updated changes soon that seems to handle the cancellation much better by holding on to the hover's cancellation token and tracking which operations are changing the editor's focus more intentionally.

}
}

function getLanguageServerFileName(): string {
Expand Down Expand Up @@ -4084,4 +4121,6 @@ class NullClient implements Client {
setShowConfigureIntelliSenseButton(show: boolean): void { }
addTrustedCompiler(path: string): Promise<void> { return Promise.resolve(); }
getIncludes(): Promise<GetIncludesResult> { return Promise.resolve({} as GetIncludesResult); }
showCopilotHover(content: string): Promise<ShowCopilotHoverResult> { return Promise.resolve({} as ShowCopilotHoverResult); }
getCopilotHoverInfo(): Promise<GetCopilotHoverInfoResult> { return Promise.resolve({} as GetCopilotHoverInfoResult); }
}
88 changes: 88 additions & 0 deletions Extension/src/LanguageServer/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { TargetPopulation } from 'vscode-tas-client';
import * as which from 'which';
import { logAndReturn } from '../Utility/Async/returns';
import * as util from '../common';
import { modelSelector } from '../constants';
import { getCrashCallStacksChannel } from '../logger';
import { PlatformInformation } from '../platform';
import * as telemetry from '../telemetry';
Expand All @@ -26,6 +27,7 @@ import { CodeActionDiagnosticInfo, CodeAnalysisDiagnosticIdentifiersAndUri, code
import { CppBuildTaskProvider } from './cppBuildTaskProvider';
import { getCustomConfigProviders } from './customProviders';
import { getLanguageConfig } from './languageConfig';
import { getLocaleId } from './localization';
import { PersistentState } from './persistentState';
import { NodeType, TreeNode } from './referencesModel';
import { CppSettings } from './settings';
Expand Down Expand Up @@ -404,6 +406,7 @@ export function registerCommands(enabled: boolean): void {
commandDisposables.push(vscode.commands.registerCommand('C_Cpp.ExtractToMemberFunction', enabled ? () => onExtractToFunction(false, true) : onDisabledCommand));
commandDisposables.push(vscode.commands.registerCommand('C_Cpp.ExpandSelection', enabled ? (r: Range) => onExpandSelection(r) : onDisabledCommand));
commandDisposables.push(vscode.commands.registerCommand('C_Cpp.getIncludes', enabled ? (maxDepth: number) => getIncludes(maxDepth) : () => Promise.resolve()));
commandDisposables.push(vscode.commands.registerCommand('C_Cpp.ShowCopilotHover', enabled ? onCopilotHover : onDisabledCommand));
}

function onDisabledCommand() {
Expand Down Expand Up @@ -1379,3 +1382,88 @@ export async function getIncludes(maxDepth: number): Promise<any> {
const includes = await clients.ActiveClient.getIncludes(maxDepth);
return includes;
}

// 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; }
spebl marked this conversation as resolved.
Show resolved Hide resolved
spebl marked this conversation as resolved.
Show resolved Hide resolved
// Check if the user has access to vscode language model.
const vscodelm = (vscode as any).lm;
if (!vscodelm) { return; }

// Prep hover with wait message and get the hover position location.
const copilotHoverResult = await clients.ActiveClient.showCopilotHover('$(loading~spin)');
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) });
spebl marked this conversation as resolved.
Show resolved Hide resolved

// 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' });
spebl marked this conversation as resolved.
Show resolved Hide resolved

// Move back and show the correct hover.
await clients.ActiveClient.showCopilotHover('$(loading~spin)');
spebl marked this conversation as resolved.
Show resolved Hide resolved
await vscode.commands.executeCommand('cursorMove', { to: 'left' });
await vscode.commands.executeCommand('editor.action.showHover', { focus: 'noAutoFocus'});

// Gather the content for the query from the client.
const response = await clients.ActiveClient.getCopilotHoverInfo();

// Ensure the content is valid before proceeding.
const request = response.content;

if (request.length === 0) {
return;
}

const locale = getLocaleId();

const messages = [
vscode.LanguageModelChatMessage
.User(request + locale)];

const [model] = await vscodelm.selectChatModels(modelSelector);

let chatResponse: vscode.LanguageModelChatResponse | undefined;
try {
chatResponse = await model.sendRequest(
messages,
{},
new vscode.CancellationTokenSource().token
);
} 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?

} else {
throw err;
}
return;
}

// Ensure we have a valid response from Copilot.
if (!chatResponse) { return; }

let content: string = '';

try {
for await (const fragment of chatResponse.text) {
content += fragment;
}
} catch (err) {
return;
}

if (!vscode.window.activeTextEditor) { return; }
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)');
Colengms marked this conversation as resolved.
Show resolved Hide resolved
await vscode.commands.executeCommand('cursorMove', { to: 'right' });
await vscode.commands.executeCommand('editor.action.showHover', { focus: 'noAutoFocus'});

// Prepare and show the real content.
await clients.ActiveClient.showCopilotHover(content);
await vscode.commands.executeCommand('cursorMove', { to: 'left' });
await vscode.commands.executeCommand('editor.action.showHover', { focus: 'noAutoFocus'});
}
12 changes: 11 additions & 1 deletion Extension/src/LanguageServer/protocolFilter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
spebl marked this conversation as resolved.
Show resolved Hide resolved
return next(document, position, token).then((value: vscode.Hover) => {
if (value && value.contents instanceof Array) {
value.contents.forEach((content) => {
if (content instanceof vscode.MarkdownString) {
content.supportThemeIcons = true;
}
});
}
return value;
});
}
return null;
}),
Expand Down
34 changes: 33 additions & 1 deletion Extension/src/LanguageServer/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import * as vscode from 'vscode';
import * as nls from 'vscode-nls';
import * as which from 'which';
import { getCachedClangFormatPath, getCachedClangTidyPath, getExtensionFilePath, getRawSetting, isArray, isArrayOfString, isBoolean, isNumber, isObject, isString, isValidMapping, setCachedClangFormatPath, setCachedClangTidyPath } from '../common';
import { isWindows } from '../constants';
import { isWindows, modelSelector } from '../constants';
import * as telemetry from '../telemetry';
import { DefaultClient, cachedEditorConfigLookups, cachedEditorConfigSettings, hasTrustedCompilerPaths } from './client';
import { clients } from './extension';
Expand Down Expand Up @@ -162,6 +162,7 @@ export interface SettingsParams {
codeAnalysisMaxMemory: number | null;
codeAnalysisUpdateDelay: number;
workspaceFolderSettings: WorkspaceFolderSettingsParams[];
copilotHover: boolean | undefined;
}

function getTarget(): vscode.ConfigurationTarget {
Expand Down Expand Up @@ -462,6 +463,37 @@ export class CppSettings extends Settings {
&& this.intelliSenseEngine === "default"
&& vscode.workspace.getConfiguration("workbench").get<any>("colorTheme") !== "Default High Contrast";
}
public get copilotHover(): PromiseLike<boolean> {
// Check if the setting is explicitly set to enabled or disabled.
const setting = super.Section.get<string>("copilotHover");
if (setting === "disabled") {
return Promise.resolve(false);
}

// Check if the user has access to vscode language model.
const vscodelm = (vscode as any).lm;
if (!vscodelm) {
return Promise.resolve(false);
}

// Check if the user has access to Copilot.
return vscodelm.selectChatModels(modelSelector).then((models: any[]) => {
// If no models are returned, the user currently does not have access.
if (models.length === 0) {
// Register to update this setting if the user gains access.
vscodelm.onDidChangeChatModels(() => {
clients.ActiveClient.sendDidChangeSettings();
});
return false;
}

if (setting === "enabled") {
return true;
}

return telemetry.isFlightEnabled("cpp.copilotHover");
});
}
public get formattingEngine(): string { return this.getAsString("formatting"); }
public get vcFormatIndentBraces(): boolean { return this.getAsBoolean("vcFormat.indent.braces"); }
public get vcFormatIndentMultiLineRelativeTo(): string { return this.getAsString("vcFormat.indent.multiLineRelativeTo"); }
Expand Down
3 changes: 3 additions & 0 deletions Extension/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,6 @@ export const isLinux = OperatingSystem === 'linux';

// if you want to see the output of verbose logging, set this to true.
export const verboseEnabled = false;

// Model selector for Copilot features
export const modelSelector = { vendor: 'copilot', family: 'gpt-4' };
3 changes: 2 additions & 1 deletion Extension/src/nativeStrings.json
Original file line number Diff line number Diff line change
Expand Up @@ -478,5 +478,6 @@
"refactor_extract_reference_return_c_code": "The function would have to return a value by reference. C code cannot return references.",
"refactor_extract_xborder_jump": "Jumps between the selected code and the surrounding code are present.",
"refactor_extract_missing_return": "In the selected code, some control paths exit without setting the return value. This is supported only for scalar, numeric, and pointer return types.",
"expand_selection": "Expand selection (to enable 'Extract to function')"
"expand_selection": "Expand selection (to enable 'Extract to function')",
"copilot_hover_link": "Generate Copilot summary"
}
4 changes: 4 additions & 0 deletions Extension/src/telemetry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,10 @@ export async function isExperimentEnabled(experimentName: string): Promise<boole
if (new CppSettings().experimentalFeatures) {
return true;
}
return isFlightEnabled(experimentName);
}

export async function isFlightEnabled(experimentName: string): Promise<boolean> {
const experimentationService: IExperimentationService | undefined = await getExperimentationService();
const isEnabled: boolean | undefined = experimentationService?.getTreatmentVariable<boolean>("vscode", experimentName);
return isEnabled ?? false;
Expand Down
Loading