Skip to content

Commit a14736c

Browse files
committed
introduce visible notebook editors
1 parent 9a38476 commit a14736c

13 files changed

Lines changed: 354 additions & 63 deletions

src/vs/vscode.proposed.d.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1879,7 +1879,8 @@ declare module 'vscode' {
18791879

18801880
export const onDidOpenNotebookDocument: Event<NotebookDocument>;
18811881
export const onDidCloseNotebookDocument: Event<NotebookDocument>;
1882-
// export const onDidChangeVisibleNotebookEditors: Event<NotebookEditor[]>;
1882+
export let visibleNotebookEditors: NotebookEditor[];
1883+
export const onDidChangeVisibleNotebookEditors: Event<NotebookEditor[]>;
18831884

18841885
// remove activeNotebookDocument, now that there is activeNotebookEditor.document
18851886
export let activeNotebookDocument: NotebookDocument | undefined;

src/vs/workbench/api/browser/mainThreadNotebook.ts

Lines changed: 129 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@
44
*--------------------------------------------------------------------------------------------*/
55

66
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
7-
import { MainContext, MainThreadNotebookShape, NotebookExtensionDescription, IExtHostContext, ExtHostNotebookShape, ExtHostContext } from '../common/extHost.protocol';
8-
import { Disposable } from 'vs/base/common/lifecycle';
7+
import { MainContext, MainThreadNotebookShape, NotebookExtensionDescription, IExtHostContext, ExtHostNotebookShape, ExtHostContext, INotebookDocumentsAndEditorsDelta } from '../common/extHost.protocol';
8+
import { Disposable, IDisposable, combinedDisposable } from 'vs/base/common/lifecycle';
99
import { URI, UriComponents } from 'vs/base/common/uri';
1010
import { INotebookService, IMainNotebookController } from 'vs/workbench/contrib/notebook/common/notebookService';
11-
import { INotebookTextModel, INotebookMimeTypeSelector, NOTEBOOK_DISPLAY_ORDER, NotebookCellOutputsSplice, NotebookDocumentMetadata, NotebookCellMetadata, ICellEditOperation, ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER, CellEditType, CellKind, INotebookKernelInfo, INotebookKernelInfoDto, INotebookTextModelBackup } from 'vs/workbench/contrib/notebook/common/notebookCommon';
11+
import { INotebookTextModel, INotebookMimeTypeSelector, NOTEBOOK_DISPLAY_ORDER, NotebookCellOutputsSplice, NotebookDocumentMetadata, NotebookCellMetadata, ICellEditOperation, ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER, CellEditType, CellKind, INotebookKernelInfo, INotebookKernelInfoDto, INotebookTextModelBackup, IEditor } from 'vs/workbench/contrib/notebook/common/notebookCommon';
1212
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
1313
import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel';
1414
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
@@ -58,11 +58,77 @@ export class MainThreadNotebookDocument extends Disposable {
5858
}
5959
}
6060

