UNPKG

5.21 kBPlain TextView Raw
1// *****************************************************************************
2// Copyright (C) 2017 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 WITH Classpath-exception-2.0
15// *****************************************************************************
16
17import { named, injectable, inject } from 'inversify';
18import URI from '../common/uri';
19import { ContributionProvider, Prioritizeable, MaybePromise, Emitter, Event, Disposable } from '../common';
20
21export interface OpenerOptions {
22}
23
24export const OpenHandler = Symbol('OpenHandler');
25/**
26 * `OpenHandler` should be implemented to provide a new opener.
27 */
28export interface OpenHandler {
29 /**
30 * A unique id of this handler.
31 */
32 readonly id: string;
33 /**
34 * A human-readable name of this handler.
35 */
36 readonly label?: string;
37 /**
38 * A css icon class of this handler.
39 */
40 readonly iconClass?: string;
41 /**
42 * Test whether this handler can open the given URI for given options.
43 * Return a nonzero number if this handler can open; otherwise it cannot.
44 * Never reject.
45 *
46 * A returned value indicating a priority of this handler.
47 */
48 canHandle(uri: URI, options?: OpenerOptions): MaybePromise<number>;
49 /**
50 * Open a widget for the given URI and options.
51 * Resolve to an opened widget or undefined, e.g. if a page is opened.
52 * Never reject if `canHandle` return a positive number; otherwise should reject.
53 */
54 open(uri: URI, options?: OpenerOptions): MaybePromise<object | undefined>;
55}
56
57export const OpenerService = Symbol('OpenerService');
58/**
59 * `OpenerService` provide an access to existing openers.
60 */
61export interface OpenerService {
62 /**
63 * Return all registered openers.
64 * Never reject.
65 */
66 getOpeners(): Promise<OpenHandler[]>;
67 /**
68 * Return all openers able to open the given URI for given options
69 * ordered according their priority.
70 * Never reject.
71 */
72 getOpeners(uri: URI, options?: OpenerOptions): Promise<OpenHandler[]>;
73 /**
74 * Return an opener with the higher priority for the given URI.
75 * Reject if such does not exist.
76 */
77 getOpener(uri: URI, options?: OpenerOptions): Promise<OpenHandler>;
78 /**
79 * Add open handler i.e. for custom editors
80 */
81 addHandler?(openHandler: OpenHandler): Disposable;
82 /**
83 * Event that fires when a new opener is added or removed.
84 */
85 onDidChangeOpeners?: Event<void>;
86}
87
88export async function open(openerService: OpenerService, uri: URI, options?: OpenerOptions): Promise<object | undefined> {
89 const opener = await openerService.getOpener(uri, options);
90 return opener.open(uri, options);
91}
92
93@injectable()
94export class DefaultOpenerService implements OpenerService {
95 // Collection of open-handlers for custom-editor contributions.
96 protected readonly customEditorOpenHandlers: OpenHandler[] = [];
97
98 protected readonly onDidChangeOpenersEmitter = new Emitter<void>();
99 readonly onDidChangeOpeners = this.onDidChangeOpenersEmitter.event;
100
101 constructor(
102 @inject(ContributionProvider) @named(OpenHandler)
103 protected readonly handlersProvider: ContributionProvider<OpenHandler>
104 ) { }
105
106 addHandler(openHandler: OpenHandler): Disposable {
107 this.customEditorOpenHandlers.push(openHandler);
108 this.onDidChangeOpenersEmitter.fire();
109
110 return Disposable.create(() => {
111 this.customEditorOpenHandlers.splice(this.customEditorOpenHandlers.indexOf(openHandler), 1);
112 this.onDidChangeOpenersEmitter.fire();
113 });
114 }
115
116 async getOpener(uri: URI, options?: OpenerOptions): Promise<OpenHandler> {
117 const handlers = await this.prioritize(uri, options);
118 if (handlers.length >= 1) {
119 return handlers[0];
120 }
121 return Promise.reject(new Error(`There is no opener for ${uri}.`));
122 }
123
124 async getOpeners(uri?: URI, options?: OpenerOptions): Promise<OpenHandler[]> {
125 return uri ? this.prioritize(uri, options) : this.getHandlers();
126 }
127
128 protected async prioritize(uri: URI, options?: OpenerOptions): Promise<OpenHandler[]> {
129 const prioritized = await Prioritizeable.prioritizeAll(this.getHandlers(), async handler => {
130 try {
131 return await handler.canHandle(uri, options);
132 } catch {
133 return 0;
134 }
135 });
136 return prioritized.map(p => p.value);
137 }
138
139 protected getHandlers(): OpenHandler[] {
140 return [
141 ...this.handlersProvider.getContributions(),
142 ...this.customEditorOpenHandlers
143 ];
144 }
145
146}