mirror of
https://github.com/Microsoft/vscode
synced 2024-09-19 18:48:00 +00:00
part/controller/widget
This commit is contained in:
parent
57d450cfc6
commit
17f0e23a7f
|
@ -3,306 +3,50 @@
|
||||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
import { $, Dimension, addDisposableListener, append, getTotalWidth, h } from 'vs/base/browser/dom';
|
|
||||||
import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar';
|
|
||||||
import { Queue, raceCancellationError } from 'vs/base/common/async';
|
import { Queue, raceCancellationError } from 'vs/base/common/async';
|
||||||
import { CancellationTokenSource } from 'vs/base/common/cancellation';
|
import { CancellationTokenSource } from 'vs/base/common/cancellation';
|
||||||
import { Codicon } from 'vs/base/common/codicons';
|
import { Codicon } from 'vs/base/common/codicons';
|
||||||
import { Event } from 'vs/base/common/event';
|
import { Event } from 'vs/base/common/event';
|
||||||
import { MarkdownString } from 'vs/base/common/htmlContent';
|
import { MarkdownString } from 'vs/base/common/htmlContent';
|
||||||
import { KeyCode } from 'vs/base/common/keyCodes';
|
import { KeyCode } from 'vs/base/common/keyCodes';
|
||||||
import { Lazy } from 'vs/base/common/lazy';
|
|
||||||
import { Disposable } from 'vs/base/common/lifecycle';
|
import { Disposable } from 'vs/base/common/lifecycle';
|
||||||
import { MarshalledId } from 'vs/base/common/marshallingIds';
|
|
||||||
import { MovingAverage } from 'vs/base/common/numbers';
|
import { MovingAverage } from 'vs/base/common/numbers';
|
||||||
import { StopWatch } from 'vs/base/common/stopwatch';
|
import { StopWatch } from 'vs/base/common/stopwatch';
|
||||||
import { assertType } from 'vs/base/common/types';
|
import { assertType } from 'vs/base/common/types';
|
||||||
import { URI } from 'vs/base/common/uri';
|
|
||||||
import { generateUuid } from 'vs/base/common/uuid';
|
import { generateUuid } from 'vs/base/common/uuid';
|
||||||
import { IActiveCodeEditor } from 'vs/editor/browser/editorBrowser';
|
import { IActiveCodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||||
import { EditorExtensionsRegistry } from 'vs/editor/browser/editorExtensions';
|
|
||||||
import { CodeEditorWidget, ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditorWidget';
|
|
||||||
import { EditorOption } from 'vs/editor/common/config/editorOptions';
|
|
||||||
import { ISingleEditOperation } from 'vs/editor/common/core/editOperation';
|
import { ISingleEditOperation } from 'vs/editor/common/core/editOperation';
|
||||||
import { Position } from 'vs/editor/common/core/position';
|
import { Position } from 'vs/editor/common/core/position';
|
||||||
import { Selection } from 'vs/editor/common/core/selection';
|
import { Selection } from 'vs/editor/common/core/selection';
|
||||||
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
|
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
|
||||||
import { TextEdit } from 'vs/editor/common/languages';
|
import { TextEdit } from 'vs/editor/common/languages';
|
||||||
import { ICursorStateComputer, ITextModel } from 'vs/editor/common/model';
|
import { ICursorStateComputer } from 'vs/editor/common/model';
|
||||||
import { IEditorWorkerService } from 'vs/editor/common/services/editorWorker';
|
import { IEditorWorkerService } from 'vs/editor/common/services/editorWorker';
|
||||||
import { IModelService } from 'vs/editor/common/services/model';
|
|
||||||
import { SnippetController2 } from 'vs/editor/contrib/snippet/browser/snippetController2';
|
|
||||||
import { SuggestController } from 'vs/editor/contrib/suggest/browser/suggestController';
|
|
||||||
import { localize } from 'vs/nls';
|
import { localize } from 'vs/nls';
|
||||||
import { MenuWorkbenchToolBar } from 'vs/platform/actions/browser/toolbar';
|
|
||||||
import { MenuId, registerAction2 } from 'vs/platform/actions/common/actions';
|
import { MenuId, registerAction2 } from 'vs/platform/actions/common/actions';
|
||||||
import { ContextKeyExpr, IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey';
|
import { ContextKeyExpr, IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||||
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||||
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||||
import { AsyncProgress } from 'vs/platform/progress/common/progress';
|
import { AsyncProgress } from 'vs/platform/progress/common/progress';
|
||||||
import { countWords } from 'vs/workbench/contrib/chat/common/chatWordCounter';
|
import { countWords } from 'vs/workbench/contrib/chat/common/chatWordCounter';
|
||||||
import { IInlineChatSessionService, ReplyResponse, Session, SessionPrompt } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession';
|
import { IInlineChatSessionService, ReplyResponse, Session, SessionPrompt } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession';
|
||||||
import { ProgressingEditsOptions, asProgressiveEdit, performAsyncTextEdit } from 'vs/workbench/contrib/inlineChat/browser/inlineChatStrategies';
|
import { ProgressingEditsOptions, asProgressiveEdit, performAsyncTextEdit } from 'vs/workbench/contrib/inlineChat/browser/inlineChatStrategies';
|
||||||
import { _inputEditorOptions } from 'vs/workbench/contrib/inlineChat/browser/inlineChatWidget';
|
|
||||||
import { CTX_INLINE_CHAT_HAS_PROVIDER, EditMode, IInlineChatProgressItem, IInlineChatRequest } from 'vs/workbench/contrib/inlineChat/common/inlineChat';
|
import { CTX_INLINE_CHAT_HAS_PROVIDER, EditMode, IInlineChatProgressItem, IInlineChatRequest } from 'vs/workbench/contrib/inlineChat/common/inlineChat';
|
||||||
import { CELL_TITLE_CELL_GROUP_ID, INotebookCellActionContext, NotebookCellAction } from 'vs/workbench/contrib/notebook/browser/controller/coreActions';
|
import { CELL_TITLE_CELL_GROUP_ID, INotebookCellActionContext, NotebookCellAction } from 'vs/workbench/contrib/notebook/browser/controller/coreActions';
|
||||||
import { insertNewCell } from 'vs/workbench/contrib/notebook/browser/controller/insertCellActions';
|
import { insertNewCell } from 'vs/workbench/contrib/notebook/browser/controller/insertCellActions';
|
||||||
import { CellFocusMode, ICellViewModel, INotebookEditorDelegate } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
|
import { ICellViewModel, INotebookEditorDelegate } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
|
||||||
import { CellContentPart } from 'vs/workbench/contrib/notebook/browser/view/cellPart';
|
import { CTX_NOTEBOOK_CELL_CHAT_FOCUSED, CellChatWidget, MENU_NOTEBOOK_CELL_CHAT_WIDGET } from 'vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatWidget';
|
||||||
import { CellKind, NotebookSetting } from 'vs/workbench/contrib/notebook/common/notebookCommon';
|
import { CellKind, NotebookSetting } from 'vs/workbench/contrib/notebook/common/notebookCommon';
|
||||||
import { NOTEBOOK_EDITOR_EDITABLE } from 'vs/workbench/contrib/notebook/common/notebookContextKeys';
|
import { NOTEBOOK_EDITOR_EDITABLE } from 'vs/workbench/contrib/notebook/common/notebookContextKeys';
|
||||||
|
|
||||||
const CTX_NOTEBOOK_CELL_CHAT_FOCUSED = new RawContextKey<boolean>('notebookCellChatFocused', false, localize('notebookCellChatFocused', "Whether the cell chat editor is focused"));
|
|
||||||
const CTX_NOTEBOOK_CHAT_HAS_ACTIVE_REQUEST = new RawContextKey<boolean>('notebookChatHasActiveRequest', false, localize('notebookChatHasActiveRequest', "Whether the cell chat editor has an active request"));
|
const CTX_NOTEBOOK_CHAT_HAS_ACTIVE_REQUEST = new RawContextKey<boolean>('notebookChatHasActiveRequest', false, localize('notebookChatHasActiveRequest', "Whether the cell chat editor has an active request"));
|
||||||
export const MENU_NOTEBOOK_CELL_CHAT_WIDGET = MenuId.for('notebookCellChatWidget');
|
|
||||||
|
|
||||||
export class CellChatPart extends CellContentPart {
|
interface ICellChatPart {
|
||||||
private readonly _elements = h(
|
activeCell: ICellViewModel | undefined;
|
||||||
'div.cell-chat-container@root',
|
getWidget(): CellChatWidget;
|
||||||
[
|
|
||||||
h('div.body', [
|
|
||||||
h('div.content@content', [
|
|
||||||
h('div.input@input', [
|
|
||||||
h('div.editor-placeholder@placeholder'),
|
|
||||||
h('div.editor-container@editor'),
|
|
||||||
]),
|
|
||||||
h('div.toolbar@editorToolbar'),
|
|
||||||
]),
|
|
||||||
]),
|
|
||||||
h('div.progress@progress'),
|
|
||||||
h('div.status@status')
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
private _controller: NotebookCellChatController | undefined;
|
|
||||||
|
|
||||||
get activeCell() {
|
|
||||||
return this.currentCell;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _widget: Lazy<CellChatWidget>;
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
private readonly _notebookEditor: INotebookEditorDelegate,
|
|
||||||
partContainer: HTMLElement,
|
|
||||||
@IInstantiationService private readonly _instantiationService: IInstantiationService,
|
|
||||||
) {
|
|
||||||
super();
|
|
||||||
|
|
||||||
this._widget = new Lazy(() => this._instantiationService.createInstance(CellChatWidget, this._notebookEditor, partContainer));
|
|
||||||
}
|
|
||||||
|
|
||||||
getWidget() {
|
|
||||||
return this._widget.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
override didRenderCell(element: ICellViewModel): void {
|
|
||||||
this._controller?.dispose();
|
|
||||||
this._controller = this._instantiationService.createInstance(NotebookCellChatController, this._notebookEditor, this, element);
|
|
||||||
|
|
||||||
super.didRenderCell(element);
|
|
||||||
}
|
|
||||||
|
|
||||||
override unrenderCell(element: ICellViewModel): void {
|
|
||||||
this._controller?.dispose();
|
|
||||||
this._controller = undefined;
|
|
||||||
super.unrenderCell(element);
|
|
||||||
}
|
|
||||||
|
|
||||||
override updateInternalLayoutNow(element: ICellViewModel): void {
|
|
||||||
this._elements.root.style.width = `${element.layoutInfo.editorWidth}px`;
|
|
||||||
this._controller?.layout();
|
|
||||||
}
|
|
||||||
|
|
||||||
override dispose() {
|
|
||||||
this._controller?.dispose();
|
|
||||||
this._controller = undefined;
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class CellChatWidget extends Disposable {
|
export class NotebookCellChatController extends Disposable {
|
||||||
private static _modelPool: number = 1;
|
|
||||||
|
|
||||||
private readonly _elements = h(
|
|
||||||
'div.cell-chat-container@root',
|
|
||||||
[
|
|
||||||
h('div.body', [
|
|
||||||
h('div.content@content', [
|
|
||||||
h('div.input@input', [
|
|
||||||
h('div.editor-placeholder@placeholder'),
|
|
||||||
h('div.editor-container@editor'),
|
|
||||||
]),
|
|
||||||
h('div.toolbar@editorToolbar'),
|
|
||||||
]),
|
|
||||||
]),
|
|
||||||
h('div.progress@progress'),
|
|
||||||
h('div.status@status')
|
|
||||||
]
|
|
||||||
);
|
|
||||||
private readonly _progressBar: ProgressBar;
|
|
||||||
private readonly _toolbar: MenuWorkbenchToolBar;
|
|
||||||
|
|
||||||
private readonly _inputEditor: IActiveCodeEditor;
|
|
||||||
private readonly _inputModel: ITextModel;
|
|
||||||
private readonly _ctxInputEditorFocused: IContextKey<boolean>;
|
|
||||||
|
|
||||||
private _activeCell: ICellViewModel | undefined;
|
|
||||||
|
|
||||||
set placeholder(value: string) {
|
|
||||||
this._elements.placeholder.innerText = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
private readonly _notebookEditor: INotebookEditorDelegate,
|
|
||||||
_partContainer: HTMLElement,
|
|
||||||
@IModelService private readonly _modelService: IModelService,
|
|
||||||
@IInstantiationService private readonly _instantiationService: IInstantiationService,
|
|
||||||
@IContextKeyService private readonly _contextKeyService: IContextKeyService
|
|
||||||
) {
|
|
||||||
super();
|
|
||||||
append(_partContainer, this._elements.root);
|
|
||||||
this._elements.input.style.height = '24px';
|
|
||||||
|
|
||||||
const codeEditorWidgetOptions: ICodeEditorWidgetOptions = {
|
|
||||||
isSimpleWidget: true,
|
|
||||||
contributions: EditorExtensionsRegistry.getSomeEditorContributions([
|
|
||||||
SnippetController2.ID,
|
|
||||||
SuggestController.ID
|
|
||||||
])
|
|
||||||
};
|
|
||||||
|
|
||||||
this._inputEditor = <IActiveCodeEditor>this._instantiationService.createInstance(CodeEditorWidget, this._elements.editor, {
|
|
||||||
..._inputEditorOptions,
|
|
||||||
ariaLabel: localize('cell-chat-aria-label', "Cell Chat Input"),
|
|
||||||
}, codeEditorWidgetOptions);
|
|
||||||
this._register(this._inputEditor);
|
|
||||||
const uri = URI.from({ scheme: 'vscode', authority: 'inline-chat', path: `/notebook-cell-chat/model${CellChatWidget._modelPool++}.txt` });
|
|
||||||
this._inputModel = this._register(this._modelService.getModel(uri) ?? this._modelService.createModel('', null, uri));
|
|
||||||
this._inputEditor.setModel(this._inputModel);
|
|
||||||
|
|
||||||
// placeholder
|
|
||||||
this._elements.placeholder.style.fontSize = `${this._inputEditor.getOption(EditorOption.fontSize)}px`;
|
|
||||||
this._elements.placeholder.style.lineHeight = `${this._inputEditor.getOption(EditorOption.lineHeight)}px`;
|
|
||||||
this._register(addDisposableListener(this._elements.placeholder, 'click', () => this._inputEditor.focus()));
|
|
||||||
|
|
||||||
const togglePlaceholder = () => {
|
|
||||||
const hasText = this._inputModel.getValueLength() > 0;
|
|
||||||
this._elements.placeholder.classList.toggle('hidden', hasText);
|
|
||||||
};
|
|
||||||
this._store.add(this._inputModel.onDidChangeContent(togglePlaceholder));
|
|
||||||
togglePlaceholder();
|
|
||||||
|
|
||||||
// toolbar
|
|
||||||
this._toolbar = this._register(this._instantiationService.createInstance(MenuWorkbenchToolBar, this._elements.editorToolbar, MENU_NOTEBOOK_CELL_CHAT_WIDGET, {
|
|
||||||
telemetrySource: 'interactiveEditorWidget-toolbar',
|
|
||||||
toolbarOptions: { primaryGroup: 'main' }
|
|
||||||
}));
|
|
||||||
|
|
||||||
// Create chat response div
|
|
||||||
const copilotGeneratedCodeSpan = $('span.copilot-generated-code', {}, 'Copilot generated code may be incorrect');
|
|
||||||
this._elements.status.appendChild(copilotGeneratedCodeSpan);
|
|
||||||
|
|
||||||
this._register(this._inputEditor.onDidFocusEditorWidget(() => {
|
|
||||||
if (this._activeCell) {
|
|
||||||
this._activeCell.focusMode = CellFocusMode.ChatInput;
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
this._ctxInputEditorFocused = CTX_NOTEBOOK_CELL_CHAT_FOCUSED.bindTo(this._contextKeyService);
|
|
||||||
this._register(this._inputEditor.onDidFocusEditorWidget(() => {
|
|
||||||
this._ctxInputEditorFocused.set(true);
|
|
||||||
}));
|
|
||||||
this._register(this._inputEditor.onDidBlurEditorWidget(() => {
|
|
||||||
this._ctxInputEditorFocused.set(false);
|
|
||||||
}));
|
|
||||||
|
|
||||||
this._progressBar = new ProgressBar(this._elements.progress);
|
|
||||||
this._register(this._progressBar);
|
|
||||||
}
|
|
||||||
|
|
||||||
show(element: ICellViewModel) {
|
|
||||||
this._elements.root.style.display = 'block';
|
|
||||||
|
|
||||||
this._activeCell = element;
|
|
||||||
|
|
||||||
this._toolbar.context = <INotebookCellActionContext>{
|
|
||||||
ui: true,
|
|
||||||
cell: element,
|
|
||||||
notebookEditor: this._notebookEditor,
|
|
||||||
$mid: MarshalledId.NotebookCellActionContext
|
|
||||||
};
|
|
||||||
|
|
||||||
this.layout();
|
|
||||||
this._inputEditor.focus();
|
|
||||||
this._activeCell.chatHeight = 62;
|
|
||||||
}
|
|
||||||
|
|
||||||
hide() {
|
|
||||||
this._elements.root.style.display = 'none';
|
|
||||||
if (this._activeCell) {
|
|
||||||
this._activeCell.chatHeight = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getInput() {
|
|
||||||
return this._inputEditor.getValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
updateProgress(show: boolean) {
|
|
||||||
if (show) {
|
|
||||||
this._progressBar.infinite();
|
|
||||||
} else {
|
|
||||||
this._progressBar.stop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
layout() {
|
|
||||||
if (this._activeCell) {
|
|
||||||
const innerEditorWidth = this._activeCell.layoutInfo.editorWidth - (getTotalWidth(this._elements.editorToolbar) + 8 /* L/R-padding */);
|
|
||||||
this._inputEditor.layout(new Dimension(innerEditorWidth, this._inputEditor.getContentHeight()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class EditStrategy {
|
|
||||||
private _editCount: number = 0;
|
|
||||||
|
|
||||||
async makeProgressiveChanges(editor: IActiveCodeEditor, edits: ISingleEditOperation[], opts: ProgressingEditsOptions): Promise<void> {
|
|
||||||
// push undo stop before first edit
|
|
||||||
if (++this._editCount === 1) {
|
|
||||||
editor.pushUndoStop();
|
|
||||||
}
|
|
||||||
|
|
||||||
const durationInSec = opts.duration / 1000;
|
|
||||||
for (const edit of edits) {
|
|
||||||
const wordCount = countWords(edit.text ?? '');
|
|
||||||
const speed = wordCount / durationInSec;
|
|
||||||
// console.log({ durationInSec, wordCount, speed: wordCount / durationInSec });
|
|
||||||
await performAsyncTextEdit(editor.getModel(), asProgressiveEdit(edit, speed, opts.token));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async makeChanges(editor: IActiveCodeEditor, edits: ISingleEditOperation[]): Promise<void> {
|
|
||||||
const cursorStateComputerAndInlineDiffCollection: ICursorStateComputer = (undoEdits) => {
|
|
||||||
let last: Position | null = null;
|
|
||||||
for (const edit of undoEdits) {
|
|
||||||
last = !last || last.isBefore(edit.range.getEndPosition()) ? edit.range.getEndPosition() : last;
|
|
||||||
// this._inlineDiffDecorations.collectEditOperation(edit);
|
|
||||||
}
|
|
||||||
return last && [Selection.fromPositions(last)];
|
|
||||||
};
|
|
||||||
|
|
||||||
// push undo stop before first edit
|
|
||||||
if (++this._editCount === 1) {
|
|
||||||
editor.pushUndoStop();
|
|
||||||
}
|
|
||||||
editor.executeEdits('inline-chat-live', edits, cursorStateComputerAndInlineDiffCollection);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class NotebookCellChatController extends Disposable {
|
|
||||||
private static _cellChatControllers = new WeakMap<ICellViewModel, NotebookCellChatController>();
|
private static _cellChatControllers = new WeakMap<ICellViewModel, NotebookCellChatController>();
|
||||||
|
|
||||||
static get(cell: ICellViewModel): NotebookCellChatController | undefined {
|
static get(cell: ICellViewModel): NotebookCellChatController | undefined {
|
||||||
|
@ -316,7 +60,7 @@ class NotebookCellChatController extends Disposable {
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly _notebookEditor: INotebookEditorDelegate,
|
private readonly _notebookEditor: INotebookEditorDelegate,
|
||||||
private readonly _chatPart: CellChatPart,
|
private readonly _chatPart: ICellChatPart,
|
||||||
private readonly _cell: ICellViewModel,
|
private readonly _cell: ICellViewModel,
|
||||||
@IContextKeyService private readonly _contextKeyService: IContextKeyService,
|
@IContextKeyService private readonly _contextKeyService: IContextKeyService,
|
||||||
@IInlineChatSessionService private readonly _inlineChatSessionService: IInlineChatSessionService,
|
@IInlineChatSessionService private readonly _inlineChatSessionService: IInlineChatSessionService,
|
||||||
|
@ -503,6 +247,42 @@ class NotebookCellChatController extends Disposable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class EditStrategy {
|
||||||
|
private _editCount: number = 0;
|
||||||
|
|
||||||
|
async makeProgressiveChanges(editor: IActiveCodeEditor, edits: ISingleEditOperation[], opts: ProgressingEditsOptions): Promise<void> {
|
||||||
|
// push undo stop before first edit
|
||||||
|
if (++this._editCount === 1) {
|
||||||
|
editor.pushUndoStop();
|
||||||
|
}
|
||||||
|
|
||||||
|
const durationInSec = opts.duration / 1000;
|
||||||
|
for (const edit of edits) {
|
||||||
|
const wordCount = countWords(edit.text ?? '');
|
||||||
|
const speed = wordCount / durationInSec;
|
||||||
|
// console.log({ durationInSec, wordCount, speed: wordCount / durationInSec });
|
||||||
|
await performAsyncTextEdit(editor.getModel(), asProgressiveEdit(edit, speed, opts.token));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async makeChanges(editor: IActiveCodeEditor, edits: ISingleEditOperation[]): Promise<void> {
|
||||||
|
const cursorStateComputerAndInlineDiffCollection: ICursorStateComputer = (undoEdits) => {
|
||||||
|
let last: Position | null = null;
|
||||||
|
for (const edit of undoEdits) {
|
||||||
|
last = !last || last.isBefore(edit.range.getEndPosition()) ? edit.range.getEndPosition() : last;
|
||||||
|
// this._inlineDiffDecorations.collectEditOperation(edit);
|
||||||
|
}
|
||||||
|
return last && [Selection.fromPositions(last)];
|
||||||
|
};
|
||||||
|
|
||||||
|
// push undo stop before first edit
|
||||||
|
if (++this._editCount === 1) {
|
||||||
|
editor.pushUndoStop();
|
||||||
|
}
|
||||||
|
editor.executeEdits('inline-chat-live', edits, cursorStateComputerAndInlineDiffCollection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
registerAction2(class extends NotebookCellAction {
|
registerAction2(class extends NotebookCellAction {
|
||||||
constructor() {
|
constructor() {
|
||||||
super(
|
super(
|
|
@ -0,0 +1,59 @@
|
||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
import { Lazy } from 'vs/base/common/lazy';
|
||||||
|
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||||
|
import { ICellViewModel, INotebookEditorDelegate } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
|
||||||
|
import { CellContentPart } from 'vs/workbench/contrib/notebook/browser/view/cellPart';
|
||||||
|
import { NotebookCellChatController } from 'vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatController';
|
||||||
|
import { CellChatWidget } from 'vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatWidget';
|
||||||
|
|
||||||
|
export class CellChatPart extends CellContentPart {
|
||||||
|
private _controller: NotebookCellChatController | undefined;
|
||||||
|
|
||||||
|
get activeCell() {
|
||||||
|
return this.currentCell;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _widget: Lazy<CellChatWidget>;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private readonly _notebookEditor: INotebookEditorDelegate,
|
||||||
|
partContainer: HTMLElement,
|
||||||
|
@IInstantiationService private readonly _instantiationService: IInstantiationService,
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this._widget = new Lazy(() => this._instantiationService.createInstance(CellChatWidget, this._notebookEditor, partContainer));
|
||||||
|
}
|
||||||
|
|
||||||
|
getWidget() {
|
||||||
|
return this._widget.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
override didRenderCell(element: ICellViewModel): void {
|
||||||
|
this._controller?.dispose();
|
||||||
|
this._controller = this._instantiationService.createInstance(NotebookCellChatController, this._notebookEditor, this, element);
|
||||||
|
|
||||||
|
super.didRenderCell(element);
|
||||||
|
}
|
||||||
|
|
||||||
|
override unrenderCell(element: ICellViewModel): void {
|
||||||
|
this._controller?.dispose();
|
||||||
|
this._controller = undefined;
|
||||||
|
super.unrenderCell(element);
|
||||||
|
}
|
||||||
|
|
||||||
|
override updateInternalLayoutNow(element: ICellViewModel): void {
|
||||||
|
this._controller?.layout();
|
||||||
|
}
|
||||||
|
|
||||||
|
override dispose() {
|
||||||
|
this._controller?.dispose();
|
||||||
|
this._controller = undefined;
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,174 @@
|
||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
import { $, Dimension, addDisposableListener, append, getTotalWidth, h } from 'vs/base/browser/dom';
|
||||||
|
import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar';
|
||||||
|
import { Disposable } from 'vs/base/common/lifecycle';
|
||||||
|
import { MarshalledId } from 'vs/base/common/marshallingIds';
|
||||||
|
import { URI } from 'vs/base/common/uri';
|
||||||
|
import { IActiveCodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||||
|
import { EditorExtensionsRegistry } from 'vs/editor/browser/editorExtensions';
|
||||||
|
import { CodeEditorWidget, ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditorWidget';
|
||||||
|
import { EditorOption } from 'vs/editor/common/config/editorOptions';
|
||||||
|
import { ITextModel } from 'vs/editor/common/model';
|
||||||
|
import { IModelService } from 'vs/editor/common/services/model';
|
||||||
|
import { SnippetController2 } from 'vs/editor/contrib/snippet/browser/snippetController2';
|
||||||
|
import { SuggestController } from 'vs/editor/contrib/suggest/browser/suggestController';
|
||||||
|
import { localize } from 'vs/nls';
|
||||||
|
import { MenuWorkbenchToolBar } from 'vs/platform/actions/browser/toolbar';
|
||||||
|
import { MenuId } from 'vs/platform/actions/common/actions';
|
||||||
|
import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||||
|
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||||
|
import { _inputEditorOptions } from 'vs/workbench/contrib/inlineChat/browser/inlineChatWidget';
|
||||||
|
import { INotebookCellActionContext } from 'vs/workbench/contrib/notebook/browser/controller/coreActions';
|
||||||
|
import { CellFocusMode, ICellViewModel, INotebookEditorDelegate } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
|
||||||
|
|
||||||
|
export const CTX_NOTEBOOK_CELL_CHAT_FOCUSED = new RawContextKey<boolean>('notebookCellChatFocused', false, localize('notebookCellChatFocused', "Whether the cell chat editor is focused"));
|
||||||
|
export const MENU_NOTEBOOK_CELL_CHAT_WIDGET = MenuId.for('notebookCellChatWidget');
|
||||||
|
|
||||||
|
export class CellChatWidget extends Disposable {
|
||||||
|
private static _modelPool: number = 1;
|
||||||
|
|
||||||
|
private readonly _elements = h(
|
||||||
|
'div.cell-chat-container@root',
|
||||||
|
[
|
||||||
|
h('div.body', [
|
||||||
|
h('div.content@content', [
|
||||||
|
h('div.input@input', [
|
||||||
|
h('div.editor-placeholder@placeholder'),
|
||||||
|
h('div.editor-container@editor'),
|
||||||
|
]),
|
||||||
|
h('div.toolbar@editorToolbar'),
|
||||||
|
]),
|
||||||
|
]),
|
||||||
|
h('div.progress@progress'),
|
||||||
|
h('div.status@status')
|
||||||
|
]
|
||||||
|
);
|
||||||
|
private readonly _progressBar: ProgressBar;
|
||||||
|
private readonly _toolbar: MenuWorkbenchToolBar;
|
||||||
|
|
||||||
|
private readonly _inputEditor: IActiveCodeEditor;
|
||||||
|
private readonly _inputModel: ITextModel;
|
||||||
|
private readonly _ctxInputEditorFocused: IContextKey<boolean>;
|
||||||
|
|
||||||
|
private _activeCell: ICellViewModel | undefined;
|
||||||
|
|
||||||
|
set placeholder(value: string) {
|
||||||
|
this._elements.placeholder.innerText = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private readonly _notebookEditor: INotebookEditorDelegate,
|
||||||
|
_partContainer: HTMLElement,
|
||||||
|
@IModelService private readonly _modelService: IModelService,
|
||||||
|
@IInstantiationService private readonly _instantiationService: IInstantiationService,
|
||||||
|
@IContextKeyService private readonly _contextKeyService: IContextKeyService
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
append(_partContainer, this._elements.root);
|
||||||
|
this._elements.input.style.height = '24px';
|
||||||
|
|
||||||
|
const codeEditorWidgetOptions: ICodeEditorWidgetOptions = {
|
||||||
|
isSimpleWidget: true,
|
||||||
|
contributions: EditorExtensionsRegistry.getSomeEditorContributions([
|
||||||
|
SnippetController2.ID,
|
||||||
|
SuggestController.ID
|
||||||
|
])
|
||||||
|
};
|
||||||
|
|
||||||
|
this._inputEditor = <IActiveCodeEditor>this._instantiationService.createInstance(CodeEditorWidget, this._elements.editor, {
|
||||||
|
..._inputEditorOptions,
|
||||||
|
ariaLabel: localize('cell-chat-aria-label', "Cell Chat Input"),
|
||||||
|
}, codeEditorWidgetOptions);
|
||||||
|
this._register(this._inputEditor);
|
||||||
|
const uri = URI.from({ scheme: 'vscode', authority: 'inline-chat', path: `/notebook-cell-chat/model${CellChatWidget._modelPool++}.txt` });
|
||||||
|
this._inputModel = this._register(this._modelService.getModel(uri) ?? this._modelService.createModel('', null, uri));
|
||||||
|
this._inputEditor.setModel(this._inputModel);
|
||||||
|
|
||||||
|
// placeholder
|
||||||
|
this._elements.placeholder.style.fontSize = `${this._inputEditor.getOption(EditorOption.fontSize)}px`;
|
||||||
|
this._elements.placeholder.style.lineHeight = `${this._inputEditor.getOption(EditorOption.lineHeight)}px`;
|
||||||
|
this._register(addDisposableListener(this._elements.placeholder, 'click', () => this._inputEditor.focus()));
|
||||||
|
|
||||||
|
const togglePlaceholder = () => {
|
||||||
|
const hasText = this._inputModel.getValueLength() > 0;
|
||||||
|
this._elements.placeholder.classList.toggle('hidden', hasText);
|
||||||
|
};
|
||||||
|
this._store.add(this._inputModel.onDidChangeContent(togglePlaceholder));
|
||||||
|
togglePlaceholder();
|
||||||
|
|
||||||
|
// toolbar
|
||||||
|
this._toolbar = this._register(this._instantiationService.createInstance(MenuWorkbenchToolBar, this._elements.editorToolbar, MENU_NOTEBOOK_CELL_CHAT_WIDGET, {
|
||||||
|
telemetrySource: 'interactiveEditorWidget-toolbar',
|
||||||
|
toolbarOptions: { primaryGroup: 'main' }
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Create chat response div
|
||||||
|
const copilotGeneratedCodeSpan = $('span.copilot-generated-code', {}, 'Copilot generated code may be incorrect');
|
||||||
|
this._elements.status.appendChild(copilotGeneratedCodeSpan);
|
||||||
|
|
||||||
|
this._register(this._inputEditor.onDidFocusEditorWidget(() => {
|
||||||
|
if (this._activeCell) {
|
||||||
|
this._activeCell.focusMode = CellFocusMode.ChatInput;
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
this._ctxInputEditorFocused = CTX_NOTEBOOK_CELL_CHAT_FOCUSED.bindTo(this._contextKeyService);
|
||||||
|
this._register(this._inputEditor.onDidFocusEditorWidget(() => {
|
||||||
|
this._ctxInputEditorFocused.set(true);
|
||||||
|
}));
|
||||||
|
this._register(this._inputEditor.onDidBlurEditorWidget(() => {
|
||||||
|
this._ctxInputEditorFocused.set(false);
|
||||||
|
}));
|
||||||
|
|
||||||
|
this._progressBar = new ProgressBar(this._elements.progress);
|
||||||
|
this._register(this._progressBar);
|
||||||
|
}
|
||||||
|
|
||||||
|
show(element: ICellViewModel) {
|
||||||
|
this._elements.root.style.display = 'block';
|
||||||
|
|
||||||
|
this._activeCell = element;
|
||||||
|
|
||||||
|
this._toolbar.context = <INotebookCellActionContext>{
|
||||||
|
ui: true,
|
||||||
|
cell: element,
|
||||||
|
notebookEditor: this._notebookEditor,
|
||||||
|
$mid: MarshalledId.NotebookCellActionContext
|
||||||
|
};
|
||||||
|
|
||||||
|
this.layout();
|
||||||
|
this._inputEditor.focus();
|
||||||
|
this._activeCell.chatHeight = 62;
|
||||||
|
}
|
||||||
|
|
||||||
|
hide() {
|
||||||
|
this._elements.root.style.display = 'none';
|
||||||
|
if (this._activeCell) {
|
||||||
|
this._activeCell.chatHeight = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getInput() {
|
||||||
|
return this._inputEditor.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
updateProgress(show: boolean) {
|
||||||
|
if (show) {
|
||||||
|
this._progressBar.infinite();
|
||||||
|
} else {
|
||||||
|
this._progressBar.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
layout() {
|
||||||
|
if (this._activeCell) {
|
||||||
|
const innerEditorWidth = this._activeCell.layoutInfo.editorWidth - (getTotalWidth(this._elements.editorToolbar) + 8 /* L/R-padding */);
|
||||||
|
this._inputEditor.layout(new Dimension(innerEditorWidth, this._inputEditor.getContentHeight()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -25,7 +25,7 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||||
import { ICellViewModel, INotebookEditorDelegate } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
|
import { ICellViewModel, INotebookEditorDelegate } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
|
||||||
import { CellPartsCollection } from 'vs/workbench/contrib/notebook/browser/view/cellPart';
|
import { CellPartsCollection } from 'vs/workbench/contrib/notebook/browser/view/cellPart';
|
||||||
import { CellChatPart } from 'vs/workbench/contrib/notebook/browser/view/cellParts/cellChatWidget';
|
import { CellChatPart } from 'vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatPart';
|
||||||
import { CellComments } from 'vs/workbench/contrib/notebook/browser/view/cellParts/cellComments';
|
import { CellComments } from 'vs/workbench/contrib/notebook/browser/view/cellParts/cellComments';
|
||||||
import { CellContextKeyPart } from 'vs/workbench/contrib/notebook/browser/view/cellParts/cellContextKeys';
|
import { CellContextKeyPart } from 'vs/workbench/contrib/notebook/browser/view/cellParts/cellContextKeys';
|
||||||
import { CellDecorations } from 'vs/workbench/contrib/notebook/browser/view/cellParts/cellDecorations';
|
import { CellDecorations } from 'vs/workbench/contrib/notebook/browser/view/cellParts/cellDecorations';
|
||||||
|
|
Loading…
Reference in a new issue