Compare commits

...
Author SHA1 Message Date
David Raygoza f70cba7acb Fix some logic to avoid editing same ranges twice. Update UI messages 2025-06-24 11:26:21 -07:00
Xinyu Sui 6825fa6c19 Merge branch 'dev/xisui/refactor_agent' of https://github.com/microsoft/vscode-cpptools into dev/xisui/refactor_agent 2025-06-22 03:22:57 -07:00
Xinyu Sui 8590fe46e2 Def info 2025-06-22 03:21:17 -07:00
David Raygoza 7905e8279f Improvements and better UI
Highlight only definition symbol now instead of entire def range, tool will identify full range text and locatio to make initial update. Improve UI messages and delays. Get symbolRefs line numbers and file paths in single loop now.
2025-06-21 23:45:37 -07:00
David Raygoza 1e68388564 Fully connect DB for FAR results instead of FAR results
Also, bug fix to avoid folding ranges that are key words are not brackets.
2025-06-20 16:10:03 -07:00
David Raygoza 1301314362 Fix folding ranges logic.
Logic changes for some reason when using cpptools extension compared to manually dlls
2025-06-20 11:14:19 -07:00
David Raygoza be6c7af3e7 Adjust UI progress messages 2025-06-19 17:04:59 -07:00
Xinyu Sui 7e5b90c68f Merge branch 'dev/xisui/refactor_agent' of https://github.com/microsoft/vscode-cpptools into dev/xisui/refactor_agent 2025-06-19 12:10:05 -07:00
Xinyu Sui dbfe128a12 API Update 2025-06-19 12:09:51 -07:00
David Raygoza 519c42cf90 Fix reiteration logic to fix errors, and open git diff UI viewer for final results display 2025-06-19 01:54:05 -07:00
Xinyu Sui 12e3af696a API update 2025-06-18 15:57:24 -07:00
David Raygoza 35eeed0052 Initial code for vc refactoring agent 2025-06-18 15:39:58 -07:00
David Raygoza 6af9b6ba90 Merge branch 'dev/xisui/refactor_agent' of https://github.com/microsoft/vscode-cpptools into dev/xisui/refactor_agent 2025-06-17 14:28:31 -07:00
Xinyu Sui 4e52c3b4de Add client request interface 2025-06-17 13:22:04 -07:00
5 changed files with 3105 additions and 0 deletions
+9
View File
@@ -3601,6 +3601,15 @@
"icon": "$(terminal)"
}
],
"chatParticipants": [
{
"id": "chat.refactoring-agent",
"fullName": "Refactoring Agent",
"name": "cppRefactor",
"description": "Enter your C++ refactoring task",
"isSticky": true
}
],
"keybindings": [
{
"command": "C_Cpp.SwitchHeaderSource",
+28
View File
@@ -590,6 +590,24 @@ export interface CopilotCompletionContextParams {
doAggregateSnippets: boolean;
}
export interface CopilotRefactorContextParams {
uri: string;
offset: number;
}
export interface CopilotSymbolRefEntry {
uri: string;
offset: number;
}
export interface CopilotRefactorContextResult {
requestId: number;
defUri: string;
defStartOffset: number;
defEndOffset: number;
symbolRefs: CopilotSymbolRefEntry[];
}
// Requests
const PreInitializationRequest: RequestType<void, string, void> = new RequestType<void, string, void>('cpptools/preinitialize');
const InitializationRequest: RequestType<CppInitializationParams, CppInitializationResult, void> = new RequestType<CppInitializationParams, CppInitializationResult, void>('cpptools/initialize');
@@ -612,6 +630,7 @@ const ChangeCppPropertiesRequest: RequestType<CppPropertiesParams, void, void> =
const IncludesRequest: RequestType<GetIncludesParams, GetIncludesResult, void> = new RequestType<GetIncludesParams, GetIncludesResult, void>('cpptools/getIncludes');
const CppContextRequest: RequestType<TextDocumentIdentifier, ChatContextResult, void> = new RequestType<TextDocumentIdentifier, ChatContextResult, void>('cpptools/getChatContext');
const CopilotCompletionContextRequest: RequestType<CopilotCompletionContextParams, CopilotCompletionContextResult, void> = new RequestType<CopilotCompletionContextParams, CopilotCompletionContextResult, void>('cpptools/getCompletionContext');
const CopilotRefactorContextRequest: RequestType<CopilotRefactorContextParams, CopilotRefactorContextResult, void> = new RequestType<CopilotRefactorContextParams, CopilotRefactorContextResult, void>('cpptools/getRefactorContext');
// Notifications to the server
const DidOpenNotification: NotificationType<DidOpenTextDocumentParams> = new NotificationType<DidOpenTextDocumentParams>('textDocument/didOpen');
@@ -847,6 +866,7 @@ export interface Client {
getChatContext(uri: vscode.Uri, token: vscode.CancellationToken): Promise<ChatContextResult>;
filesEncodingChanged(filesEncodingChanged: FilesEncodingChanged): void;
getCompletionContext(fileName: vscode.Uri, caretOffset: number, featureFlag: CopilotCompletionContextFeatures, maxSnippetCount: number, maxSnippetLength: number, doAggregateSnippets: boolean, token: vscode.CancellationToken): Promise<CopilotCompletionContextResult>;
getRefactorContext(file: vscode.Uri, offset: number, token: vscode.CancellationToken): Promise<CopilotRefactorContextResult>
}
export function createClient(workspaceFolder?: vscode.WorkspaceFolder): Client {
@@ -2369,6 +2389,13 @@ export class DefaultClient implements Client {
{ uri: file.toString(), caretOffset, featureFlag, maxSnippetCount, maxSnippetLength, doAggregateSnippets }, token), token);
}
public async getRefactorContext(file: vscode.Uri, offset: number,
token: vscode.CancellationToken): Promise<CopilotRefactorContextResult> {
await withCancellation(this.ready, token);
return DefaultClient.withLspCancellationHandling(
() => this.languageClient.sendRequest(CopilotRefactorContextRequest, { uri: file.toString(), offset }, token), token);
}
/**
* a Promise that can be awaited to know when it's ok to proceed.
*
@@ -4323,4 +4350,5 @@ class NullClient implements Client {
getChatContext(uri: vscode.Uri, token: vscode.CancellationToken): Promise<ChatContextResult> { return Promise.resolve({} as ChatContextResult); }
filesEncodingChanged(filesEncodingChanged: FilesEncodingChanged): void { }
getCompletionContext(file: vscode.Uri, caretOffset: number, featureFlag: CopilotCompletionContextFeatures, maxSnippetCount: number, maxSnippetLength: number, doAggregateSnippets: boolean, token: vscode.CancellationToken): Promise<CopilotCompletionContextResult> { return Promise.resolve({} as CopilotCompletionContextResult); }
getRefactorContext(file: vscode.Uri, offset: number, token: vscode.CancellationToken): Promise<CopilotRefactorContextResult> { return Promise.resolve({} as CopilotRefactorContextResult); }
}
+15
View File
@@ -37,6 +37,7 @@ import { NodeType, TreeNode } from './referencesModel';
import { CppSettings } from './settings';
import { LanguageStatusUI, getUI } from './ui';
import { makeLspRange, rangeEquals, showInstallCompilerWalkthrough } from './utils';
import { invokeRefactoringAgent } from './refactoring-agent';
nls.config({ messageFormat: nls.MessageFormat.bundle, bundleFormat: nls.BundleFormat.standalone })();
const localize: nls.LocalizeFunc = nls.loadMessageBundle();
@@ -167,6 +168,20 @@ export async function activate(): Promise<void> {
clients = new ClientCollection();
ui = getUI();
const refactoringAgenthandler: vscode.ChatRequestHandler = async (
request: vscode.ChatRequest,
context: vscode.ChatContext,
stream: vscode.ChatResponseStream,
token: vscode.CancellationToken
) =>
{
await invokeRefactoringAgent(request, context, stream, token);
return;
};
// Create refactoring agent chat participant.
vscode.chat.createChatParticipant('chat.refactoring-agent', refactoringAgenthandler);
// There may have already been registered CustomConfigurationProviders.
// Request for configurations from those providers.
clients.forEach(client => {
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,443 @@
import * as vscode from 'vscode';
import { clients } from './extension';
const path = require('path'); // Import Node.js path module
let suffixPrompt = " Output the changed code along with the rest of the entire input code snippet unchanged. Make sure entire output is formatted correctly";
interface ErrorDiagnostic {
filePath: string;
message: string;
range: vscode.Range;
}
export interface ContextItem {
updatedTextSpan?: string,
filePath: string;
startLine: number;
closestRange?: vscode.Range;
}
async function parseChatResponse(chatResponse: vscode.LanguageModelChatResponse): Promise<string[]> {
let accumulatedResponse = '';
for await (const fragment of chatResponse.text) {
accumulatedResponse += fragment;
}
const codeSnippets: string[] = [];
// Use a regular expression to extract all code blocks
const codeBlockRegex = /```(?:\w+)?\n([\s\S]*?)```/g;
let match: RegExpExecArray | null;
while ((match = codeBlockRegex.exec(accumulatedResponse)) !== null) {
codeSnippets.push(match[1].trim());
}
if (codeSnippets.length === 0) {
// vscode.window.showErrorMessage('No code blocks found in the response.');
}
return codeSnippets;
}
async function validateEdits(): Promise<boolean> {
// execute CMake: Build command and check if build output is exit code 0 for pass or 1 for failure.
try {
// Rescan workspace to get rid of any stuck diagnostics left over.
await vscode.commands.executeCommand('C_Cpp.RescanWorkspace');
// await vscode.commands.executeCommand('cmake.clean');
var buildResult = await vscode.commands.executeCommand('cmake.build');
if (buildResult === 0) {
return true;
} else {
return false;
}
} catch (error) {
return false;
}
}
// Purposely ignore if, else keywords since Copilot is not always accuraete with those responses.
const foldableKeyWords: { [keyWord: string]: boolean } = {
"if": true,
"else": true,
"while": true,
"for": true,
"switch": true,
"case": true,
"do": true,
};
async function getClosestFoldingRange(editor: vscode.TextEditor, startLine: number): Promise<vscode.FoldingRange | undefined> {
await new Promise(resolve => setTimeout(resolve, 1000));
const foldingRanges = await vscode.commands.executeCommand<vscode.FoldingRange[]>('vscode.executeFoldingRangeProvider', editor.document.uri);
let closestRange: vscode.FoldingRange | undefined;
if (foldingRanges && foldingRanges.length > 0) {
const selectionStart = startLine;
for (const range of foldingRanges) {
if (
range.start <= selectionStart &&
range.end >= selectionStart &&
(!closestRange ||
(range.start >= closestRange.start && range.end <= closestRange.end))
) {
const start = editor.document.lineAt(range.start).range.start;
const end = editor.document.lineAt(range.end).range.end;
var snippetInputRange = new vscode.Range(start, end);
var nearestRangeTextSpan = editor.document.getText(snippetInputRange);
const startsWithKeyword = Object.keys(foldableKeyWords).some(keyword => nearestRangeTextSpan.trim().startsWith(keyword));
// const startsWithBracket = nearestRangeTextSpan.trim().startsWith('{');
// if (startsWithBracket) {
// closestRange = range;
// closestRange.start += 1; // Increment start by 1 if it starts with a bracket to avoid adding bracket into the snippet.
// }
if (!startsWithKeyword) {
// Make sure to avoid foldableKeyWords since Copilot is not always accurate with those.
closestRange = range;
}
}
}
}
return closestRange;
}
async function fallsWithinPreviouslyEditedRanges(previouslyEditedRanges: { [filePath: string]: vscode.Range[] }, closestRange: vscode.FoldingRange, fileVisiting: string): Promise<boolean> {
if (!previouslyEditedRanges[fileVisiting] || !closestRange) {
return false;
}
for (const range of previouslyEditedRanges[fileVisiting]) {
if (
(closestRange.start >= range.start.line &&
closestRange.end <= range.end.line) ||
closestRange.start === range.start.line // when a range is updated the end line may change, but the start line remains the same.
) {
return true;
}
}
return false;
}
async function reiterate(
buildError: ErrorDiagnostic,
stream: vscode.ChatResponseStream,
request: vscode.ChatRequest,
context: vscode.ChatContext,
token: vscode.CancellationToken
): Promise<void> {
const document = await vscode.workspace.openTextDocument(buildError.filePath);
await vscode.window.showTextDocument(document, { preview: false });
const editor = vscode.window.activeTextEditor;
if (!editor || editor.document.languageId !== 'cpp') {
return;
}
var closestRange = await getClosestFoldingRange(editor, buildError.range.start.line);
var nearestRangeTextSpan: string = '';
let snippetInputRange: vscode.Range | undefined = undefined;
if (closestRange) {
// Store text span of closestRange in nearestRangeTextSpan.
const start = editor.document.lineAt(closestRange.start).range.start;
const end = editor.document.lineAt(closestRange.end).range.end;
snippetInputRange = new vscode.Range(start, end);
nearestRangeTextSpan = editor.document.getText(snippetInputRange);
}
let errorReiterationPrompt = `@refactor The build failed with the following error:\n\n\`\`\`cpp\n${buildError.message}\n\`\`\`\n\Fix the error in the following code snippet.${suffixPrompt}Could it be there are missing #includes?\n\n${nearestRangeTextSpan}`;
var messages = [vscode.LanguageModelChatMessage.User(errorReiterationPrompt)];
const reiterationResponse = await request.model.sendRequest(messages, {}, token);
var reiteratedCodeSnippets = await parseChatResponse(reiterationResponse);
if (reiteratedCodeSnippets.length > 0) {
// Parse for includes.
var includeRegex = /#include\s*["<](.*?)[">]/g;
var includes: string[] = [];
let match: RegExpExecArray | null;
while ((match = includeRegex.exec(reiteratedCodeSnippets[reiteratedCodeSnippets.length - 1])) !== null) {
includes.push(match[0]);
}
// If any includes exist in the fix responde then insert includes at the top of the file.
// Rest of fix response snippet can be ignored for now since the includes may have been the root issue. If not, it will be caught in the next build+reiteration.
if (includes.length > 0) {
const topOfFileRange = new vscode.Range(0, 0, 0, 0);
const includesText = includes.join('\n') + '\n';
await applyDynamicEdit(buildError.filePath, topOfFileRange, includesText, stream, undefined, true);
}
else {
await applyDynamicEdit(buildError.filePath, snippetInputRange, reiteratedCodeSnippets[reiteratedCodeSnippets.length - 1], stream, undefined, true);
}
}
}
async function applyDynamicEdit(filePath: string, range: vscode.Range | undefined, updatedTextSpan: string | undefined, stream: vscode.ChatResponseStream, refStartLine: number | undefined, reiterationEdit: boolean): Promise<void> {
const document = await vscode.workspace.openTextDocument(filePath);
await vscode.window.showTextDocument(document, { preview: false });
const editor = vscode.window.activeTextEditor;
if (editor && editor.document.languageId === 'cpp') {
if (!range) {
return;
}
editor.selection = new vscode.Selection(range.start, range.end);
editor.revealRange(range, vscode.TextEditorRevealType.InCenter);
// Replace the range with updatedTextSpan if it exists
if (updatedTextSpan) {
const singleEdit = new vscode.WorkspaceEdit();
await new Promise(resolve => setTimeout(resolve, 1000));
singleEdit.replace(editor.document.uri, range, updatedTextSpan);
await vscode.workspace.applyEdit(singleEdit);
await new Promise(resolve => setTimeout(resolve, 1000));
await editor.document.save();
await new Promise(resolve => setTimeout(resolve, 1000));
var startLine: number = range.start.line;
if (refStartLine !== undefined) {
startLine = refStartLine; // Use the provided start line if it exists.
}
var messageText: string = "Updated code snippet in";
if (reiterationEdit) {
messageText = "Applied fix in";
}
stream.markdown(`\n\n${messageText} \`${path.basename(filePath)}\` at line \`${startLine + 1}\``);
}
editor.selection = new vscode.Selection(range.end, range.end);
}
}
// This method is called when your extension is activated
export async function invokeRefactoringAgent(request: vscode.ChatRequest, context: vscode.ChatContext, stream: vscode.ChatResponseStream, token: vscode.CancellationToken): Promise<void> {
const batchableEditCount = 1;
// Add in the user's message.
var usersPrompt: string = request.prompt;
const messages = [vscode.LanguageModelChatMessage.User(usersPrompt)];
messages.push(vscode.LanguageModelChatMessage.User(suffixPrompt));
let primaryFilePath = '';
var primaryOffset: number = 1852;
const activeEditor = vscode.window.activeTextEditor;
if (!activeEditor || activeEditor.selection.isEmpty || activeEditor.document.languageId !== 'cpp') {
return;
}
primaryFilePath = activeEditor.document.uri.fsPath;
const selectedSymbol = activeEditor.document.getText(activeEditor.selection);
const cursorPosition = activeEditor.selection.active;
primaryOffset = activeEditor.document.offsetAt(cursorPosition) - 1; // Gets offset from the highlighted symbol, minus 1 to account for the cursor being at the end of the symbol.
// Get full method range based on the current symbol position.
const symbolsResult = await vscode.commands.executeCommand('vscode.executeDocumentSymbolProvider', activeEditor.document.uri);
const symbols = Array.isArray(symbolsResult) ? symbolsResult as vscode.DocumentSymbol[] : [];
if (!symbols || symbols.length === 0) {
return;
}
var primaryMethodRange: vscode.Range | undefined = undefined;
for (const symbol of symbols) {
if (symbol.name.startsWith(selectedSymbol)) {
if (symbol.range) {
primaryMethodRange = symbol.range;
break;
}
}
}
if (!primaryMethodRange) {
return;
}
const fullPrimaryMethodTextSpan = activeEditor.document.getText(primaryMethodRange);
messages.push(vscode.LanguageModelChatMessage.User(`\n\n\`\`\`cpp\n${fullPrimaryMethodTextSpan}\n\`\`\``));
const initialChatResponse = await request.model.sendRequest(messages, {}, token);
// Stream the initial chat response to the Copilot Chat UI.
for await (const fragment of initialChatResponse.text) {
stream.markdown(fragment);
}
var initialChatResponseSnippet = await parseChatResponse(initialChatResponse);
if (initialChatResponseSnippet.length > 0) {
const updatedTextSpan = initialChatResponseSnippet[0].toString();
await applyDynamicEdit(primaryFilePath, primaryMethodRange, updatedTextSpan, stream, undefined, false);
}
stream.progress("\n\nSearching for references in database...");
const client = clients.getClientFor(activeEditor.document.uri);
if (!client) { throw new Error('No active client found'); }
const cancellationToken = new vscode.CancellationTokenSource().token;
var symbolRefs = await client.getRefactorContext(activeEditor.document.uri, primaryOffset, cancellationToken);
// TODO: Later: Ask user if that is the result they expected?
// TODO: Later: Generate an example and ask user to confirm if it is correct before applying edits to all refs based on that example.
// TODO: Later: Extract the symbol from this prompt or selected text to run FAR on it. Ben M. is working on something similar.
stream.markdown("\n\nFound \`" + symbolRefs.symbolRefs.length + "\` references in database to update...");
var numContextItemsUpdated = symbolRefs.symbolRefs.length;
const previouslyEditedRanges: { [filePath: string]: vscode.Range[] } = {};
while (symbolRefs.symbolRefs.length > 0) {
const numEditsToBeBatched = Math.min(batchableEditCount, symbolRefs.symbolRefs.length);
const batchedCodeSnippets = symbolRefs.symbolRefs.splice(-numEditsToBeBatched, numEditsToBeBatched);
var generateEditsPrompt: string = `@refactor Now update all of the FillShader::Fill references below to reflect the new functionality of \"${usersPrompt}\".${suffixPrompt}`;
var shouldExecuteRequest: boolean = false;
var leadingWhiteSpace: string = '';
var currentContextItemFilePath: string = '';
var currentContextItemStartLine: number = 0;
var currentContextItemClosestFoldingRange: vscode.Range | undefined = undefined;
for (let i = batchedCodeSnippets.length - 1; i >= 0; i--) {
var codeSnippetInput = batchedCodeSnippets[i];
const uri = vscode.Uri.parse(codeSnippetInput.uri);
currentContextItemFilePath = uri.fsPath.toLowerCase();
var editor = vscode.window.activeTextEditor;
if (currentContextItemFilePath != editor?.document.uri.fsPath) {
const document = await vscode.workspace.openTextDocument(currentContextItemFilePath);
await vscode.window.showTextDocument(document, { preview: false });
editor = vscode.window.activeTextEditor;
}
if (!editor || editor.document.languageId !== 'cpp') {
return;
}
currentContextItemStartLine = editor.document.positionAt(codeSnippetInput.offset).line;
if (currentContextItemFilePath.toLowerCase() == primaryFilePath.toLowerCase() && codeSnippetInput.offset == primaryOffset) {
continue; // Primary file and offset are already handled in initial user prompt so we can skip that reference now.
}
var closestRange = await getClosestFoldingRange(editor, currentContextItemStartLine);
var nearestRangeTextSpan: string = '';
if (closestRange) {
if (!previouslyEditedRanges[currentContextItemFilePath.toLowerCase()]) {
previouslyEditedRanges[currentContextItemFilePath.toLowerCase()] = [];
}
const start = editor.document.lineAt(closestRange.start).range.start;
const end = editor.document.lineAt(closestRange.end).range.end;
const range = new vscode.Range(start, end);
if (await fallsWithinPreviouslyEditedRanges(previouslyEditedRanges, closestRange, currentContextItemFilePath.toLowerCase())) {
continue; // If already edited this range, then skip it.
}
previouslyEditedRanges[currentContextItemFilePath.toLowerCase()].push(range);
}
if (closestRange) {
// Store text span of closestRange in nearestRangeTextSpan.
const start = editor.document.lineAt(closestRange.start).range.start;
const end = editor.document.lineAt(closestRange.end).range.end;
let snippetInputRange = new vscode.Range(start, end);
nearestRangeTextSpan = editor.document.getText(snippetInputRange);
// trim leading whitespace, indentation, and newlines from nearestRangeTextSpan and store in leadingWhiteSpace.
const matchLeadingWhitespace = nearestRangeTextSpan.match(/^\s*/);
leadingWhiteSpace = matchLeadingWhitespace ? matchLeadingWhitespace[0] : '';
currentContextItemClosestFoldingRange = snippetInputRange;
}
stream.progress("Updating code snippet in " + path.basename(currentContextItemFilePath) + " at line " + (currentContextItemStartLine + 1) + "...");
generateEditsPrompt += `\n\n\`\`\`${nearestRangeTextSpan}\`\`\``;
var shouldExecuteRequest: boolean = true;
}
if (!shouldExecuteRequest) {
// Usually occurs if there was no new range(s) that needed to be edited.
continue;
}
// Send dynamic request and get response for each context item.
// Check token size before sending the request
const messages = [vscode.LanguageModelChatMessage.User(generateEditsPrompt)];
// TODO: Use tokenCount to determine if request needs to be split into smaller requests instead of batching.
// const tokenCount = await request.model.countTokens(generateEditsPrompt);
const generateEditsChatResponse = await request.model.sendRequest(messages, {}, token);
var codeSnippets = await parseChatResponse(generateEditsChatResponse);
if (codeSnippets.length === 0) {
continue;
}
// Apply the edits one at a time.
var codeSnippetResponseCounter = 0;
for (let i = batchedCodeSnippets.length - 1; i >= 0; i--) {
var updatedTextSpan: string = codeSnippets[codeSnippetResponseCounter].toString();
var codeSnippetInput = batchedCodeSnippets[i];
await applyDynamicEdit(currentContextItemFilePath, currentContextItemClosestFoldingRange, leadingWhiteSpace + updatedTextSpan, stream, currentContextItemStartLine, false);
codeSnippetResponseCounter++;
}
} // End of while loop foriterating through all FARContextItems.
stream.progress("\n\nBuilding project...");
var buildResult = await validateEdits();
var reiterationAttemptsCounter: number = 0;
while (!buildResult) {
if (reiterationAttemptsCounter >= 3) {
reiterationAttemptsCounter = 0;
// TODO: Ask user to manually fix errors, then click this button to continue the agent.
}
stream.progress("\n\nBuild failed. Agent will attempt to fix error(s)...");
const allDiagnostics = vscode.languages.getDiagnostics();
let buildErrorsInHeaderFiles: ErrorDiagnostic[] = [];
let buildErrorsInSourceFiles: ErrorDiagnostic[] = [];
for (const [uri, diagnostics] of allDiagnostics) {
for (const diagnostic of diagnostics) {
// const normalizedFsPath = uri.fsPath.replace(/\\/g, '/');
if (diagnostic.severity === vscode.DiagnosticSeverity.Error && uri.fsPath.toLowerCase() in previouslyEditedRanges) {
const isHeader = uri.fsPath.endsWith('.h') || uri.fsPath.endsWith('.hpp');
if (isHeader) {
const error: ErrorDiagnostic = {
filePath: uri.fsPath,
message: diagnostic.message,
range: diagnostic.range
};
buildErrorsInHeaderFiles.push(error);
}
else {
const error: ErrorDiagnostic = {
filePath: uri.fsPath,
message: diagnostic.message,
range: diagnostic.range
};
buildErrorsInSourceFiles.push(error);
}
}
}
}
// Identify the correct build error to pass in first to reiterate on.
let buildErrorToReiterateOn: ErrorDiagnostic | undefined;
if (buildErrorsInHeaderFiles.length > 0) {
// Iterate through errors from header files first from top to bottom since those are more likely to be the root cause of cascading issues.
buildErrorToReiterateOn = buildErrorsInHeaderFiles[0];
}
else if (buildErrorsInSourceFiles.length > 0) {
buildErrorToReiterateOn = buildErrorsInSourceFiles[0];
}
if (buildErrorToReiterateOn) {
// Reiterate and build again to validate new edits.
await reiterate(buildErrorToReiterateOn, stream, request, context, token);
stream.progress("\n\nBuilding project...");
buildResult = await validateEdits();
reiterationAttemptsCounter++;
}
} // while loop for validating edits and reiterating.
// Build has fully succeeded.
stream.markdown(`\n\nBuild succeeded. Successfully applied \`${numContextItemsUpdated}\` edits`);
// Launch the git diff viewer tool to show all the changes made.
try {
await vscode.commands.executeCommand('workbench.view.scm');
} catch (err) {
console.error('Failed to open Source Control view:', err);
}
const finalDocToDisplay = await vscode.workspace.openTextDocument(primaryFilePath);
await vscode.window.showTextDocument(finalDocToDisplay, { preview: false });
return;
}