8000 Add ability to compile and run react controls in a webViewPanel (#7) · microsoft/vscode-python@76f6822 · GitHub
[go: up one dir, main page]

Skip to content

Commit 76f6822

Browse files
authored
Add ability to compile and run react controls in a webViewPanel (#7)
Implement basic webview support. This should allow us to iterate on the datascience controls using react. This commit creates a history pane that just looks like the default "react" page but works inside of a webview panel. Additionally it still makes it possible to load the index.html into a browser for debugging the react code standalone.
1 parent 942e1c4 commit 76f6822

20 files changed

+444
-51
lines changed

gulpfile.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,6 @@ gulp.task('compile-webviews', () => {
156156
});
157157

158158
const webify = (file) => {
159-
160159
// First check if we have an index.js. That's what we need to webify
161160
const split = path.parse(file.path);
162161
const js = path.join(split.dir, split.name + ".js");
@@ -216,8 +215,8 @@ const webify = (file) => {
216215
printBuildError(err);
217216
process.exit(1);
218217
}
219-
)
220-
}
218+
)
219+
}
221220
}
222221

223222
function hasNativeDependencies() {

package-lock.json

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -224,8 +224,8 @@
224224
"category": "Python"
225225
},
226226
{
227-
"command": "python.datascience",
228-
"title": "%python.command.python.datascience.datascience.title%",
227+
"command": "python.datascience.showhistorypane",
228+
"title": "%python.command.python.datascience.showhistorypane.title%",
229229
"category": "Python"
230230
}
231231
],
@@ -1617,6 +1617,7 @@
16171617
"@types/winreg": "^1.2.30",
16181618
"@types/xml2js": "^0.4.2",
16191619
"JSONStream": "^1.3.2",
1620+
"async-file": "^2.0.2",
16201621
"babel-loader": "^8.0.3",
16211622
"chai": "^4.1.2",
16221623
"chai-arrays": "^2.0.0",

package.nls.it.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,5 +45,6 @@
4545
"python.snippet.launch.attach.label": "Python: Allega",
4646
"python.snippet.launch.attach.description&qu 10000 ot;: "Allega debugger per debug remoto",
4747
"python.snippet.launch.scrapy.label": "Python: Scrapy",
48-
"python.snippet.launch.scrapy.description": "Scrapy con terminale integrato"
48+
"python.snippet.launch.scrapy.description": "Scrapy con terminale integrato",
49+
"LanguageServiceSurveyBanner.bannerLabelYes": "Sì, prenderò il sondaggio ora"
4950
}

package.nls.json

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
"python.command.python.setLinter.title": "Select Linter",
2424
"python.command.python.enableLinting.title": "Enable Linting",
2525
"python.command.python.runLinting.title": "Run Linting",
26-
"python.command.python.datascience.datascience.title": "Data Science Test Command",
26+
"python.command.python.datascience.showhistorypane.title": "Show History Pane",
2727
"python.snippet.launch.standard.label": "Python: Current File",
2828
"python.snippet.launch.standard.description": "Debug a Python Program with Standard Output",
2929
"python.snippet.launch.pyspark.label": "Python: PySpark",
@@ -49,5 +49,8 @@
4949
"python.snippet.launch.attach.label": "Python: Attach",
5050
"python.snippet.launch.attach.description": "Attach the Debugger for Remote Debugging",
5151
"python.snippet.launch.scrapy.label": "Python: Scrapy",
52-
"python.snippet.launch.scrapy.description": "Scrapy with Integrated Terminal/Console"
52+
"python.snippet.launch.scrapy.description": "Scrapy with Integrated Terminal/Console",
53+
"LanguageServiceSurveyBanner.bannerMessage": "Can you please take 2 minutes to tell us how the Python Language Server is working for you?",
54+
"LanguageServiceSurveyBanner.bannerLabelYes": "Yes, take survey now",
55+
"LanguageServiceSurveyBanner.bannerLabelNo": "No, thanks"
5356
}

