-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
1a9475b
commit 53c23bb
Showing
3 changed files
with
171 additions
and
158 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,171 +1,184 @@ | ||
import { ChatEditCommand } from "tabby-agent"; | ||
import { Config } from "../Config"; | ||
import { CancellationTokenSource, QuickPickItem, ThemeIcon, QuickPickItemKind, window, ProgressLocation, TextEditor, Selection, Position, QuickPick, QuickPickItemButtonEvent } from "vscode"; | ||
import { | ||
CancellationTokenSource, | ||
QuickPickItem, | ||
ThemeIcon, | ||
QuickPickItemKind, | ||
window, | ||
ProgressLocation, | ||
TextEditor, | ||
Selection, | ||
Position, | ||
QuickPick, | ||
QuickPickItemButtonEvent, | ||
} from "vscode"; | ||
import { Client } from "../lsp/Client"; | ||
import { ContextVariables } from "../ContextVariables"; | ||
|
||
export class InlineEditController { | ||
private chatEditCancellationTokenSource: CancellationTokenSource | null = null; | ||
private quickPick: QuickPick<EditCommand>; | ||
|
||
private recentlyCommand: string[] = []; | ||
private suggestedCommand: ChatEditCommand[] = []; | ||
|
||
constructor(private client: Client, private config: Config, | ||
private contextVariables: ContextVariables, | ||
private editor: TextEditor, | ||
private editLocation: EditLocation) { | ||
this.recentlyCommand = this.config.chatEditRecentlyCommand.slice(0, this.config.maxChatEditHistory); | ||
|
||
const fetchingSuggestedCommandCancellationTokenSource = new CancellationTokenSource(); | ||
this.client.chat.provideEditCommands( | ||
{ location: editLocation }, | ||
{ commands: this.suggestedCommand, callback: () => this.updateQuickPickList() }, | ||
fetchingSuggestedCommandCancellationTokenSource.token, | ||
); | ||
|
||
const quickPick = window.createQuickPick<EditCommand>(); | ||
quickPick.placeholder = "Enter the command for editing"; | ||
quickPick.matchOnDescription = true; | ||
quickPick.onDidChangeValue(() => this.updateQuickPickList()); | ||
quickPick.onDidHide(() => { | ||
fetchingSuggestedCommandCancellationTokenSource.cancel(); | ||
}); | ||
quickPick.onDidAccept(this.onDidAccept, this); | ||
quickPick.onDidTriggerItemButton(this.onDidTriggerItemButton, this); | ||
|
||
this.quickPick = quickPick; | ||
quickPick.show(); | ||
} | ||
|
||
private onDidAccept() { | ||
const startPosition = new Position(this.editLocation.range.start.line, this.editLocation.range.start.character); | ||
const quickPick = this.quickPick; | ||
quickPick.hide(); | ||
|
||
|
||
const command = quickPick.selectedItems[0]?.value; | ||
|
||
if (command) { | ||
const updatedRecentlyCommand = [command] | ||
.concat(this.recentlyCommand.filter((item) => item !== command)) | ||
.slice(0, this.config.maxChatEditHistory); | ||
this.config.chatEditRecentlyCommand = updatedRecentlyCommand; | ||
|
||
window.withProgress( | ||
{ | ||
location: ProgressLocation.Notification, | ||
title: "Editing in progress...", | ||
cancellable: true, | ||
}, | ||
async (_, token) => { | ||
this.editor.selection = new Selection(startPosition, startPosition); | ||
this.contextVariables.chatEditInProgress = true; | ||
if (token.isCancellationRequested) { | ||
return; | ||
} | ||
this.chatEditCancellationTokenSource = new CancellationTokenSource(); | ||
token.onCancellationRequested(() => { | ||
this.chatEditCancellationTokenSource?.cancel(); | ||
}); | ||
try { | ||
await this.client.chat.provideEdit( | ||
{ | ||
location: this.editLocation, | ||
command, | ||
format: "previewChanges", | ||
}, | ||
this.chatEditCancellationTokenSource.token, | ||
); | ||
} catch (error) { | ||
if (typeof error === "object" && error && "message" in error && typeof error["message"] === "string") { | ||
window.showErrorMessage(error["message"]); | ||
} | ||
} | ||
this.chatEditCancellationTokenSource.dispose(); | ||
this.chatEditCancellationTokenSource = null; | ||
this.contextVariables.chatEditInProgress = false; | ||
this.editor.selection = new Selection(startPosition, startPosition); | ||
}, | ||
private chatEditCancellationTokenSource: CancellationTokenSource | null = null; | ||
private quickPick: QuickPick<EditCommand>; | ||
|
||
private recentlyCommand: string[] = []; | ||
private suggestedCommand: ChatEditCommand[] = []; | ||
|
||
constructor( | ||
private client: Client, | ||
private config: Config, | ||
private contextVariables: ContextVariables, | ||
private editor: TextEditor, | ||
private editLocation: EditLocation, | ||
) { | ||
this.recentlyCommand = this.config.chatEditRecentlyCommand.slice(0, this.config.maxChatEditHistory); | ||
|
||
const fetchingSuggestedCommandCancellationTokenSource = new CancellationTokenSource(); | ||
this.client.chat.provideEditCommands( | ||
{ location: editLocation }, | ||
{ commands: this.suggestedCommand, callback: () => this.updateQuickPickList() }, | ||
fetchingSuggestedCommandCancellationTokenSource.token, | ||
); | ||
|
||
const quickPick = window.createQuickPick<EditCommand>(); | ||
quickPick.placeholder = "Enter the command for editing"; | ||
quickPick.matchOnDescription = true; | ||
quickPick.onDidChangeValue(() => this.updateQuickPickList()); | ||
quickPick.onDidHide(() => { | ||
fetchingSuggestedCommandCancellationTokenSource.cancel(); | ||
}); | ||
quickPick.onDidAccept(this.onDidAccept, this); | ||
quickPick.onDidTriggerItemButton(this.onDidTriggerItemButton, this); | ||
|
||
this.quickPick = quickPick; | ||
quickPick.show(); | ||
} | ||
|
||
private onDidAccept() { | ||
const startPosition = new Position(this.editLocation.range.start.line, this.editLocation.range.start.character); | ||
const quickPick = this.quickPick; | ||
quickPick.hide(); | ||
|
||
const command = quickPick.selectedItems[0]?.value; | ||
|
||
if (command) { | ||
const updatedRecentlyCommand = [command] | ||
.concat(this.recentlyCommand.filter((item) => item !== command)) | ||
.slice(0, this.config.maxChatEditHistory); | ||
this.config.chatEditRecentlyCommand = updatedRecentlyCommand; | ||
|
||
window.withProgress( | ||
{ | ||
location: ProgressLocation.Notification, | ||
title: "Editing in progress...", | ||
cancellable: true, | ||
}, | ||
async (_, token) => { | ||
this.editor.selection = new Selection(startPosition, startPosition); | ||
this.contextVariables.chatEditInProgress = true; | ||
if (token.isCancellationRequested) { | ||
return; | ||
} | ||
this.chatEditCancellationTokenSource = new CancellationTokenSource(); | ||
token.onCancellationRequested(() => { | ||
this.chatEditCancellationTokenSource?.cancel(); | ||
}); | ||
try { | ||
await this.client.chat.provideEdit( | ||
{ | ||
location: this.editLocation, | ||
command, | ||
format: "previewChanges", | ||
}, | ||
this.chatEditCancellationTokenSource.token, | ||
); | ||
} | ||
|
||
} | ||
|
||
private onDidTriggerItemButton(event: QuickPickItemButtonEvent<EditCommand>) { | ||
const item = event.item; | ||
const button = event.button; | ||
if (button.iconPath instanceof ThemeIcon && button.iconPath.id === "settings-remove") { | ||
const index = this.recentlyCommand.indexOf(item.value); | ||
if (index !== -1) { | ||
this.recentlyCommand.splice(index, 1); | ||
this.config.chatEditRecentlyCommand = this.recentlyCommand; | ||
this.updateQuickPickList(); | ||
} catch (error) { | ||
if (typeof error === "object" && error && "message" in error && typeof error["message"] === "string") { | ||
window.showErrorMessage(error["message"]); | ||
} | ||
} | ||
|
||
if (button.iconPath instanceof ThemeIcon && button.iconPath.id === "edit") { | ||
this.quickPick.value = item.value; | ||
} | ||
} | ||
this.chatEditCancellationTokenSource.dispose(); | ||
this.chatEditCancellationTokenSource = null; | ||
this.contextVariables.chatEditInProgress = false; | ||
this.editor.selection = new Selection(startPosition, startPosition); | ||
}, | ||
); | ||
} | ||
} | ||
|
||
private onDidTriggerItemButton(event: QuickPickItemButtonEvent<EditCommand>) { | ||
const item = event.item; | ||
const button = event.button; | ||
if (button.iconPath instanceof ThemeIcon && button.iconPath.id === "settings-remove") { | ||
const index = this.recentlyCommand.indexOf(item.value); | ||
if (index !== -1) { | ||
this.recentlyCommand.splice(index, 1); | ||
this.config.chatEditRecentlyCommand = this.recentlyCommand; | ||
this.updateQuickPickList(); | ||
} | ||
} | ||
|
||
private updateQuickPickList() { | ||
const input = this.quickPick.value; | ||
const list: (QuickPickItem & { value: string })[] = []; | ||
list.push( | ||
...this.suggestedCommand.map((item) => ({ | ||
label: item.label, | ||
value: item.command, | ||
iconPath: item.source === "preset" ? new ThemeIcon("run") : new ThemeIcon("spark"), | ||
description: item.source === "preset" ? item.command : "Suggested", | ||
})), | ||
); | ||
if (list.length > 0) { | ||
list.push({ | ||
label: "", | ||
value: "", | ||
kind: QuickPickItemKind.Separator, | ||
alwaysShow: true, | ||
}); | ||
} | ||
const recentlyCommandToAdd = this.recentlyCommand.filter((item) => !list.find((i) => i.value === item)); | ||
list.push( | ||
...recentlyCommandToAdd.map((item) => ({ | ||
label: item, | ||
value: item, | ||
iconPath: new ThemeIcon("history"), | ||
description: "History", | ||
buttons: [ | ||
{ | ||
iconPath: new ThemeIcon("edit"), | ||
}, | ||
{ | ||
iconPath: new ThemeIcon("settings-remove"), | ||
}, | ||
], | ||
})), | ||
); | ||
if (input.length > 0 && !list.find((i) => i.value === input)) { | ||
list.unshift({ | ||
label: input, | ||
value: input, | ||
iconPath: new ThemeIcon("run"), | ||
description: "", | ||
alwaysShow: true, | ||
}); | ||
} | ||
this.quickPick.items = list; | ||
}; | ||
if (button.iconPath instanceof ThemeIcon && button.iconPath.id === "edit") { | ||
this.quickPick.value = item.value; | ||
} | ||
} | ||
|
||
private updateQuickPickList() { | ||
const input = this.quickPick.value; | ||
const list: (QuickPickItem & { value: string })[] = []; | ||
list.push( | ||
...this.suggestedCommand.map((item) => ({ | ||
label: item.label, | ||
value: item.command, | ||
iconPath: item.source === "preset" ? new ThemeIcon("run") : new ThemeIcon("spark"), | ||
description: item.source === "preset" ? item.command : "Suggested", | ||
})), | ||
); | ||
if (list.length > 0) { | ||
list.push({ | ||
label: "", | ||
value: "", | ||
kind: QuickPickItemKind.Separator, | ||
alwaysShow: true, | ||
}); | ||
} | ||
const recentlyCommandToAdd = this.recentlyCommand.filter((item) => !list.find((i) => i.value === item)); | ||
list.push( | ||
...recentlyCommandToAdd.map((item) => ({ | ||
label: item, | ||
value: item, | ||
iconPath: new ThemeIcon("history"), | ||
description: "History", | ||
buttons: [ | ||
{ | ||
iconPath: new ThemeIcon("edit"), | ||
}, | ||
{ | ||
iconPath: new ThemeIcon("settings-remove"), | ||
}, | ||
], | ||
})), | ||
); | ||
if (input.length > 0 && !list.find((i) => i.value === input)) { | ||
list.unshift({ | ||
label: input, | ||
value: input, | ||
iconPath: new ThemeIcon("run"), | ||
description: "", | ||
alwaysShow: true, | ||
}); | ||
} | ||
this.quickPick.items = list; | ||
} | ||
} | ||
|
||
interface EditCommand extends QuickPickItem { | ||
value: string | ||
value: string; | ||
} | ||
|
||
interface EditLocation { | ||
uri: string; | ||
range: { | ||
start: { line: number; character: number }; | ||
end: { line: number; character: number }; | ||
}; | ||
} | ||
uri: string; | ||
range: { | ||
start: { line: number; character: number }; | ||
end: { line: number; character: number }; | ||
}; | ||
} |