6
6
import * as dom from 'vs/base/browser/dom' ;
7
7
import { CancellationTokenSource } from 'vs/base/common/cancellation' ;
8
8
import { Emitter , Event } from 'vs/base/common/event' ;
9
- import { Disposable , IDisposable , toDisposable } from 'vs/base/common/lifecycle' ;
9
+ import { Disposable , IDisposable , IReference , RefCountedDisposable , toDisposable } from 'vs/base/common/lifecycle' ;
10
10
import { Schemas } from 'vs/base/common/network' ;
11
11
import { isEqual } from 'vs/base/common/resources' ;
12
+ import { assertType } from 'vs/base/common/types' ;
12
13
import { URI } from 'vs/base/common/uri' ;
13
14
import { generateUuid } from 'vs/base/common/uuid' ;
14
15
import { ISingleEditOperation } from 'vs/editor/common/core/editOperation' ;
15
16
import { TextEdit } from 'vs/editor/common/languages' ;
16
17
import { createTextBufferFactoryFromSnapshot } from 'vs/editor/common/model/textModel' ;
17
18
import { IModelService } from 'vs/editor/common/services/model' ;
18
19
import { DefaultModelSHA1Computer } from 'vs/editor/common/services/modelService' ;
19
- import { ITextModelService } from 'vs/editor/common/services/resolverService' ;
20
+ import { IResolvedTextEditorModel , ITextModelService } from 'vs/editor/common/services/resolverService' ;
20
21
import { localize } from 'vs/nls' ;
21
22
import { MenuId } from 'vs/platform/actions/common/actions' ;
22
- import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation' ;
23
+ import { InstantiationType , registerSingleton } from 'vs/platform/instantiation/common/extensions' ;
24
+ import { createDecorator , IInstantiationService } from 'vs/platform/instantiation/common/instantiation' ;
23
25
import { IChatListItemRendererOptions } from 'vs/workbench/contrib/chat/browser/chat' ;
24
26
import { IDisposableReference , ResourcePool } from 'vs/workbench/contrib/chat/browser/chatContentParts/chatCollections' ;
25
27
import { IChatContentPart , IChatContentPartRenderContext } from 'vs/workbench/contrib/chat/browser/chatContentParts/chatContentParts' ;
@@ -28,13 +30,20 @@ import { ChatEditorOptions } from 'vs/workbench/contrib/chat/browser/chatOptions
28
30
import { CodeCompareBlockPart , ICodeCompareBlockData , ICodeCompareBlockDiffData } from 'vs/workbench/contrib/chat/browser/codeBlockPart' ;
29
31
import { IChatProgressRenderableResponseContent , IChatTextEditGroup } from 'vs/workbench/contrib/chat/common/chatModel' ;
30
32
import { IChatService } from 'vs/workbench/contrib/chat/common/chatService' ;
31
- import { isResponseVM } from 'vs/workbench/contrib/chat/common/chatViewModel' ;
33
+ import { IChatResponseViewModel , isResponseVM } from 'vs/workbench/contrib/chat/common/chatViewModel' ;
32
34
33
35
const $ = dom . $ ;
34
36
37
+ const ICodeCompareModelService = createDecorator < ICodeCompareModelService > ( 'ICodeCompareModelService' ) ;
38
+
39
+ interface ICodeCompareModelService {
40
+ _serviceBrand : undefined ;
41
+ createModel ( response : IChatResponseViewModel , chatTextEdit : IChatTextEditGroup ) : Promise < IReference < { originalSha1 : string ; original : IResolvedTextEditorModel ; modified : IResolvedTextEditorModel } > > ;
42
+ }
43
+
35
44
export class ChatTextEditContentPart extends Disposable implements IChatContentPart {
36
45
public readonly domNode : HTMLElement ;
37
- private readonly ref : IDisposableReference < CodeCompareBlockPart > | undefined ;
46
+ private readonly comparePart : IDisposableReference < CodeCompareBlockPart > | undefined ;
38
47
39
48
private readonly _onDidChangeHeight = this . _register ( new Emitter < void > ( ) ) ;
40
49
public readonly onDidChangeHeight = this . _onDidChangeHeight . event ;
@@ -45,16 +54,16 @@ export class ChatTextEditContentPart extends Disposable implements IChatContentP
45
54
rendererOptions : IChatListItemRendererOptions ,
46
55
diffEditorPool : DiffEditorPool ,
47
56
currentWidth : number ,
48
- @ITextModelService private readonly textModelService : ITextModelService ,
49
- @IModelService private readonly modelService : IModelService ,
50
- @IChatService private readonly chatService : IChatService ,
57
+ @ICodeCompareModelService private readonly codeCompareModelService : ICodeCompareModelService
51
58
) {
52
59
super ( ) ;
53
60
const element = context . element ;
54
61
62
+ assertType ( isResponseVM ( element ) ) ;
63
+
55
64
// TODO@jrieken move this into the CompareCodeBlock and properly say what kind of changes happen
56
65
if ( rendererOptions . renderTextEditsAsSummary ?.( chatTextEdit . uri ) ) {
57
- if ( isResponseVM ( element ) && element . response . value . every ( item => item . kind === 'textEditGroup' ) ) {
66
+ if ( element . response . value . every ( item => item . kind === 'textEditGroup' ) ) {
58
67
this . domNode = $ ( '.interactive-edits-summary' , undefined , ! element . isComplete
59
68
? ''
60
69
: element . isCanceled
@@ -75,14 +84,13 @@ export class ChatTextEditContentPart extends Disposable implements IChatContentP
75
84
this . _register ( toDisposable ( ( ) => {
76
85
isDisposed = true ;
77
86
cts . dispose ( true ) ;
78
- this . ref ?. object . clearModel ( ) ;
79
87
} ) ) ;
80
88
81
- this . ref = this . _register ( diffEditorPool . get ( ) ) ;
89
+ this . comparePart = this . _register ( diffEditorPool . get ( ) ) ;
82
90
83
91
// Attach this after updating text/layout of the editor, so it should only be fired when the size updates later (horizontal scrollbar, wrapping)
84
92
// not during a renderElement OR a progressive render (when we will be firing this event anyway at the end of the render)
85
- this . _register ( this . ref . object . onDidChangeContentHeight ( ( ) => {
93
+ this . _register ( this . comparePart . object . onDidChangeContentHeight ( ( ) => {
86
94
this . _onDidChangeHeight . fire ( ) ;
87
95
} ) ) ;
88
96
@@ -91,7 +99,7 @@ export class ChatTextEditContentPart extends Disposable implements IChatContentP
91
99
edit : chatTextEdit ,
92
100
diffData : ( async ( ) => {
93
101
94
- const ref = await this . textModelService . createModelReference ( chatTextEdit . uri ) ;
102
+ const ref = await this . codeCompareModelService . createModel ( element , chatTextEdit ) ;
95
103
96
104
if ( isDisposed ) {
97
105
ref . dispose ( ) ;
@@ -100,70 +108,21 @@ export class ChatTextEditContentPart extends Disposable implements IChatContentP
100
108
101
109
this . _register ( ref ) ;
102
110
103
- const original = ref . object . textEditorModel ;
104
- let originalSha1 : string = '' ;
105
-
106
- if ( chatTextEdit . state ) {
107
- originalSha1 = chatTextEdit . state . sha1 ;
108
- } else {
109
- const sha1 = new DefaultModelSHA1Computer ( ) ;
110
- if ( sha1 . canComputeSHA1 ( original ) ) {
111
- originalSha1 = sha1 . computeSHA1 ( original ) ;
112
- chatTextEdit . state = { sha1 : originalSha1 , applied : 0 } ;
113
- }
114
- }
115
-
116
- const modified = this . modelService . createModel (
117
- createTextBufferFactoryFromSnapshot ( original . createSnapshot ( ) ) ,
118
- { languageId : original . getLanguageId ( ) , onDidChange : Event . None } ,
119
- URI . from ( { scheme : Schemas . vscodeChatCodeBlock , path : original . uri . path , query : generateUuid ( ) } ) ,
120
- false
121
- ) ;
122
- const modRef = await this . textModelService . createModelReference ( modified . uri ) ;
123
- this . _register ( modRef ) ;
124
-
125
- const editGroups : ISingleEditOperation [ ] [ ] = [ ] ;
126
- if ( isResponseVM ( element ) ) {
127
- const chatModel = this . chatService . getSession ( element . sessionId ) ! ;
128
-
129
- for ( const request of chatModel . getRequests ( ) ) {
130
- if ( ! request . response ) {
131
- continue ;
132
- }
133
- for ( const item of request . response . response . value ) {
134
- if ( item . kind !== 'textEditGroup' || item . state ?. applied || ! isEqual ( item . uri , chatTextEdit . uri ) ) {
135
- continue ;
136
- }
137
- for ( const group of item . edits ) {
138
- const edits = group . map ( TextEdit . asEditOperation ) ;
139
- editGroups . push ( edits ) ;
140
- }
141
- }
142
- if ( request . response === element . model ) {
143
- break ;
144
- }
145
- }
146
- }
147
-
148
- for ( const edits of editGroups ) {
149
- modified . pushEditOperations ( null , edits , ( ) => null ) ;
150
- }
151
-
152
111
return {
153
- modified,
154
- original,
155
- originalSha1
112
+ modified : ref . object . modified . textEditorModel ,
113
+ original : ref . object . original . textEditorModel ,
114
+ originalSha1 : ref . object . originalSha1
156
115
} satisfies ICodeCompareBlockDiffData ;
157
116
} ) ( )
158
117
} ;
159
- this . ref . object . render ( data , currentWidth , cts . token ) ;
118
+ this . comparePart . object . render ( data , currentWidth , cts . token ) ;
160
119
161
- this . domNode = this . ref . object . element ;
120
+ this . domNode = this . comparePart . object . element ;
162
121
}
163
122
}
164
123
165
124
layout ( width : number ) : void {
166
- this . ref ?. object . layout ( width ) ;
125
+ this . comparePart ?. object . layout ( width ) ;
167
126
}
168
127
169
128
hasSameContent ( other : IChatProgressRenderableResponseContent ) : boolean {
@@ -210,3 +169,87 @@ export class DiffEditorPool extends Disposable {
210
169
} ;
211
170
}
212
171
}
172
+
173
+ class CodeCompareModelService implements ICodeCompareModelService {
174
+
175
+ declare readonly _serviceBrand : undefined ;
176
+
177
+ constructor (
178
+ @ITextModelService private readonly textModelService : ITextModelService ,
179
+ @IModelService private readonly modelService : IModelService ,
180
+ @IChatService private readonly chatService : IChatService ,
181
+ ) { }
182
+
183
+ async createModel ( element : IChatResponseViewModel , chatTextEdit : IChatTextEditGroup ) : Promise < IReference < { originalSha1 : string ; original : IResolvedTextEditorModel ; modified : IResolvedTextEditorModel } > > {
184
+
185
+ const original = await this . textModelService . createModelReference ( chatTextEdit . uri ) ;
186
+
187
+ const modified = await this . textModelService . createModelReference ( ( this . modelService . createModel (
188
+ createTextBufferFactoryFromSnapshot ( original . object . textEditorModel . createSnapshot ( ) ) ,
189
+ { languageId : original . object . textEditorModel . getLanguageId ( ) , onDidChange : Event . None } ,
190
+ URI . from ( { scheme : Schemas . vscodeChatCodeBlock , path : chatTextEdit . uri . path , query : generateUuid ( ) } ) ,
191
+ false
192
+ ) ) . uri ) ;
193
+
194
+ const d = new RefCountedDisposable ( toDisposable ( ( ) => {
195
+ original . dispose ( ) ;
196
+ modified . dispose ( ) ;
197
+ } ) ) ;
198
+
199
+ // compute the sha1 of the original model
200
+ let originalSha1 : string = '' ;
201
+ if ( chatTextEdit . state ) {
202
+ originalSha1 = chatTextEdit . state . sha1 ;
203
+ } else {
204
+ const sha1 = new DefaultModelSHA1Computer ( ) ;
205
+ if ( sha1 . canComputeSHA1 ( original . object . textEditorModel ) ) {
206
+ originalSha1 = sha1 . computeSHA1 ( original . object . textEditorModel ) ;
207
+ chatTextEdit . state = { sha1 : originalSha1 , applied : 0 } ;
208
+ }
209
+ }
210
+
211
+ // apply edits to the "modified" model
212
+ const chatModel = this . chatService . getSession ( element . sessionId ) ! ;
213
+ const editGroups : ISingleEditOperation [ ] [ ] = [ ] ;
214
+ for ( const request of chatModel . getRequests ( ) ) {
215
+ if ( ! request . response ) {
216
+ continue ;
217
+ }
218
+ for ( const item of request . response . response . value ) {
219
+ if ( item . kind !== 'textEditGroup' || item . state ?. applied || ! isEqual ( item . uri , chatTextEdit . uri ) ) {
220
+ continue ;
221
+ }
222
+ for ( const group of item . edits ) {
223
+ const edits = group . map ( TextEdit . asEditOperation ) ;
224
+ editGroups . push ( edits ) ;
225
+ }
226
+ }
227
+ if ( request . response === element . model ) {
228
+ break ;
229
+ }
230
+ }
231
+ for ( const edits of editGroups ) {
232
+ modified . object . textEditorModel . pushEditOperations ( null , edits , ( ) => null ) ;
233
+ }
234
+
235
+ // self-acquire a reference to diff models for a short while
236
+ // because streaming usually means we will be using the original-model
237
+ // repeatedly and thereby also should reuse the modified-model and just
238
+ // update it with more edits
239
+ d . acquire ( ) ;
240
+ setTimeout ( ( ) => d . release ( ) , 5000 ) ;
241
+
242
+ return {
243
+ object : {
244
+ originalSha1,
245
+ original : original . object ,
246
+ modified : modified . object
247
+ } ,
248
+ dispose ( ) {
249
+ d . release ( ) ;
250
+ } ,
251
+ } ;
252
+ }
253
+ }
254
+
255
+ registerSingleton ( ICodeCompareModelService , CodeCompareModelService , InstantiationType . Delayed ) ;
0 commit comments