Skip to content

Commit 1790bd0

Browse files
error handling, messaging and refactoring. (#1333)
* error handling, messaging and refactoring. * resolve comments.
1 parent d6f749c commit 1790bd0

File tree

7 files changed

+100
-31
lines changed

7 files changed

+100
-31
lines changed

src/copilot/Copilot.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ export default class Copilot {
2828
const messages = [...systemMessagesOrSamples];
2929
const _send = async (message: string): Promise<boolean> => {
3030
rounds++;
31-
logger.info(`User: \n`, message);
31+
logger.debug(`User: \n`, message);
32+
logger.info(`User: ${message.split('\n')[0]}...`);
3233
messages.push(new LanguageModelChatMessage(LanguageModelChatMessageRole.User, message));
3334
logger.info('Copilot: thinking...');
3435

@@ -47,7 +48,8 @@ export default class Copilot {
4748
throw new Error(`Failed to send request to copilot: ${e}`);
4849
}
4950
messages.push(new LanguageModelChatMessage(LanguageModelChatMessageRole.Assistant, rawAnswer));
50-
logger.info(`Copilot: \n`, rawAnswer);
51+
logger.debug(`Copilot: \n`, rawAnswer);
52+
logger.info(`Copilot: ${rawAnswer.split('\n')[0]}...`);
5153
answer += rawAnswer;
5254
return answer.trim().endsWith(this.endMark);
5355
};

src/copilot/inspect/DocumentRenderer.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,11 @@ export class DocumentRenderer {
104104
if (settings.length === 0) {
105105
settings.push('diagnostics');
106106
settings.push('rulerhighlights');
107-
settings.push(isCodeLensDisabled() ? 'guttericons' : 'codelenses');
107+
const disabled = isCodeLensDisabled();
108+
if (disabled) {
109+
logger.warn('CodeLens is disabled, fallback to GutterIcons');
110+
}
111+
settings.push(disabled ? 'guttericons' : 'codelenses');
108112
}
109113
return settings;
110114
}

src/copilot/inspect/InspectionCopilot.ts

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import Copilot from "../Copilot";
33
import { getClassesContainedInRange, getInnermostClassContainsRange, getIntersectionMethodsOfRange, getProjectJavaVersion, getUnionRange, logger } from "../utils";
44
import { Inspection } from "./Inspection";
55
import path from "path";
6-
import { TextDocument, SymbolKind, ProgressLocation, commands, Position, Range, Selection, window, LanguageModelChatMessage, LanguageModelChatMessageRole } from "vscode";
6+
import { TextDocument, SymbolKind, ProgressLocation, commands, Position, Range, Selection, window, LanguageModelChatMessage } from "vscode";
77
import { COMMAND_FIX_INSPECTION } from "./commands";
88
import InspectionCache from "./InspectionCache";
99
import { SymbolNode } from "./SymbolNode";
@@ -162,21 +162,21 @@ export default class InspectionCopilot extends Copilot {
162162
const symbolKind = SymbolKind[symbols[0].kind].toLowerCase();
163163
const inspections = await window.withProgress({
164164
location: ProgressLocation.Notification,
165-
title: `Inspecting ${symbolKind} ${symbolName}... of \"${path.basename(document.fileName)}\"`,
165+
title: `Inspecting ${symbolKind} ${symbolName}... of "${path.basename(document.fileName)}"`,
166166
cancellable: false
167167
}, (_progress) => {
168168
return this.doInspectRange(document, expandedRange);
169169
});
170170

171171
// show message based on the number of inspections
172172
if (inspections.length < 1) {
173-
void window.showInformationMessage(`Inspected ${symbolKind} ${symbolName}... of \"${path.basename(document.fileName)}\" and got 0 suggestions.`);
173+
void window.showInformationMessage(`Inspected ${symbolKind} ${symbolName}... of "${path.basename(document.fileName)}" and got 0 suggestions.`);
174174
} else if (inspections.length == 1) {
175175
// apply the only suggestion automatically
176176
void commands.executeCommand(COMMAND_FIX_INSPECTION, inspections[0].problem, inspections[0].solution, 'auto');
177177
} else {
178178
// show message to go to the first suggestion
179-
void window.showInformationMessage(`Inspected ${symbolKind} ${symbolName}... of \"${path.basename(document.fileName)}\" and got ${inspections.length} suggestions.`, "Go to").then(selection => {
179+
void window.showInformationMessage(`Inspected ${symbolKind} ${symbolName}... of "${path.basename(document.fileName)}" and got ${inspections.length} suggestions.`, "Go to").then(selection => {
180180
selection === "Go to" && void Inspection.revealFirstLineOfInspection(inspections[0]);
181181
});
182182
}
@@ -220,10 +220,8 @@ export default class InspectionCopilot extends Copilot {
220220
const adjustedRange = new Range(new Position(range.start.line, 0), new Position(range.end.line, document.lineAt(range.end.line).text.length));
221221
const content: string = document.getText(adjustedRange);
222222
const startLine = range.start.line;
223-
const javaVersion = await getProjectJavaVersion(document);
224-
const inspections = await this.inspectCode(content, {
225-
javaVersion
226-
});
223+
const projectContext = await this.collectProjectContext(document);
224+
const inspections = await this.inspectCode(content, projectContext);
227225
inspections.forEach(s => {
228226
s.document = document;
229227
// real line index to the start of the document
@@ -356,6 +354,11 @@ export default class InspectionCopilot extends Copilot {
356354
}
357355
return codeLines;
358356
}
357+
358+
async collectProjectContext(document: TextDocument): Promise<ProjectContext> {
359+
const javaVersion = await getProjectJavaVersion(document);
360+
return { javaVersion };
361+
}
359362
}
360363

361364
export interface ProjectContext {

src/copilot/inspect/commands.ts

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
import { TextDocument, Range, Selection, commands } from "vscode";
1+
import { TextDocument, Range, Selection, commands, window } from "vscode";
22
import { instrumentOperationAsVsCodeCommand, sendInfo } from "vscode-extension-telemetry-wrapper";
33
import InspectionCopilot from "./InspectionCopilot";
44
import { Inspection, InspectionProblem } from "./Inspection";
5-
import { uncapitalize } from "../utils";
5+
import { logger, uncapitalize } from "../utils";
66
import { SymbolNode } from "./SymbolNode";
77
import { DocumentRenderer } from "./DocumentRenderer";
88
import InspectionCache from "./InspectionCache";
9+
import path from "path";
910

1011
export const COMMAND_INSPECT_CLASS = 'java.copilot.inspect.class';
1112
export const COMMAND_INSPECT_RANGE = 'java.copilot.inspect.range';
@@ -14,12 +15,24 @@ export const COMMAND_IGNORE_INSPECTIONS = 'java.copilot.inspection.ignore';
1415

1516
export function registerCommands(copilot: InspectionCopilot, renderer: DocumentRenderer) {
1617
instrumentOperationAsVsCodeCommand(COMMAND_INSPECT_CLASS, async (document: TextDocument, clazz: SymbolNode) => {
17-
await copilot.inspectClass(document, clazz);
18+
try {
19+
await copilot.inspectClass(document, clazz);
20+
} catch (e) {
21+
window.showErrorMessage(`Failed to inspect class "${clazz.symbol.name}" with error ${e}.`);
22+
logger.error(`Failed to inspect class "${clazz.symbol.name}".`, e);
23+
throw e;
24+
}
1825
renderer.rerender(document);
1926
});
2027

2128
instrumentOperationAsVsCodeCommand(COMMAND_INSPECT_RANGE, async (document: TextDocument, range: Range | Selection) => {
22-
await copilot.inspectRange(document, range);
29+
try {
30+
await copilot.inspectRange(document, range);
31+
} catch (e) {
32+
window.showErrorMessage(`Failed to inspect range of "${path.basename(document.fileName)}" with error ${e}.`);
33+
logger.error(`Failed to inspect range of "${path.basename(document.fileName)}".`, e);
34+
throw e;
35+
}
2336
renderer.rerender(document);
2437
});
2538

src/copilot/inspect/index.ts

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export const DEPENDENT_EXTENSIONS = ['github.copilot-chat', 'redhat.java'];
1111

1212
export async function activateCopilotInspection(context: ExtensionContext): Promise<void> {
1313
logger.info('Waiting for dependent extensions to be ready...');
14+
await waitUntilExtensionsInstalled(DEPENDENT_EXTENSIONS);
1415
await waitUntilExtensionsActivated(DEPENDENT_EXTENSIONS);
1516
logger.info('Activating Java Copilot features...');
1617
doActivate(context);
@@ -49,25 +50,41 @@ async function rewrite(document: TextDocument, range: Range | Selection, _contex
4950
export async function waitUntilExtensionsActivated(extensionIds: string[], interval: number = 1500) {
5051
const start = Date.now();
5152
return new Promise<void>((resolve) => {
52-
if (extensionIds.every(id => extensions.getExtension(id)?.isActive)) {
53+
const notActivatedExtensionIds = extensionIds.filter(id => !extensions.getExtension(id)?.isActive);
54+
if (notActivatedExtensionIds.length == 0) {
5355
logger.info(`All dependent extensions [${extensionIds.join(', ')}] are activated.`);
5456
return resolve();
5557
}
56-
const notInstalledExtensionIds = extensionIds.filter(id => !extensions.getExtension(id));
57-
if (notInstalledExtensionIds.length > 0) {
58-
sendInfo('java.copilot.inspection.dependentExtensions.notInstalledExtensions', { extensionIds: `[${notInstalledExtensionIds.join(',')}]` });
59-
logger.info(`Dependent extensions [${notInstalledExtensionIds.join(', ')}] are not installed, setting checking interval to 10s.`);
60-
} else {
61-
logger.info(`All dependent extensions are installed, but some are not activated, keep checking interval ${interval}ms.`);
62-
}
63-
interval = notInstalledExtensionIds.length > 0 ? 10000 : interval;
58+
logger.info(`Dependent extensions [${notActivatedExtensionIds.join(', ')}] are not activated, waiting...`);
6459
const id = setInterval(() => {
6560
if (extensionIds.every(id => extensions.getExtension(id)?.isActive)) {
6661
clearInterval(id);
67-
sendInfo('java.copilot.inspection.dependentExtensions.waited', { time: Date.now() - start });
68-
logger.info(`waited for ${Date.now() - start}ms for all dependent extensions [${extensionIds.join(', ')}] to be installed/activated.`);
62+
sendInfo('java.copilot.inspection.dependentExtensions.waitActivated', { time: Date.now() - start });
63+
logger.info(`waited for ${Date.now() - start}ms for all dependent extensions [${extensionIds.join(', ')}] to be activated.`);
6964
resolve();
7065
}
7166
}, interval);
7267
});
68+
}
69+
70+
export async function waitUntilExtensionsInstalled(extensionIds: string[]) {
71+
const start = Date.now();
72+
return new Promise<void>((resolve) => {
73+
const notInstalledExtensionIds = extensionIds.filter(id => !extensions.getExtension(id));
74+
if (notInstalledExtensionIds.length == 0) {
75+
logger.info(`All dependent extensions [${extensionIds.join(', ')}] are installed.`);
76+
return resolve();
77+
}
78+
sendInfo('java.copilot.inspection.dependentExtensions.notInstalledExtensions', { extensionIds: `[${notInstalledExtensionIds.join(',')}]` });
79+
logger.info(`Dependent extensions [${notInstalledExtensionIds.join(', ')}] are not installed, waiting...`);
80+
81+
const disposable = extensions.onDidChange(() => {
82+
if (extensionIds.every(id => extensions.getExtension(id))) {
83+
disposable.dispose();
84+
sendInfo('java.copilot.inspection.dependentExtensions.waitInstalled', { time: Date.now() - start });
85+
logger.info(`waited for ${Date.now() - start}ms for all dependent extensions [${extensionIds.join(', ')}] to be installed.`);
86+
resolve();
87+
}
88+
});
89+
});
7390
}

src/copilot/utils.ts

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import { LogOutputChannel, SymbolKind, TextDocument, commands, window, Range, Selection, workspace, DocumentSymbol } from "vscode";
1+
import { LogOutputChannel, SymbolKind, TextDocument, commands, window, Range, Selection, workspace, DocumentSymbol, ProgressLocation, version } from "vscode";
22
import { SymbolNode } from "./inspect/SymbolNode";
3+
import { SemVer } from "semver";
34

45
export const CLASS_KINDS: SymbolKind[] = [SymbolKind.Class, SymbolKind.Interface, SymbolKind.Enum];
56
export const METHOD_KINDS: SymbolKind[] = [SymbolKind.Method, SymbolKind.Constructor];
@@ -85,6 +86,32 @@ export function isCodeLensDisabled(): boolean {
8586
export async function getProjectJavaVersion(document: TextDocument): Promise<number> {
8687
const uri = document.uri.toString();
8788
const key = "org.eclipse.jdt.core.compiler.source";
88-
const settings: { [key]: string } = await commands.executeCommand("java.project.getSettings", uri, [key]);
89-
return parseInt(settings[key]) || 17;
89+
try {
90+
const settings: { [key]: string } = await retryOnFailure(async () => {
91+
return await commands.executeCommand("java.project.getSettings", uri, [key]);
92+
});
93+
return parseInt(settings[key]) || 17;
94+
} catch (e) {
95+
throw new Error(`Failed to get Java version, please check if the project is loaded normally: ${e}`);
96+
}
9097
}
98+
99+
export async function retryOnFailure<T>(task: () => Promise<T>, timeout: number = 15000, retryInterval: number = 3000): Promise<T> {
100+
const startTime = Date.now();
101+
102+
while (true) {
103+
try {
104+
return await task();
105+
} catch (error) {
106+
if (Date.now() - startTime >= timeout) {
107+
throw error;
108+
} else {
109+
await new Promise(resolve => setTimeout(resolve, retryInterval));
110+
}
111+
}
112+
}
113+
}
114+
115+
export function isLlmApiReady(): boolean {
116+
return new SemVer(version).compare(new SemVer("1.90.0-insider")) >= 0;
117+
}

src/extension.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import { scheduleAction } from "./utils/scheduler";
2424
import { showWelcomeWebview, WelcomeViewSerializer } from "./welcome";
2525
import { activateCopilotInspection } from "./copilot/inspect";
2626
import { ProjectSettingsViewSerializer } from "./project-settings/projectSettingsView";
27+
import { isLlmApiReady } from "./copilot/utils";
2728

2829
let cleanJavaWorkspaceIndicator: string;
2930
let activatedTimestamp: number;
@@ -47,7 +48,7 @@ async function initializeExtension(_operationId: string, context: vscode.Extensi
4748
initDaemon(context);
4849

4950
activatedTimestamp = performance.now();
50-
if(context.storageUri) {
51+
if (context.storageUri) {
5152
const javaWorkspaceStoragePath = path.join(context.storageUri.fsPath, "..", "redhat.java");
5253
cleanJavaWorkspaceIndicator = path.join(javaWorkspaceStoragePath, "jdt_ws", ".cleanWorkspace");
5354
}
@@ -83,7 +84,9 @@ async function initializeExtension(_operationId: string, context: vscode.Extensi
8384
});
8485
}
8586

86-
activateCopilotInspection(context);
87+
if (isLlmApiReady()) {
88+
activateCopilotInspection(context);
89+
}
8790
}
8891

8992
async function presentFirstView(context: vscode.ExtensionContext) {

0 commit comments

Comments
 (0)