src/client/common/application/types.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -656,3 +656,47 @@ export interface IApplicationEnvironment {
656656
*/
657657
sessionId: string;
658658
}
659+
660+
export const IWebPanelMessageListener = Symbol('IWebPanelMessageListener');
661+
export interface IWebPanelMessageListener {
662+
/**
663+
* Listens to web panel messages
664+
* @param message: the message being sent
665+
* @param payload: extra data that came with the message
666+
* @return A IWebPanel that can be used to show html pages.
667+
*/
668+
onMessage(message: string, payload: any): Promise<void>;
669+
670+
/**
671+
* Called when the panel is closed/disposed
672+
*/
673+
onDisposed(): void;
674+
}
675+
676+
// Wraps the VS Code webview panel
677+
export const IWebPanel = Symbol('IWebPanel');
678+
export interface IWebPanel {
679+
/**
680+
* Makes the webpanel show up.
681+
* @return A Promise that can be waited on
682+
*/
683+
show(): Promise<void>;
684+
685+
/**
686+
* Indicates if this web panel is visible or not.
687+
*/
688+
isVisible(): boolean;
689+
}
690+
691+
// Wraps the VS Code api for creating a web panel
692+
export const IWebPanelProvider = Symbol('IWebPanelProvider');
693+
export interface IWebPanelProvider {
694+
/**
695+
* Creates a new webpanel
696+
* @param listener for messages from the panel
697+
* @param title: title of the panel when it shows
698+
* @param: mainScriptPath: full path in the output folder to the script
699+
* @return A IWebPanel that can be used to show html pages.
700+
*/
701+
create(listener: IWebPanelMessageListener, title: string, mainScriptPath: string): IWebPanel;
702+
}
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
'use strict';
5+
6+
import * as fs from 'async-file';
7+
import * as path from 'path';
8+
import { Uri, ViewColumn, WebviewPanel, window } from 'vscode';
9+
import '../../common/extensions';
10+
11+
import * as localize from '../../../utils/localize';
12+
import { IServiceContainer } from '../../ioc/types';
13+
import { IDisposableRegistry } from '../types';
14+
import { IWebPanel, IWebPanelMessageListener } from './types';
15+
16+
export class WebPanel implements IWebPanel {
17+
18+
private listener: IWebPanelMessageListener;
19+
private panel: WebviewPanel | undefined;
20+
private loadPromise: Promise<void>;
21+
private disposableRegistry: IDisposableRegistry;
22+
private rootPath: string;
23+
24+
constructor(serviceContainer: IServiceContainer, listener: IWebPanelMessageListener, title: string, mainScriptPath: string) {
25+
this.disposableRegistry = serviceContainer.get<IDisposableRegistry>(IDisposableRegistry);
26+
this.listener = listener;
27+
this.rootPath = path.dirname(mainScriptPath);
28+
this.panel = window.createWebviewPanel(
29+
title.toLowerCase().replace(' ', ''),
30+
title,
31+
ViewColumn.Two,
32+
{
33+
enableScripts: true,
34+
retainContextWhenHidden: true,
35+
localResourceRoots: [Uri.file(this.rootPath)]
36+
});
37+
this.loadPromise = this.load(mainScriptPath);
38+
}
39+
40+
public async show() {
41+
await this.loadPromise;
42+
if (this.panel) {
43+
this.panel.reveal(ViewColumn.Two);
44+
}
45+
}
46+
47+
public isVisible() : boolean {
48+
return this.panel ? this.panel.visible : false;
49+
}
50+
51+
private async load(mainScriptPath: string) {
52+
if (this.panel) {
53+
if (await fs.exists(mainScriptPath)) {
54+
55+
// Call our special function that sticks this script inside of an html page
56+
// and translates all of the paths to vscode-resource URIs
57+
this.panel.webview.html = this.generateReactHtml(mainScriptPath);
58+
59+
// Reset when the current panel is closed
60+
this.disposableRegistry.push(this.panel.onDidDispose(() => {
61+
this.panel = undefined;
62+
this.listener.onDisposed();
63+
}));
64+
65+
this.disposableRegistry.push(this.panel.webview.onDidReceiveMessage(message => {
66+
// Pass the message onto our listener
67+
this.listener.onMessage(message.command, message).ignoreErrors();
68+
}));
69+
} else {
70+
// Indicate that we can't load the file path
71+
const badPanelString = localize.DataScience.badWebPanelFormatString();
72+
this.panel.webview.html = badPanelString.format(mainScriptPath);
73+
}
74+
}
75+
}
76+
77+
private generateReactHtml(mainScriptPath: string) {
78+
const uriBasePath = Uri.file(`${path.dirname(mainScriptPath)}/`);
79+
const uriPath = Uri.file(mainScriptPath);
80+
const uriBase = uriBasePath.with({ scheme: 'vscode-resource'});
81+
const uri = uriPath.with({ scheme: 'vscode-resource' });
82+
83+
return `<!doctype html>
84+
<html lang="en">
85+
<head>
86+
<meta charset="utf-8">
87+
<meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no">
88+
<meta name="theme-color" cont 10000 ent="#000000">
89+
<title>React App</title>
90+
<base href="${uriBase}"/>
91+
</head>
92+
<body>
93+
<noscript>You need to enable JavaScript to run this app.</noscript>
94+
<div id="root"></div>
95+
<script type="text/javascript">
96+
function resolvePath(relativePath) {
97+
if (relativePath && relativePath[0] == '.' && relativePath[1] != '.') {
98+
return "${uriBase}" + relativePath.substring(1);
99+
}
100+
101+
return "${uriBase}" + relativePath;
102+
}
103+
</script>
104+
<script type="text/javascript" src="${uri}"></script></body>
105+
</html>`;
106+
}
107+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
'use strict';
5+
6+
import { inject, injectable } from 'inversify';
7+
import { IServiceContainer } from '../../ioc/types';
8+
import { IWebPanelMessageListener, IWebPanelProvider } from './types';
9+
import { WebPanel } from './webpanel';
10+
11+
@injectable()
12+
export class WebPanelProvider implements IWebPanelProvider {
13+
constructor(@inject(IServiceContainer) private serviceContainer: IServiceContainer) {
14+
}
15+
16+
public create(listener: IWebPanelMessageListener, title: string, mainScriptPath: string) {
17+
return new WebPanel(this.serviceContainer, listener, title, mainScriptPath);
18+
}
19+
}

src/client/common/installer/serviceRegistry.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import { PipInstaller } from './pipInstaller';
1111
import { CTagsProductPathService, FormatterProductPathService, LinterProductPathService, RefactoringLibraryProductPathService, TestFrameworkProductPathService } from './productPath';
1212
import { ProductService } from './productService';
1313
import { IInstallationChannelManager, IModuleInstaller, IProductPathService, IProductService } from './types';
14+
import { IWebPanelProvider } from '../application/types';
15+
import { WebPanelProvider } from '../application/webPanelProvider';
1416

1517
export function registerTypes(serviceManager: IServiceManager) {
1618
serviceManager.addSingleton<IModuleInstaller>(IModuleInstaller, CondaInstaller);
@@ -24,4 +26,5 @@ export function registerTypes(serviceManager: IServiceManager) {
2426
serviceManager.addSingleton<IProductPathService>(IProductPathService, LinterProductPathService, ProductType.Linter);
2527
serviceManager.addSingleton<IProductPathService>(IProductPathService, TestFrameworkProductPathService, ProductType.TestFramework);
2628
serviceManager.addSingleton<IProductPathService>(IProductPathService, RefactoringLibraryProductPathService, ProductType.RefactoringLibrary);
29+
serviceManager.addSingleton<IWebPanelProvider>(IWebPanelProvider, WebPanelProvider);
2730
}

src/client/datascience/constants.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,5 @@
44
'use strict';
55

66
export namespace Commands {
7-
export const DataScience = 'python.datascience';
7+
export const ShowHistoryPane = 'python.datascience.showhistorypane';
88
}

0 commit comments

Comments
 (0)
0