Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/src/util/common/test/shims/notebookDocument.ts
13405 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import type * as vscode from 'vscode';
7
import { StringSHA1 } from '../../../vs/base/common/hash';
8
import { Schemas } from '../../../vs/base/common/network';
9
import { URI as Uri } from '../../../vs/base/common/uri';
10
import { NotebookCellKind, NotebookCellOutput, NotebookCellOutputItem, NotebookData } from '../../../vs/workbench/api/common/extHostTypes/notebooks';
11
import { createTextDocumentData, IExtHostDocumentData } from './textDocument';
12
13
interface ISimulationWorkspace {
14
addDocument(doc: IExtHostDocumentData): void;
15
addNotebookDocument(notebook: ExtHostNotebookDocumentData): void;
16
}
17
18
export interface NotebookCellExecutionSummary {
19
}
20
21
declare type OutputType = 'execute_result' | 'display_data' | 'stream' | 'error' | 'update_display_data';
22
23
function concatMultilineString(str: string | string[], trim?: boolean): string {
24
const nonLineFeedWhiteSpaceTrim = /(^[\t\f\v\r ]+|[\t\f\v\r ]+$)/g;
25
if (Array.isArray(str)) {
26
let result = '';
27
for (let i = 0; i < str.length; i += 1) {
28
const s = str[i];
29
if (i < str.length - 1 && !s.endsWith('\n')) {
30
result = result.concat(`${s}\n`);
31
} else {
32
result = result.concat(s);
33
}
34
}
35
36
// Just trim whitespace. Leave \n in place
37
return trim ? result.replace(nonLineFeedWhiteSpaceTrim, '') : result;
38
}
39
return trim ? str.toString().replace(nonLineFeedWhiteSpaceTrim, '') : str.toString();
40
}
41
42
enum CellOutputMimeTypes {
43
error = 'application/vnd.code.notebook.error',
44
stderr = 'application/vnd.code.notebook.stderr',
45
stdout = 'application/vnd.code.notebook.stdout'
46
}
47
48
const textMimeTypes = ['text/plain', 'text/markdown', 'text/latex', CellOutputMimeTypes.stderr, CellOutputMimeTypes.stdout];
49
50
51
function convertJupyterOutputToBuffer(mime: string, value: unknown): NotebookCellOutputItem {
52
if (!value) {
53
return NotebookCellOutputItem.text('', mime);
54
}
55
try {
56
if (
57
(mime.startsWith('text/') || textMimeTypes.includes(mime)) &&
58
(Array.isArray(value) || typeof value === 'string')
59
) {
60
const stringValue = Array.isArray(value) ? concatMultilineString(value) : value;
61
return NotebookCellOutputItem.text(stringValue, mime);
62
} else if (mime.startsWith('image/') && typeof value === 'string' && mime !== 'image/svg+xml') {
63
// Images in Jupyter are stored in base64 encoded format.
64
// VS Code expects bytes when rendering images.
65
if (typeof Buffer !== 'undefined' && typeof Buffer.from === 'function') {
66
return new NotebookCellOutputItem(Buffer.from(value, 'base64'), mime);
67
} else {
68
const data = Uint8Array.from(atob(value), c => c.charCodeAt(0));
69
return new NotebookCellOutputItem(data, mime);
70
}
71
} else if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
72
return NotebookCellOutputItem.text(JSON.stringify(value), mime);
73
} else if (mime === 'application/json') {
74
return NotebookCellOutputItem.json(value, mime);
75
} else {
76
// For everything else, treat the data as strings (or multi-line strings).
77
value = Array.isArray(value) ? concatMultilineString(value) : value;
78
return NotebookCellOutputItem.text(value as string, mime);
79
}
80
} catch (ex) {
81
return NotebookCellOutputItem.error(ex);
82
}
83
}
84
85
export function translateDisplayDataOutput(
86
output: any
87
): NotebookCellOutput {
88
const items: NotebookCellOutputItem[] = [];
89
if (output.data) {
90
for (const key in output.data) {
91
items.push(convertJupyterOutputToBuffer(key, output.data[key]));
92
}
93
}
94
95
return new NotebookCellOutput(items, {});
96
}
97
98
export function translateErrorOutput(output?: any): NotebookCellOutput {
99
output = output || { output_type: 'error', ename: '', evalue: '', traceback: [] };
100
return new NotebookCellOutput(
101
[
102
NotebookCellOutputItem.error({
103
name: output?.ename || '',
104
message: output?.evalue || '',
105
stack: (output?.traceback || []).join('\n')
106
})
107
],
108
{ originalError: output }
109
);
110
}
111
112
export function translateStreamOutput(output: any): NotebookCellOutput {
113
const value = concatMultilineString(output.text);
114
const item = output.name === 'stderr' ? NotebookCellOutputItem.stderr(value) : NotebookCellOutputItem.stdout(value);
115
return new NotebookCellOutput([item], {});
116
}
117
118
const cellOutputMappers = new Map<OutputType, (output: any) => NotebookCellOutput>();
119
cellOutputMappers.set('display_data', translateDisplayDataOutput);
120
cellOutputMappers.set('execute_result', translateDisplayDataOutput);
121
cellOutputMappers.set('update_display_data', translateDisplayDataOutput);
122
cellOutputMappers.set('error', translateErrorOutput);
123
cellOutputMappers.set('stream', translateStreamOutput);
124
125
126
export function jupyterCellOutputToCellOutput(output: any): NotebookCellOutput {
127
const fn = cellOutputMappers.get(output.output_type);
128
let result: NotebookCellOutput;
129
if (fn) {
130
result = fn(output);
131
} else {
132
result = translateDisplayDataOutput(output as any);
133
}
134
return result;
135
}
136
137
const textDecoder = new TextDecoder();
138
139
export interface CellOutputMetadata {
140
metadata?: any;
141
transient?: {
142
display_id?: string;
143
} & any;
144
145
outputType: OutputType | string;
146
executionCount?: number | null;
147
__isJson?: boolean;
148
}
149
150
function splitMultilineString(source: string | string[]): string[] {
151
if (Array.isArray(source)) {
152
return source as string[];
153
}
154
const str = source.toString();
155
if (str.length > 0) {
156
// Each line should be a separate entry, but end with a \n if not last entry
157
const arr = str.split('\n');
158
return arr
159
.map((s, i) => {
160
if (i < arr.length - 1) {
161
return `${s}\n`;
162
}
163
return s;
164
})
165
.filter(s => s.length > 0); // Skip last one if empty (it's the only one that could be length 0)
166
}
167
return [];
168
}
169
170
function translateCellErrorOutput(output: vscode.NotebookCellOutput) {
171
// it should have at least one output item
172
const firstItem = output.items[0];
173
// Bug in VS Code.
174
if (!firstItem.data) {
175
return {
176
output_type: 'error',
177
ename: '',
178
evalue: '',
179
traceback: []
180
};
181
}
182
const originalError = output.metadata?.originalError;
183
const value: Error = JSON.parse(textDecoder.decode(firstItem.data));
184
return {
185
output_type: 'error',
186
ename: value.name,
187
evalue: value.message,
188
// VS Code needs an `Error` object which requires a `stack` property as a string.
189
// Its possible the format could change when converting from `traceback` to `string` and back again to `string`
190
// When .NET stores errors in output (with their .NET kernel),
191
// stack is empty, hence store the message instead of stack (so that somethign gets displayed in ipynb).
192
traceback: originalError?.traceback || splitMultilineString(value.stack || value.message || '')
193
};
194
}
195
196
function convertStreamOutput(output: vscode.NotebookCellOutput) {
197
const outputs: string[] = [];
198
output.items
199
.filter((opit) => opit.mime === CellOutputMimeTypes.stderr || opit.mime === CellOutputMimeTypes.stdout)
200
.map((opit) => textDecoder.decode(opit.data))
201
.forEach(value => {
202
// Ensure each line is a separate entry in an array (ending with \n).
203
const lines = value.split('\n');
204
// If the last item in `outputs` is not empty and the first item in `lines` is not empty, then concate them.
205
// As they are part of the same line.
206
if (outputs.length && lines.length && lines[0].length > 0) {
207
outputs[outputs.length - 1] = `${outputs[outputs.length - 1]}${lines.shift()!}`;
208
}
209
for (const line of lines) {
210
outputs.push(line);
211
}
212
});
213
214
for (let index = 0; index < (outputs.length - 1); index++) {
215
outputs[index] = `${outputs[index]}\n`;
216
}
217
218
// Skip last one if empty (it's the only one that could be length 0)
219
if (outputs.length && outputs[outputs.length - 1].length === 0) {
220
outputs.pop();
221
}
222
223
const streamType = getOutputStreamType(output) || 'stdout';
224
225
return {
226
output_type: 'stream',
227
name: streamType,
228
text: outputs
229
};
230
}
231
232
function getOutputStreamType(output: vscode.NotebookCellOutput): string | undefined {
233
if (output.items.length > 0) {
234
return output.items[0].mime === CellOutputMimeTypes.stderr ? 'stderr' : 'stdout';
235
}
236
237
return;
238
}
239
240
function convertOutputMimeToJupyterOutput(mime: string, value: Uint8Array) {
241
if (!value) {
242
return '';
243
}
244
try {
245
if (mime === CellOutputMimeTypes.error) {
246
const stringValue = textDecoder.decode(value);
247
return JSON.parse(stringValue);
248
} else if (mime.startsWith('text/') || textMimeTypes.includes(mime)) {
249
const stringValue = textDecoder.decode(value);
250
return splitMultilineString(stringValue);
251
} else if (mime.startsWith('image/') && mime !== 'image/svg+xml') {
252
// Images in Jupyter are stored in base64 encoded format.
253
// VS Code expects bytes when rendering images.
254
if (typeof Buffer !== 'undefined' && typeof Buffer.from === 'function') {
255
return Buffer.from(value).toString('base64');
256
} else {
257
return btoa(value.reduce((s: string, b: number) => s + String.fromCharCode(b), ''));
258
}
259
} else if (mime.toLowerCase().includes('json')) {
260
const stringValue = textDecoder.decode(value);
261
return stringValue.length > 0 ? JSON.parse(stringValue) : stringValue;
262
} else if (mime === 'image/svg+xml') {
263
return splitMultilineString(textDecoder.decode(value));
264
} else {
265
return textDecoder.decode(value);
266
}
267
} catch (ex) {
268
return '';
269
}
270
}
271
272
function translateCellDisplayOutput(output: vscode.NotebookCellOutput): any {
273
const customMetadata = output.metadata as CellOutputMetadata | undefined;
274
let result;
275
// Possible some other extension added some output (do best effort to translate & save in ipynb).
276
// In which case metadata might not contain `outputType`.
277
const outputType = customMetadata?.outputType as OutputType;
278
switch (outputType) {
279
case 'error': {
280
result = translateCellErrorOutput(output);
281
break;
282
}
283
case 'stream': {
284
result = convertStreamOutput(output);
285
break;
286
}
287
case 'display_data': {
288
result = {
289
output_type: 'display_data',
290
data: output.items.reduce((prev: any, curr) => {
291
prev[curr.mime] = convertOutputMimeToJupyterOutput(curr.mime, curr.data as Uint8Array);
292
return prev;
293
}, {}),
294
metadata: customMetadata?.metadata || {} // This can never be undefined.
295
};
296
break;
297
}
298
case 'execute_result': {
299
result = {
300
output_type: 'execute_result',
301
data: output.items.reduce((prev: any, curr) => {
302
prev[curr.mime] = convertOutputMimeToJupyterOutput(curr.mime, curr.data as Uint8Array);
303
return prev;
304
}, {}),
305
metadata: customMetadata?.metadata || {}, // This can never be undefined.
306
execution_count:
307
typeof customMetadata?.executionCount === 'number' ? customMetadata?.executionCount : null // This can never be undefined, only a number or `null`.
308
};
309
break;
310
}
311
case 'update_display_data': {
312
result = {
313
output_type: 'update_display_data',
314
data: output.items.reduce((prev: any, curr) => {
315
prev[curr.mime] = convertOutputMimeToJupyterOutput(curr.mime, curr.data as Uint8Array);
316
return prev;
317
}, {}),
318
metadata: customMetadata?.metadata || {} // This can never be undefined.
319
};
320
break;
321
}
322
default: {
323
const isError =
324
output.items.length === 1 && output.items.every((item) => item.mime === CellOutputMimeTypes.error);
325
const isStream = output.items.every(
326
(item) => item.mime === CellOutputMimeTypes.stderr || item.mime === CellOutputMimeTypes.stdout
327
);
328
329
if (isError) {
330
return translateCellErrorOutput(output);
331
}
332
333
// In the case of .NET & other kernels, we need to ensure we save ipynb correctly.
334
// Hence if we have stream output, save the output as Jupyter `stream` else `display_data`
335
// Unless we already know its an unknown output type.
336
const outputType: OutputType =
337
<OutputType>customMetadata?.outputType || (isStream ? 'stream' : 'display_data');
338
let unknownOutput: any;
339
if (outputType === 'stream') {
340
// If saving as `stream` ensure the mandatory properties are set.
341
unknownOutput = convertStreamOutput(output);
342
} else if (outputType === 'display_data') {
343
// If saving as `display_data` ensure the mandatory properties are set.
344
const displayData = {
345
data: {},
346
metadata: {},
347
output_type: 'display_data'
348
};
349
unknownOutput = displayData;
350
} else {
351
unknownOutput = {
352
output_type: outputType
353
};
354
}
355
if (customMetadata?.metadata) {
356
unknownOutput.metadata = customMetadata.metadata;
357
}
358
if (output.items.length > 0) {
359
unknownOutput.data = output.items.reduce((prev: any, curr) => {
360
prev[curr.mime] = convertOutputMimeToJupyterOutput(curr.mime, curr.data as Uint8Array);
361
return prev;
362
}, {});
363
}
364
result = unknownOutput;
365
break;
366
}
367
}
368
369
// Account for transient data as well
370
// `transient.display_id` is used to update cell output in other cells, at least thats one use case we know of.
371
if (result && customMetadata && customMetadata.transient) {
372
result.transient = customMetadata.transient;
373
}
374
return result;
375
}
376
377
378
export class ExtHostCell {
379
index: number;
380
notebook: ExtHostNotebookDocumentData;
381
kind: NotebookCellKind;
382
documentData: IExtHostDocumentData;
383
metadata: { readonly [key: string]: any };
384
private _outputs: vscode.NotebookCellOutput[];
385
executionSummary: NotebookCellExecutionSummary | undefined;
386
387
get document() {
388
return this.documentData.document;
389
}
390
391
private _apiCell: vscode.NotebookCell | undefined;
392
393
constructor(
394
index: number,
395
kind: NotebookCellKind,
396
notebook: ExtHostNotebookDocumentData,
397
documentData: IExtHostDocumentData,
398
metadata: { readonly [key: string]: any },
399
outputs: vscode.NotebookCellOutput[],
400
executionSummary: NotebookCellExecutionSummary | undefined,
401
) {
402
this.documentData = documentData;
403
this.index = index;
404
this.kind = kind;
405
this.metadata = metadata;
406
this._outputs = outputs;
407
this.executionSummary = executionSummary;
408
this.notebook = notebook;
409
}
410
411
get apiCell(): vscode.NotebookCell {
412
if (!this._apiCell) {
413
const that = this;
414
const apiCell: vscode.NotebookCell = {
415
get index() { return that.notebook.getCellIndex(that); },
416
notebook: that.notebook.document,
417
kind: that.kind,
418
document: that.document,
419
get outputs() { return that._outputs.slice(0); },
420
get metadata() { return that.metadata; },
421
get executionSummary() { return that.executionSummary; }
422
};
423
this._apiCell = Object.freeze(apiCell);
424
}
425
return this._apiCell;
426
}
427
428
appendOutput(outputs: vscode.NotebookCellOutput[]) {
429
this._outputs.push(...outputs);
430
}
431
}
432
433
434
function generateCellFragment(index: number): string {
435
const hash = new StringSHA1();
436
hash.update(`index${index}`);
437
return hash.digest().substring(0, 8);
438
}
439
440
export class ExtHostNotebookDocumentData {
441
public static createJupyterNotebook(uri: Uri, contents: string, simulationWorkspace?: ISimulationWorkspace): ExtHostNotebookDocumentData {
442
const notebook = JSON.parse(contents);
443
const codeLanguageId = notebook.metadata?.language_info?.language ?? notebook.metadata?.language_info?.name ?? 'python';
444
const notebookDocument = new ExtHostNotebookDocumentData(uri, 'jupyter-notebook', notebook.metadata, []);
445
const cells: ExtHostCell[] = [];
446
447
for (const [index, cell] of notebook.cells.entries()) {
448
const content = cell.source.join('');
449
450
if (cell.cell_type === 'code') {
451
const doc = createTextDocumentData(uri.with({ scheme: Schemas.vscodeNotebookCell, fragment: generateCellFragment(index) }), content, codeLanguageId);
452
if (simulationWorkspace) {
453
simulationWorkspace.addDocument(doc);
454
}
455
const cellOutputs = Array.isArray(cell.outputs) ? cell.outputs : [];
456
const outputs = cellOutputs.map(jupyterCellOutputToCellOutput);
457
458
cells.push(new ExtHostCell(index, NotebookCellKind.Code, notebookDocument, doc, cell.metadata, outputs, undefined));
459
} else {
460
const doc = createTextDocumentData(uri.with({ scheme: Schemas.vscodeNotebookCell, fragment: generateCellFragment(index) }), content, 'markdown');
461
if (simulationWorkspace) {
462
simulationWorkspace.addDocument(doc);
463
}
464
cells.push(new ExtHostCell(index, NotebookCellKind.Markup, notebookDocument, doc, cell.metadata, [], undefined));
465
}
466
}
467
notebookDocument.cells = cells;
468
469
if (simulationWorkspace) {
470
simulationWorkspace.addNotebookDocument(notebookDocument);
471
}
472
473
return notebookDocument;
474
}
475
476
public static createGithubIssuesNotebook(uri: Uri, contents: string, simulationWorkspace?: ISimulationWorkspace): ExtHostNotebookDocumentData {
477
const notebook = JSON.parse(contents);
478
const notebookDocument = new ExtHostNotebookDocumentData(uri, 'github-issues', {}, []);
479
const cells: ExtHostCell[] = [];
480
481
for (const [index, cell] of notebook.entries()) {
482
const doc = createTextDocumentData(uri.with({ scheme: Schemas.vscodeNotebookCell, fragment: generateCellFragment(index) }), cell.value, cell.language);
483
if (simulationWorkspace) {
484
simulationWorkspace.addDocument(doc);
485
}
486
cells.push(new ExtHostCell(index, cell.kind, notebookDocument, doc, {}, [], undefined));
487
}
488
notebookDocument.cells = cells;
489
490
if (simulationWorkspace) {
491
simulationWorkspace.addNotebookDocument(notebookDocument);
492
}
493
return notebookDocument;
494
}
495
496
public static fromNotebookData(uri: Uri, data: NotebookData, notebookType: string, simulationWorkspace?: ISimulationWorkspace): ExtHostNotebookDocumentData {
497
const notebookDocument = new ExtHostNotebookDocumentData(uri, notebookType, data.metadata || {}, []);
498
const cells: ExtHostCell[] = [];
499
500
for (const [index, cell] of data.cells.entries()) {
501
const doc = createTextDocumentData(uri.with({ scheme: Schemas.vscodeNotebookCell, fragment: generateCellFragment(index) }), cell.value, cell.languageId);
502
if (cell.outputs?.length) {
503
throw new Error('Not implemented');
504
}
505
if (simulationWorkspace) {
506
simulationWorkspace.addDocument(doc);
507
}
508
cells.push(new ExtHostCell(index, cell.kind, notebookDocument, doc, cell.metadata || {}, [], undefined));
509
}
510
notebookDocument.cells = cells;
511
if (simulationWorkspace) {
512
simulationWorkspace.addNotebookDocument(notebookDocument);
513
}
514
515
return notebookDocument;
516
}
517
518
public static applyEdits(notebookDocument: ExtHostNotebookDocumentData, edits: vscode.NotebookEdit[], simulationWorkspace?: ISimulationWorkspace) {
519
for (const edit of edits) {
520
if (edit.newNotebookMetadata) {
521
throw new Error('Not Supported');
522
}
523
if (edit.newCellMetadata) {
524
throw new Error('Not Supported');
525
}
526
if (edit.newCells) {
527
ExtHostNotebookDocumentData.replaceCells(notebookDocument, edit.range, edit.newCells, simulationWorkspace);
528
} else {
529
notebookDocument._cells.splice(edit.range.start, edit.range.end - edit.range.start);
530
}
531
}
532
}
533
534
private static replaceCells(notebookDocument: ExtHostNotebookDocumentData, range: vscode.NotebookRange, cells: vscode.NotebookCellData[], simulationWorkspace?: ISimulationWorkspace) {
535
const uri = notebookDocument.uri;
536
const docs = cells.map((cell, index) => {
537
const doc = createTextDocumentData(uri.with({ scheme: Schemas.vscodeNotebookCell, fragment: generateCellFragment(notebookDocument.cells.length + index + 1) }), cell.value, cell.languageId);
538
if (simulationWorkspace) {
539
simulationWorkspace.addDocument(doc);
540
}
541
if (cell.outputs?.length) {
542
// throw new Error('Not implemented');
543
}
544
return doc;
545
});
546
const extCells = docs.map((doc, index) => new ExtHostCell(index, cells[index].kind, notebookDocument, doc, cells[index].metadata || {}, [], undefined));
547
if (notebookDocument.cells.length) {
548
notebookDocument.cells.splice(range.start, range.end > range.start ? range.end - range.start : 0, ...extCells);
549
} else {
550
notebookDocument.cells.push(...extCells);
551
}
552
}
553
554
private _cells: ExtHostCell[] = [];
555
set cells(cells: ExtHostCell[]) {
556
this._cells = cells;
557
}
558
get cells() {
559
return this._cells;
560
}
561
uri: Uri;
562
563
private readonly _notebookType: string;
564
565
private _notebook: vscode.NotebookDocument | undefined;
566
private _metadata: Record<string, any>;
567
private _versionId: number = 0;
568
private _isDirty: boolean = false;
569
private _disposed: boolean = false;
570
571
constructor(
572
uri: Uri,
573
notebookType: string,
574
metadata: { [key: string]: any },
575
cells: ExtHostCell[],
576
) {
577
this.uri = uri;
578
this._notebookType = notebookType;
579
this._metadata = metadata;
580
this._cells = cells;
581
}
582
583
get document(): vscode.NotebookDocument {
584
if (!this._notebook) {
585
const that = this;
586
const apiObject: vscode.NotebookDocument = {
587
get uri() { return that.uri; },
588
get version() { return that._versionId; },
589
get notebookType() { return that._notebookType; },
590
get isDirty() { return that._isDirty; },
591
get isUntitled() { return that.uri.scheme === 'untitled'; },
592
get isClosed() { return that._disposed; },
593
get metadata() { return that._metadata; },
594
get cellCount() { return that._cells.length; },
595
cellAt(index) {
596
return that._cells[index].apiCell;
597
},
598
getCells(range) {
599
const cells = range ? that._getCells(range) : that._cells;
600
return cells.map(cell => cell.apiCell);
601
},
602
save() {
603
return Promise.resolve(true);
604
}
605
};
606
this._notebook = Object.freeze(apiObject);
607
}
608
return this._notebook;
609
}
610
611
get cellCount(): number {
612
return this._cells.length;
613
}
614
cellAt(index: number): ExtHostCell {
615
return this._cells[index];
616
}
617
618
private _getCells(range: vscode.NotebookRange): ExtHostCell[] {
619
const result: ExtHostCell[] = [];
620
for (let i = range.start; i < range.end; i++) {
621
result.push(this._cells[i]);
622
}
623
return result;
624
}
625
626
getCellIndex(cell: ExtHostCell): number {
627
return this._cells.indexOf(cell);
628
}
629
630
getText(): string {
631
return JSON.stringify({
632
cells: this._cells.map(cell => ({
633
cell_type: cell.kind === 2 ? 'code' : 'markdown',
634
source: [cell.document.getText()],
635
metadata: cell.metadata,
636
outputs: (cell.apiCell.outputs || []).map(translateCellDisplayOutput),
637
})),
638
metadata: this._metadata,
639
}, undefined, 4);
640
}
641
642
appendCellOutput(cellIndex: number, outputs: vscode.NotebookCellOutput[]): void {
643
this._cells[cellIndex].appendOutput(outputs);
644
}
645
}
646
// export const _documents = new ResourceMap<ExtHostNotebookDocumentData>();
647
648
// export function addNotebookDocument(notebook: ExtHostNotebookDocumentData) {
649
// _documents.set(notebook.uri, notebook);
650
// }
651
652
// export function getNotebookDocuments(): vscode.NotebookDocument[] {
653
// return Array.from(_documents.values()).map(data => data.document);
654
// }
655
656