Skip to content

Commit e22a87d

Browse files
add inspectRange/inspectClass based on inspectCode. (#1315)
* add `inspectRange`/`inspectClass` based on `inspectCode`. random selections will be adjusted(expanded) to the union range of container symbols.
1 parent 0aff0b2 commit e22a87d

File tree

3 files changed

+140
-2
lines changed

3 files changed

+140
-2
lines changed

src/copilot/inspect/Inspection.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,14 @@ export interface Inspection {
2828
}
2929
solution: string;
3030
severity: string;
31+
}
32+
33+
export namespace Inspection {
34+
export function fix(inspection: Inspection, source: string) {
35+
//TODO: implement me
36+
}
37+
38+
export function highlight(inspection: Inspection) {
39+
//TODO: implement me
40+
}
3141
}

src/copilot/inspect/InspectionCopilot.ts

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import { instrumentSimpleOperation, sendInfo } from "vscode-extension-telemetry-wrapper";
22
import Copilot from "../Copilot";
3-
import { logger } from "../utils";
3+
import { getClassesContainedInRange, getInnermostClassContainsRange, getIntersectionMethodsOfRange, getUnionRange, logger } from "../utils";
44
import { Inspection } from "./Inspection";
5+
import path from "path";
6+
import { TextDocument, DocumentSymbol, SymbolKind, ProgressLocation, Position, Range, Selection, window } from "vscode";
57

