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' ;
86import { Queue , raceCancellationError } from 'vs/base/common/async' ;
97import { CancellationTokenSource } from 'vs/base/common/cancellation' ;
108import { Codicon } from 'vs/base/common/codicons' ;
119import { Event } from 'vs/base/common/event' ;
1210import { MarkdownString } from 'vs/base/common/htmlContent' ;
1311import { KeyCode } from 'vs/base/common/keyCodes' ;
14- import { Lazy } from 'vs/base/common/lazy' ;
1512import { Disposable } from 'vs/base/common/lifecycle' ;
16- import { MarshalledId } from 'vs/base/common/marshallingIds' ;
1713import { MovingAverage } from 'vs/base/common/numbers' ;
1814import { StopWatch } from 'vs/base/common/stopwatch' ;
1915import { assertType } from 'vs/base/common/types' ;
20- import { URI } from 'vs/base/common/uri' ;
2116import { generateUuid } from 'vs/base/common/uuid' ;
2217import { 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' ;
2618import { ISingleEditOperation } from 'vs/editor/common/core/editOperation' ;
2719import { Position } from 'vs/editor/common/core/position' ;
2820import { Selection } from 'vs/editor/common/core/selection' ;
2921import { EditorContextKeys } from 'vs/editor/common/editorContextKeys' ;
3022import { TextEdit } from 'vs/editor/common/languages' ;
31- import { ICursorStateComputer , ITextModel } from 'vs/editor/common/model' ;
23+ import { ICursorStateComputer } from 'vs/editor/common/model' ;
3224import { 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' ;
3625import { localize } from 'vs/nls' ;
37- import { MenuWorkbenchToolBar } from 'vs/platform/actions/browser/toolbar' ;
3826import { MenuId , registerAction2 } from 'vs/platform/actions/common/actions' ;
3927import { 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' ;
4129import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry' ;
4230import { AsyncProgress } from 'vs/platform/progress/common/progress' ;
4331import { countWords } from 'vs/workbench/contrib/chat/common/chatWordCounter' ;
4432import { IInlineChatSessionService , ReplyResponse , Session , SessionPrompt } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession' ;
4533import { ProgressingEditsOptions , asProgressiveEdit , performAsyncTextEdit } from 'vs/workbench/contrib/inlineChat/browser/inlineChatStrategies' ;
46- import { _inputEditorOptions } from 'vs/workbench/contrib/inlineChat/browser/inlineChatWidget' ;
4734import { CTX_INLINE_CHAT_HAS_PROVIDER , EditMode , IInlineChatProgressItem , IInlineChatRequest } from 'vs/workbench/contrib/inlineChat/common/inlineChat' ;
4835import { CELL_TITLE_CELL_GROUP_ID , INotebookCellActionContext , NotebookCellAction } from 'vs/workbench/contrib/notebook/browser/controller/coreActions' ;
4936import { 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 ' ;
5239import { CellKind , NotebookSetting } from 'vs/workbench/contrib/notebook/common/notebookCommon' ;
5340import { 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" ) ) ;
5642const 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+
506286registerAction2 ( class extends NotebookCellAction {
507287 constructor ( ) {
508288 super (
0 commit comments