mirror of
https://github.com/Microsoft/vscode
synced 2024-08-27 13:00:58 +00:00
feat: enable attaching symbols to chat via @
(#213347)
* feat: enable attaching symbols to chat via `@` * Oop
This commit is contained in:
parent
c1ebab91fb
commit
7bb6755241
|
@ -12,7 +12,7 @@ import { IRange } from 'vs/editor/common/core/range';
|
|||
import { IDiffEditor, IEditor, ScrollType } from 'vs/editor/common/editorCommon';
|
||||
import { IModelDeltaDecoration, ITextModel, OverviewRulerLane } from 'vs/editor/common/model';
|
||||
import { overviewRulerRangeHighlight } from 'vs/editor/common/core/editorColorRegistry';
|
||||
import { IQuickAccessProvider } from 'vs/platform/quickinput/common/quickAccess';
|
||||
import { IQuickAccessProvider, IQuickAccessProviderRunOptions } from 'vs/platform/quickinput/common/quickAccess';
|
||||
import { IKeyMods, IQuickPick, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput';
|
||||
import { themeColorFromId } from 'vs/platform/theme/common/themeService';
|
||||
import { status } from 'vs/base/browser/ui/aria/aria';
|
||||
|
@ -52,7 +52,7 @@ export abstract class AbstractEditorNavigationQuickAccessProvider implements IQu
|
|||
|
||||
//#region Provider methods
|
||||
|
||||
provide(picker: IQuickPick<IQuickPickItem>, token: CancellationToken): IDisposable {
|
||||
provide(picker: IQuickPick<IQuickPickItem>, token: CancellationToken, runOptions?: IQuickAccessProviderRunOptions): IDisposable {
|
||||
const disposables = new DisposableStore();
|
||||
|
||||
// Apply options if any
|
||||
|
@ -63,7 +63,7 @@ export abstract class AbstractEditorNavigationQuickAccessProvider implements IQu
|
|||
|
||||
// Provide based on current active editor
|
||||
const pickerDisposable = disposables.add(new MutableDisposable());
|
||||
pickerDisposable.value = this.doProvide(picker, token);
|
||||
pickerDisposable.value = this.doProvide(picker, token, runOptions);
|
||||
|
||||
// Re-create whenever the active editor changes
|
||||
disposables.add(this.onDidActiveTextEditorControlChange(() => {
|
||||
|
@ -78,7 +78,7 @@ export abstract class AbstractEditorNavigationQuickAccessProvider implements IQu
|
|||
return disposables;
|
||||
}
|
||||
|
||||
private doProvide(picker: IQuickPick<IQuickPickItem>, token: CancellationToken): IDisposable {
|
||||
private doProvide(picker: IQuickPick<IQuickPickItem>, token: CancellationToken, runOptions?: IQuickAccessProviderRunOptions): IDisposable {
|
||||
const disposables = new DisposableStore();
|
||||
|
||||
// With text control
|
||||
|
@ -113,7 +113,7 @@ export abstract class AbstractEditorNavigationQuickAccessProvider implements IQu
|
|||
disposables.add(toDisposable(() => this.clearDecorations(editor)));
|
||||
|
||||
// Ask subclass for entries
|
||||
disposables.add(this.provideWithTextEditor(context, picker, token));
|
||||
disposables.add(this.provideWithTextEditor(context, picker, token, runOptions));
|
||||
}
|
||||
|
||||
// Without text control
|
||||
|
@ -134,7 +134,7 @@ export abstract class AbstractEditorNavigationQuickAccessProvider implements IQu
|
|||
/**
|
||||
* Subclasses to implement to provide picks for the picker when an editor is active.
|
||||
*/
|
||||
protected abstract provideWithTextEditor(context: IQuickAccessTextEditorContext, picker: IQuickPick<IQuickPickItem>, token: CancellationToken): IDisposable;
|
||||
protected abstract provideWithTextEditor(context: IQuickAccessTextEditorContext, picker: IQuickPick<IQuickPickItem>, token: CancellationToken, runOptions?: IQuickAccessProviderRunOptions): IDisposable;
|
||||
|
||||
/**
|
||||
* Subclasses to implement to provide picks for the picker when no editor is active.
|
||||
|
|
|
@ -22,16 +22,26 @@ import { IQuickInputButton, IQuickPick, IQuickPickItem, IQuickPickSeparator } fr
|
|||
import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
import { findLast } from 'vs/base/common/arraysFind';
|
||||
import { IQuickAccessProviderRunOptions } from 'vs/platform/quickinput/common/quickAccess';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
|
||||
export interface IGotoSymbolQuickPickItem extends IQuickPickItem {
|
||||
kind: SymbolKind;
|
||||
index: number;
|
||||
score?: number;
|
||||
uri?: URI;
|
||||
symbolName?: string;
|
||||
range?: { decoration: IRange; selection: IRange };
|
||||
}
|
||||
|
||||
export interface IGotoSymbolQuickAccessProviderOptions extends IEditorNavigationQuickAccessOptions {
|
||||
openSideBySideDirection?: () => undefined | 'right' | 'down';
|
||||
/**
|
||||
* A handler to invoke when an item is accepted for
|
||||
* this particular showing of the quick access.
|
||||
* @param item The item that was accepted.
|
||||
*/
|
||||
readonly handleAccept?: (item: IQuickPickItem) => void;
|
||||
}
|
||||
|
||||
export abstract class AbstractGotoSymbolQuickAccessProvider extends AbstractEditorNavigationQuickAccessProvider {
|
||||
|
@ -59,7 +69,7 @@ export abstract class AbstractGotoSymbolQuickAccessProvider extends AbstractEdit
|
|||
return Disposable.None;
|
||||
}
|
||||
|
||||
protected provideWithTextEditor(context: IQuickAccessTextEditorContext, picker: IQuickPick<IGotoSymbolQuickPickItem>, token: CancellationToken): IDisposable {
|
||||
protected provideWithTextEditor(context: IQuickAccessTextEditorContext, picker: IQuickPick<IGotoSymbolQuickPickItem>, token: CancellationToken, runOptions?: IQuickAccessProviderRunOptions): IDisposable {
|
||||
const editor = context.editor;
|
||||
const model = this.getModel(editor);
|
||||
if (!model) {
|
||||
|
@ -68,7 +78,7 @@ export abstract class AbstractGotoSymbolQuickAccessProvider extends AbstractEdit
|
|||
|
||||
// Provide symbols from model if available in registry
|
||||
if (this._languageFeaturesService.documentSymbolProvider.has(model)) {
|
||||
return this.doProvideWithEditorSymbols(context, model, picker, token);
|
||||
return this.doProvideWithEditorSymbols(context, model, picker, token, runOptions);
|
||||
}
|
||||
|
||||
// Otherwise show an entry for a model without registry
|
||||
|
@ -127,7 +137,7 @@ export abstract class AbstractGotoSymbolQuickAccessProvider extends AbstractEdit
|
|||
return symbolProviderRegistryPromise.p;
|
||||
}
|
||||
|
||||
private doProvideWithEditorSymbols(context: IQuickAccessTextEditorContext, model: ITextModel, picker: IQuickPick<IGotoSymbolQuickPickItem>, token: CancellationToken): IDisposable {
|
||||
private doProvideWithEditorSymbols(context: IQuickAccessTextEditorContext, model: ITextModel, picker: IQuickPick<IGotoSymbolQuickPickItem>, token: CancellationToken, runOptions?: IQuickAccessProviderRunOptions): IDisposable {
|
||||
const editor = context.editor;
|
||||
const disposables = new DisposableStore();
|
||||
|
||||
|
@ -137,6 +147,8 @@ export abstract class AbstractGotoSymbolQuickAccessProvider extends AbstractEdit
|
|||
if (item && item.range) {
|
||||
this.gotoLocation(context, { range: item.range.selection, keyMods: picker.keyMods, preserveFocus: event.inBackground });
|
||||
|
||||
runOptions?.handleAccept?.(item);
|
||||
|
||||
if (!event.inBackground) {
|
||||
picker.hide();
|
||||
}
|
||||
|
@ -171,7 +183,7 @@ export abstract class AbstractGotoSymbolQuickAccessProvider extends AbstractEdit
|
|||
picker.busy = true;
|
||||
try {
|
||||
const query = prepareQuery(picker.value.substr(AbstractGotoSymbolQuickAccessProvider.PREFIX.length).trim());
|
||||
const items = await this.doGetSymbolPicks(symbolsPromise, query, undefined, picksCts.token);
|
||||
const items = await this.doGetSymbolPicks(symbolsPromise, query, undefined, picksCts.token, model);
|
||||
if (token.isCancellationRequested) {
|
||||
return;
|
||||
}
|
||||
|
@ -218,7 +230,7 @@ export abstract class AbstractGotoSymbolQuickAccessProvider extends AbstractEdit
|
|||
return disposables;
|
||||
}
|
||||
|
||||
protected async doGetSymbolPicks(symbolsPromise: Promise<DocumentSymbol[]>, query: IPreparedQuery, options: { extraContainerLabel?: string } | undefined, token: CancellationToken): Promise<Array<IGotoSymbolQuickPickItem | IQuickPickSeparator>> {
|
||||
protected async doGetSymbolPicks(symbolsPromise: Promise<DocumentSymbol[]>, query: IPreparedQuery, options: { extraContainerLabel?: string } | undefined, token: CancellationToken, model: ITextModel): Promise<Array<IGotoSymbolQuickPickItem | IQuickPickSeparator>> {
|
||||
const symbols = await symbolsPromise;
|
||||
if (token.isCancellationRequested) {
|
||||
return [];
|
||||
|
@ -326,6 +338,8 @@ export abstract class AbstractGotoSymbolQuickAccessProvider extends AbstractEdit
|
|||
selection: Range.collapseToStart(symbol.selectionRange),
|
||||
decoration: symbol.range
|
||||
},
|
||||
uri: model.uri,
|
||||
symbolName: symbolLabel,
|
||||
strikethrough: deprecated,
|
||||
buttons
|
||||
});
|
||||
|
|
|
@ -7,10 +7,12 @@ import { CancellationToken } from 'vs/base/common/cancellation';
|
|||
import { Codicon } from 'vs/base/common/codicons';
|
||||
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { IRange } from 'vs/editor/common/core/range';
|
||||
import { ThemeIcon } from 'vs/base/common/themables';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { ServicesAccessor } from 'vs/editor/browser/editorExtensions';
|
||||
import { Command } from 'vs/editor/common/languages';
|
||||
import { AbstractGotoSymbolQuickAccessProvider, IGotoSymbolQuickPickItem } from 'vs/editor/contrib/quickAccess/browser/gotoSymbolQuickAccess';
|
||||
import { localize, localize2 } from 'vs/nls';
|
||||
import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
|
@ -20,7 +22,6 @@ import { IQuickInputService, IQuickPickItem, QuickPickItem } from 'vs/platform/q
|
|||
import { CHAT_CATEGORY } from 'vs/workbench/contrib/chat/browser/actions/chatActions';
|
||||
import { IChatWidget, IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat';
|
||||
import { ChatContextAttachments } from 'vs/workbench/contrib/chat/browser/contrib/chatContextAttachments';
|
||||
import { SelectAndInsertFileAction } from 'vs/workbench/contrib/chat/browser/contrib/chatDynamicVariables';
|
||||
import { ChatAgentLocation, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents';
|
||||
import { CONTEXT_CHAT_LOCATION, CONTEXT_IN_CHAT_INPUT } from 'vs/workbench/contrib/chat/common/chatContextKeys';
|
||||
import { IChatRequestVariableEntry } from 'vs/workbench/contrib/chat/common/chatModel';
|
||||
|
@ -32,9 +33,10 @@ export function registerChatContextActions() {
|
|||
registerAction2(AttachContextAction);
|
||||
}
|
||||
|
||||
export type IChatContextQuickPickItem = IFileQuickPickItem | IDynamicVariableQuickPickItem | IStaticVariableQuickPickItem;
|
||||
export type IChatContextQuickPickItem = IFileQuickPickItem | IDynamicVariableQuickPickItem | IStaticVariableQuickPickItem | IGotoSymbolQuickPickItem;
|
||||
|
||||
export interface IFileQuickPickItem extends IQuickPickItem {
|
||||
kind: 'file';
|
||||
id: string;
|
||||
name: string;
|
||||
value: URI;
|
||||
|
@ -44,6 +46,7 @@ export interface IFileQuickPickItem extends IQuickPickItem {
|
|||
}
|
||||
|
||||
export interface IDynamicVariableQuickPickItem extends IQuickPickItem {
|
||||
kind: 'dynamic';
|
||||
id: string;
|
||||
name?: string;
|
||||
value: unknown;
|
||||
|
@ -54,6 +57,7 @@ export interface IDynamicVariableQuickPickItem extends IQuickPickItem {
|
|||
}
|
||||
|
||||
export interface IStaticVariableQuickPickItem extends IQuickPickItem {
|
||||
kind: 'static';
|
||||
id: string;
|
||||
name: string;
|
||||
value: unknown;
|
||||
|
@ -92,8 +96,14 @@ class AttachContextAction extends Action2 {
|
|||
});
|
||||
}
|
||||
|
||||
private _getFileContextId(item: { resource: URI }) {
|
||||
return item.resource.toString();
|
||||
private _getFileContextId(item: { resource: URI } | { uri: URI; range: IRange }) {
|
||||
if ('resource' in item) {
|
||||
return item.resource.toString();
|
||||
}
|
||||
|
||||
return item.uri.toString() + (item.range.startLineNumber !== item.range.endLineNumber ?
|
||||
`:${item.range.startLineNumber}-${item.range.endLineNumber}` :
|
||||
`:${item.range.startLineNumber}`);
|
||||
}
|
||||
|
||||
private async _attachContext(widget: IChatWidget, commandService: ICommandService, ...picks: IChatContextQuickPickItem[]) {
|
||||
|
@ -121,14 +131,27 @@ class AttachContextAction extends Action2 {
|
|||
id: this._getFileContextId(pick),
|
||||
value: pick.resource,
|
||||
name: pick.label,
|
||||
isFile: true,
|
||||
isDynamic: true
|
||||
});
|
||||
} else if ('symbolName' in pick && pick.uri && pick.range) {
|
||||
// Symbol
|
||||
toAttach.push({
|
||||
...pick,
|
||||
range: undefined,
|
||||
id: this._getFileContextId({ uri: pick.uri, range: pick.range.decoration }),
|
||||
value: { uri: pick.uri, range: pick.range.decoration },
|
||||
fullName: pick.label,
|
||||
name: pick.symbolName!,
|
||||
isDynamic: true
|
||||
});
|
||||
} else {
|
||||
// All other dynamic variables and static variables
|
||||
toAttach.push({
|
||||
...pick,
|
||||
id: pick.id,
|
||||
value: pick.value,
|
||||
range: undefined,
|
||||
id: pick.id ?? '',
|
||||
value: 'value' in pick ? pick.value : undefined,
|
||||
fullName: pick.label,
|
||||
name: 'name' in pick && typeof pick.name === 'string' ? pick.name : pick.label,
|
||||
icon: 'icon' in pick && ThemeIcon.isThemeIcon(pick.icon) ? pick.icon : undefined
|
||||
|
@ -186,12 +209,11 @@ class AttachContextAction extends Action2 {
|
|||
|
||||
}
|
||||
|
||||
if (chatVariablesService.hasVariable(SelectAndInsertFileAction.Name)) {
|
||||
quickPickItems.push(SelectAndInsertFileAction.Item, { type: 'separator' });
|
||||
}
|
||||
|
||||
quickInputService.quickAccess.show('', {
|
||||
enabledProviderPrefixes: [AnythingQuickAccessProvider.PREFIX],
|
||||
enabledProviderPrefixes: [
|
||||
AnythingQuickAccessProvider.PREFIX,
|
||||
AbstractGotoSymbolQuickAccessProvider.PREFIX
|
||||
],
|
||||
placeholder: localize('chatContext.attach.placeholder', 'Search attachments'),
|
||||
providerOptions: <AnythingQuickAccessProviderRunOptions>{
|
||||
handleAccept: (item: IChatContextQuickPickItem) => {
|
||||
|
@ -208,7 +230,11 @@ class AttachContextAction extends Action2 {
|
|||
&& !attachedContext.has(this._getFileContextId({ resource: item.resource })); // Hack because Typescript doesn't narrow this type correctly
|
||||
}
|
||||
|
||||
if (!('command' in item)) {
|
||||
if (item && typeof item === 'object' && 'uri' in item && item.uri && item.range) {
|
||||
return !attachedContext.has(this._getFileContextId({ uri: item.uri, range: item.range.decoration }));
|
||||
}
|
||||
|
||||
if (!('command' in item) && item.id) {
|
||||
return !attachedContext.has(item.id);
|
||||
}
|
||||
|
||||
|
|
|
@ -442,7 +442,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge
|
|||
const widget = dom.append(container, $('.chat-attached-context-attachment.show-file-icons'));
|
||||
const label = this._contextResourceLabels.create(widget, { supportIcons: true });
|
||||
const file = URI.isUri(attachment.value) ? attachment.value : attachment.value && typeof attachment.value === 'object' && 'uri' in attachment.value && URI.isUri(attachment.value.uri) ? attachment.value.uri : undefined;
|
||||
if (file) {
|
||||
if (file && attachment.isFile) {
|
||||
label.setFile(file, {
|
||||
fileKind: FileKind.FILE,
|
||||
hidePath: true,
|
||||
|
|
|
@ -33,6 +33,7 @@ export interface IChatRequestVariableEntry {
|
|||
value: IChatRequestVariableValue;
|
||||
references?: IChatContentReference[];
|
||||
isDynamic?: boolean;
|
||||
isFile?: boolean;
|
||||
}
|
||||
|
||||
export interface IChatRequestVariableData {
|
||||
|
|
|
@ -118,7 +118,7 @@ export class GotoSymbolQuickAccessProvider extends AbstractGotoSymbolQuickAccess
|
|||
return [];
|
||||
}
|
||||
|
||||
return this.doGetSymbolPicks(this.getDocumentSymbols(model, token), prepareQuery(filter), options, token);
|
||||
return this.doGetSymbolPicks(this.getDocumentSymbols(model, token), prepareQuery(filter), options, token, model);
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
|
Loading…
Reference in a new issue