3
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
4
*--------------------------------------------------------------------------------------------*/
5
5
6
- import { raceCancellation } from 'vs/base/common/async' ;
6
+ import { Queue , raceCancellation } from 'vs/base/common/async' ;
7
7
import { CancellationToken } from 'vs/base/common/cancellation' ;
8
- import { DisposableStore , IDisposable , MutableDisposable , dispose } from 'vs/base/common/lifecycle' ;
8
+ import { DisposableStore , IDisposable , MutableDisposable , combinedDisposable , dispose } from 'vs/base/common/lifecycle' ;
9
9
import { ICodeEditor , isCodeEditor } from 'vs/editor/browser/editorBrowser' ;
10
- import { ITextModel } from 'vs/editor/common/model' ;
11
10
import { localize } from 'vs/nls' ;
12
11
import { IConfigurationService } from 'vs/platform/configuration/common/configuration' ;
13
12
import { IProgress , IProgressStep } from 'vs/platform/progress/common/progress' ;
14
- import { DEFAULT_EDITOR_ASSOCIATION , SaveReason } from 'vs/workbench/common/editor' ;
13
+ import { SaveReason } from 'vs/workbench/common/editor' ;
15
14
import { Session } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession' ;
16
15
import { IInlineChatSessionService } from './inlineChatSessionService' ;
17
16
import { InlineChatConfigKeys } from 'vs/workbench/contrib/inlineChat/common/inlineChat' ;
@@ -22,8 +21,16 @@ import { ITextFileService } from 'vs/workbench/services/textfile/common/textfile
22
21
import { IInlineChatSavingService } from './inlineChatSavingService' ;
23
22
import { Iterable } from 'vs/base/common/iterator' ;
24
23
import { IResourceEditorInput } from 'vs/platform/editor/common/editor' ;
24
+ import { Schemas } from 'vs/base/common/network' ;
25
+ import { CellUri } from 'vs/workbench/contrib/notebook/common/notebookCommon' ;
26
+ import { getNotebookEditorFromEditorPane } from 'vs/workbench/contrib/notebook/browser/notebookBrowser' ;
27
+ import { compare } from 'vs/base/common/strings' ;
28
+ import { IWorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService' ;
29
+ import { URI } from 'vs/base/common/uri' ;
30
+ import { ILogService } from 'vs/platform/log/common/log' ;
25
31
26
32
interface SessionData {
33
+ readonly resourceUri : URI ;
27
34
readonly dispose : ( ) => void ;
28
35
readonly session : Session ;
29
36
readonly groupCandidate : IEditorGroup ;
@@ -44,6 +51,8 @@ export class InlineChatSavingServiceImpl implements IInlineChatSavingService {
44
51
@IEditorService private readonly _editorService : IEditorService ,
45
52
@IInlineChatSessionService private readonly _inlineChatSessionService : IInlineChatSessionService ,
46
53
@IConfigurationService private readonly _configService : IConfigurationService ,
54
+ @IWorkingCopyFileService private readonly _workingCopyFileService : IWorkingCopyFileService ,
55
+ @ILogService private readonly _logService : ILogService ,
47
56
) {
48
57
this . _store . add ( _inlineChatSessionService . onDidEndSession ( e => {
49
58
this . _sessionData . get ( e . session ) ?. dispose ( ) ;
@@ -58,16 +67,28 @@ export class InlineChatSavingServiceImpl implements IInlineChatSavingService {
58
67
markChanged ( session : Session ) : void {
59
68
if ( ! this . _sessionData . has ( session ) ) {
60
69
70
+ let uri = session . textModelN . uri ;
71
+
72
+ // notebooks: use the notebook-uri because saving happens on the notebook-level
73
+ if ( uri . scheme === Schemas . vscodeNotebookCell ) {
74
+ const data = CellUri . parse ( uri ) ;
75
+ if ( ! data ) {
76
+ return ;
77
+ }
78
+ uri = data ?. notebook ;
79
+ }
80
+
61
81
if ( this . _sessionData . size === 0 ) {
62
82
this . _installSaveParticpant ( ) ;
63
83
}
64
84
65
- const saveConfig = this . _fileConfigService . disableAutoSave ( session . textModelN . uri ) ;
85
+ const saveConfigOverride = this . _fileConfigService . disableAutoSave ( uri ) ;
66
86
this . _sessionData . set ( session , {
87
+ resourceUri : uri ,
67
88
groupCandidate : this . _editorGroupService . activeGroup ,
68
89
session,
69
90
dispose : ( ) => {
70
- saveConfig . dispose ( ) ;
91
+ saveConfigOverride . dispose ( ) ;
71
92
this . _sessionData . delete ( session ) ;
72
93
if ( this . _sessionData . size === 0 ) {
73
94
this . _saveParticipant . clear ( ) ;
@@ -78,12 +99,23 @@ export class InlineChatSavingServiceImpl implements IInlineChatSavingService {
78
99
}
79
100
80
101
private _installSaveParticpant ( ) : void {
81
- this . _saveParticipant . value = this . _textFileService . files . addSaveParticipant ( {
82
- participate : ( model , context , progress , token ) => this . _participate ( model . textEditorModel , context . reason , progress , token )
102
+
103
+ const queue = new Queue < void > ( ) ;
104
+
105
+ const d1 = this . _textFileService . files . addSaveParticipant ( {
106
+ participate : ( model , context , progress , token ) => {
107
+ return queue . queue ( ( ) => this . _participate ( model . textEditorModel ?. uri , context . reason , progress , token ) ) ;
108
+ }
83
109
} ) ;
110
+ const d2 = this . _workingCopyFileService . addSaveParticipant ( {
111
+ participate : ( workingCopy , env , progress , token ) => {
112
+ return queue . queue ( ( ) => this . _participate ( workingCopy . resource , env . reason , progress , token ) ) ;
113
+ }
114
+ } ) ;
115
+ this . _saveParticipant . value = combinedDisposable ( d1 , d2 , queue ) ;
84
116
}
85
117
86
- private async _participate ( model : ITextModel | null , reason : SaveReason , progress : IProgress < IProgressStep > , token : CancellationToken ) : Promise < void > {
118
+ private async _participate ( uri : URI | undefined , reason : SaveReason , progress : IProgress < IProgressStep > , token : CancellationToken ) : Promise < void > {
87
119
88
120
if ( reason !== SaveReason . EXPLICIT ) {
89
121
// all saves that we are concerned about are explicit
@@ -98,7 +130,7 @@ export class InlineChatSavingServiceImpl implements IInlineChatSavingService {
98
130
99
131
const sessions = new Map < Session , SessionData > ( ) ;
100
132
for ( const [ session , data ] of this . _sessionData ) {
101
- if ( model === session . textModelN ) {
133
+ if ( uri ?. toString ( ) === data . resourceUri . toString ( ) ) {
102
134
sessions . set ( session , data ) ;
103
135
}
104
136
}
@@ -123,7 +155,7 @@ export class InlineChatSavingServiceImpl implements IInlineChatSavingService {
123
155
} ) ;
124
156
125
157
// fallback: resolve when all sessions for this model have been resolved. this is independent of the editor opening
126
- const allSessionsEnded = this . _waitForSessions ( Iterable . concat ( groups . values ( ) , orphans ) , token ) ;
158
+ const allSessionsEnded = this . _whenSessionsEnded ( Iterable . concat ( groups . map ( tuple => tuple [ 1 ] ) , orphans ) , token ) ;
127
159
128
160
await Promise . race ( [ allSessionsEnded , editorsOpenedAndSessionsEnded ] ) ;
129
161
}
@@ -138,19 +170,20 @@ export class InlineChatSavingServiceImpl implements IInlineChatSavingService {
138
170
}
139
171
}
140
172
141
- const groups = new Map < IEditorGroup , SessionData > ( ) ;
173
+ const groups : [ IEditorGroup , SessionData ] [ ] = [ ] ;
142
174
const orphans = new Set < SessionData > ( ) ;
143
175
144
176
for ( const data of sessions ) {
177
+
145
178
const editor = this . _inlineChatSessionService . getCodeEditor ( data . session ) ;
146
179
const group = groupByEditor . get ( editor ) ;
147
180
if ( group ) {
148
181
// there is only one session per group because all sessions have the same model
149
182
// because we save one file.
150
- groups . set ( group , data ) ;
183
+ groups . push ( [ group , data ] ) ;
151
184
} else if ( this . _editorGroupService . groups . includes ( data . groupCandidate ) ) {
152
185
// the group candidate is still there. use it
153
- groups . set ( data . groupCandidate , data ) ;
186
+ groups . push ( [ data . groupCandidate , data ] ) ;
154
187
} else {
155
188
orphans . add ( data ) ;
156
189
}
@@ -159,22 +192,61 @@ export class InlineChatSavingServiceImpl implements IInlineChatSavingService {
159
192
}
160
193
161
194
private async _openAndWait ( groups : Iterable < [ IEditorGroup , SessionData ] > , token : CancellationToken ) {
162
- const sessions = new Set < SessionData > ( ) ;
195
+
196
+ const dataByGroup = new Map < IEditorGroup , SessionData [ ] > ( ) ;
163
197
for ( const [ group , data ] of groups ) {
164
- const input : IResourceEditorInput = { resource : data . session . textModelN . uri , options : { override : DEFAULT_EDITOR_ASSOCIATION . id } } ;
165
- const pane = await this . _editorService . openEditor ( input , group ) ;
166
- const ctrl = pane ?. getControl ( ) ;
167
- if ( ! isCodeEditor ( ctrl ) ) {
168
- // PANIC
169
- return ;
198
+ let array = dataByGroup . get ( group ) ;
199
+ if ( ! array ) {
200
+ array = [ ] ;
201
+ dataByGroup . set ( group , array ) ;
202
+ }
203
+ array . push ( data ) ;
204
+ }
205
+
206
+ for ( const [ group , array ] of dataByGroup ) {
207
+
208
+ if ( token . isCancellationRequested ) {
209
+ break ;
210
+ }
211
+
212
+ array . sort ( ( a , b ) => compare ( a . session . textModelN . uri . toString ( ) , b . session . textModelN . uri . toString ( ) ) ) ;
213
+
214
+
215
+ for ( const data of array ) {
216
+
217
+ const input : IResourceEditorInput = { resource : data . resourceUri } ;
218
+ const pane = await this . _editorService . openEditor ( input , group ) ;
219
+ let editor : ICodeEditor | undefined ;
220
+ if ( data . session . textModelN . uri . scheme === Schemas . vscodeNotebookCell ) {
221
+ const notebookEditor = getNotebookEditorFromEditorPane ( pane ) ;
222
+ const uriData = CellUri . parse ( data . session . textModelN . uri ) ;
223
+ if ( notebookEditor && notebookEditor . hasModel ( ) && uriData ) {
224
+ const cell = notebookEditor . getCellByHandle ( uriData . handle ) ;
225
+ if ( cell ) {
226
+ await notebookEditor . revealRangeInCenterIfOutsideViewportAsync ( cell , data . session . wholeRange . value ) ;
227
+ }
228
+ const tuple = notebookEditor . codeEditors . find ( tuple => tuple [ 1 ] . getModel ( ) ?. uri . toString ( ) === data . session . textModelN . uri . toString ( ) ) ;
229
+ editor = tuple ?. [ 1 ] ;
230
+ }
231
+
232
+ } else {
233
+ if ( isCodeEditor ( pane ?. getControl ( ) ) ) {
234
+ editor = < ICodeEditor > pane . getControl ( ) ;
235
+ }
236
+ }
237
+
238
+ if ( ! editor ) {
239
+ // PANIC
240
+ break ;
241
+ }
242
+ this . _inlineChatSessionService . moveSession ( data . session , editor ) ;
243
+ this . _logService . info ( 'WAIT for session to end' , editor . getId ( ) , data . session . textModelN . uri . toString ( ) ) ;
244
+ await this . _whenSessionsEnded ( Iterable . single ( data ) , token ) ;
170
245
}
171
- this . _inlineChatSessionService . moveSession ( data . session , ctrl ) ;
172
- sessions . add ( data ) ;
173
246
}
174
- await this . _waitForSessions ( sessions , token ) ;
175
247
}
176
248
177
- private async _waitForSessions ( iterable : Iterable < SessionData > , token : CancellationToken ) {
249
+ private async _whenSessionsEnded ( iterable : Iterable < SessionData > , token : CancellationToken ) {
178
250
179
251
const sessions = new Map < Session , SessionData > ( ) ;
180
252
for ( const item of iterable ) {
0 commit comments