Skip to content

Commit 17f0e23

Browse files
committed
part/controller/widget
1 parent 57d450c commit 17f0e23

4 files changed

Lines changed: 279 additions & 266 deletions

File tree

src/vs/workbench/contrib/notebook/browser/view/cellParts/cellChatWidget.ts renamed to src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatController.ts

Lines changed: 45 additions & 265 deletions
Original file line numberDiff line numberDiff line change
@@ -3,306 +3,50 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6-
import { $, Dimension, addDisposableListener, append, getTotalWidth, h } from 'vs/base/browser/dom';
7-
import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar';
86
import { Queue, raceCancellationError } from 'vs/base/common/async';
97
import { CancellationTokenSource } from 'vs/base/common/cancellation';
108
import { Codicon } from 'vs/base/common/codicons';
119
import { Event } from 'vs/base/common/event';
1210
import { MarkdownString } from 'vs/base/common/htmlContent';
1311
import { KeyCode } from 'vs/base/common/keyCodes';
14-
import { Lazy } from 'vs/base/common/lazy';
1512
import { Disposable } from 'vs/base/common/lifecycle';
16-
import { MarshalledId } from 'vs/base/common/marshallingIds';
1713
import { MovingAverage } from 'vs/base/common/numbers';
1814
import { StopWatch } from 'vs/base/common/stopwatch';
1915
import { assertType } from 'vs/base/common/types';
20-
import { URI } from 'vs/base/common/uri';
2116
import { generateUuid } from 'vs/base/common/uuid';
2217
import { IActiveCodeEditor } from 'vs/editor/browser/editorBrowser';
23-
import { EditorExtensionsRegistry } from 'vs/editor/browser/editorExtensions';
24-
import { CodeEditorWidget, ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditorWidget';
25-
import { EditorOption } from 'vs/editor/common/config/editorOptions';
2618
import { ISingleEditOperation } from 'vs/editor/common/core/editOperation';
2719
import { Position } from 'vs/editor/common/core/position';
2820
import { Selection } from 'vs/editor/common/core/selection';
2921
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
3022
import { TextEdit } from 'vs/editor/common/languages';
31-
import { ICursorStateComputer, ITextModel } from 'vs/editor/common/model';
23+
import { ICursorStateComputer } from 'vs/editor/common/model';
3224
import { IEditorWorkerService } from 'vs/editor/common/services/editorWorker';
33-
import { IModelService } from 'vs/editor/common/services/model';
34-
import { SnippetController2 } from 'vs/editor/contrib/snippet/browser/snippetController2';
35-
import { SuggestController } from 'vs/editor/contrib/suggest/browser/suggestController';
3625
import { localize } from 'vs/nls';
37-
import { MenuWorkbenchToolBar } from 'vs/platform/actions/browser/toolbar';
3826
import { MenuId, registerAction2 } from 'vs/platform/actions/common/actions';
3927
import { ContextKeyExpr, IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey';
40-
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
28+
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
4129
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
4230
import { AsyncProgress } from 'vs/platform/progress/common/progress';
4331
import { countWords } from 'vs/workbench/contrib/chat/common/chatWordCounter';
4432
import { IInlineChatSessionService, ReplyResponse, Session, SessionPrompt } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession';
4533
import { ProgressingEditsOptions, asProgressiveEdit, performAsyncTextEdit } from 'vs/workbench/contrib/inlineChat/browser/inlineChatStrategies';
46-
import { _inputEditorOptions } from 'vs/workbench/contrib/inlineChat/browser/inlineChatWidget';
4734
import { CTX_INLINE_CHAT_HAS_PROVIDER, EditMode, IInlineChatProgressItem, IInlineChatRequest } from 'vs/workbench/contrib/inlineChat/common/inlineChat';
4835
import { CELL_TITLE_CELL_GROUP_ID, INotebookCellActionContext, NotebookCellAction } from 'vs/workbench/contrib/notebook/browser/controller/coreActions';
4936
import { insertNewCell } from 'vs/workbench/contrib/notebook/browser/controller/insertCellActions';
50-
import { CellFocusMode, ICellViewModel, INotebookEditorDelegate } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
51-
import { CellContentPart } from 'vs/workbench/contrib/notebook/browser/view/cellPart';
37+
import { ICellViewModel, INotebookEditorDelegate } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
38+
import { CTX_NOTEBOOK_CELL_CHAT_FOCUSED, CellChatWidget, MENU_NOTEBOOK_CELL_CHAT_WIDGET } from 'vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatWidget';
5239
import { CellKind, NotebookSetting } from 'vs/workbench/contrib/notebook/common/notebookCommon';
5340
import { NOTEBOOK_EDITOR_EDITABLE } from 'vs/workbench/contrib/notebook/common/notebookContextKeys';
5441

55-
const CTX_NOTEBOOK_CELL_CHAT_FOCUSED = new RawContextKey<boolean>('notebookCellChatFocused', false, localize('notebookCellChatFocused', "Whether the cell chat editor is focused"));
5642
const CTX_NOTEBOOK_CHAT_HAS_ACTIVE_REQUEST = new RawContextKey<boolean>('notebookChatHasActiveRequest', false, localize('notebookChatHasActiveRequest', "Whether the cell chat editor has an active request"));
57-
export const MENU_NOTEBOOK_CELL_CHAT_WIDGET = MenuId.for('notebookCellChatWidget');
58-
59-
export class CellChatPart extends CellContentPart {
60-
private readonly _elements = h(
61-
'div.cell-chat-container@root',
62-
[
63-
h('div.body', [
64-
h('div.content@content', [
65-
h('div.input@input', [
66-
h('div.editor-placeholder@placeholder'),
67-
h('div.editor-container@editor'),
68-
]),
69-
h('div.toolbar@editorToolbar'),
70-
]),
71-
]),
72-
h('div.progress@progress'),
73-
h('div.status@status')
74-
]
75-
);
76-
77-
private _controller: NotebookCellChatController | undefined;
78-
79-
get activeCell() {
80-
return this.currentCell;
81-
}
82-
83-
private _widget: Lazy<CellChatWidget>;
84-
85-
constructor(
86-
private readonly _notebookEditor: INotebookEditorDelegate,
87-
partContainer: HTMLElement,
88-
@IInstantiationService private readonly _instantiationService: IInstantiationService,
89-
) {
90-
super();
91-
92-
this._widget = new Lazy(() => this._instantiationService.createInstance(CellChatWidget, this._notebookEditor, partContainer));
93-
}
94-
95-
getWidget() {
96-
return this._widget.value;
97-
}
98-
99-
override didRenderCell(element: ICellViewModel): void {
100-
this._controller?.dispose();
101-
this._controller = this._instantiationService.createInstance(NotebookCellChatController, this._notebookEditor, this, element);
102-
103-
super.didRenderCell(element);
104-
}
105-
106-
override unrenderCell(element: ICellViewModel): void {
107-
this._controller?.dispose();
108-
this._controller = undefined;
109-
super.unrenderCell(element);
110-
}
111-
112-
override updateInternalLayoutNow(element: ICellViewModel): void {
113-
this._elements.root.style.width = `${element.layoutInfo.editorWidth}px`;
114-
this._controller?.layout();
115-
}
116-
117-
override dispose() {
118-
this._controller?.dispose();
119-
this._controller = undefined;
120-
super.dispose();
121-
}
122-
}
123-
124-
class CellChatWidget extends Disposable {
125-
private static _modelPool: number = 1;
126-
127-
private readonly _elements = h(
128-
'div.cell-chat-container@root',
129-
[
130-
h('div.body', [
131-
h('div.content@content', [
132-
h('div.input@input', [
133-
h('div.editor-placeholder@placeholder'),
134-
h('div.editor-container@editor'),
135-
]),
136-
h('div.toolbar@editorToolbar'),
137-
]),
138-
]),
139-
h('div.progress@progress'),
140-
h('div.status@status')
141-
]
142-
);
143-
private readonly _progressBar: ProgressBar;
144-
private readonly _toolbar: MenuWorkbenchToolBar;
145-
146-
private readonly _inputEditor: IActiveCodeEditor;
147-
private readonly _inputModel: ITextModel;
148-
private readonly _ctxInputEditorFocused: IContextKey<boolean>;
149-
150-
private _activeCell: ICellViewModel | undefined;
151-
152-
set placeholder(value: string) {
153-
this._elements.placeholder.innerText = value;
154-
}
155-
156-
157-
constructor(
158-
private readonly _notebookEditor: INotebookEditorDelegate,
159-
_partContainer: HTMLElement,
160-
@IModelService private readonly _modelService: IModelService,
161-
@IInstantiationService private readonly _instantiationService: IInstantiationService,
162-
@IContextKeyService private readonly _contextKeyService: IContextKeyService
163-
) {
164-
super();
165-
append(_partContainer, this._elements.root);
166-
this._elements.input.style.height = '24px';
167-
168-
const codeEditorWidgetOptions: ICodeEditorWidgetOptions = {
169-
isSimpleWidget: true,
170-
contributions: EditorExtensionsRegistry.getSomeEditorContributions([
171-
SnippetController2.ID,
172-
SuggestController.ID
173-
])
174-
};
175-
176-
this._inputEditor = <IActiveCodeEditor>this._instantiationService.createInstance(CodeEditorWidget, this._elements.editor, {
177-
..._inputEditorOptions,
178-
ariaLabel: localize('cell-chat-aria-label', "Cell Chat Input"),
179-
}, codeEditorWidgetOptions);
180-
this._register(this._inputEditor);
181-
const uri = URI.from({ scheme: 'vscode', authority: 'inline-chat', path: `/notebook-cell-chat/model${CellChatWidget._modelPool++}.txt` });
182-
this._inputModel = this._register(this._modelService.getModel(uri) ?? this._modelService.createModel('', null, uri));
183-
this._inputEditor.setModel(this._inputModel);
184-
185-
// placeholder
186-
this._elements.placeholder.style.fontSize = `${this._inputEditor.getOption(EditorOption.fontSize)}px`;
187-
this._elements.placeholder.style.lineHeight = `${this._inputEditor.getOption(EditorOption.lineHeight)}px`;
188-
this._register(addDisposableListener(this._elements.placeholder, 'click', () => this._inputEditor.focus()));
189-
190-
const togglePlaceholder = () => {
191-
const hasText = this._inputModel.getValueLength() > 0;
192-
this._elements.placeholder.classList.toggle('hidden', hasText);
193-
};
194-
this._store.add(this._inputModel.onDidChangeContent(togglePlaceholder));
195-
togglePlaceholder();
196-
197-
// toolbar
198-
this._toolbar = this._register(this._instantiationService.createInstance(MenuWorkbenchToolBar, this._elements.editorToolbar, MENU_NOTEBOOK_CELL_CHAT_WIDGET, {
199-
telemetrySource: 'interactiveEditorWidget-toolbar',
200-
toolbarOptions: { primaryGroup: 'main' }
201-
}));
202-
203-
// Create chat response div
204-
const copilotGeneratedCodeSpan = $('span.copilot-generated-code', {}, 'Copilot generated code may be incorrect');
205-
this._elements.status.appendChild(copilotGeneratedCodeSpan);
206-
207-
this._register(this._inputEditor.onDidFocusEditorWidget(() => {
208-
if (this._activeCell) {
209-
this._activeCell.focusMode = CellFocusMode.ChatInput;
210-
}
211-
}));
212-
213-
this._ctxInputEditorFocused = CTX_NOTEBOOK_CELL_CHAT_FOCUSED.bindTo(this._contextKeyService);
214-
this._register(this._inputEditor.onDidFocusEditorWidget(() => {
215-
this._ctxInputEditorFocused.set(true);
216-
}));
217-
this._register(this._inputEditor.onDidBlurEditorWidget(() => {
218-
this._ctxInputEditorFocused.set(false);
219-
}));
220-
221-
this._progressBar = new ProgressBar(this._elements.progress);
222-
this._register(this._progressBar);
223-
}
224-
225-
show(element: ICellViewModel) {
226-
this._elements.root.style.display = 'block';
227-
228-
this._activeCell = element;
229-
230-
this._toolbar.context = <INotebookCellActionContext>{
231-
ui: true,
232-
cell: element,
233-
notebookEditor: this._notebookEditor,
234-
$mid: MarshalledId.NotebookCellActionContext
235-
};
236-
237-
this.layout();
238-
this._inputEditor.focus();
239-
this._activeCell.chatHeight = 62;
240-
}
241-
242-
hide() {
243-
this._elements.root.style.display = 'none';
244-
if (this._activeCell) {
245-
this._activeCell.chatHeight = 0;
246-
}
247-
}
248-
249-
getInput() {
250-
return this._inputEditor.getValue();
251-
}
252-
253-
updateProgress(show: boolean) {
254-
if (show) {
255-
this._progressBar.infinite();
256-
} else {
257-
this._progressBar.stop();
258-
}
259-
}
260-
261-
layout() {
262-
if (this._activeCell) {
263-
const innerEditorWidth = this._activeCell.layoutInfo.editorWidth - (getTotalWidth(this._elements.editorToolbar) + 8 /* L/R-padding */);
264-
this._inputEditor.layout(new Dimension(innerEditorWidth, this._inputEditor.getContentHeight()));
265-
}
266-
}
267-
}
268-
269-
class EditStrategy {
270-
private _editCount: number = 0;
271-
272-
async makeProgressiveChanges(editor: IActiveCodeEditor, edits: ISingleEditOperation[], opts: ProgressingEditsOptions): Promise<void> {
273-
// push undo stop before first edit
274-
if (++this._editCount === 1) {
275-
editor.pushUndoStop();
276-
}
277-
278-
const durationInSec = opts.duration / 1000;
279-
for (const edit of edits) {
280-
const wordCount = countWords(edit.text ?? '');
281-
const speed = wordCount / durationInSec;
282-
// console.log({ durationInSec, wordCount, speed: wordCount / durationInSec });
283-
await performAsyncTextEdit(editor.getModel(), asProgressiveEdit(edit, speed, opts.token));
284-
}
285-
}
28643

287-
async makeChanges(editor: IActiveCodeEditor, edits: ISingleEditOperation[]): Promise<void> {
288-
const cursorStateComputerAndInlineDiffCollection: ICursorStateComputer = (undoEdits) => {
289-
let last: Position | null = null;
290-
for (const edit of undoEdits) {
291-
last = !last || last.isBefore(edit.range.getEndPosition()) ? edit.range.getEndPosition() : last;
292-
// this._inlineDiffDecorations.collectEditOperation(edit);
293-
}
294-
return last && [Selection.fromPositions(last)];
295-
};
296-
297-
// push undo stop before first edit
298-
if (++this._editCount === 1) {
299-
editor.pushUndoStop();
300-
}
301-
editor.executeEdits('inline-chat-live', edits, cursorStateComputerAndInlineDiffCollection);
302-
}
44+
interface ICellChatPart {
45+
activeCell: ICellViewModel | undefined;
46+
getWidget(): CellChatWidget;
30347
}
30448

305-
class NotebookCellChatController extends Disposable {
49+
export class NotebookCellChatController extends Disposable {
30650
private static _cellChatControllers = new WeakMap<ICellViewModel, NotebookCellChatController>();
30751

30852
static get(cell: ICellViewModel): NotebookCellChatController | undefined {
@@ -316,7 +60,7 @@ class NotebookCellChatController extends Disposable {
31660

31761
constructor(
31862
private readonly _notebookEditor: INotebookEditorDelegate,
319-
private readonly _chatPart: CellChatPart,
63+
private readonly _chatPart: ICellChatPart,
32064
private readonly _cell: ICellViewModel,
32165
@IContextKeyService private readonly _contextKeyService: IContextKeyService,
32266
@IInlineChatSessionService private readonly _inlineChatSessionService: IInlineChatSessionService,
@@ -503,6 +247,42 @@ class NotebookCellChatController extends Disposable {
503247
}
504248
}
505249

250+
class EditStrategy {
251+
private _editCount: number = 0;
252+
253+
async makeProgressiveChanges(editor: IActiveCodeEditor, edits: ISingleEditOperation[], opts: ProgressingEditsOptions): Promise<void> {
254+
// push undo stop before first edit
255+
if (++this._editCount === 1) {
256+
editor.pushUndoStop();
257+
}
258+
259+
const durationInSec = opts.duration / 1000;
260+
for (const edit of edits) {
261+
const wordCount = countWords(edit.text ?? '');
262+
const speed = wordCount / durationInSec;
263+
// console.log({ durationInSec, wordCount, speed: wordCount / durationInSec });
264+
await performAsyncTextEdit(editor.getModel(), asProgressiveEdit(edit, speed, opts.token));
265+
}
266+
}
267+
268+
async makeChanges(editor: IActiveCodeEditor, edits: ISingleEditOperation[]): Promise<void> {
269+
const cursorStateComputerAndInlineDiffCollection: ICursorStateComputer = (undoEdits) => {
270+
let last: Position | null = null;
271+
for (const edit of undoEdits) {
272+
last = !last || last.isBefore(edit.range.getEndPosition()) ? edit.range.getEndPosition() : last;
273+
// this._inlineDiffDecorations.collectEditOperation(edit);
274+
}
275+
return last && [Selection.fromPositions(last)];
276+
};
277+
278+
// push undo stop before first edit
279+
if (++this._editCount === 1) {
280+
editor.pushUndoStop();
281+
}
282+
editor.executeEdits('inline-chat-live', edits, cursorStateComputerAndInlineDiffCollection);
283+
}
284+
}
285+
506286
registerAction2(class extends NotebookCellAction {
507287
constructor() {
508288
super(

0 commit comments

Comments
 (0)