Compare commits

...
10 changed files with 234 additions and 6 deletions
+11
View File
@@ -3277,6 +3277,17 @@
"default": false,
"markdownDescription": "%c_cpp.configuration.addNodeAddonIncludePaths.markdownDescription%",
"scope": "application"
},
"C_Cpp.copilotHover": {
"type": "string",
"enum": [
"default",
"enabled",
"disabled"
],
"default": "default",
"markdownDescription": "%c_cpp.configuration.copilotHover.markdownDescription%",
"scope": "window"
}
}
}
+6
View File
@@ -768,6 +768,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": [
@@ -0,0 +1,33 @@
/* --------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All Rights Reserved.
* See 'LICENSE' in the project root for license information.
* ------------------------------------------------------------------------------------------ */
import * as vscode from 'vscode';
import { CppSettings } from '../settings';
import { HoverProvider } from './HoverProvider';
export class CopilotHoverProvider implements vscode.HoverProvider {
private provider: HoverProvider;
constructor(provider: HoverProvider) {
this.provider = provider;
}
public async provideHover(document: vscode.TextDocument, _position: vscode.Position, _token: vscode.CancellationToken): Promise<vscode.Hover | undefined> {
const settings: CppSettings = new CppSettings(vscode.workspace.getWorkspaceFolder(document.uri)?.uri);
if (settings.hover === "disabled") {
return undefined;
}
// Why does it show "Loading..." and is that vscode?
// TODO: add intermediate loading state spinner before long ops
const content = await this.provider.showCopilotHover;
if (!content) {
return undefined;
}
const markdownContent = new vscode.MarkdownString(content);
markdownContent.supportThemeIcons = true;
return new vscode.Hover(markdownContent);
}
}
@@ -4,6 +4,7 @@
* ------------------------------------------------------------------------------------------ */
import * as vscode from 'vscode';
import { Position, ResponseError, TextDocumentPositionParams } from 'vscode-languageclient';
import { ManualSignal } from '../../Utility/Async/manualSignal';
import { DefaultClient, HoverRequest } from '../client';
import { RequestCancelled, ServerCancelled } from '../protocolFilter';
import { CppSettings } from '../settings';
@@ -54,4 +55,6 @@ export class HoverProvider implements vscode.HoverProvider {
return new vscode.Hover(strings, range);
}
public showCopilotHover = new ManualSignal<string>(true);
}
+51 -4
View File
@@ -43,6 +43,7 @@ import { localizedStringCount, lookupString } from '../nativeStrings';
import { SessionState } from '../sessionState';
import * as telemetry from '../telemetry';
import { TestHook, getTestHook } from '../testHook';
import { CopilotHoverProvider } from './Providers/CopilotHoverProvider';
import { HoverProvider } from './Providers/HoverProvider';
import {
CodeAnalysisDiagnosticIdentifiersAndUri,
@@ -542,6 +543,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');
@@ -562,6 +578,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');
@@ -792,6 +810,9 @@ export interface Client {
setShowConfigureIntelliSenseButton(show: boolean): void;
addTrustedCompiler(path: string): Promise<void>;
getIncludes(maxDepth: number): Promise<GetIncludesResult>;
showCopilotHover_old(content: string): Promise<ShowCopilotHoverResult>;
showCopilotHover(content: string): Promise<void>;
getCopilotHoverInfo(): Promise<GetCopilotHoverInfoResult>;
}
export function createClient(workspaceFolder?: vscode.WorkspaceFolder): Client {
@@ -813,6 +834,7 @@ export class DefaultClient implements Client {
private inlayHintsProvider: InlayHintsProvider | undefined;
private semanticTokensProvider: SemanticTokensProvider | undefined;
private semanticTokensProviderDisposable: vscode.Disposable | undefined;
private hoverProvider: HoverProvider | undefined;
private innerConfiguration?: configs.CppProperties;
private rootPathFileWatcher?: vscode.FileSystemWatcher;
private rootFolder?: vscode.WorkspaceFolder;
@@ -1256,8 +1278,10 @@ export class DefaultClient implements Client {
this.registerFileWatcher();
initializedClientCount = 0;
this.inlayHintsProvider = new InlayHintsProvider();
this.hoverProvider = new HoverProvider(this);
this.disposables.push(vscode.languages.registerHoverProvider(util.documentSelector, new HoverProvider(this)));
this.disposables.push(vscode.languages.registerHoverProvider(util.documentSelector, this.hoverProvider));
this.disposables.push(vscode.languages.registerHoverProvider(util.documentSelector, new CopilotHoverProvider(this.hoverProvider)));
this.disposables.push(vscode.languages.registerInlayHintsProvider(util.documentSelector, this.inlayHintsProvider));
this.disposables.push(vscode.languages.registerRenameProvider(util.documentSelector, new RenameProvider(this)));
this.disposables.push(vscode.languages.registerReferenceProvider(util.documentSelector, new FindAllReferencesProvider(this)));
@@ -1443,7 +1467,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();
@@ -1469,6 +1493,7 @@ export class DefaultClient implements Client {
codeAnalysisMaxConcurrentThreads: workspaceSettings.codeAnalysisMaxConcurrentThreads,
codeAnalysisMaxMemory: workspaceSettings.codeAnalysisMaxMemory,
codeAnalysisUpdateDelay: workspaceSettings.codeAnalysisUpdateDelay,
copilotHover: await workspaceSettings.copilotHover,
workspaceFolderSettings: workspaceFolderSettingsParams
};
}
@@ -1538,7 +1563,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()
};
this.loggingLevel = util.getNumericLoggingLevel(cppInitializationParams.settings.loggingLevel);
@@ -1579,6 +1604,9 @@ 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: should I set the output channel? Does this sort output between servers?
@@ -1604,7 +1632,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>> {
@@ -4002,6 +4030,22 @@ export class DefaultClient implements Client {
compilerDefaults = await this.requestCompiler(path);
DebugConfigurationProvider.ClearDetectedBuildTasks();
}
public async showCopilotHover_old(content: string): Promise<ShowCopilotHoverResult> {
const params: ShowCopilotHoverParams = {content: content};
await this.ready;
return this.languageClient.sendRequest(ShowCopilotHoverRequest, params);
}
public async showCopilotHover(content: string): Promise<void> {
this.hoverProvider?.showCopilotHover.resolve(content);
this.hoverProvider?.showCopilotHover.reset();
}
public async getCopilotHoverInfo(): Promise<GetCopilotHoverInfoResult> {
await this.ready;
return this.languageClient.sendRequest(GetCopilotHoverInfoRequest, null);
}
}
function getLanguageServerFileName(): string {
@@ -4114,4 +4158,7 @@ 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_old(content: string): Promise<ShowCopilotHoverResult> { return Promise.resolve({} as ShowCopilotHoverResult); }
showCopilotHover(content: string): Promise<void> { return Promise.resolve(); }
getCopilotHoverInfo(): Promise<GetCopilotHoverInfoResult> { return Promise.resolve({} as GetCopilotHoverInfoResult); }
}
+88
View File
@@ -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';
@@ -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';
@@ -403,6 +405,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() {
@@ -1372,3 +1375,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; }
// 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) });
// 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' });
// Move back and show the correct hover.
//await clients.ActiveClient.showCopilotHover('$(loading~spin)');
//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);
} 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)');
//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'});
}
+33 -1
View File
@@ -14,7 +14,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 { cachedEditorConfigLookups, DefaultClient, hasTrustedCompilerPaths } from './client';
import { getEditorConfigSettings, mapIndentationReferenceToEditorConfig, mapIndentToEditorConfig, mapNewOrSameLineToEditorConfig, mapWrapToEditorConfig } from './editorConfig';
@@ -161,6 +161,7 @@ export interface SettingsParams {
codeAnalysisMaxMemory: number | null;
codeAnalysisUpdateDelay: number;
workspaceFolderSettings: WorkspaceFolderSettingsParams[];
copilotHover: boolean | undefined;
}
function getTarget(): vscode.ConfigurationTarget {
@@ -460,6 +461,37 @@ export class CppSettings extends Settings {
&& this.intelliSenseEngine.toLowerCase() === "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"); }
+3
View File
@@ -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' };
+2 -1
View File
@@ -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
View File
@@ -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;