Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit d1120c6

Browse files
committedOct 2, 2017
[extension-manager] preserve extension widgets between sessions
Signed-off-by: Anton Kosiakov <anton.kosyakov@typefox.io>
1 parent 46941cc commit d1120c6

13 files changed

+165
-98
lines changed
 

‎.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ jdt.ls-java-project
99
lerna-debug.log
1010
.nyc_output
1111
coverage
12+
errorShots
1213
examples/*/src-gen
1314
examples/*/webpack.config.js
1415
.browser_modules

‎.vscode/settings.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,15 @@
99
"editor.formatOnSave": true,
1010
"search.exclude": {
1111
"**/node_modules": true,
12-
"**/lib": true
12+
"**/lib": true,
13+
"**/coverage": true
1314
},
1415
"lcov.path": [
1516
"packages/core/coverage/lcov.info",
1617
"packages/cpp/coverage/lcov.info",
1718
"packages/editor/coverage/lcov.info",
1819
"packages/filesystem/coverage/lcov.info",
20+
"packages/extension-manager/coverage/lcov.info",
1921
"packages/go/coverage/lcov.info",
2022
"packages/java/coverage/lcov.info",
2123
"packages/languages/coverage/lcov.info",

‎packages/core/src/browser/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,4 @@ export * from './messaging';
1717
export * from './endpoint';
1818
export * from './common-frontend-contribution';
1919
export * from './quick-open';
20+
export * from './widget-manager';

‎packages/core/src/browser/widget-manager.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import { inject, named, injectable } from 'inversify';
99
import { Widget } from '@phosphor/widgets';
1010
import { ContributionProvider } from '../common/contribution-provider';
11-
import { ILogger } from '../common';
11+
import { ILogger, MaybePromise } from '../common';
1212

1313
// tslint:disable:no-any
1414
export const WidgetFactory = Symbol("WidgetFactory");
@@ -26,7 +26,7 @@ export interface WidgetFactory {
2626
* Creates a widget and attaches it to the shell
2727
* The options need to be serializable JSON data.
2828
*/
29-
createWidget(options?: any): Promise<Widget>;
29+
createWidget(options?: any): MaybePromise<Widget>;
3030
}
3131

3232
/*

‎packages/extension-manager/src/browser/extension-detail-widget-service.ts

-49
This file was deleted.

‎packages/extension-manager/src/browser/extension-detail-widget.ts

+17-23
Original file line numberDiff line numberDiff line change
@@ -12,24 +12,18 @@ import { h } from '@phosphor/virtualdom/lib';
1212

1313
export class ExtensionDetailWidget extends VirtualWidget {
1414

15-
constructor(id: string,
16-
protected resolvedExtension: ResolvedExtension) {
15+
constructor(
16+
protected readonly resolvedExtension: ResolvedExtension
17+
) {
1718
super();
18-
this.id = id;
1919
this.addClass('theia-extension-detail');
20-
this.title.closable = true;
21-
this.title.label = resolvedExtension.name;
22-
23-
resolvedExtension.resolve().then(rex => {
24-
this.update();
25-
});
26-
2720

2821
resolvedExtension.onDidChange(change => {
2922
if (change.name === this.resolvedExtension.name) {
3023
this.update();
3124
}
3225
});
26+
this.update();
3327
}
3428

3529
protected onUpdateRequest(msg: Message): void {
@@ -44,25 +38,25 @@ export class ExtensionDetailWidget extends VirtualWidget {
4438
protected render(): h.Child {
4539
const r = this.resolvedExtension;
4640

47-
const name = h.h2({className: 'extensionName'}, r.name);
48-
const extversion = h.div({className: 'extensionVersion'}, r.version);
49-
const author = h.div({className: 'extensionAuthor'}, r.author);
50-
const titleInfo = h.div({className: 'extensionSubtitle'}, author, extversion);
51-
const titleContainer = h.div({className: 'extensionTitleContainer'},
41+
const name = h.h2({ className: 'extensionName' }, r.name);
42+
const extversion = h.div({ className: 'extensionVersion' }, r.version);
43+
const author = h.div({ className: 'extensionAuthor' }, r.author);
44+
const titleInfo = h.div({ className: 'extensionSubtitle' }, author, extversion);
45+
const titleContainer = h.div({ className: 'extensionTitleContainer' },
5246
name, titleInfo);
5347

54-
const description = h.div({className: 'extensionDescription'}, r.description);
48+
const description = h.div({ className: 'extensionDescription' }, r.description);
5549

56-
const buttonRow = h.div({className: 'extensionButtonRow'},
50+
const buttonRow = h.div({ className: 'extensionButtonRow' },
5751
VirtualRenderer.flatten(this.createButtons(this.resolvedExtension)));
5852

59-
const buttonContainer = h.div({className: 'extensionButtonContainer'}, buttonRow);
53+
const buttonContainer = h.div({ className: 'extensionButtonContainer' }, buttonRow);
6054

61-
const headerContainer = h.div({className: 'extensionHeaderContainer'},
55+
const headerContainer = h.div({ className: 'extensionHeaderContainer' },
6256
titleContainer, description, buttonContainer);
6357

64-
const documentation = h.div({className: 'extensionDocumentation', id: this.id + 'Doc'}, '');
65-
const docContainer = h.div({className: 'extensionDocContainer flexcontainer'}, documentation);
58+
const documentation = h.div({ className: 'extensionDocumentation', id: this.id + 'Doc' }, '');
59+
const docContainer = h.div({ className: 'extensionDocContainer flexcontainer' }, documentation);
6660

6761
return [headerContainer, docContainer];
6862
}
@@ -74,7 +68,7 @@ export class ExtensionDetailWidget extends VirtualWidget {
7468
btnLabel = 'Uninstall';
7569
}
7670

77-
const faEl = h.i({className: 'fa fa-spinner fa-pulse fa-fw'});
71+
const faEl = h.i({ className: 'fa fa-spinner fa-pulse fa-fw' });
7872
const content = extension.busy ? faEl : btnLabel;
7973

8074
buttonArr.push(h.div({
@@ -107,4 +101,4 @@ export class ExtensionDetailWidget extends VirtualWidget {
107101
return buttonArr;
108102
}
109103

110-
}
104+
}

‎packages/extension-manager/src/browser/extension-frontend-module.ts

+9-3
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,13 @@
66
*/
77

88
import { ContainerModule } from 'inversify';
9-
import { FrontendApplicationContribution, WebSocketConnectionProvider } from '@theia/core/lib/browser';
9+
import { FrontendApplicationContribution, WebSocketConnectionProvider, WidgetFactory, OpenHandler } from '@theia/core/lib/browser';
1010
import { ExtensionServer, extensionPath } from '../common/extension-protocol';
1111
import { ExtensionManager } from '../common';
1212
import { ExtensionContribution } from './extension-contribution';
1313
import { ExtensionWidget } from './extension-widget';
14-
import { ExtensionDetailWidgetService } from './extension-detail-widget-service';
14+
import { ExtensionWidgetFactory } from './extension-widget-factory';
15+
import { ExtensionOpenHandler } from './extension-open-handler';
1516

1617
import '../../src/browser/style/index.css';
1718

@@ -23,6 +24,11 @@ export default new ContainerModule(bind => {
2324
bind(ExtensionManager).toSelf().inSingletonScope();
2425

2526
bind(FrontendApplicationContribution).to(ExtensionContribution).inSingletonScope();
26-
bind(ExtensionDetailWidgetService).toSelf().inSingletonScope();
2727
bind(ExtensionWidget).toSelf().inSingletonScope();
28+
29+
bind(ExtensionWidgetFactory).toSelf().inSingletonScope();
30+
bind(WidgetFactory).toDynamicValue(ctx => ctx.container.get(ExtensionWidgetFactory)).inSingletonScope();
31+
32+
bind(ExtensionOpenHandler).toSelf().inSingletonScope();
33+
bind(OpenHandler).toDynamicValue(ctx => ctx.container.get(ExtensionOpenHandler)).inSingletonScope();
2834
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
* Copyright (C) 2017 TypeFox and others.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
6+
*/
7+
8+
import { injectable, inject } from "inversify";
9+
import URI from "@theia/core/lib/common/uri";
10+
import { OpenHandler, WidgetManager, FrontendApplication } from "@theia/core/lib/browser";
11+
import { ExtensionUri } from "./extension-uri";
12+
import { ExtensionWidgetOptions } from './extension-widget-factory';
13+
import { ExtensionDetailWidget } from './extension-detail-widget';
14+
15+
@injectable()
16+
export class ExtensionOpenHandler implements OpenHandler {
17+
18+
readonly id = ExtensionUri.scheme;
19+
20+
constructor(
21+
@inject(FrontendApplication) protected readonly app: FrontendApplication,
22+
@inject(WidgetManager) protected readonly widgetManager: WidgetManager
23+
) { }
24+
25+
canHandle(uri: URI): number {
26+
try {
27+
ExtensionUri.toExtensionName(uri);
28+
return 100;
29+
} catch {
30+
return 0;
31+
}
32+
}
33+
34+
async open(uri: URI): Promise<ExtensionDetailWidget> {
35+
const options: ExtensionWidgetOptions = {
36+
name: ExtensionUri.toExtensionName(uri)
37+
};
38+
const widget = await this.widgetManager.getOrCreateWidget<ExtensionDetailWidget>(ExtensionUri.scheme, options);
39+
this.app.shell.activateMain(widget.id);
40+
return widget;
41+
}
42+
43+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/*
2+
* Copyright (C) 2017 TypeFox and others.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
6+
*/
7+
8+
import URI from "@theia/core/lib/common/uri";
9+
10+
export namespace ExtensionUri {
11+
export const scheme = 'extension';
12+
export function toUri(extensionName: string): URI {
13+
return new URI('').withScheme(scheme).withFragment(extensionName);
14+
}
15+
export function toExtensionName(uri: URI): string {
16+
if (uri.scheme === scheme) {
17+
return uri.fragment;
18+
}
19+
throw new Error('The given uri is not an extension URI, uri: ' + uri);
20+
}
21+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
* Copyright (C) 2017 TypeFox and others.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
6+
*/
7+
8+
import { injectable, inject } from "inversify";
9+
import { WidgetFactory, FrontendApplication } from "@theia/core/lib/browser";
10+
import { ExtensionManager } from '../common';
11+
import { ExtensionUri } from "./extension-uri";
12+
import { ExtensionDetailWidget } from './extension-detail-widget';
13+
14+
export class ExtensionWidgetOptions {
15+
readonly name: string;
16+
}
17+
18+
@injectable()
19+
export class ExtensionWidgetFactory implements WidgetFactory {
20+
21+
readonly id = ExtensionUri.scheme;
22+
23+
constructor(
24+
@inject(FrontendApplication) protected readonly app: FrontendApplication,
25+
@inject(ExtensionManager) protected readonly extensionManager: ExtensionManager
26+
) { }
27+
28+
async createWidget(options: ExtensionWidgetOptions): Promise<ExtensionDetailWidget> {
29+
const extension = await this.extensionManager.resolve(options.name);
30+
const widget = new ExtensionDetailWidget(extension);
31+
widget.id = 'extension:' + options.name;
32+
widget.title.closable = true;
33+
widget.title.label = options.name;
34+
this.app.shell.addToMainArea(widget);
35+
return widget;
36+
}
37+
38+
}

‎packages/extension-manager/src/browser/extension-widget.ts

+8-11
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@
55
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
66
*/
77

8-
import { Extension, ExtensionManager } from '../common';
98
import { injectable, inject } from 'inversify';
10-
import { VirtualWidget, VirtualRenderer } from '@theia/core/lib/browser';
119
import { h, VirtualNode } from '@phosphor/virtualdom/lib';
1210
import { DisposableCollection, Disposable } from '@theia/core';
13-
import { ExtensionDetailWidgetService } from './extension-detail-widget-service';
11+
import { VirtualWidget, VirtualRenderer, OpenerService, open } from '@theia/core/lib/browser';
12+
import { Extension, ExtensionManager } from '../common';
13+
import { ExtensionUri } from './extension-uri';
1414

1515
@injectable()
1616
export class ExtensionWidget extends VirtualWidget {
@@ -21,8 +21,10 @@ export class ExtensionWidget extends VirtualWidget {
2121
protected readonly toDisposedOnFetch = new DisposableCollection();
2222
protected ready = false;
2323

24-
constructor( @inject(ExtensionManager) protected readonly extensionManager: ExtensionManager,
25-
@inject(ExtensionDetailWidgetService) protected readonly detailWidgetService: ExtensionDetailWidgetService) {
24+
constructor(
25+
@inject(ExtensionManager) protected readonly extensionManager: ExtensionManager,
26+
@inject(OpenerService) protected readonly openerService: OpenerService
27+
) {
2628
super();
2729
this.id = 'extensions';
2830
this.title.label = 'Extensions';
@@ -130,12 +132,7 @@ export class ExtensionWidget extends VirtualWidget {
130132

131133
const container = h.div({
132134
className: 'extensionHeaderContainer',
133-
onclick: event => {
134-
extension.resolve().then(rawExt => {
135-
this.detailWidgetService.openOrFocusDetailWidget(rawExt);
136-
return false;
137-
});
138-
}
135+
onclick: () => open(this.openerService, ExtensionUri.toUri(extension.name))
139136
}, leftColumn);
140137
return container;
141138
}

‎packages/extension-manager/src/common/extension-manager.ts

+14-3
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@ export class Extension extends protocol.Extension {
2424
protected readonly onDidChangedEmitter = new Emitter<ExtensionChange>();
2525

2626
constructor(extension: protocol.Extension,
27-
protected readonly server: protocol.ExtensionServer,
28-
protected readonly manager: ExtensionManager) {
27+
protected readonly server: protocol.ExtensionServer,
28+
protected readonly manager: ExtensionManager) {
2929
super();
3030
Object.assign(this, extension);
3131
manager.onDidChange(change => {
@@ -96,7 +96,9 @@ export class ExtensionManager implements Disposable {
9696
protected readonly onDidStopInstallationEmitter = new Emitter<protocol.DidStopInstallationParam>();
9797
protected readonly toDispose = new DisposableCollection();
9898

99-
constructor(@inject(protocol.ExtensionServer) protected readonly server: protocol.ExtensionServer) {
99+
constructor(
100+
@inject(protocol.ExtensionServer) protected readonly server: protocol.ExtensionServer
101+
) {
100102
this.toDispose.push(server);
101103
this.toDispose.push(this.onChangedEmitter);
102104
this.toDispose.push(this.onWillStartInstallationEmitter);
@@ -112,6 +114,15 @@ export class ExtensionManager implements Disposable {
112114
this.toDispose.dispose();
113115
}
114116

117+
/**
118+
* Resolve the detailed extension for the given name.
119+
*/
120+
async resolve(name: string): Promise<ResolvedExtension> {
121+
const raw = await this.server.resolve(name);
122+
const extension = new Extension(raw, this.server, this);
123+
return extension as ResolvedExtension;
124+
}
125+
115126
/**
116127
* List installed extensions if the given query is undefined or empty.
117128
* Otherwise look up extensions from the repository matching the given query

‎yarn.lock

+8-6
Original file line numberDiff line numberDiff line change
@@ -221,13 +221,14 @@
221221
"@types/glob" "*"
222222
"@types/node" "*"
223223

224-
"@types/sinon@^2.3.5":
225-
version "2.3.5"
226-
resolved "https://registry.yarnpkg.com/@types/sinon/-/sinon-2.3.5.tgz#68f1e0ac15f2eb6cc682b7af87cd517acc77b589"
227224
"@types/showdown@^1.7.1":
228225
version "1.7.1"
229226
resolved "https://registry.yarnpkg.com/@types/showdown/-/showdown-1.7.1.tgz#9ba121fc2b2dcad646040034a5581e82ccecc066"
230227

228+
"@types/sinon@^2.3.5":
229+
version "2.3.5"
230+
resolved "https://registry.yarnpkg.com/@types/sinon/-/sinon-2.3.5.tgz#68f1e0ac15f2eb6cc682b7af87cd517acc77b589"
231+
231232
"@types/temp@^0.8.29":
232233
version "0.8.29"
233234
resolved "https://registry.yarnpkg.com/@types/temp/-/temp-0.8.29.tgz#c3cdc113e3c138f90e5e970d51e41b0b7f62678d"
@@ -5829,6 +5830,7 @@ safe-json-stringify@~1:
58295830
samsam@1.x, samsam@^1.1.3:
58305831
version "1.2.1"
58315832
resolved "https://registry.yarnpkg.com/samsam/-/samsam-1.2.1.tgz#edd39093a3184370cb859243b2bdf255e7d8ea67"
5833+
58325834
sanitize-html@^1.14.1:
58335835
version "1.14.1"
58345836
resolved "https://registry.yarnpkg.com/sanitize-html/-/sanitize-html-1.14.1.tgz#730ffa2249bdf18333effe45b286173c9c5ad0b8"
@@ -6685,9 +6687,9 @@ typedoc@^0.8:
66856687
typedoc-default-themes "^0.5.0"
66866688
typescript "2.4.1"
66876689

6688-
typescript-lsp@^0.0.5:
6689-
version "0.0.5"
6690-
resolved "https://registry.yarnpkg.com/typescript-lsp/-/typescript-lsp-0.0.5.tgz#79a2d3099091676e7fd6716092bbdede8edf5b42"
6690+
typescript-lsp@^0.0.7:
6691+
version "0.0.7"
6692+
resolved "https://registry.yarnpkg.com/typescript-lsp/-/typescript-lsp-0.0.7.tgz#bcbed117fbb7cd97e867366b96becd742df36412"
66916693
dependencies:
66926694
commander "^2.11.0"
66936695
vscode-languageserver "^3.4.3"

0 commit comments

Comments
 (0)
Please sign in to comment.