Skip to content

Commit d0d2200

Browse files
authored
make sure save constent also works for notebooks (microsoft#202666)
1 parent 6ec5b10 commit d0d2200

File tree

3 files changed

+129
-32
lines changed

3 files changed

+129
-32
lines changed

src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ export class InlineChatController implements IEditorContribution {
194194
}
195195
this._strategy?.dispose();
196196
this._store.dispose();
197-
this._log('controller disposed');
197+
this._log('DISPOSED controller');
198198
}
199199

200200
private _log(message: string | Error, ...more: any[]): void {

src/vs/workbench/contrib/inlineChat/browser/inlineChatNotebook.ts

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,17 +23,42 @@ export class InlineChatNotebookContribution {
2323
) {
2424

2525
this._store.add(sessionService.registerSessionKeyComputer(Schemas.vscodeNotebookCell, {
26-
getComparisonKey: (_editor, uri) => {
26+
getComparisonKey: (editor, uri) => {
2727
const data = CellUri.parse(uri);
2828
if (!data) {
29-
throw illegalState('Expected notebook');
29+
throw illegalState('Expected notebook cell uri');
3030
}
31-
for (const editor of notebookEditorService.listNotebookEditors()) {
32-
if (isEqual(editor.textModel?.uri, data.notebook)) {
33-
return `<notebook>${editor.getId()}#${uri}`;
31+
let fallback: string | undefined;
32+
for (const notebookEditor of notebookEditorService.listNotebookEditors()) {
33+
if (notebookEditor.hasModel() && isEqual(notebookEditor.textModel.uri, data.notebook)) {
34+
35+
const candidate = `<notebook>${notebookEditor.getId()}#${uri}`;
36+
37+
if (!fallback) {
38+
fallback = candidate;
39+
}
40+
41+
// find the code editor in the list of cell-code editors
42+
if (notebookEditor.codeEditors.find((tuple) => tuple[1] === editor)) {
43+
return candidate;
44+
}
45+
46+
// // reveal cell and try to find code editor again
47+
// const cell = notebookEditor.getCellByHandle(data.handle);
48+
// if (cell) {
49+
// notebookEditor.revealInViewAtTop(cell);
50+
// if (notebookEditor.codeEditors.find((tuple) => tuple[1] === editor)) {
51+
// return candidate;
52+
// }
53+
// }
3454
}
3555
}
36-
throw illegalState('Expected notebook');
56+
57+
if (fallback) {
58+
return fallback;
59+
}
60+
61+
throw illegalState('Expected notebook editor');
3762
}
3863
}));
3964

src/vs/workbench/contrib/inlineChat/browser/inlineChatSavingServiceImpl.ts

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

6-
import { raceCancellation } from 'vs/base/common/async';
6+
import { Queue, raceCancellation } from 'vs/base/common/async';
77
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';
99
import { ICodeEditor, isCodeEditor } from 'vs/editor/browser/editorBrowser';
10-
import { ITextModel } from 'vs/editor/common/model';
1110
import { localize } from 'vs/nls';
1211
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
1312
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';
1514
import { Session } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession';
1615
import { IInlineChatSessionService } from './inlineChatSessionService';
1716
import { InlineChatConfigKeys } from 'vs/workbench/contrib/inlineChat/common/inlineChat';
@@ -22,8 +21,16 @@ import { ITextFileService } from 'vs/workbench/services/textfile/common/textfile
2221
import { IInlineChatSavingService } from './inlineChatSavingService';
2322
import { Iterable } from 'vs/base/common/iterator';
2423
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';
2531

2632
interface SessionData {
33+
readonly resourceUri: URI;
2734
readonly dispose: () => void;
2835
readonly session: Session;
2936
readonly groupCandidate: IEditorGroup;
@@ -44,6 +51,8 @@ export class InlineChatSavingServiceImpl implements IInlineChatSavingService {
4451
@IEditorService private readonly _editorService: IEditorService,
4552
@IInlineChatSessionService private readonly _inlineChatSessionService: IInlineChatSessionService,
4653
@IConfigurationService private readonly _configService: IConfigurationService,
54+
@IWorkingCopyFileService private readonly _workingCopyFileService: IWorkingCopyFileService,
55+
@ILogService private readonly _logService: ILogService,
4756
) {
4857
this._store.add(_inlineChatSessionService.onDidEndSession(e => {
4958
this._sessionData.get(e.session)?.dispose();
@@ -58,16 +67,28 @@ export class InlineChatSavingServiceImpl implements IInlineChatSavingService {
5867
markChanged(session: Session): void {
5968
if (!this._sessionData.has(session)) {
6069

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+
6181
if (this._sessionData.size === 0) {
6282
this._installSaveParticpant();
6383
}
6484

65-
const saveConfig = this._fileConfigService.disableAutoSave(session.textModelN.uri);
85+
const saveConfigOverride = this._fileConfigService.disableAutoSave(uri);
6686
this._sessionData.set(session, {
87+
resourceUri: uri,
6788
groupCandidate: this._editorGroupService.activeGroup,
6889
session,
6990
dispose: () => {
70-
saveConfig.dispose();
91+
saveConfigOverride.dispose();
7192
this._sessionData.delete(session);
7293
if (this._sessionData.size === 0) {
7394
this._saveParticipant.clear();
@@ -78,12 +99,23 @@ export class InlineChatSavingServiceImpl implements IInlineChatSavingService {
7899
}
79100

80101
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+
}
83109
});
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);
84116
}
85117

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> {
87119

88120
if (reason !== SaveReason.EXPLICIT) {
89121
// all saves that we are concerned about are explicit
@@ -98,7 +130,7 @@ export class InlineChatSavingServiceImpl implements IInlineChatSavingService {
98130

99131
const sessions = new Map<Session, SessionData>();
100132
for (const [session, data] of this._sessionData) {
101-
if (model === session.textModelN) {
133+
if (uri?.toString() === data.resourceUri.toString()) {
102134
sessions.set(session, data);
103135
}
104136
}
@@ -123,7 +155,7 @@ export class InlineChatSavingServiceImpl implements IInlineChatSavingService {
123155
});
124156

125157
// 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);
127159

128160
await Promise.race([allSessionsEnded, editorsOpenedAndSessionsEnded]);
129161
}
@@ -138,19 +170,20 @@ export class InlineChatSavingServiceImpl implements IInlineChatSavingService {
138170
}
139171
}
140172

141-
const groups = new Map<IEditorGroup, SessionData>();
173+
const groups: [IEditorGroup, SessionData][] = [];
142174
const orphans = new Set<SessionData>();
143175

144176
for (const data of sessions) {
177+
145178
const editor = this._inlineChatSessionService.getCodeEditor(data.session);
146179
const group = groupByEditor.get(editor);
147180
if (group) {
148181
// there is only one session per group because all sessions have the same model
149182
// because we save one file.
150-
groups.set(group, data);
183+
groups.push([group, data]);
151184
} else if (this._editorGroupService.groups.includes(data.groupCandidate)) {
152185
// the group candidate is still there. use it
153-
groups.set(data.groupCandidate, data);
186+
groups.push([data.groupCandidate, data]);
154187
} else {
155188
orphans.add(data);
156189
}
@@ -159,22 +192,61 @@ export class InlineChatSavingServiceImpl implements IInlineChatSavingService {
159192
}
160193

161194
private async _openAndWait(groups: Iterable<[IEditorGroup, SessionData]>, token: CancellationToken) {
162-
const sessions = new Set<SessionData>();
195+
196+
const dataByGroup = new Map<IEditorGroup, SessionData[]>();
163197
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);
170245
}
171-
this._inlineChatSessionService.moveSession(data.session, ctrl);
172-
sessions.add(data);
173246
}
174-
await this._waitForSessions(sessions, token);
175247
}
176248

177-
private async _waitForSessions(iterable: Iterable<SessionData>, token: CancellationToken) {
249+
private async _whenSessionsEnded(iterable: Iterable<SessionData>, token: CancellationToken) {
178250

179251
const sessions = new Map<Session, SessionData>();
180252
for (const item of iterable) {

0 commit comments

Comments
 (0)