68
export default class InspectionCopilot extends Copilot {
79

@@ -110,6 +112,61 @@ export default class InspectionCopilot extends Copilot {
110112
super(messages);
111113
}
112114

115+
public async inspectDocument(document: TextDocument): Promise<Inspection[]> {
116+
logger.info('inspecting document:', document.fileName);
117+
const range = new Range(document.lineAt(0).range.start, document.lineAt(document.lineCount - 1).range.end);
118+
return this.inspectRange(document, range);
119+
}
120+
121+
public async inspectClass(document: TextDocument, clazz: DocumentSymbol): Promise<Inspection[]> {
122+
logger.info('inspecting class:', clazz.name);
123+
return this.inspectRange(document, clazz.range);
124+
}
125+
126+
public async inspectSymbol(document: TextDocument, symbol: DocumentSymbol): Promise<Inspection[]> {
127+
logger.info(`inspecting symbol ${SymbolKind[symbol.kind]} ${symbol.name}`);
128+
return this.inspectRange(document, symbol.range);
129+
}
130+
131+
public async inspectRange(document: TextDocument, range: Range | Selection): Promise<Inspection[]> {
132+
// ajust the range to the minimal container class or (multiple) method symbols
133+
const methods: DocumentSymbol[] = await getIntersectionMethodsOfRange(range, document);
134+
const classes: DocumentSymbol[] = await getClassesContainedInRange(range, document);
135+
const symbols: DocumentSymbol[] = [...classes, ...methods];
136+
if (symbols.length < 1) {
137+
const containingClass: DocumentSymbol = await getInnermostClassContainsRange(range, document);
138+
symbols.push(containingClass);
139+
}
140+
141+
// get the union range of the container symbols, which will be insepcted by copilot
142+
const expandedRange: Range = getUnionRange(symbols);
143+
144+
// inspect the expanded union range
145+
const symbolName = symbols[0].name;
146+
const symbolKind = SymbolKind[symbols[0].kind].toLowerCase();
147+
const inspections = await window.withProgress({
148+
location: ProgressLocation.Notification,
149+
title: `Inspecting ${symbolKind} ${symbolName}... of \"${path.basename(document.fileName)}\"`,
150+
cancellable: false
151+
}, (_progress) => {
152+
return this.doInspectRange(document, expandedRange);
153+
});
154+
155+
// show message based on the number of inspections
156+
if (inspections.length < 1) {
157+
void window.showInformationMessage(`Inspected ${symbolKind} ${symbolName}... of \"${path.basename(document.fileName)}\" and got 0 suggestions.`);
158+
} else if (inspections.length == 1) {
159+
// apply the only suggestion automatically
160+
void Inspection.fix(inspections[0], 'auto');
161+
} else {
162+
// show message to go to the first suggestion
163+
void window.showInformationMessage(`Inspected ${symbolKind} ${symbolName}... of \"${path.basename(document.fileName)}\" and got ${inspections.length} suggestions.`, "Go to").then(selection => {
164+
selection === "Go to" && void Inspection.highlight(inspections[0]);
165+
});
166+
}
167+
return inspections;
168+
}
169+
113170
/**
114171
* inspect the given code (debouncely if `key` is provided) using copilot and return the inspections
115172
* @param code code to inspect
@@ -139,6 +196,19 @@ export default class InspectionCopilot extends Copilot {
139196
});
140197
}
141198

199+
private async doInspectRange(document: TextDocument, range: Range | Selection): Promise<Inspection[]> {
200+
const adjustedRange = new Range(new Position(range.start.line, 0), new Position(range.end.line, document.lineAt(range.end.line).text.length));
201+
const content: string = document.getText(adjustedRange);
202+
const startLine = range.start.line;
203+
const inspections = await this.inspectCode(content);
204+
inspections.forEach(s => {
205+
s.document = document;
206+
// real line index to the start of the document
207+
s.problem.position.line = s.problem.position.relativeLine + startLine;
208+
});
209+
return inspections;
210+
}
211+
142212
private async doInspectCode(code: string): Promise<Inspection[]> {
143213
const originalLines: string[] = code.split(/\r?\n/);
144214
// code lines without empty lines and comments

src/copilot/utils.ts

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,61 @@
1-
import { LogOutputChannel, window } from "vscode";
1+
import { DocumentSymbol, LogOutputChannel, SymbolKind, TextDocument, commands, window, Range, Selection } from "vscode";
2+
3+
export const CLASS_KINDS: SymbolKind[] = [SymbolKind.Class, SymbolKind.Interface, SymbolKind.Enum];
4+
export const METHOD_KINDS: SymbolKind[] = [SymbolKind.Method, SymbolKind.Constructor];
25

36
export const logger: LogOutputChannel = window.createOutputChannel("Java Rewriting Suggestions", { log: true });
7+
8+
/**
9+
* get all the class symbols contained in the `range` in the `document`
10+
*/
11+
export async function getClassesContainedInRange(range: Range | Selection, document: TextDocument): Promise<DocumentSymbol[]> {
12+
const symbols = await getClassesAndMethodsOfDocument(document);
13+
return symbols.filter(symbol => CLASS_KINDS.includes(symbol.kind))
14+
.filter(clazz => range.contains(clazz.range));
15+
}
16+
17+
/**
18+
* get the innermost class symbol that completely contains the `range` in the `document`
19+
*/
20+
export async function getInnermostClassContainsRange(range: Range | Selection, document: TextDocument): Promise<DocumentSymbol> {
21+
const symbols = await getClassesAndMethodsOfDocument(document);
22+
return symbols.filter(symbol => CLASS_KINDS.includes(symbol.kind))
23+
// reverse the classes to get the innermost class first
24+
.reverse().filter(clazz => clazz.range.contains(range))[0];
25+
}
26+
27+
/**
28+
* get all the method symbols that are completely or partially contained in the `range` in the `document`
29+
*/
30+
export async function getIntersectionMethodsOfRange(range: Range | Selection, document: TextDocument): Promise<DocumentSymbol[]> {
31+
const symbols = await getClassesAndMethodsOfDocument(document);
32+
return symbols.filter(symbol => METHOD_KINDS.includes(symbol.kind))
33+
.filter(method => method.range.intersection(range));
34+
}
35+
36+
export function getUnionRange(symbols: DocumentSymbol[]): Range {
37+
let result: Range = new Range(symbols[0].range.start, symbols[0].range.end);
38+
for (const symbol of symbols) {
39+
result = result.union(symbol.range);
40+
}
41+
return result;
42+
}
43+
44+
/**
45+
* get all classes (classes inside methods are not considered) and methods of a document in a pre-order traversal manner
46+
*/
47+
async function getClassesAndMethodsOfDocument(document: TextDocument): Promise<DocumentSymbol[]> {
48+
const stack = ((await commands.executeCommand<DocumentSymbol[]>('vscode.executeDocumentSymbolProvider', document.uri)) ?? []).reverse();
49+
50+
const result: DocumentSymbol[] = [];
51+
while (stack.length > 0) {
52+
const symbol = stack.pop() as DocumentSymbol;
53+
if (CLASS_KINDS.includes(symbol.kind)) {
54+
result.push(symbol);
55+
stack.push(...symbol.children.reverse());
56+
} else if (METHOD_KINDS.includes(symbol.kind)) {
57+
result.push(symbol);
58+
}
59+
}
60+
return result;
61+
}

0 commit comments

Comments
 (0)