More codeblock actions and padding tweak (#180017)

* Fix chat codeblock padding

* Add more codeblock actions
This commit is contained in:
Rob Lourens
2023-04-14 17:22:06 -07:00
committed by GitHub
parent 6da2ce447d
commit 682afdd61d
5 changed files with 163 additions and 7 deletions
@@ -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,
}
});
}
});
}
@@ -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
View File
@@ -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;