pasting code block in chat (#234301)

* first pass code block paste

* added better styling

* some more work

* revert

* added better architecture

* cleanup

* remove unused check

* more clenaup

* use correct URI thing

* new opaque control, new chat request variable type for pastes

* fix hierach type

* fixtypoo

* address more comments

* more cleanup, fix title

* fix merge conflcit

* smol cleanup

* fix compile/hygiene

* i always forget to localize :(

* an actual merge conclift fix

* add create model referencing
This commit is contained in:
Justin Chen
2024-11-26 15:57:43 +00:00
committed by GitHub
parent a4447a9618
commit 6ff5e0475d
12 changed files with 354 additions and 16 deletions

View File

@@ -1758,7 +1758,14 @@ export interface IWorkspaceTextEdit {
}
export interface WorkspaceEdit {
edits: Array<IWorkspaceTextEdit | IWorkspaceFileEdit>;
edits: Array<IWorkspaceTextEdit | IWorkspaceFileEdit | ICustomEdit>;
}
export interface ICustomEdit {
readonly resource: URI;
readonly metadata?: WorkspaceEditMetadata;
undo(): Promise<void> | void;
redo(): Promise<void> | void;
}
export interface Rejection {

9
src/vs/monaco.d.ts vendored
View File

@@ -7962,7 +7962,14 @@ declare namespace monaco.languages {
}
export interface WorkspaceEdit {
edits: Array<IWorkspaceTextEdit | IWorkspaceFileEdit>;
edits: Array<IWorkspaceTextEdit | IWorkspaceFileEdit | ICustomEdit>;
}
export interface ICustomEdit {
readonly resource: Uri;
readonly metadata?: WorkspaceEditMetadata;
undo(): Promise<void> | void;
redo(): Promise<void> | void;
}
export interface Rejection {

View File

@@ -28,6 +28,7 @@ import { BulkTextEdits } from './bulkTextEdits.js';
import { IEditorService } from '../../../services/editor/common/editorService.js';
import { ILifecycleService, ShutdownReason } from '../../../services/lifecycle/common/lifecycle.js';
import { IWorkingCopyService } from '../../../services/workingCopy/common/workingCopyService.js';
import { OpaqueEdits, ResourceAttachmentEdit } from './opaqueEdits.js';
function liftEdits(edits: ResourceEdit[]): ResourceEdit[] {
return edits.map(edit => {
@@ -40,6 +41,11 @@ function liftEdits(edits: ResourceEdit[]): ResourceEdit[] {
if (ResourceNotebookCellEdit.is(edit)) {
return ResourceNotebookCellEdit.lift(edit);
}
if (ResourceAttachmentEdit.is(edit)) {
return ResourceAttachmentEdit.lift(edit);
}
throw new Error('Unsupported edit');
});
}
@@ -122,6 +128,8 @@ class BulkEdit {
resources.push(await this._performTextEdits(<ResourceTextEdit[]>group, this._undoRedoGroup, this._undoRedoSource, progress));
} else if (group[0] instanceof ResourceNotebookCellEdit) {
resources.push(await this._performCellEdits(<ResourceNotebookCellEdit[]>group, this._undoRedoGroup, this._undoRedoSource, progress));
} else if (group[0] instanceof ResourceAttachmentEdit) {
resources.push(await this._performOpaqueEdits(<ResourceAttachmentEdit[]>group, this._undoRedoGroup, this._undoRedoSource, progress));
} else {
console.log('UNKNOWN EDIT');
}
@@ -148,6 +156,12 @@ class BulkEdit {
const model = this._instaService.createInstance(BulkCellEdits, undoRedoGroup, undoRedoSource, progress, this._token, edits);
return await model.apply();
}
private async _performOpaqueEdits(edits: ResourceAttachmentEdit[], undoRedoGroup: UndoRedoGroup, undoRedoSource: UndoRedoSource | undefined, progress: IProgress<void>): Promise<readonly URI[]> {
this._logService.debug('_performOpaqueEdits', JSON.stringify(edits));
const model = this._instaService.createInstance(OpaqueEdits, undoRedoGroup, undoRedoSource, progress, this._token, edits);
return await model.apply();
}
}
export class BulkEditService implements IBulkEditService {

View File

@@ -0,0 +1,79 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { CancellationToken } from '../../../../base/common/cancellation.js';
import { isObject } from '../../../../base/common/types.js';
import { URI } from '../../../../base/common/uri.js';
import { ResourceEdit } from '../../../../editor/browser/services/bulkEditService.js';
import { ICustomEdit, WorkspaceEditMetadata } from '../../../../editor/common/languages.js';
import { IProgress } from '../../../../platform/progress/common/progress.js';
import { IUndoRedoService, UndoRedoElementType, UndoRedoGroup, UndoRedoSource } from '../../../../platform/undoRedo/common/undoRedo.js';
export class ResourceAttachmentEdit extends ResourceEdit implements ICustomEdit {
static is(candidate: any): candidate is ICustomEdit {
if (candidate instanceof ResourceAttachmentEdit) {
return true;
} else {
return isObject(candidate)
&& (Boolean((<ICustomEdit>candidate).undo && (<ICustomEdit>candidate).redo));
}
}
static lift(edit: ICustomEdit): ResourceAttachmentEdit {
if (edit instanceof ResourceAttachmentEdit) {
return edit;
} else {
return new ResourceAttachmentEdit(edit.resource, edit.undo, edit.redo, edit.metadata);
}
}
constructor(
readonly resource: URI,
readonly undo: () => Promise<void> | void,
readonly redo: () => Promise<void> | void,
metadata?: WorkspaceEditMetadata
) {
super(metadata);
}
}
export class OpaqueEdits {
constructor(
private readonly _undoRedoGroup: UndoRedoGroup,
private readonly _undoRedoSource: UndoRedoSource | undefined,
private readonly _progress: IProgress<void>,
private readonly _token: CancellationToken,
private readonly _edits: ResourceAttachmentEdit[],
@IUndoRedoService private readonly _undoRedoService: IUndoRedoService,
) { }
async apply(): Promise<readonly URI[]> {
const resources: URI[] = [];
for (const edit of this._edits) {
if (this._token.isCancellationRequested) {
break;
}
await edit.redo();
this._undoRedoService.pushElement({
type: UndoRedoElementType.Resource,
resource: edit.resource,
label: edit.metadata?.label || 'Custom Edit',
code: 'paste',
undo: edit.undo,
redo: edit.redo,
}, this._undoRedoGroup, this._undoRedoSource);
this._progress.report(undefined);
resources.push(edit.resource);
}
return resources;
}
}

View File

@@ -59,6 +59,7 @@ import { ChatEditor, IChatEditorOptions } from './chatEditor.js';
import { registerChatEditorActions } from './chatEditorActions.js';
import { ChatEditorController } from './chatEditorController.js';
import { ChatEditorInput, ChatEditorInputSerializer } from './chatEditorInput.js';
import { ChatInputBoxContentProvider } from './chatEdinputInputContentProvider.js';
import { ChatEditorSaving } from './chatEditorSaving.js';
import { agentSlashCommandToMarkdown, agentToMarkdown } from './chatMarkdownDecorationsRenderer.js';
import { ChatCompatibilityNotifier, ChatExtensionPointHandler } from './chatParticipant.contribution.js';
@@ -211,6 +212,8 @@ AccessibleViewRegistry.register(new ChatResponseAccessibleView());
AccessibleViewRegistry.register(new PanelChatAccessibilityHelp());
AccessibleViewRegistry.register(new QuickChatAccessibilityHelp());
registerEditorFeature(ChatInputBoxContentProvider);
class ChatSlashStaticSlashCommandsContribution extends Disposable {
static readonly ID = 'workbench.contrib.chatSlashStaticSlashCommands';

View File

@@ -4,6 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import * as dom from '../../../../../base/browser/dom.js';
import { IManagedHoverTooltipMarkdownString } from '../../../../../base/browser/ui/hover/hover.js';
import { createInstantHoverDelegate } from '../../../../../base/browser/ui/hover/hoverDelegateFactory.js';
import { Emitter } from '../../../../../base/common/event.js';
import { Disposable, DisposableStore } from '../../../../../base/common/lifecycle.js';
@@ -19,8 +20,8 @@ import { IInstantiationService } from '../../../../../platform/instantiation/com
import { IOpenerService, OpenInternalOptions } from '../../../../../platform/opener/common/opener.js';
import { FolderThemeIcon, IThemeService } from '../../../../../platform/theme/common/themeService.js';
import { ResourceLabels } from '../../../../browser/labels.js';
import { IChatRequestVariableEntry, isPasteVariableEntry } from '../../common/chatModel.js';
import { revealInSideBarCommand } from '../../../files/browser/fileActions.contribution.js';
import { IChatRequestVariableEntry } from '../../common/chatModel.js';
import { ChatResponseReferencePartStatusKind, IChatContentReference } from '../../common/chatService.js';
export class ChatAttachmentsContentPart extends Disposable {
@@ -126,6 +127,25 @@ export class ChatAttachmentsContentPart extends Disposable {
if (!this.attachedContextDisposables.isDisposed) {
this.attachedContextDisposables.add(this.hoverService.setupManagedHover(hoverDelegate, widget, hoverElement));
}
} else if (isPasteVariableEntry(attachment)) {
ariaLabel = localize('chat.attachment', "Attached context, {0}", attachment.name);
const hoverContent: IManagedHoverTooltipMarkdownString = {
markdown: {
value: `\`\`\`${attachment.language}\n${attachment.code}\n\`\`\``,
},
markdownNotSupportedFallback: attachment.code,
};
const classNames = ['file-icon', `${attachment.language}-lang-file-icon`];
label.setLabel(attachment.fileName, undefined, { extraClasses: classNames });
widget.appendChild(dom.$('span.attachment-additional-info', {}, `Pasted ${attachment.pastedLines}`));
widget.style.position = 'relative';
if (!this.attachedContextDisposables.isDisposed) {
this.attachedContextDisposables.add(this.hoverService.setupManagedHover(hoverDelegate, widget, hoverContent, { trapFocus: true }));
}
} else {
const attachmentLabel = attachment.fullName ?? attachment.name;
const withIcon = attachment.icon?.id ? `$(${attachment.icon.id}) ${attachmentLabel}` : attachmentLabel;

View File

@@ -0,0 +1,32 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Disposable } from '../../../../base/common/lifecycle.js';
import { URI } from '../../../../base/common/uri.js';
import { ILanguageService } from '../../../../editor/common/languages/language.js';
import { ITextModel } from '../../../../editor/common/model.js';
import { IModelService } from '../../../../editor/common/services/model.js';
import { ITextModelContentProvider, ITextModelService } from '../../../../editor/common/services/resolverService.js';
import { ChatInputPart } from './chatInputPart.js';
export class ChatInputBoxContentProvider extends Disposable implements ITextModelContentProvider {
constructor(
@ITextModelService textModelService: ITextModelService,
@IModelService private readonly modelService: IModelService,
@ILanguageService private readonly languageService: ILanguageService
) {
super();
this._register(textModelService.registerTextModelContentProvider(ChatInputPart.INPUT_SCHEME, this));
}
async provideTextContent(resource: URI): Promise<ITextModel | null> {
const existing = this.modelService.getModel(resource);
if (existing) {
return existing;
}
return this.modelService.createModel('', this.languageService.createById('chatinput'), resource);
}
}

View File

@@ -11,6 +11,7 @@ import { StandardKeyboardEvent } from '../../../../base/browser/keyboardEvent.js
import { StandardMouseEvent } from '../../../../base/browser/mouseEvent.js';
import * as aria from '../../../../base/browser/ui/aria/aria.js';
import { Button } from '../../../../base/browser/ui/button/button.js';
import { IManagedHoverTooltipMarkdownString } from '../../../../base/browser/ui/hover/hover.js';
import { IHoverDelegate } from '../../../../base/browser/ui/hover/hoverDelegate.js';
import { getBaseLayerHoverDelegate } from '../../../../base/browser/ui/hover/hoverDelegate2.js';
import { createInstantHoverDelegate } from '../../../../base/browser/ui/hover/hoverDelegateFactory.js';
@@ -39,6 +40,7 @@ import { IRange, Range } from '../../../../editor/common/core/range.js';
import { ILanguageService } from '../../../../editor/common/languages/language.js';
import { ITextModel } from '../../../../editor/common/model.js';
import { IModelService } from '../../../../editor/common/services/model.js';
import { ITextModelService } from '../../../../editor/common/services/resolverService.js';
import { CopyPasteController } from '../../../../editor/contrib/dropOrPasteInto/browser/copyPasteController.js';
import { ContentHoverController } from '../../../../editor/contrib/hover/browser/contentHoverController.js';
import { GlyphHoverController } from '../../../../editor/contrib/hover/browser/glyphHoverController.js';
@@ -78,7 +80,7 @@ import { revealInSideBarCommand } from '../../files/browser/fileActions.contribu
import { ChatAgentLocation, IChatAgentService } from '../common/chatAgents.js';
import { ChatContextKeys } from '../common/chatContextKeys.js';
import { ChatEditingSessionState, IChatEditingService, IChatEditingSession, WorkingSetEntryState } from '../common/chatEditingService.js';
import { IChatRequestVariableEntry } from '../common/chatModel.js';
import { IChatRequestVariableEntry, isPasteVariableEntry } from '../common/chatModel.js';
import { ChatRequestDynamicVariablePart } from '../common/chatParserTypes.js';
import { IChatFollowup } from '../common/chatService.js';
import { IChatResponseViewModel } from '../common/chatViewModel.js';
@@ -282,6 +284,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge
@IMenuService private readonly menuService: IMenuService,
@ILanguageService private readonly languageService: ILanguageService,
@IThemeService private readonly themeService: IThemeService,
@ITextModelService private readonly textModelResolverService: ITextModelService,
@IStorageService private readonly storageService: IStorageService,
) {
super();
@@ -727,9 +730,17 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge
let inputModel = this.modelService.getModel(this.inputUri);
if (!inputModel) {
inputModel = this.modelService.createModel('', null, this.inputUri, true);
this._register(inputModel);
}
this.textModelResolverService.createModelReference(this.inputUri).then(ref => {
// make sure to hold a reference so that the model doesn't get disposed by the text model service
if (this._store.isDisposed) {
ref.dispose();
return;
}
this._register(ref);
});
this.inputModel = inputModel;
this.inputModel.updateOptions({ bracketColorizationOptions: { enabled: false, independentColorPoolPerBracketType: false } });
this._inputEditor.setModel(this.inputModel);
@@ -864,6 +875,23 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge
store.add(this.hoverService.setupManagedHover(hoverDelegate, widget, hoverElement, { trapFocus: false }));
resolve();
}));
} else if (isPasteVariableEntry(attachment)) {
ariaLabel = localize('chat.attachment', "Attached context, {0}", attachment.name);
const hoverContent: IManagedHoverTooltipMarkdownString = {
markdown: {
value: `\`\`\`${attachment.language}\n${attachment.code}\n\`\`\``,
},
markdownNotSupportedFallback: attachment.code,
};
const classNames = ['file-icon', `${attachment.language}-lang-file-icon`];
label.setLabel(attachment.fileName, undefined, { extraClasses: classNames });
widget.appendChild(dom.$('span.attachment-additional-info', {}, `Pasted ${attachment.pastedLines}`));
widget.style.position = 'relative';
store.add(this.hoverService.setupManagedHover(hoverDelegate, widget, hoverContent, { trapFocus: true }));
this.attachButtonAndDisposables(widget, index, attachment, hoverDelegate);
} else {
const attachmentLabel = attachment.fullName ?? attachment.name;
const withIcon = attachment.icon?.id ? `$(${attachment.icon.id}) ${attachmentLabel}` : attachmentLabel;

View File

@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { CancellationToken } from '../../../../base/common/cancellation.js';
import { IDataTransferItem, IReadonlyVSDataTransfer } from '../../../../base/common/dataTransfer.js';
import { createStringDataTransferItem, IDataTransferItem, IReadonlyVSDataTransfer, VSDataTransfer } from '../../../../base/common/dataTransfer.js';
import { HierarchicalKind } from '../../../../base/common/hierarchicalKind.js';
import { IRange } from '../../../../editor/common/core/range.js';
import { DocumentPasteContext, DocumentPasteEditProvider, DocumentPasteEditsSession } from '../../../../editor/common/languages.js';
@@ -17,19 +17,25 @@ import { Codicon } from '../../../../base/common/codicons.js';
import { localize } from '../../../../nls.js';
import { IChatRequestVariableEntry } from '../common/chatModel.js';
import { IExtensionService, isProposedApiEnabled } from '../../../services/extensions/common/extensions.js';
import { Mimes } from '../../../../base/common/mime.js';
import { URI } from '../../../../base/common/uri.js';
const COPY_MIME_TYPES = 'application/vnd.code.additional-editor-data';
export class PasteImageProvider implements DocumentPasteEditProvider {
public readonly kind = new HierarchicalKind('image');
public readonly copyMimeTypes = ['image/*'];
public readonly kind = new HierarchicalKind('chat.attach.image');
public readonly providedPasteEditKinds = [this.kind];
public readonly copyMimeTypes = [];
public readonly pasteMimeTypes = ['image/*'];
constructor(
private readonly chatWidgetService: IChatWidgetService,
private readonly extensionService: IExtensionService
private readonly extensionService: IExtensionService,
) { }
async provideDocumentPasteEdits(_model: ITextModel, _ranges: readonly IRange[], dataTransfer: IReadonlyVSDataTransfer, context: DocumentPasteContext, token: CancellationToken): Promise<DocumentPasteEditsSession | undefined> {
async provideDocumentPasteEdits(model: ITextModel, ranges: readonly IRange[], dataTransfer: IReadonlyVSDataTransfer, context: DocumentPasteContext, token: CancellationToken): Promise<DocumentPasteEditsSession | undefined> {
if (!this.extensionService.extensions.some(ext => isProposedApiEnabled(ext, 'chatReferenceBinaryData'))) {
return;
}
@@ -63,7 +69,7 @@ export class PasteImageProvider implements DocumentPasteEditProvider {
return;
}
const widget = this.chatWidgetService.getWidgetByInputUri(_model.uri);
const widget = this.chatWidgetService.getWidgetByInputUri(model.uri);
if (!widget) {
return;
}
@@ -88,9 +94,7 @@ export class PasteImageProvider implements DocumentPasteEditProvider {
return;
}
widget.attachmentModel.addContext(imageContext);
return;
return getCustomPaste(model, imageContext, mimeType, this.kind, localize('pastedImageAttachment', 'Pasted Image Attachment'), this.chatWidgetService);
}
}
@@ -107,7 +111,6 @@ async function getImageAttachContext(data: Uint8Array, mimeType: string, token:
isImage: true,
icon: Codicon.fileMedia,
isDynamic: true,
isFile: false,
mimeType
};
}
@@ -137,6 +140,126 @@ export function isImage(array: Uint8Array): boolean {
);
}
export class CopyTextProvider implements DocumentPasteEditProvider {
public readonly providedPasteEditKinds = [];
public readonly copyMimeTypes = [COPY_MIME_TYPES];
public readonly pasteMimeTypes = [];
async prepareDocumentPaste(model: ITextModel, ranges: readonly IRange[], dataTransfer: IReadonlyVSDataTransfer, token: CancellationToken): Promise<undefined | IReadonlyVSDataTransfer> {
if (model.uri.scheme === ChatInputPart.INPUT_SCHEME) {
return;
}
const customDataTransfer = new VSDataTransfer();
const rangesString = JSON.stringify({ ranges: ranges[0], uri: model.uri.toString() });
customDataTransfer.append(COPY_MIME_TYPES, createStringDataTransferItem(rangesString));
return customDataTransfer;
}
}
export class PasteTextProvider implements DocumentPasteEditProvider {
public readonly kind = new HierarchicalKind('chat.attach.text');
public readonly providedPasteEditKinds = [this.kind];
public readonly copyMimeTypes = [];
public readonly pasteMimeTypes = [COPY_MIME_TYPES];
constructor(
private readonly chatWidgetService: IChatWidgetService
) { }
async provideDocumentPasteEdits(model: ITextModel, ranges: readonly IRange[], dataTransfer: IReadonlyVSDataTransfer, context: DocumentPasteContext, token: CancellationToken): Promise<DocumentPasteEditsSession | undefined> {
if (model.uri.scheme !== ChatInputPart.INPUT_SCHEME) {
return;
}
const text = dataTransfer.get(Mimes.text);
const editorData = dataTransfer.get('vscode-editor-data');
const additionalEditorData = dataTransfer.get(COPY_MIME_TYPES);
if (!editorData || !text || !additionalEditorData) {
return;
}
const textdata = await text.asString();
const metadata = JSON.parse(await editorData.asString());
const additionalData = JSON.parse(await additionalEditorData.asString());
const widget = this.chatWidgetService.getWidgetByInputUri(model.uri);
if (!widget) {
return;
}
const copiedContext = await getCopiedContext(textdata, additionalData.uri, metadata.mode, additionalData.ranges);
if (token.isCancellationRequested || !copiedContext) {
return;
}
const currentContextIds = widget.attachmentModel.getAttachmentIDs();
if (currentContextIds.has(copiedContext.id)) {
return;
}
return getCustomPaste(model, copiedContext, Mimes.text, this.kind, localize('pastedCodeAttachment', 'Pasted Code Attachment'), this.chatWidgetService);
}
}
async function getCopiedContext(code: string, file: string, language: string, ranges: IRange): Promise<IChatRequestVariableEntry | undefined> {
const fileName = file.split('/').pop() || 'unknown file';
const start = ranges.startLineNumber;
const end = ranges.endLineNumber;
const resultText = `Copied Selection of Code: \n\n\n From the file: ${fileName} From lines ${start} to ${end} \n \`\`\`${code}\`\`\``;
const pastedLines = start === end ? localize('pastedAttachment.oneLine', '1 line') : localize('pastedAttachment.multipleLines', '{0} lines', end + 1 - start);
return {
kind: 'paste',
value: resultText,
id: `${fileName}${start}${end}${ranges.startColumn}${ranges.endColumn}`,
name: `${fileName} ${pastedLines}`,
icon: Codicon.code,
isDynamic: true,
pastedLines,
language,
fileName,
code,
references: [{
reference: URI.parse(file),
kind: 'reference'
}]
};
}
async function getCustomPaste(model: ITextModel, context: IChatRequestVariableEntry, handledMimeType: string, kind: HierarchicalKind, title: string, chatWidgetService: IChatWidgetService): Promise<DocumentPasteEditsSession> {
const customEdit = {
resource: model.uri,
variable: context,
undo: () => {
const widget = chatWidgetService.getWidgetByInputUri(model.uri);
if (!widget) {
throw new Error('No widget found for undo');
}
widget.attachmentModel.delete(context.id);
},
redo: () => {
const widget = chatWidgetService.getWidgetByInputUri(model.uri);
if (!widget) {
throw new Error('No widget found for redo');
}
widget.attachmentModel.addContext(context);
},
metadata: { needsConfirmation: false, label: context.name }
};
return {
edits: [{
insertText: '', title, kind, handledMimeType,
additionalEdit: {
edits: [customEdit],
}
}],
dispose() { },
};
}
export class ChatPasteProvidersFeature extends Disposable {
constructor(
@ILanguageFeaturesService languageFeaturesService: ILanguageFeaturesService,
@@ -145,5 +268,7 @@ export class ChatPasteProvidersFeature extends Disposable {
) {
super();
this._register(languageFeaturesService.documentPasteEditProvider.register({ scheme: ChatInputPart.INPUT_SCHEME, pattern: '*', hasAccessToAllModels: true }, new PasteImageProvider(chatWidgetService, extensionService)));
this._register(languageFeaturesService.documentPasteEditProvider.register({ scheme: ChatInputPart.INPUT_SCHEME, pattern: '*', hasAccessToAllModels: true }, new PasteTextProvider(chatWidgetService)));
this._register(languageFeaturesService.documentPasteEditProvider.register('*', new CopyTextProvider()));
}
}

View File

@@ -1307,6 +1307,11 @@ have to be updated for changes to the rules above, or to support more deeply nes
border: none;
}
.chat-attached-context-attachment .attachment-additional-info {
opacity: 0.7;
font-size: .9em;
}
.chat-attached-context-attachment .chat-attached-context-pill-image {
width: 14px;
height: 14px;

View File

@@ -56,6 +56,14 @@ export interface IChatRequestImplicitVariableEntry extends Omit<IBaseChatRequest
enabled: boolean;
}
export interface IChatRequestPasteVariableEntry extends Omit<IBaseChatRequestVariableEntry, 'kind'> {
readonly kind: 'paste';
code: string;
language: string;
fileName: string;
pastedLines: string;
}
export interface ISymbolVariableEntry extends Omit<IBaseChatRequestVariableEntry, 'kind'> {
readonly kind: 'symbol';
readonly isDynamic: true;
@@ -67,12 +75,16 @@ export interface ICommandResultVariableEntry extends Omit<IBaseChatRequestVariab
readonly isDynamic: true;
}
export type IChatRequestVariableEntry = IChatRequestImplicitVariableEntry | ISymbolVariableEntry | ICommandResultVariableEntry | IBaseChatRequestVariableEntry;
export type IChatRequestVariableEntry = IChatRequestImplicitVariableEntry | IChatRequestPasteVariableEntry | ISymbolVariableEntry | ICommandResultVariableEntry | IBaseChatRequestVariableEntry;
export function isImplicitVariableEntry(obj: IChatRequestVariableEntry): obj is IChatRequestImplicitVariableEntry {
return obj.kind === 'implicit';
}
export function isPasteVariableEntry(obj: IChatRequestVariableEntry): obj is IChatRequestPasteVariableEntry {
return obj.kind === 'paste';
}
export function isChatRequestVariableEntry(obj: unknown): obj is IChatRequestVariableEntry {
const entry = obj as IChatRequestVariableEntry;
return typeof entry === 'object' &&

View File

@@ -67,6 +67,9 @@ import { InlineChatSessionServiceImpl } from '../../browser/inlineChatSessionSer
import { TestWorkerService } from './testWorkerService.js';
import { ILanguageModelsService, LanguageModelsService } from '../../../chat/common/languageModels.js';
import { IChatEditingService, IChatEditingSession } from '../../../chat/common/chatEditingService.js';
import { ITextModelService } from '../../../../../editor/common/services/resolverService.js';
import { TextModelResolverService } from '../../../../services/textmodelResolver/common/textModelResolverService.js';
import { ChatInputBoxContentProvider } from '../../../chat/browser/chatEdinputInputContentProvider.js';
suite('InteractiveChatController', function () {
@@ -193,6 +196,7 @@ suite('InteractiveChatController', function () {
}],
[IWorkbenchAssignmentService, new NullWorkbenchAssignmentService()],
[ILanguageModelsService, new SyncDescriptor(LanguageModelsService)],
[ITextModelService, new SyncDescriptor(TextModelResolverService)],
);
instaService = store.add((store.add(workbenchInstantiationService(undefined, store))).createChild(serviceCollection));
@@ -210,6 +214,8 @@ suite('InteractiveChatController', function () {
store.add(instaService.get(ILanguageModelsService) as LanguageModelsService);
store.add(instaService.createInstance(ChatInputBoxContentProvider));
model = store.add(instaService.get(IModelService).createModel('Hello\nWorld\nHello Again\nHello World\n', null));
model.setEOL(EndOfLineSequence.LF);
editor = store.add(instantiateTestCodeEditor(instaService, model));