mirror of
https://github.com/microsoft/vscode.git
synced 2026-04-27 12:04:04 +01:00
Pretty good ranges based on RefactorInfo
Results are not pretty good yet. They are still below average.
This commit is contained in:
@@ -346,6 +346,7 @@ class InlinedCodeAction extends vscode.CodeAction {
|
||||
public readonly action: Proto.RefactorActionInfo,
|
||||
public readonly range: vscode.Range,
|
||||
public readonly bonus?: vscode.Command,
|
||||
public readonly airename?: (x: Proto.RefactorEditInfo) => vscode.Command,
|
||||
) {
|
||||
super(action.description, InlinedCodeAction.getKind(action));
|
||||
|
||||
@@ -391,9 +392,10 @@ class InlinedCodeAction extends vscode.CodeAction {
|
||||
command: CompositeCommand.ID,
|
||||
title: '',
|
||||
arguments: coalesce([
|
||||
// TODO: this.bonus should really get renameLocation as well (at least most of the time)
|
||||
this.bonus, // TODO: This should actually go second. Maybe?
|
||||
this.command,
|
||||
{
|
||||
this.airename ? this.airename(response.body) : {
|
||||
command: 'editor.action.rename',
|
||||
arguments: [[
|
||||
this.document.uri,
|
||||
@@ -459,7 +461,7 @@ class InferTypesAction extends vscode.CodeAction {
|
||||
this.command = {
|
||||
title,
|
||||
command: EditorChatReplacementCommand2.ID,
|
||||
arguments: [<EditorChatReplacementCommand2.Args>{ message: 'Add types to this code. Add separate interfaces when possible. Do not change the code except for adding types.', document, rangeOrSelection }]
|
||||
arguments: [<EditorChatReplacementCommand2.Args>{ message: 'Add types to this code. Add separate interfaces when possible. Do not change the code except for adding types.', document, range: rangeOrSelection }]
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -595,19 +597,26 @@ class TypeScriptRefactorProvider implements vscode.CodeActionProvider<TsCodeActi
|
||||
codeAction = new MoveToFileCodeAction(document, action, rangeOrSelection);
|
||||
} else {
|
||||
let bonus: vscode.Command | undefined
|
||||
let airename: ((rename: Proto.RefactorEditInfo) => vscode.Command) | undefined
|
||||
if (vscode.workspace.getConfiguration('typescript', null).get('experimental.aiQuickFix')) {
|
||||
if (Extract_Constant.matches(action)
|
||||
|| Extract_Function.matches(action)
|
||||
|| Extract_Type.matches(action)
|
||||
|| Extract_Interface.matches(action)
|
||||
|| action.name.startsWith('Infer function return')) { // TODO: There's no CodeActionKind for infer function return; maybe that's why it doesn't work
|
||||
const kind = Extract_Constant.matches(action) ? 'variable'
|
||||
: Extract_Function.matches(action) ? 'function'
|
||||
: Extract_Type.matches(action) ? 'type'
|
||||
: Extract_Interface.matches(action) ? 'type'
|
||||
: action.name.startsWith('Infer function return') ? 'return type'
|
||||
// const keyword = Extract_Constant.matches(action) ? 'const'
|
||||
// : Extract_Function.matches(action) ? 'function'
|
||||
// : Extract_Type.matches(action) ? 'type'
|
||||
// : Extract_Interface.matches(action) ? 'interface'
|
||||
// : action.name.startsWith('Infer function return') ? 'return'
|
||||
// : '';
|
||||
const newName = Extract_Constant.matches(action) ? 'newLocal'
|
||||
: Extract_Function.matches(action) ? 'newFunction'
|
||||
: Extract_Type.matches(action) ? 'NewType'
|
||||
: Extract_Interface.matches(action) ? 'NewInterface'
|
||||
: action.name.startsWith('Infer function return') ? 'newReturnType'
|
||||
: '';
|
||||
bonus = {
|
||||
bonus = undefined /*{
|
||||
command: ChatPanelFollowup.ID,
|
||||
arguments: [<ChatPanelFollowup.Args>{
|
||||
prompt: `Suggest 5 ${kind} names for the code below:
|
||||
@@ -618,11 +627,25 @@ ${document.getText(rangeOrSelection)}.
|
||||
expand: 'navtree-function',
|
||||
document }],
|
||||
title: ''
|
||||
}
|
||||
if (action.name.startsWith('Infer function return')) console.log(JSON.stringify(bonus))
|
||||
}*/
|
||||
airename = refactorInfo => ({
|
||||
title: '',
|
||||
command: EditorChatReplacementCommand2.ID,
|
||||
arguments: [<EditorChatReplacementCommand2.Args>{
|
||||
expand: Extract_Constant.matches(action) ? 'navtree-function' : 'refactor-info',
|
||||
refactor: refactorInfo,
|
||||
message: `Rename ${newName} to a better name based on usage.`,
|
||||
document,
|
||||
// TODO: only start is used for everything but 'default'; refactor-info ignores it entirely
|
||||
range: new vscode.Range(
|
||||
typeConverters.Position.fromLocation(refactorInfo.renameLocation!),
|
||||
typeConverters.Position.fromLocation({ ...refactorInfo.renameLocation!, offset: refactorInfo.renameLocation!.offset + 12 }))
|
||||
}]
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
codeAction = new InlinedCodeAction(this.client, document, refactor, action, rangeOrSelection, bonus);
|
||||
codeAction = new InlinedCodeAction(this.client, document, refactor, action, rangeOrSelection, bonus, airename);
|
||||
}
|
||||
|
||||
codeAction.isPreferred = TypeScriptRefactorProvider.isPreferred(action, allActions);
|
||||
|
||||
@@ -17,7 +17,7 @@ export namespace EditorChatReplacementCommand1 {
|
||||
};
|
||||
}
|
||||
export class EditorChatReplacementCommand1 implements Command {
|
||||
public static readonly ID = '_typescript.quickFix.editorChatReplacement';
|
||||
public static readonly ID = '_typescript.quickFix.editorChatReplacement1';
|
||||
public readonly id = EditorChatReplacementCommand1.ID;
|
||||
|
||||
constructor( private readonly client: ITypeScriptServiceClient, private readonly diagnosticManager: DiagnosticsManager) {
|
||||
@@ -30,19 +30,27 @@ export class EditorChatReplacementCommand1 implements Command {
|
||||
}
|
||||
}
|
||||
export class EditorChatReplacementCommand2 implements Command {
|
||||
public static readonly ID = "_typescript.quickFix.editorChatReplacement";
|
||||
public static readonly ID = '_typescript.quickFix.editorChatReplacement2';
|
||||
public readonly id = EditorChatReplacementCommand2.ID;
|
||||
constructor(
|
||||
private readonly client: ITypeScriptServiceClient,
|
||||
) {
|
||||
}
|
||||
async execute({ message, document, rangeOrSelection }: EditorChatReplacementCommand2.Args) {
|
||||
async execute({ message, document, range: range, expand, marker, refactor }: EditorChatReplacementCommand2.Args) {
|
||||
// TODO: "this code" is not specific; might get better results with a more specific referent
|
||||
// TODO: Doesn't work in JS files? Is this the span-finder's fault? Try falling back to startLine plus something.
|
||||
// TODO: Need to emit jsdoc in JS files once it's working at all
|
||||
// TODO: When there are "enough" types around, leave off the "Add separate interfaces when possible" because it's not helpful.
|
||||
// (brainstorming: enough non-primitives, or evidence of type aliases in the same file, or imported)
|
||||
const initialRange = await findScopeEndLineFromNavTree(this.client, document, rangeOrSelection.start.line)
|
||||
const initialRange = expand === 'navtree-function' ? await findScopeEndLineFromNavTree(this.client, document, range.start.line)
|
||||
: expand === 'identifier' ? findScopeEndMarker(document, range.start, marker!)
|
||||
: expand === 'refactor-info' ? findRefactorScope(document, refactor!)
|
||||
: range;
|
||||
if (initialRange) {
|
||||
console.log("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" + message
|
||||
+ `\nWith context(${expand}): ` + document.getText().slice(document.offsetAt(initialRange.start), document.offsetAt(initialRange.end))
|
||||
+ "\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~")
|
||||
}
|
||||
await vscode.commands.executeCommand('vscode.editorChat.start', { initialRange, message, autoSend: true });
|
||||
}
|
||||
}
|
||||
@@ -50,11 +58,13 @@ export namespace EditorChatReplacementCommand2 {
|
||||
export interface Args {
|
||||
readonly message: string;
|
||||
readonly document: vscode.TextDocument;
|
||||
readonly rangeOrSelection: vscode.Range | vscode.Selection;
|
||||
readonly range: vscode.Range;
|
||||
readonly expand: Expand;
|
||||
readonly marker?: string;
|
||||
readonly refactor?: Proto.RefactorEditInfo;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class EditorChatFollowUp implements Command {
|
||||
|
||||
id: string = '_typescript.quickFix.editorChatFollowUp';
|
||||
@@ -68,39 +78,16 @@ export class EditorChatFollowUp implements Command {
|
||||
}
|
||||
}
|
||||
|
||||
export function findScopeEndLineFromNavTreeWorker(startLine: number, navigationTree: Proto.NavigationTree[]): vscode.Range | undefined {
|
||||
for (const node of navigationTree) {
|
||||
const range = typeConverters.Range.fromTextSpan(node.spans[0]);
|
||||
if (startLine === range.start.line) {
|
||||
return range;
|
||||
} else if (startLine > range.start.line && startLine <= range.end.line && node.childItems) {
|
||||
return findScopeEndLineFromNavTreeWorker(startLine, node.childItems);
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export async function findScopeEndLineFromNavTree(client: ITypeScriptServiceClient, document: vscode.TextDocument, startLine: number) {
|
||||
const filepath = client.toOpenTsFilePath(document);
|
||||
if (!filepath) {
|
||||
return;
|
||||
}
|
||||
const response = await client.execute('navtree', { file: filepath }, nulToken);
|
||||
if (response.type !== 'response' || !response.body?.childItems) {
|
||||
return;
|
||||
}
|
||||
return findScopeEndLineFromNavTreeWorker(startLine, response.body.childItems);
|
||||
}
|
||||
|
||||
export namespace ChatPanelFollowup {
|
||||
export interface Args {
|
||||
readonly prompt: string;
|
||||
readonly document: vscode.TextDocument;
|
||||
readonly range: vscode.Range;
|
||||
readonly expand: Expand;
|
||||
readonly marker?: string;
|
||||
readonly refactor?: Proto.RefactorEditInfo;
|
||||
}
|
||||
// assuming there is an ast to walk, I'm convinced I can do a more consistent job than the navtree code.
|
||||
export type Expand = 'none' | 'navtree-function' | 'statement' | 'ast-statement'
|
||||
}
|
||||
export class ChatPanelFollowup implements Command {
|
||||
public readonly id = ChatPanelFollowup.ID;
|
||||
@@ -109,9 +96,11 @@ export class ChatPanelFollowup implements Command {
|
||||
constructor(private readonly client: ITypeScriptServiceClient) {
|
||||
}
|
||||
|
||||
async execute({ prompt, document, range, expand }: ChatPanelFollowup.Args) {
|
||||
async execute({ prompt, document, range, expand, marker }: ChatPanelFollowup.Args) {
|
||||
console.log("-------------------------------" + prompt + "------------------------------")
|
||||
const enclosingRange = expand === 'navtree-function' && findScopeEndLineFromNavTree(this.client, document, range.start.line) || range;
|
||||
const enclosingRange = expand === 'navtree-function' ? await findScopeEndLineFromNavTree(this.client, document, range.start.line)
|
||||
: expand === 'identifier' ? findScopeEndMarker(document, range.start, marker!)
|
||||
: range;
|
||||
vscode.interactive.sendInteractiveRequestToProvider('copilot', { message: prompt, autoSend: true, initialRange: enclosingRange } as any)
|
||||
}
|
||||
}
|
||||
@@ -127,3 +116,62 @@ export class CompositeCommand implements Command {
|
||||
}
|
||||
}
|
||||
|
||||
export type Expand = 'none' | 'navtree-function' | 'identifier' | 'refactor-info' | 'statement' | 'ast-statement'
|
||||
|
||||
function findScopeEndLineFromNavTreeWorker(startLine: number, navigationTree: Proto.NavigationTree[]): vscode.Range | undefined {
|
||||
for (const node of navigationTree) {
|
||||
const range = typeConverters.Range.fromTextSpan(node.spans[0]);
|
||||
if (startLine === range.start.line) {
|
||||
return range;
|
||||
} else if (startLine > range.start.line && startLine <= range.end.line && node.childItems) {
|
||||
return findScopeEndLineFromNavTreeWorker(startLine, node.childItems);
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
async function findScopeEndLineFromNavTree(client: ITypeScriptServiceClient, document: vscode.TextDocument, startLine: number) {
|
||||
const filepath = client.toOpenTsFilePath(document);
|
||||
if (!filepath) {
|
||||
return;
|
||||
}
|
||||
const response = await client.execute('navtree', { file: filepath }, nulToken);
|
||||
if (response.type !== 'response' || !response.body?.childItems) {
|
||||
return;
|
||||
}
|
||||
return findScopeEndLineFromNavTreeWorker(startLine, response.body.childItems);
|
||||
}
|
||||
|
||||
function findScopeEndMarker(document: vscode.TextDocument, start: vscode.Position, marker: string): vscode.Range {
|
||||
const text = document.getText();
|
||||
const offset = text.indexOf(marker, text.indexOf(marker)) + marker.length
|
||||
// TODO: Expand to the end of whatever marker is. (OR, chain to findScopeEndLineFromnavTree)
|
||||
return new vscode.Range(start, document.positionAt(offset))
|
||||
}
|
||||
|
||||
function findRefactorScope(document: vscode.TextDocument, refactor: Proto.RefactorEditInfo): vscode.Range {
|
||||
let first = typeConverters.Position.fromLocation(refactor.edits[0].textChanges[0].start)
|
||||
let firstChange = refactor.edits[0].textChanges[0]
|
||||
let lastChange = refactor.edits[0].textChanges[0]
|
||||
let last = typeConverters.Position.fromLocation(refactor.edits[0].textChanges[0].start)
|
||||
for (const edit of refactor.edits) {
|
||||
for (const change of edit.textChanges) {
|
||||
const start = typeConverters.Position.fromLocation(change.start)
|
||||
const end = typeConverters.Position.fromLocation(change.end)
|
||||
if (start.compareTo(first) < 0) {
|
||||
first = start
|
||||
firstChange = change
|
||||
}
|
||||
if (end.compareTo(last) > 0) {
|
||||
last = end
|
||||
lastChange = change
|
||||
}
|
||||
}
|
||||
}
|
||||
const text = document.getText()
|
||||
let startIndex = text.indexOf(firstChange.newText)
|
||||
let start = startIndex > -1 ? document.positionAt(startIndex) : first
|
||||
let endIndex = text.lastIndexOf(lastChange.newText)
|
||||
let end = endIndex > -1 ? document.positionAt(endIndex + lastChange.newText.length) : last
|
||||
return new vscode.Range(start, end)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user