Skip to content

Commit 7b06521

Browse files
committed
added ability to append and clear results
1 parent 531bb15 commit 7b06521

4 files changed

Lines changed: 204 additions & 41 deletions

File tree

src/client/jupyter/browser/main.ts

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@
33
const transformime = require('transformime');
44
const MarkdownTransform = require('transformime-marked');
55
const transform = transformime.createTransform([MarkdownTransform]) as Function;
6+
const ResultsContainerId = 'resultsContainer';
67

78
function displayData(data: any, whiteBg: boolean): Promise<HTMLElement> {
9+
const container = document.getElementById(ResultsContainerId);
810
if (typeof data['text/html'] === 'string') {
911
data['text/html'] = data['text/html'].replace(/<\/scripts>/g, '</script>');
1012
}
@@ -15,12 +17,10 @@ function displayData(data: any, whiteBg: boolean): Promise<HTMLElement> {
1517
div.style.backgroundColor = 'white';
1618
div.style.display = 'inline-block';
1719
div.appendChild(result.el);
18-
document.body.appendChild(div);
19-
return div;
20+
return container.appendChild(div);
2021
}
2122
else {
22-
document.body.appendChild(result.el);
23-
return result.el;
23+
return container.appendChild(result.el);
2424
}
2525
});
2626
}
@@ -30,33 +30,49 @@ function displayData(data: any, whiteBg: boolean): Promise<HTMLElement> {
3030
(window as any).__dirname = rootDirName;
3131
try {
3232
const color = decodeURIComponent(window.location.search.substring(window.location.search.indexOf('?color=') + 7, window.location.search.indexOf('&fontFamily=')));
33-
if (color.length > 0){
33+
if (color.length > 0) {
3434
window.document.body.style.color = color;
3535
}
3636
const fontFamily = decodeURIComponent(window.location.search.substring(window.location.search.indexOf('&fontFamily=') + 12));
37-
if (fontFamily.length > 0){
37+
if (fontFamily.length > 0) {
3838
window.document.body.style.fontFamily = fontFamily;
3939
}
4040
}
41-
catch (ex){
41+
catch (ex) {
4242
}
4343

44-
debugger;
44+
document.getElementById('clearResults').addEventListener('click', () => {
45+
document.getElementById(ResultsContainerId).innerHTML = '';
46+
});
47+
4548
try {
4649
if (typeof port === 'number' && port > 0) {
4750
var socket = (window as any).io.connect('http://localhost:' + port);
48-
socket.on('results', function (results: any[]) {
51+
socket.on('results', (results: any[]) => {
4952
const promises = results.map(data => displayData(data, whiteBg));
5053
Promise.all<HTMLElement>(promises).then(elements => {
5154
// Bring the first item into view
5255
if (elements.length > 0) {
53-
elements[0].scrollIntoView(true);
56+
try {
57+
elements[0].scrollIntoView(true);
58+
}
59+
catch (ex) {
60+
}
5461
}
5562
});
5663
});
64+
socket.on('clientExists', (data: any) => {
65+
socket.emit('clientExists', { id: data.id });
66+
});
67+
const displayStyleEle = document.getElementById('displayStyle') as HTMLSelectElement;
68+
displayStyleEle.addEventListener('change', () => {
69+
socket.emit('appendResults', { append: displayStyleEle.value });
70+
});
5771
}
5872
}
5973
catch (ex) {
74+
document.getElementById('displayStyle').style.display = 'none';
75+
6076
const errorDiv = document.createElement('div');
6177
errorDiv.innerHTML = 'Initializing live updates for results failed with the following error:\n' + ex.message;
6278
errorDiv.style.color = 'red';
@@ -67,7 +83,10 @@ function displayData(data: any, whiteBg: boolean): Promise<HTMLElement> {
6783
Promise.all<HTMLElement>(promises).then(elements => {
6884
// Bring the first item into view
6985
if (elements.length > 0) {
70-
elements[0].scrollIntoView(true);
86+
try {
87+
elements[0].scrollIntoView(true);
88+
}
89+
catch (ex) { }
7190
}
7291
});
7392
};

src/client/jupyter/display/main.ts

Lines changed: 31 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,48 +3,63 @@ import {KernelPicker} from './kernelPicker';
33
import {Commands} from '../../common/constants';
44
import {KernelspecMetadata} from '../contracts';
55
import {TextDocumentContentProvider} from './resultView';
6-
import {Server} from '../server/main';
76
import {CellOptions} from './cellOptions';
87
import {JupyterCodeLensProvider} from '../editorIntegration/codeLensProvider';
98
import {JupyterCellHighlightProvider} from '../editorIntegration/cellHighlightProvider';
9+
import {Server} from './server';
1010

1111
const jupyterSchema = 'jupyter-result-viewer';
1212
const previewUri = vscode.Uri.parse(jupyterSchema + '://authority/jupyter');
1313

1414
export class JupyterDisplay extends vscode.Disposable {
1515
private disposables: vscode.Disposable[];
1616
private previewWindow: TextDocumentContentProvider;
17-
private server: Server;
1817
private cellOptions: CellOptions;
18+
private server: Server;
1919
constructor(cellCodeLenses: JupyterCodeLensProvider, cellHighlightProvider: JupyterCellHighlightProvider) {
2020
super(() => { });
2121
this.disposables = [];
22-
this.disposables.push(new KernelPicker());
23-
this.disposables.push(vscode.commands.registerCommand(Commands.Jupyter.Kernel_Options, this.showKernelOptions.bind(this)));
2422
this.server = new Server();
2523
this.disposables.push(this.server);
24+
this.disposables.push(new KernelPicker());
25+
this.disposables.push(vscode.commands.registerCommand(Commands.Jupyter.Kernel_Options, this.showKernelOptions.bind(this)));
2626
this.previewWindow = new TextDocumentContentProvider();
2727
this.disposables.push(vscode.workspace.registerTextDocumentContentProvider(jupyterSchema, this.previewWindow));
2828
this.cellOptions = new CellOptions(cellCodeLenses, cellHighlightProvider);
2929
this.disposables.push(this.cellOptions);
30+
this.server.on('appendResults', appendType => {
31+
this.appendResults = appendType === 'append';
32+
});
3033
}
3134

3235
private displayed = false;
33-
public showResults(result: string, data: any): Promise<any> {
36+
private appendResults = false;
37+
public showResults(result: string, data: any): Thenable<any> {
3438
return this.server.start().then(port => {
3539
this.previewWindow.ServerPort = port;
36-
this.previewWindow.setText(result, data);
37-
// Dirty hack to support instances when document has been closed
38-
if (this.displayed) {
39-
this.previewWindow.update();
40+
// If we need to append the results, then do so if we have any result windows open
41+
let sendDataToResultView = Promise.resolve(false);
42+
if (this.appendResults) {
43+
sendDataToResultView = this.server.clientsConnected(2000);
4044
}
41-
this.displayed = true;
42-
return vscode.commands.executeCommand('vscode.previewHtml', previewUri, vscode.ViewColumn.Two, 'Results')
43-
.then(() => {
44-
// Do nothing
45-
}, reason => {
46-
vscode.window.showErrorMessage(reason);
47-
});
45+
return sendDataToResultView.then(clientConnected => {
46+
if (clientConnected) {
47+
return this.server.sendResults(data);
48+
}
49+
this.previewWindow.setText(result, data);
50+
this.previewWindow.AppendResults = this.appendResults;
51+
// Dirty hack to support instances when document has been closed
52+
if (this.displayed) {
53+
this.previewWindow.update();
54+
}
55+
this.displayed = true;
56+
return vscode.commands.executeCommand('vscode.previewHtml', previewUri, vscode.ViewColumn.Two, 'Results')
57+
.then(() => {
58+
// Do nothing
59+
}, reason => {
60+
vscode.window.showErrorMessage(reason);
61+
});
62+
});
4863
});
4964
}
5065

src/client/jupyter/display/resultView.ts

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export class TextDocumentContentProvider extends Disposable implements vscode.Te
1313
private results: any[];
1414
private serverPort: number;
1515
private tmpFileCleanup: Function[] = [];
16+
private appendResults: boolean;
1617
constructor() {
1718
super(() => { });
1819
}
@@ -27,6 +28,9 @@ export class TextDocumentContentProvider extends Disposable implements vscode.Te
2728
public set ServerPort(value: number) {
2829
this.serverPort = value;
2930
}
31+
public set AppendResults(value: boolean) {
32+
this.appendResults = value;
33+
}
3034
public provideTextDocumentContent(uri: vscode.Uri, token: vscode.CancellationToken): Thenable<string> {
3135
this.lastUri = uri;
3236
return this.generateResultsView();
@@ -48,10 +52,6 @@ export class TextDocumentContentProvider extends Disposable implements vscode.Te
4852
return vscode.Uri.file(path.join(__dirname, '..', '..', '..', '..', 'out', 'client', 'jupyter', 'browser', resourceName)).toString();
4953
}
5054

51-
private generateErrorView(error: string): string {
52-
return `<head></head><body>${error}</body>`;
53-
}
54-
5555
private tmpHtmlFile: string;
5656
private buildHtmlContent(): Promise<string> {
5757
// Don't put this, stuffs up SVG hrefs
@@ -62,19 +62,23 @@ export class TextDocumentContentProvider extends Disposable implements vscode.Te
6262
<html>
6363
<head>
6464
<script src="http://localhost:${this.serverPort}/socket.io/socket.io.js"></script>
65-
</head>
66-
<body onload="initializeResults('${dirNameForScripts}', ${this.serverPort})">
67-
<select id="displayStyle">
68-
<option value="append">Append results</option>
69-
<option value="clear">Clear previous results</option>
70-
</select>
71-
&nbsp;
72-
<button id="clearResults">Clear Results</button>
73-
<br>
7465
<script type="text/javascript">
7566
window.JUPYTER_DATA = ${JSON.stringify(this.results)};
7667
</script>
7768
<script src="${this.getScriptFilePath('bundle.js')}?x=${new Date().getMilliseconds()}"></script>
69+
</head>
70+
<body onload="initializeResults('${dirNameForScripts}', ${this.serverPort})">
71+
<div id="resultMenu">
72+
<select id="displayStyle">
73+
<option value="append" ${this.appendResults ? 'selected' : ''}>Append results</option>
74+
<option value="clear" ${this.appendResults ? '' : 'selected'}>Clear previous results</option>
75+
</select>
76+
&nbsp;
77+
<button id="clearResults">Clear Results</button>
78+
<br>
79+
</div>
80+
<div id="resultsContainer">
81+
</div>
7882
</body>
7983
</html>
8084
`;
@@ -133,7 +137,7 @@ export class TextDocumentContentProvider extends Disposable implements vscode.Te
133137
if (err) {
134138
return def.reject(err);
135139
}
136-
// fs.writeFileSync('/Users/donjayamanne/.vscode/extensions/pythonVSCode/results.html', htmlContent);
140+
fs.writeFileSync('/Users/donjayamanne/.vscode/extensions/pythonVSCode/results.html', htmlContent);
137141
def.resolve(htmlContent);
138142
});
139143

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
/// https://github.com/DefinitelyTyped/DefinitelyTyped/issues/10097
2+
/// The following line in 'socket.io/index.d.ts' causes a compiler error
3+
/// </reference types="node" />
4+
/// Solution is to use typescript 2.0
5+
/// <xreference path="../../../../node_modules/@types/socket.io/index.d.ts" />
6+
// import * as io from 'socket.io';
7+
// Temporary solution is to create our own definitions
8+
const io: (app: any) => SocketIO.Server = require('socket.io');
9+
namespace SocketIO {
10+
export interface Server {
11+
close();
12+
on(event: string, callback: Function);
13+
}
14+
export interface Socket {
15+
id: string;
16+
connected: boolean;
17+
emit(event: string, data: any);
18+
on(event: string, callback: Function);
19+
}
20+
}
21+
import * as http from 'http';
22+
import {createDeferred, Deferred} from '../../common/helpers';
23+
import {EventEmitter} from 'events';
24+
25+
26+
export class Server extends EventEmitter {
27+
private server: SocketIO.Server;
28+
private httpServer: http.Server;
29+
private clients: SocketIO.Socket[] = [];
30+
constructor() {
31+
super();
32+
this.responsePromises = new Map<string, Deferred<boolean>>();
33+
}
34+
35+
public dispose() {
36+
if (this.httpServer) {
37+
this.httpServer.close();
38+
this.httpServer = null;
39+
}
40+
if (this.server) {
41+
this.server.close();
42+
this.server = null;
43+
}
44+
this.port = null;
45+
}
46+
47+
private port: number;
48+
public start(): Promise<number> {
49+
if (this.port) {
50+
return Promise.resolve(this.port);
51+
}
52+
53+
let def = createDeferred<number>();
54+
this.httpServer = http.createServer(this.listener.bind(this));
55+
this.server = io(this.httpServer);
56+
57+
this.httpServer.listen(0, () => {
58+
this.port = this.httpServer.address().port;
59+
def.resolve(this.port);
60+
def = null;
61+
});
62+
this.httpServer.on('error', error => {
63+
if (def) {
64+
def.reject(error);
65+
}
66+
});
67+
68+
this.server.on('connection', this.onSocketConnection.bind(this));
69+
return def.promise;
70+
}
71+
public sendResults(data: any[]) {
72+
this.broadcast('results', data);
73+
}
74+
private broadcast(eventName: string, data: any) {
75+
this.clients = this.clients.filter(client => client && client.connected);
76+
this.clients.forEach(client => {
77+
try {
78+
client.emit(eventName, data);
79+
}
80+
catch (ex) {
81+
}
82+
});
83+
}
84+
85+
private listener(request: http.IncomingMessage, response: http.ServerResponse) {
86+
}
87+
88+
private onSocketConnection(socket: SocketIO.Socket) {
89+
this.clients.push(socket);
90+
socket.on('disconnect', () => {
91+
const index = this.clients.findIndex(sock => sock.id === socket.id);
92+
if (index >= 0) {
93+
this.clients.splice(index, 1);
94+
}
95+
});
96+
socket.on('clientExists', (data: { id: string }) => {
97+
if (!this.responsePromises.has(data.id)) {
98+
return;
99+
}
100+
const def = this.responsePromises.get(data.id);
101+
this.responsePromises.delete(data.id);
102+
def.resolve(true);
103+
});
104+
socket.on('appendResults', (data: { append: string }) => {
105+
this.emit('appendResults', data.append);
106+
});
107+
}
108+
109+
private responsePromises: Map<string, Deferred<boolean>>;
110+
public clientsConnected(timeoutMilliSeconds: number): Promise<any> {
111+
const id = new Date().getTime().toString();
112+
const def = createDeferred<boolean>();
113+
this.broadcast('clientExists', { id: id });
114+
this.responsePromises.set(id, def);
115+
116+
setTimeout(() => {
117+
if (this.responsePromises.has(id)) {
118+
this.responsePromises.delete(id);
119+
def.resolve(false);
120+
}
121+
}, timeoutMilliSeconds);
122+
123+
return def.promise;
124+
}
125+
}

0 commit comments

Comments
 (0)