61+
class DocumentAndEditorState {
62+
static ofMaps<K, V>(before: Map<K, V>, after: Map<K, V>): { removed: V[], added: V[] } {
63+
const removed: V[] = [];
64+
const added: V[] = [];
65+
before.forEach((value, index) => {
66+
if (!after.has(index)) {
67+
removed.push(value);
68+
}
69+
});
70+
after.forEach((value, index) => {
71+
if (!before.has(index)) {
72+
added.push(value);
73+
}
74+
});
75+
return { removed, added };
76+
}
77+
78+
static compute(before: DocumentAndEditorState | undefined, after: DocumentAndEditorState): INotebookDocumentsAndEditorsDelta {
79+
if (!before) {
80+
const apiEditors = [];
81+
for (let id in after.textEditors) {
82+
const editor = after.textEditors.get(id)!;
83+
apiEditors.push({ id, documentUri: editor.uri!, selections: editor!.textModel!.selections });
84+
}
85+
86+
return {
87+
addedDocuments: [],
88+
addedEditors: apiEditors
89+
};
90+
}
91+
// const documentDelta = delta.ofSets(before.documents, after.documents);
92+
const editorDelta = DocumentAndEditorState.ofMaps(before.textEditors, after.textEditors);
93+
const addedAPIEditors = editorDelta.added.map(add => ({
94+
id: add.getId(),
95+
documentUri: add.uri!,
96+
selections: add.textModel!.selections
97+
}));
98+
99+
const removedAPIEditors = editorDelta.removed.map(removed => removed.getId());
100+
101+
// const oldActiveEditor = before.activeEditor !== after.activeEditor ? before.activeEditor : undefined;
102+
const newActiveEditor = before.activeEditor !== after.activeEditor ? after.activeEditor : undefined;
103+
104+
// return new DocumentAndEditorStateDelta(
105+
// documentDelta.removed, documentDelta.added,
106+
// editorDelta.removed, editorDelta.added,
107+
// oldActiveEditor, newActiveEditor
108+
// );
109+
return {
110+
addedEditors: addedAPIEditors,
111+
removedEditors: removedAPIEditors,
112+
newActiveEditor: newActiveEditor
113+
};
114+
}
115+
116+
constructor(
117+
readonly documents: Set<URI>,
118+
readonly textEditors: Map<string, IEditor>,
119+
readonly activeEditor: string | null | undefined,
120+
) {
121+
//
122+
}
123+
}
124+
61125
@extHostNamedCustomer(MainContext.MainThreadNotebook)
62126
export class MainThreadNotebooks extends Disposable implements MainThreadNotebookShape {
63127
private readonly _notebookProviders = new Map<string, MainThreadNotebookController>();
64128
private readonly _notebookKernels = new Map<string, MainThreadNotebookKernel>();
65129
private readonly _proxy: ExtHostNotebookShape;
130+
private _toDisposeOnEditorRemove = new Map<string, IDisposable>();
131+
private _currentState?: DocumentAndEditorState;
66132

67133
constructor(
68134
extHostContext: IExtHostContext,
@@ -90,10 +156,18 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo
90156
registerListeners() {
91157
this._register(this._notebookService.onDidChangeActiveEditor(e => {
92158
this._proxy.$acceptDocumentAndEditorsDelta({
93-
newActiveEditor: e.uri
159+
newActiveEditor: e
94160
});
95161
}));
96162

163+
this._register(this._notebookService.onNotebookEditorAdd(editor => {
164+
this._addNotebookEditor(editor);
165+
}));
166+
167+
this._register(this._notebookService.onNotebookEditorRemove(editor => {
168+
this._removeNotebookEditor(editor);
169+
}));
170+
97171
const updateOrder = () => {
98172
let userOrder = this.configurationService.getValue<string[]>('notebook.displayOrder');
99173
this._proxy.$acceptDisplayOrder({
@@ -115,6 +189,57 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo
115189
}));
116190
}
117191

192+
private _addNotebookEditor(e: IEditor) {
193+
this._toDisposeOnEditorRemove.set(e.getId(), combinedDisposable(
194+
e.onDidChangeModel(() => this._updateState()),
195+
e.onDidFocusEditorWidget(() => this._updateState(e)),
196+
));
197+
198+
this._updateState();
199+
}
200+
201+
private _removeNotebookEditor(e: IEditor) {
202+
const sub = this._toDisposeOnEditorRemove.get(e.getId());
203+
if (sub) {
204+
this._toDisposeOnEditorRemove.delete(e.getId());
205+
sub.dispose();
206+
this._updateState();
207+
}
208+
}
209+
210+
private async _updateState(focusedNotebookEditor?: IEditor) {
211+
const documents = new Set<URI>();
212+
this._notebookService.listNotebookDocuments().forEach(document => {
213+
documents.add(document.uri);
214+
});
215+
216+
const editors = new Map<string, IEditor>();
217+
let activeEditor: string | null = null;
218+
219+
for (const editor of this._notebookService.listNotebookEditors()) {
220+
if (editor.hasModel()) {
221+
editors.set(editor.getId(), editor);
222+
if (editor.hasFocus()) {
223+
activeEditor = editor.getId();
224+
}
225+
}
226+
}
227+
228+
// editors always have view model attached, which means there is already a document in exthost.
229+
const newState = new DocumentAndEditorState(documents, editors, activeEditor);
230+
const delta = DocumentAndEditorState.compute(this._currentState, newState);
231+
// const isEmptyChange = (!delta.addedDocuments || delta.addedDocuments.length === 0)
232+
// && (!delta.removedDocuments || delta.removedDocuments.length === 0)
233+
// && (!delta.addedEditors || delta.addedEditors.length === 0)
234+
// && (!delta.removedEditors || delta.removedEditors.length === 0)
235+
// && (delta.newActiveEditor === undefined)
236+
237+
// if (!isEmptyChange) {
238+
this._currentState = newState;
239+
await this._proxy.$acceptDocumentAndEditorsDelta(delta);
240+
// }
241+
}
242+
118243
async $registerNotebookRenderer(extension: NotebookExtensionDescription, type: string, selectors: INotebookMimeTypeSelector, handle: number, preloads: UriComponents[]): Promise<void> {
119244
this._notebookService.registerNotebookRenderer(handle, extension, type, selectors, preloads.map(uri => URI.revive(uri)));
120245
}

src/vs/workbench/api/common/extHost.api.impl.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -918,6 +918,12 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
918918
checkProposedApiEnabled(extension);
919919
return extHostNotebook.onDidCloseNotebookDocument;
920920
},
921+
get visibleNotebookEditors() {
922+
return extHostNotebook.visibleNotebookEditors;
923+
},
924+
get onDidChangeVisibleNotebookEditors() {
925+
return extHostNotebook.onDidChangeVisibleNotebookEditors;
926+
},
921927
registerNotebookContentProvider: (viewType: string, provider: vscode.NotebookContentProvider) => {
922928
checkProposedApiEnabled(extension);
923929
return extHostNotebook.registerNotebookContentProvider(extension, viewType, provider);

src/vs/workbench/api/common/extHost.protocol.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1553,12 +1553,18 @@ export interface INotebookModelAddedData {
15531553
metadata?: NotebookDocumentMetadata;
15541554
}
15551555

1556+
export interface INotebookEditorAddData {
1557+
id: string;
1558+
documentUri: UriComponents;
1559+
selections: number[];
1560+
}
1561+
15561562
export interface INotebookDocumentsAndEditorsDelta {
15571563
removedDocuments?: UriComponents[];
15581564
addedDocuments?: INotebookModelAddedData[];
1559-
// removedEditors?: string[];
1560-
// addedEditors?: ITextEditorAddData[];
1561-
newActiveEditor?: UriComponents | null;
1565+
removedEditors?: string[];
1566+
addedEditors?: INotebookEditorAddData[];
1567+
newActiveEditor?: string | null;
15621568
}
15631569

15641570
export interface ExtHostNotebookShape {

src/vs/workbench/api/common/extHostNotebook.ts

Lines changed: 85 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,8 @@ export class ExtHostNotebookDocument extends Disposable implements vscode.Notebo
219219
return this._versionId;
220220
}
221221

222+
webviewId: string = '';
223+
222224
constructor(
223225
private readonly _proxy: MainThreadNotebookShape,
224226
private _documentsAndEditors: ExtHostDocumentsAndEditors,
@@ -499,7 +501,7 @@ export class ExtHostNotebookEditor extends Disposable implements vscode.Notebook
499501
public uri: URI,
500502
private _proxy: MainThreadNotebookShape,
501503
private _onDidReceiveMessage: Emitter<any>,
502-
private _webviewId: string,
504+
public _webviewId: string,
503505
private _webviewInitData: WebviewInitData,
504506
public document: ExtHostNotebookDocument,
505507
private _documentsAndEditors: ExtHostDocumentsAndEditors
@@ -627,8 +629,6 @@ export interface ExtHostNotebookOutputRenderingHandler {
627629
}
628630

629631
export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostNotebookOutputRenderingHandler {
630-
private static _handlePool: number = 0;
631-
632632
private readonly _proxy: MainThreadNotebookShape;
633633
private readonly _notebookContentProviders = new Map<string, { readonly provider: vscode.NotebookContentProvider, readonly extension: IExtensionDescription; }>();
634634
private readonly _notebookKernels = new Map<string, { readonly kernel: vscode.NotebookKernel, readonly extension: IExtensionDescription; }>();
@@ -662,6 +662,9 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN
662662
onDidOpenNotebookDocument: Event<vscode.NotebookDocument> = this._onDidOpenNotebookDocument.event;
663663
private _onDidCloseNotebookDocument = new Emitter<vscode.NotebookDocument>();
664664
onDidCloseNotebookDocument: Event<vscode.NotebookDocument> = this._onDidCloseNotebookDocument.event;
665+
visibleNotebookEditors: ExtHostNotebookEditor[] = [];
666+
private _onDidChangeVisibleNotebookEditors = new Emitter<vscode.NotebookEditor[]>();
667+
onDidChangeVisibleNotebookEditors = this._onDidChangeVisibleNotebookEditors.event;
665668

666669
constructor(mainContext: IMainContext, commands: ExtHostCommands, private _documentsAndEditors: ExtHostDocumentsAndEditors, private readonly _webviewInitData: WebviewInitData) {
667670
this._proxy = mainContext.getProxy(MainContext.MainThreadNotebook);
@@ -934,16 +937,29 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN
934937
this._outputDisplayOrder = displayOrder;
935938
}
936939

940+
// TODO: remove document - editor one on one mapping
941+
private _getEditorFromURI(uriComponents: UriComponents) {
942+
const uriStr = URI.revive(uriComponents).toString();
943+
let editor: { editor: ExtHostNotebookEditor, onDidReceiveMessage: Emitter<any>; } | undefined;
944+
this._editors.forEach(e => {
945+
if (e.editor.uri.toString() === uriStr) {
946+
editor = e;
947+
}
948+
});
949+
950+
return editor;
951+
}
952+
937953
$onDidReceiveMessage(uri: UriComponents, message: any): void {
938-
let editor = this._editors.get(URI.revive(uri).toString());
954+
let editor = this._getEditorFromURI(uri);
939955

940956
if (editor) {
941957
editor.onDidReceiveMessage.fire(message);
942958
}
943959
}
944960

945961
$acceptModelChanged(uriComponents: UriComponents, event: NotebookCellsChangedEvent): void {
946-
let editor = this._editors.get(URI.revive(uriComponents).toString());
962+
let editor = this._getEditorFromURI(uriComponents);
947963

948964
if (editor) {
949965
editor.editor.document.accpetModelChanged(event);
@@ -956,7 +972,7 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN
956972
}
957973

958974
$acceptEditorPropertiesChanged(uriComponents: UriComponents, data: INotebookEditorPropertiesChangeData): void {
959-
let editor = this._editors.get(URI.revive(uriComponents).toString());
975+
let editor = this._getEditorFromURI(uriComponents);
960976

961977
if (!editor) {
962978
return;
@@ -1029,34 +1045,79 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN
10291045
]
10301046
});
10311047

1048+
document.webviewId = modelData.webviewId;
10321049
this._documents.set(revivedUriStr, document);
10331050
}
10341051

1035-
const onDidReceiveMessage = new Emitter<any>();
10361052
const document = this._documents.get(revivedUriStr)!;
1053+
this._onDidOpenNotebookDocument.fire(document);
1054+
});
1055+
}
10371056

