Skip to content

Commit 469bd74

Browse files
authored
ported the full output optimization code from VSCode (#13137)
* ported the full output optimization code from VSCode Signed-off-by: Jonah Iden <[email protected]> * added commit id to licence header Signed-off-by: Jonah Iden <[email protected]> * added file to comment Signed-off-by: Jonah Iden <[email protected]> * fixed multiple outputs now updating correctly Signed-off-by: Jonah Iden <[email protected]> --------- Signed-off-by: Jonah Iden <[email protected]>
1 parent 273c7e2 commit 469bd74

File tree

3 files changed

+126
-6
lines changed

3 files changed

+126
-6
lines changed
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
// *****************************************************************************
2+
// Copyright (C) 2023 TypeFox and others.
3+
//
4+
// This program and the accompanying materials are made available under the
5+
// terms of the Eclipse Public License v. 2.0 which is available at
6+
// http://www.eclipse.org/legal/epl-2.0.
7+
//
8+
// This Source Code may also be made available under the following Secondary
9+
// Licenses when the conditions for such availability set forth in the Eclipse
10+
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
11+
// with the GNU Classpath Exception which is available at
12+
// https://www.gnu.org/software/classpath/license.html.
13+
//
14+
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
15+
// *****************************************************************************
16+
/*---------------------------------------------------------------------------------------------
17+
* Copyright (c) Microsoft Corporation. All rights reserved.
18+
* Licensed under the MIT License. See License.txt in the project root for license information.
19+
* Copied from commit 18b2c92451b076943e5b508380e0eba66ba7d934 from file src\vs\workbench\contrib\notebook\common\notebookCommon.ts
20+
*--------------------------------------------------------------------------------------------*/
21+
22+
import { BinaryBuffer } from '@theia/core/lib/common/buffer';
23+
24+
const textDecoder = new TextDecoder();
25+
26+
/**
27+
* Given a stream of individual stdout outputs, this function will return the compressed lines, escaping some of the common terminal escape codes.
28+
* E.g. some terminal escape codes would result in the previous line getting cleared, such if we had 3 lines and
29+
* last line contained such a code, then the result string would be just the first two lines.
30+
* @returns a single VSBuffer with the concatenated and compressed data, and whether any compression was done.
31+
*/
32+
export function compressOutputItemStreams(outputs: Uint8Array[]): { data: BinaryBuffer, didCompression: boolean } {
33+
const buffers: Uint8Array[] = [];
34+
let startAppending = false;
35+
36+
// Pick the first set of outputs with the same mime type.
37+
for (const output of outputs) {
38+
if ((buffers.length === 0 || startAppending)) {
39+
buffers.push(output);
40+
startAppending = true;
41+
}
42+
}
43+
44+
let didCompression = compressStreamBuffer(buffers);
45+
const concatenated = BinaryBuffer.concat(buffers.map(buffer => BinaryBuffer.wrap(buffer)));
46+
const data = formatStreamText(concatenated);
47+
didCompression = didCompression || data.byteLength !== concatenated.byteLength;
48+
return { data, didCompression };
49+
}
50+
51+
export const MOVE_CURSOR_1_LINE_COMMAND = `${String.fromCharCode(27)}[A`;
52+
const MOVE_CURSOR_1_LINE_COMMAND_BYTES = MOVE_CURSOR_1_LINE_COMMAND.split('').map(c => c.charCodeAt(0));
53+
const LINE_FEED = 10;
54+
function compressStreamBuffer(streams: Uint8Array[]): boolean {
55+
let didCompress = false;
56+
streams.forEach((stream, index) => {
57+
if (index === 0 || stream.length < MOVE_CURSOR_1_LINE_COMMAND.length) {
58+
return;
59+
}
60+
61+
const previousStream = streams[index - 1];
62+
63+
// Remove the previous line if required.
64+
const command = stream.subarray(0, MOVE_CURSOR_1_LINE_COMMAND.length);
65+
if (command[0] === MOVE_CURSOR_1_LINE_COMMAND_BYTES[0] && command[1] === MOVE_CURSOR_1_LINE_COMMAND_BYTES[1] && command[2] === MOVE_CURSOR_1_LINE_COMMAND_BYTES[2]) {
66+
const lastIndexOfLineFeed = previousStream.lastIndexOf(LINE_FEED);
67+
if (lastIndexOfLineFeed === -1) {
68+
return;
69+
}
70+
71+
didCompress = true;
72+
streams[index - 1] = previousStream.subarray(0, lastIndexOfLineFeed);
73+
streams[index] = stream.subarray(MOVE_CURSOR_1_LINE_COMMAND.length);
74+
}
75+
});
76+
return didCompress;
77+
}
78+
79+
const BACKSPACE_CHARACTER = '\b'.charCodeAt(0);
80+
const CARRIAGE_RETURN_CHARACTER = '\r'.charCodeAt(0);
81+
function formatStreamText(buffer: BinaryBuffer): BinaryBuffer {
82+
// We have special handling for backspace and carriage return characters.
83+
// Don't unnecessary decode the bytes if we don't need to perform any processing.
84+
if (!buffer.buffer.includes(BACKSPACE_CHARACTER) && !buffer.buffer.includes(CARRIAGE_RETURN_CHARACTER)) {
85+
return buffer;
86+
}
87+
// Do the same thing jupyter is doing
88+
return BinaryBuffer.fromString(fixCarriageReturn(fixBackspace(textDecoder.decode(buffer.buffer))));
89+
}
90+
91+
/**
92+
* Took this from jupyter/notebook
93+
* https://github.com/jupyter/notebook/blob/b8b66332e2023e83d2ee04f83d8814f567e01a4e/notebook/static/base/js/utils.js
94+
* Remove characters that are overridden by backspace characters
95+
*/
96+
function fixBackspace(txt: string): string {
97+
let tmp = txt;
98+
do {
99+
txt = tmp;
100+
// Cancel out anything-but-newline followed by backspace
101+
tmp = txt.replace(/[^\n]\x08/gm, '');
102+
} while (tmp.length < txt.length);
103+
return txt;
104+
}
105+
106+
/**
107+
* Remove chunks that should be overridden by the effect of carriage return characters
108+
* From https://github.com/jupyter/notebook/blob/master/notebook/static/base/js/utils.js
109+
*/
110+
function fixCarriageReturn(txt: string): string {
111+
txt = txt.replace(/\r+\n/gm, '\n'); // \r followed by \n --> newline
112+
while (txt.search(/\r[^$]/g) > -1) {
113+
const base = txt.match(/^(.*)\r+/m)![1];
114+
let insert = txt.match(/\r+(.*)$/m)![1];
115+
insert = insert + base.slice(insert.length, base.length);
116+
txt = txt.replace(/\r+.*$/m, '\r').replace(/^.*\r/m, insert);
117+
}
118+
return txt;
119+
}

packages/notebook/src/browser/view-model/notebook-cell-output-model.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@
1515
// *****************************************************************************
1616

1717
import { Disposable, Emitter } from '@theia/core';
18-
import { BinaryBuffer } from '@theia/core/lib/common/buffer';
1918
import { CellOutput, CellOutputItem, isTextStreamMime } from '../../common';
19+
import { compressOutputItemStreams } from '../notebook-output-utils';
2020

2121
export class NotebookCellOutputModel implements Disposable {
2222

@@ -73,24 +73,25 @@ export class NotebookCellOutputModel implements Disposable {
7373
if (this.outputs.length > 1 && this.outputs.every(item => isTextStreamMime(item.mime))) {
7474
// Look for the mimes in the items, and keep track of their order.
7575
// Merge the streams into one output item, per mime type.
76-
const mimeOutputs = new Map<string, BinaryBuffer[]>();
76+
const mimeOutputs = new Map<string, Uint8Array[]>();
7777
const mimeTypes: string[] = [];
7878
this.outputs.forEach(item => {
79-
let items: BinaryBuffer[];
79+
let items: Uint8Array[];
8080
if (mimeOutputs.has(item.mime)) {
8181
items = mimeOutputs.get(item.mime)!;
8282
} else {
8383
items = [];
8484
mimeOutputs.set(item.mime, items);
8585
mimeTypes.push(item.mime);
8686
}
87-
items.push(item.data);
87+
items.push(item.data.buffer);
8888
});
8989
this.outputs.length = 0;
9090
mimeTypes.forEach(mime => {
91+
const compressionResult = compressOutputItemStreams(mimeOutputs.get(mime)!);
9192
this.outputs.push({
9293
mime,
93-
data: BinaryBuffer.concat(mimeOutputs.get(mime)!)
94+
data: compressionResult.data
9495
});
9596
});
9697
}

packages/plugin-ext/src/main/browser/notebooks/renderers/cell-output-webview.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ export class CellOutputWebviewImpl implements CellOutputWebview, Disposable {
7676
protected async init(): Promise<void> {
7777
this.cell.onDidChangeOutputs(outputChange => this.updateOutput(outputChange));
7878
this.cell.onDidChangeOutputItems(output => {
79-
this.updateOutput({start: this.cell.outputs.findIndex(o => o.getData().outputId === o.outputId), deleteCount: 1, newOutputs: [output]});
79+
this.updateOutput({start: this.cell.outputs.findIndex(o => o.outputId === output.outputId), deleteCount: 1, newOutputs: [output]});
8080
});
8181

8282
this.webviewWidget = await this.widgetManager.getOrCreateWidget(WebviewWidget.FACTORY_ID, { id: this.id });

0 commit comments

Comments
 (0)