mirror of
https://github.com/microsoft/vscode.git
synced 2026-09-06 05:38:13 +01:00
More codeblock actions and padding tweak (#180017)
* Fix chat codeblock padding * Add more codeblock actions
This commit is contained in:
+98
@@ -11,14 +11,18 @@ import { Range } from 'vs/editor/common/core/range';
|
||||
import { localize } from 'vs/nls';
|
||||
import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions';
|
||||
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
|
||||
import { TerminalLocation } from 'vs/platform/terminal/common/terminal';
|
||||
import { IUntitledTextResourceEditorInput } from 'vs/workbench/common/editor';
|
||||
import { INTERACTIVE_SESSION_CATEGORY } from 'vs/workbench/contrib/interactiveSession/browser/actions/interactiveSessionActions';
|
||||
import { IInteractiveSessionCopyAction, IInteractiveSessionService, IInteractiveSessionUserActionEvent, InteractiveSessionCopyKind } from 'vs/workbench/contrib/interactiveSession/common/interactiveSessionService';
|
||||
import { IInteractiveResponseViewModel } from 'vs/workbench/contrib/interactiveSession/common/interactiveSessionViewModel';
|
||||
import { ITerminalEditorService, ITerminalGroupService, ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
|
||||
export interface IInteractiveSessionCodeBlockActionContext {
|
||||
code: string;
|
||||
languageId: string;
|
||||
codeBlockIndex: number;
|
||||
element: IInteractiveResponseViewModel;
|
||||
}
|
||||
@@ -137,4 +141,98 @@ export function registerInteractiveSessionCodeBlockActions() {
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
registerAction2(class InsertIntoNewFileAction extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: 'workbench.action.interactiveSession.insertIntoNewFile',
|
||||
title: {
|
||||
value: localize('interactive.insertIntoNewFile.label', "Insert Into New File"),
|
||||
original: 'Insert Into New File'
|
||||
},
|
||||
f1: false,
|
||||
category: INTERACTIVE_SESSION_CATEGORY,
|
||||
menu: {
|
||||
id: MenuId.InteractiveSessionCodeBlock,
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async run(accessor: ServicesAccessor, ...args: any[]) {
|
||||
const context = args[0];
|
||||
if (!isCodeBlockActionContext(context)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const editorService = accessor.get(IEditorService);
|
||||
const interactiveSessionService = accessor.get(IInteractiveSessionService);
|
||||
editorService.openEditor(<IUntitledTextResourceEditorInput>{ contents: context.code, languageId: context.languageId, resource: undefined });
|
||||
|
||||
interactiveSessionService.notifyUserAction(<IInteractiveSessionUserActionEvent>{
|
||||
providerId: context.element.providerId,
|
||||
action: {
|
||||
kind: 'insert',
|
||||
responseId: context.element.providerResponseId,
|
||||
codeBlockIndex: context.codeBlockIndex,
|
||||
totalCharacters: context.code.length,
|
||||
newFile: true
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
registerAction2(class RunInTerminalAction extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: 'workbench.action.interactiveSession.runInTerminal',
|
||||
title: {
|
||||
value: localize('interactive.runInTerminal.label', "Run in Terminal"),
|
||||
original: 'Run in Terminal'
|
||||
},
|
||||
f1: false,
|
||||
category: INTERACTIVE_SESSION_CATEGORY,
|
||||
menu: {
|
||||
id: MenuId.InteractiveSessionCodeBlock,
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async run(accessor: ServicesAccessor, ...args: any[]) {
|
||||
const context = args[0];
|
||||
if (!isCodeBlockActionContext(context)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const interactiveSessionService = accessor.get(IInteractiveSessionService);
|
||||
const terminalService = accessor.get(ITerminalService);
|
||||
const editorService = accessor.get(IEditorService);
|
||||
const terminalEditorService = accessor.get(ITerminalEditorService);
|
||||
const terminalGroupService = accessor.get(ITerminalGroupService);
|
||||
|
||||
let terminal = await terminalService.getActiveOrCreateInstance();
|
||||
|
||||
// Why does getActiveOrCreateInstance return a disposed terminal?
|
||||
terminal = terminal.isDisposed ? await terminalService.createTerminal() : terminal;
|
||||
|
||||
await terminal.focusWhenReady();
|
||||
if (terminal.target === TerminalLocation.Editor) {
|
||||
const existingEditors = editorService.findEditors(terminal.resource);
|
||||
terminalEditorService.openEditor(terminal, { viewColumn: existingEditors?.[0].groupId });
|
||||
} else {
|
||||
terminalGroupService.showPanel(true);
|
||||
}
|
||||
|
||||
terminal.sendText(context.code, false);
|
||||
|
||||
interactiveSessionService.notifyUserAction(<IInteractiveSessionUserActionEvent>{
|
||||
providerId: context.element.providerId,
|
||||
action: {
|
||||
kind: 'runInTerminal',
|
||||
responseId: context.element.providerResponseId,
|
||||
codeBlockIndex: context.codeBlockIndex,
|
||||
languageId: context.languageId,
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
+27
-5
@@ -19,6 +19,7 @@ import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecyc
|
||||
import { ResourceMap } from 'vs/base/common/map';
|
||||
import { FileAccess } from 'vs/base/common/network';
|
||||
import { ThemeIcon } from 'vs/base/common/themables';
|
||||
import { withNullAsUndefined } from 'vs/base/common/types';
|
||||
import { EditorExtensionsRegistry } from 'vs/editor/browser/editorExtensions';
|
||||
import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget';
|
||||
import { EDITOR_FONT_DEFAULTS, IEditorOptions } from 'vs/editor/common/config/editorOptions';
|
||||
@@ -520,6 +521,8 @@ export interface IInteractiveResultCodeBlockInfo {
|
||||
|
||||
export const codeBlockInfosByModelUri = new ResourceMap<IInteractiveResultCodeBlockInfo>();
|
||||
|
||||
const defaultCodeblockPadding = 10;
|
||||
|
||||
class CodeBlockPart extends Disposable implements IInteractiveResultCodeBlockPart {
|
||||
private readonly _onDidChangeContentHeight = this._register(new Emitter<number>());
|
||||
public readonly onDidChangeContentHeight = this._onDidChangeContentHeight.event;
|
||||
@@ -531,6 +534,8 @@ class CodeBlockPart extends Disposable implements IInteractiveResultCodeBlockPar
|
||||
public readonly textModel: ITextModel;
|
||||
public readonly element: HTMLElement;
|
||||
|
||||
private currentScrollWidth = 0;
|
||||
|
||||
constructor(
|
||||
private readonly options: InteractiveSessionEditorOptions,
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@@ -558,7 +563,7 @@ class CodeBlockPart extends Disposable implements IInteractiveResultCodeBlockPar
|
||||
scrollBeyondLastLine: false,
|
||||
lineDecorationsWidth: 8,
|
||||
dragAndDrop: false,
|
||||
padding: { top: 2, bottom: 2 },
|
||||
padding: { top: defaultCodeblockPadding, bottom: defaultCodeblockPadding },
|
||||
mouseWheelZoom: false,
|
||||
scrollbar: {
|
||||
alwaysConsumeMouseWheel: false
|
||||
@@ -583,6 +588,9 @@ class CodeBlockPart extends Disposable implements IInteractiveResultCodeBlockPar
|
||||
this.editor.updateOptions(this.getEditorOptionsFromConfig());
|
||||
}));
|
||||
|
||||
this._register(this.editor.onDidScrollChange(e => {
|
||||
this.currentScrollWidth = e.scrollWidth;
|
||||
}));
|
||||
this._register(this.editor.onDidContentSizeChange(e => {
|
||||
if (e.contentHeightChanged) {
|
||||
this._onDidChangeContentHeight.fire(e.contentHeight);
|
||||
@@ -600,6 +608,17 @@ class CodeBlockPart extends Disposable implements IInteractiveResultCodeBlockPar
|
||||
this.editor.setModel(this.textModel);
|
||||
}
|
||||
|
||||
private updatePaddingForLayout() {
|
||||
// scrollWidth = "the width of the content that needs to be scrolled"
|
||||
// contentWidth = "the width of the area where content is displayed"
|
||||
const horizontalScrollbarVisible = this.currentScrollWidth > this.editor.getLayoutInfo().contentWidth;
|
||||
const scrollbarHeight = this.editor.getLayoutInfo().horizontalScrollbarHeight;
|
||||
const bottomPadding = horizontalScrollbarVisible ?
|
||||
Math.max(defaultCodeblockPadding - scrollbarHeight, 2) :
|
||||
defaultCodeblockPadding;
|
||||
this.editor.updateOptions({ padding: { top: defaultCodeblockPadding, bottom: bottomPadding } });
|
||||
}
|
||||
|
||||
private getEditorOptionsFromConfig(): IEditorOptions {
|
||||
return {
|
||||
wordWrap: this.options.configuration.resultEditor.wordWrap,
|
||||
@@ -618,6 +637,7 @@ class CodeBlockPart extends Disposable implements IInteractiveResultCodeBlockPar
|
||||
const realContentHeight = this.editor.getContentHeight();
|
||||
const editorBorder = 2;
|
||||
this.editor.layout({ width: width - editorBorder, height: realContentHeight });
|
||||
this.updatePaddingForLayout();
|
||||
}
|
||||
|
||||
render(data: IInteractiveResultCodeBlockData, width: number): void {
|
||||
@@ -631,7 +651,9 @@ class CodeBlockPart extends Disposable implements IInteractiveResultCodeBlockPar
|
||||
|
||||
const text = this.fixCodeText(data.text, data.languageId);
|
||||
this.setText(text);
|
||||
this.setLanguage(data.languageId);
|
||||
|
||||
const vscodeLanguageId = withNullAsUndefined(this.languageService.getLanguageIdByLanguageName(data.languageId));
|
||||
this.setLanguage(vscodeLanguageId);
|
||||
|
||||
this.layout(width);
|
||||
|
||||
@@ -649,7 +671,8 @@ class CodeBlockPart extends Disposable implements IInteractiveResultCodeBlockPar
|
||||
this.toolbar.context = <IInteractiveSessionCodeBlockActionContext>{
|
||||
code: data.text,
|
||||
codeBlockIndex: data.codeBlockIndex,
|
||||
element: data.element
|
||||
element: data.element,
|
||||
languageId: vscodeLanguageId
|
||||
};
|
||||
}
|
||||
|
||||
@@ -692,8 +715,7 @@ class CodeBlockPart extends Disposable implements IInteractiveResultCodeBlockPar
|
||||
}
|
||||
}
|
||||
|
||||
private setLanguage(languageId: string): void {
|
||||
const vscodeLanguageId = this.languageService.getLanguageIdByLanguageName(languageId);
|
||||
private setLanguage(vscodeLanguageId: string | undefined): void {
|
||||
this.textModel.setLanguage(vscodeLanguageId ?? PLAINTEXT_LANGUAGE_ID);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,6 +111,14 @@ export interface IInteractiveSessionInsertAction {
|
||||
responseId: string;
|
||||
codeBlockIndex: number;
|
||||
totalCharacters: number;
|
||||
newFile?: boolean;
|
||||
}
|
||||
|
||||
export interface IInteractiveSessionTerminalAction {
|
||||
kind: 'runInTerminal';
|
||||
responseId: string;
|
||||
codeBlockIndex: number;
|
||||
languageId?: string;
|
||||
}
|
||||
|
||||
export interface IInteractiveSessionCommandAction {
|
||||
@@ -118,7 +126,7 @@ export interface IInteractiveSessionCommandAction {
|
||||
command: IInteractiveSessionResponseCommandFollowup;
|
||||
}
|
||||
|
||||
export type InteractiveSessionUserAction = IInteractiveSessionVoteAction | IInteractiveSessionCopyAction | IInteractiveSessionInsertAction | IInteractiveSessionCommandAction;
|
||||
export type InteractiveSessionUserAction = IInteractiveSessionVoteAction | IInteractiveSessionCopyAction | IInteractiveSessionInsertAction | IInteractiveSessionTerminalAction | IInteractiveSessionCommandAction;
|
||||
|
||||
export interface IInteractiveSessionUserActionEvent {
|
||||
action: InteractiveSessionUserAction;
|
||||
|
||||
@@ -70,10 +70,12 @@ type InteractiveSessionCopyClassification = {
|
||||
|
||||
type InteractiveSessionInsertEvent = {
|
||||
providerId: string;
|
||||
newFile: boolean;
|
||||
};
|
||||
|
||||
type InteractiveSessionInsertClassification = {
|
||||
providerId: { classification: 'PublicNonPersonalData'; purpose: 'FeatureInsight'; comment: 'The identifier of the provider that this codeblock response came from.' };
|
||||
newFile: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether the code was inserted into a new untitled file.' };
|
||||
owner: 'roblourens';
|
||||
comment: 'Provides insight into the usage of InteractiveSession features.';
|
||||
};
|
||||
@@ -90,6 +92,18 @@ type InteractiveSessionCommandClassification = {
|
||||
comment: 'Provides insight into the usage of InteractiveSession features.';
|
||||
};
|
||||
|
||||
type InteractiveSessionTerminalEvent = {
|
||||
providerId: string;
|
||||
languageId: string;
|
||||
};
|
||||
|
||||
type InteractiveSessionTerminalClassification = {
|
||||
providerId: { classification: 'PublicNonPersonalData'; purpose: 'FeatureInsight'; comment: 'The identifier of the provider that this codeblock response came from.' };
|
||||
languageId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The language of the code that was run in the terminal.' };
|
||||
owner: 'roblourens';
|
||||
comment: 'Provides insight into the usage of InteractiveSession features.';
|
||||
};
|
||||
|
||||
export class InteractiveSessionService extends Disposable implements IInteractiveSessionService {
|
||||
declare _serviceBrand: undefined;
|
||||
|
||||
@@ -148,6 +162,7 @@ export class InteractiveSessionService extends Disposable implements IInteractiv
|
||||
} else if (action.action.kind === 'insert') {
|
||||
this.telemetryService.publicLog2<InteractiveSessionInsertEvent, InteractiveSessionInsertClassification>('interactiveSessionInsert', {
|
||||
providerId: action.providerId,
|
||||
newFile: !!action.action.newFile
|
||||
});
|
||||
} else if (action.action.kind === 'command') {
|
||||
const command = CommandsRegistry.getCommand(action.action.command.commandId);
|
||||
@@ -156,6 +171,11 @@ export class InteractiveSessionService extends Disposable implements IInteractiv
|
||||
providerId: action.providerId,
|
||||
commandId
|
||||
});
|
||||
} else if (action.action.kind === 'runInTerminal') {
|
||||
this.telemetryService.publicLog2<InteractiveSessionTerminalEvent, InteractiveSessionTerminalClassification>('interactiveSessionRunInTerminal', {
|
||||
providerId: action.providerId,
|
||||
languageId: action.action.languageId ?? ''
|
||||
});
|
||||
}
|
||||
|
||||
this._onDidPerformUserAction.fire(action);
|
||||
|
||||
+9
-1
@@ -185,6 +185,14 @@ declare module 'vscode' {
|
||||
responseId: string;
|
||||
codeBlockIndex: number;
|
||||
totalCharacters: number;
|
||||
newFile?: boolean;
|
||||
}
|
||||
|
||||
export interface InteractiveSessionTerminalAction {
|
||||
kind: 'runInTerminal';
|
||||
responseId: string;
|
||||
codeBlockIndex: number;
|
||||
languageId?: string;
|
||||
}
|
||||
|
||||
export interface InteractiveSessionCommandAction {
|
||||
@@ -192,7 +200,7 @@ declare module 'vscode' {
|
||||
command: InteractiveResponseCommand;
|
||||
}
|
||||
|
||||
export type InteractiveSessionUserAction = InteractiveSessionVoteAction | InteractiveSessionCopyAction | InteractiveSessionInsertAction | InteractiveSessionCommandAction;
|
||||
export type InteractiveSessionUserAction = InteractiveSessionVoteAction | InteractiveSessionCopyAction | InteractiveSessionInsertAction | InteractiveSessionTerminalAction | InteractiveSessionCommandAction;
|
||||
|
||||
export interface InteractiveSessionUserActionEvent {
|
||||
action: InteractiveSessionUserAction;
|
||||
|
||||
Reference in New Issue
Block a user