1038-
let editor = new ExtHostNotebookEditor(
1039-
viewType,
1040-
`${ExtHostNotebookController._handlePool++}`,
1041-
revivedUri,
1042-
this._proxy,
1043-
onDidReceiveMessage,
1044-
modelData.webviewId,
1045-
this._webviewInitData,
1046-
document,
1047-
this._documentsAndEditors
1048-
);
1057+
let editorChanged = false;
10491058

1050-
this._onDidOpenNotebookDocument.fire(document);
1059+
if (delta.addedEditors) {
1060+
delta.addedEditors.forEach(editorModelData => {
1061+
const revivedUri = URI.revive(editorModelData.documentUri);
1062+
const document = this._documents.get(revivedUri.toString());
1063+
1064+
if (document) {
1065+
const onDidReceiveMessage = new Emitter<any>();
1066+
1067+
let editor = new ExtHostNotebookEditor(
1068+
document.viewType,
1069+
editorModelData.id,
1070+
revivedUri,
1071+
this._proxy,
1072+
onDidReceiveMessage,
1073+
document.webviewId,
1074+
this._webviewInitData,
1075+
document,
1076+
this._documentsAndEditors
1077+
);
1078+
1079+
const cells = editor.document.cells;
1080+
1081+
if (editorModelData.selections.length) {
1082+
const firstCell = editorModelData.selections[0];
1083+
editor.selection = cells.find(cell => cell.handle === firstCell);
1084+
} else {
1085+
editor.selection = undefined;
1086+
}
1087+
1088+
editorChanged = true;
1089+
1090+
this._editors.set(editorModelData.id, { editor, onDidReceiveMessage });
1091+
}
1092+
});
1093+
}
1094+
1095+
if (delta.removedEditors) {
1096+
delta.removedEditors.forEach(editorid => {
1097+
const editor = this._editors.get(editorid);
10511098

1052-
// TODO, does it already exist?
1053-
this._editors.set(revivedUriStr, { editor, onDidReceiveMessage });
1099+
if (editor) {
1100+
editorChanged = true;
1101+
this._editors.delete(editorid);
1102+
1103+
// TODO, dispose the editor
1104+
}
10541105
});
10551106
}
10561107

1057-
if (delta.newActiveEditor) {
1058-
this._activeNotebookDocument = this._documents.get(URI.revive(delta.newActiveEditor).toString());
1059-
this._activeNotebookEditor = this._editors.get(URI.revive(delta.newActiveEditor).toString())?.editor;
1108+
if (editorChanged) {
1109+
this.visibleNotebookEditors = [...this._editors.values()].map(e => e.editor);
1110+
this._onDidChangeVisibleNotebookEditors.fire(this.visibleNotebookEditors);
1111+
}
1112+
1113+
if (delta.newActiveEditor !== undefined) {
1114+
if (delta.newActiveEditor) {
1115+
this._activeNotebookEditor = this._editors.get(delta.newActiveEditor)?.editor;
1116+
this._activeNotebookDocument = this._documents.get(this._activeNotebookEditor!.uri.toString());
1117+
} else {
1118+
this._activeNotebookEditor = undefined;
1119+
this._activeNotebookDocument = undefined;
1120+
}
10601121
}
10611122
}
10621123
}

0 commit comments

Comments
 (0)