From 7c03a689ed27e751775f7be6207eb828daf6f2b9 Mon Sep 17 00:00:00 2001 From: Stefan Dirix Date: Thu, 24 Sep 2020 11:03:36 +0200 Subject: [PATCH] Add API Documentation in core package * Document 'BackendContribution' * Document 'MessageService' and related message interfaces * Extend documentation of MenuContribution and related menu interfaces and classes * Document 'PreferenceService', 'PreferenceProxy' and related interfaces, classes and functions Signed-off-by: Stefan Dirix Contributed on behalf of STMicroelectronics --- .../browser/preferences/preference-proxy.ts | 83 ++++++++++ .../browser/preferences/preference-service.ts | 156 ++++++++++++++++-- packages/core/src/common/menu.ts | 145 ++++++++++++++-- .../src/common/message-service-protocol.ts | 34 ++++ packages/core/src/common/message-service.ts | 117 +++++++++++++ packages/core/src/node/backend-application.ts | 25 ++- 6 files changed, 532 insertions(+), 28 deletions(-) diff --git a/packages/core/src/browser/preferences/preference-proxy.ts b/packages/core/src/browser/preferences/preference-proxy.ts index 17e1cc1ce7a35..1699e395dc39d 100644 --- a/packages/core/src/browser/preferences/preference-proxy.ts +++ b/packages/core/src/browser/preferences/preference-proxy.ts @@ -33,6 +33,24 @@ export interface PreferenceEventEmitter { readonly ready: Promise; } +/** + * Generic interface to declare a typesafe get function based on the given + * configuration type. + * + * ### Example + * + * ```typescript + * interface PreferenceConfiguration { + * 'myext.decorations.enabled': boolean, + * 'myext.debug': boolean, + * 'myext.editor.previewtype': string + * } + * const prefs : PreferenceRetrieval = createPreferenceProxy(...); + * const enabled : boolean = prefs.get('myext.decorations.enabled'); // valid + * const debug : string = prefs.get('myext.debug'); // invalid + * prefs.get('foobar'); // invalid + * ``` + */ export interface PreferenceRetrieval { get(preferenceName: K | { preferenceName: K, @@ -40,14 +58,79 @@ export interface PreferenceRetrieval { }, defaultValue?: T[K], resourceUri?: string): T[K]; } +/** + * Typesafe schema-based preferences utility based on the {@link PreferenceService}. + * + * Can be used to set and get preferences as well as listen to preference changes. + * + * ### Example usage + * + * ``` typescript + * const MyPreferencesSchema: PreferenceSchema = { + * 'type': 'object', + * 'properties': { + * 'myext.decorations.enabled': { + * 'type': 'boolean', + * 'description': 'Show file status', + * 'default': true + * }, + * // [...] + * } + * } + * + * interface MyPrefConfiguration { + * 'myext.decorations.enabled': boolean; + * } + * + * type MyPreferences = PreferenceProxy; + * + * const preferences : MyPreferences = createPreferenceProxy(preferenceService, MyPreferenceSchema); + * + * preferences.onPreferenceChanged(({ preferenceName, newValue }) => { console.log(preferenceName, newValue) }); + * const enabled = preferences['myext.decorations.enabled']; + * ``` + * + */ export type PreferenceProxy = Readonly & Disposable & PreferenceEventEmitter & PreferenceRetrieval; +/** + * Proxy configuration parameters. + */ export interface PreferenceProxyOptions { + /** + * Prefix which is transparently added to all preference identifiers. + */ prefix?: string; + /** + * The default resourceUri to use if none was specified when calling "set" or "get". + */ resourceUri?: string; + /** + * The overrideIdentifier to use with the underlying preferenceService. + * Useful to potentially override existing values while keeping both values in store. + * + * For example to store different editor settings, e.g. "[markdown].editor.autoIndent", + * "[json].editor.autoIndent" and "editor.autoIndent" + */ overrideIdentifier?: string; + /** + * Indicates whether '.' in schema properties shall be interpreted as regular names (flat), + * as declaring nested objects (deep) or both. Default is flat. + * + * When 'deep' or 'both' is given, nested preference proxies can be retrieved. + */ style?: 'flat' | 'deep' | 'both'; } +/** + * Creates a preference proxy for typesafe preference handling. + * + * @param preferences the underlying preference service to use for preference handling. + * @param schema the JSON Schema which describes which preferences are available including types and descriptions. + * @param options configuration options. + * + * @returns the created preference proxy. + * + */ export function createPreferenceProxy(preferences: PreferenceService, schema: PreferenceSchema, options?: PreferenceProxyOptions): PreferenceProxy { const opts = options || {}; const prefix = opts.prefix || ''; diff --git a/packages/core/src/browser/preferences/preference-service.ts b/packages/core/src/browser/preferences/preference-service.ts index a6c9ca0ad7e73..86de09716a37f 100644 --- a/packages/core/src/browser/preferences/preference-service.ts +++ b/packages/core/src/browser/preferences/preference-service.ts @@ -66,34 +66,163 @@ export interface PreferenceChanges { } export const PreferenceService = Symbol('PreferenceService'); +/** + * Service to store and retrieve preference values. + * + * Depending on your use case you might also want to look at `createPreferenceProxy` with which + * you can easily create a typesafe schema-based interface for your preferences. Internally the proxy + * uses the PreferenceService so both approaches are compatible. + */ export interface PreferenceService extends Disposable { readonly ready: Promise; + /** + * Retrieve the stored value for the given preference. + * + * @param preferenceName the preference identifier. + * + * @returns the value stored for the given preference when it exists, `undefined` otherwise. + */ get(preferenceName: string): T | undefined; + /** + * Retrieve the stored value for the given preference. + * + * @param preferenceName the preference identifier. + * @param defaultValue the value to return when no value for the given preference is stored. + * + * @returns the value stored for the given preference when it exists, otherwise the given default value. + */ get(preferenceName: string, defaultValue: T): T; + /** + * Retrieve the stored value for the given preference and resourceUri. + * + * @param preferenceName the preference identifier. + * @param defaultValue the value to return when no value for the given preference is stored. + * @param resourceUri the uri of the resource for which the preference is stored. This used to retrieve + * a potentially different value for the same preference for different resources, for example `files.encoding`. + * + * @returns the value stored for the given preference and resourceUri when it exists, otherwise the given + * default value. + */ get(preferenceName: string, defaultValue: T, resourceUri?: string): T; + /** + * Retrieve the stored value for the given preference and resourceUri. + * + * @param preferenceName the preference identifier. + * @param defaultValue the value to return when no value for the given preference is stored. + * @param resourceUri the uri of the resource for which the preference is stored. This used to retrieve + * a potentially different value for the same preference for different resources, for example `files.encoding`. + * + * @returns the value stored for the given preference and resourceUri when it exists, otherwise the given + * default value. + */ get(preferenceName: string, defaultValue?: T, resourceUri?: string): T | undefined; + /** + * Sets the given preference to the given value. + * + * @param preferenceName the preference identifier. + * @param value the new value of the preference. + * @param scope the scope for which the value shall be set, i.e. user, workspace etc. + * When the folder scope is specified a resourceUri must be provided. + * @param resourceUri the uri of the resource for which the preference is stored. This used to store + * a potentially different value for the same preference for different resources, for example `files.encoding`. + * + * @returns a promise which resolves to `undefined` when setting the preference was successful. Otherwise it rejects + * with an error. + */ set(preferenceName: string, value: any, scope?: PreferenceScope, resourceUri?: string): Promise; + /** + * Registers a callback which will be called whenever a preference is changed. + */ onPreferenceChanged: Event; + /** + * Registers a callback which will be called whenever one or more preferences are changed. + */ onPreferencesChanged: Event; - - inspect(preferenceName: string, resourceUri?: string): { - preferenceName: string, - defaultValue: T | undefined, - globalValue: T | undefined, // User Preference - workspaceValue: T | undefined, // Workspace Preference - workspaceFolderValue: T | undefined // Folder Preference - } | undefined; - + /** + * Retrieve the stored value for the given preference and resourceUri in all available scopes. + * + * @param preferenceName the preference identifier. + * @param resourceUri the uri of the resource for which the preference is stored. + * + * @return an object containing the value of the given preference for all scopes. + */ + inspect(preferenceName: string, resourceUri?: string): PreferenceInspection | undefined; + /** + * Returns a new preference identifier based on the given OverridePreferenceName. + * + * @param options the override specification. + * + * @returns the calculated string based on the given OverridePreferenceName. + */ overridePreferenceName(options: OverridePreferenceName): string; + /** + * Tries to split the given preference identifier into the original OverridePreferenceName attributes + * with which this identifier was created. Returns `undefined` if this is not possible, for example + * when the given preference identifier was not generated by `overridePreferenceName`. + * + * This method is checked when resolving preferences. Therefore together with "overridePreferenceName" + * this can be used to handle specialized preferences, e.g. "[markdown].editor.autoIndent" and "editor.autoIndent". + * + * @param preferenceName the preferenceName which might have been created via {@link PreferenceService.overridePreferenceName}. + * + * @returns the OverridePreferenceName which was used to create the given `preferenceName` if this was the case, + * `undefined` otherwise. + */ overriddenPreferenceName(preferenceName: string): OverridePreferenceName | undefined; - + /** + * Retrieve the stored value for the given preference and resourceUri. + * + * @param preferenceName the preference identifier. + * @param defaultValue the value to return when no value for the given preference is stored. + * @param resourceUri the uri of the resource for which the preference is stored. This used to retrieve + * a potentially different value for the same preference for different resources, for example `files.encoding`. + * + * @returns an object containing the value stored for the given preference and resourceUri when it exists, + * otherwise the given default value. If determinable the object will also contain the uri of the configuration + * resource in which the preference was stored. + */ resolve(preferenceName: string, defaultValue?: T, resourceUri?: string): PreferenceResolveResult; + /** + * Returns the uri of the configuration resource for the given scope and optional resource uri. + * + * @param scope the PreferenceScope to query for. + * @param resourceUri the optional uri of the resource-specific preference handling + * + * @returns the uri of the configuration resource for the given scope and optional resource uri it it exists, + * `undefined` otherwise. + */ getConfigUri(scope: PreferenceScope, resourceUri?: string): URI | undefined; } +/** + * Return type of the {@link PreferenceService.inspect} call. + */ +export interface PreferenceInspection { + /** + * The preference identifier. + */ + preferenceName: string, + /** + * Value in default scope. + */ + defaultValue: T | undefined, + /** + * Value in user scope. + */ + globalValue: T | undefined, + /** + * Value in workspace scope. + */ + workspaceValue: T | undefined, + /** + * Value in folder scope. + */ + workspaceFolderValue: T | undefined +} + /** * We cannot load providers directly in the case if they depend on `PreferenceService` somehow. - * It allows to load them lazilly after DI is configured. + * It allows to load them lazily after DI is configured. */ export const PreferenceProviderProvider = Symbol('PreferenceProviderProvider'); export type PreferenceProviderProvider = (scope: PreferenceScope, uri?: URI) => PreferenceProvider; @@ -233,10 +362,7 @@ export class PreferenceServiceImpl implements PreferenceService { return this.resolve(preferenceName, defaultValue, resourceUri).value; } - resolve(preferenceName: string, defaultValue?: T, resourceUri?: string): { - configUri?: URI, - value?: T - } { + resolve(preferenceName: string, defaultValue?: T, resourceUri?: string): PreferenceResolveResult { const { value, configUri } = this.doResolve(preferenceName, defaultValue, resourceUri); if (value === undefined) { const overridden = this.overriddenPreferenceName(preferenceName); diff --git a/packages/core/src/common/menu.ts b/packages/core/src/common/menu.ts index 01ae1908c934c..7a393463d51ba 100644 --- a/packages/core/src/common/menu.ts +++ b/packages/core/src/common/menu.ts @@ -19,16 +19,37 @@ import { Disposable } from './disposable'; import { CommandRegistry, Command } from './command'; import { ContributionProvider } from './contribution-provider'; +/** + * A menu entry representing an action, e.g. "New File". + */ export interface MenuAction { + /** + * The command to execute. + */ commandId: string /** * In addition to the mandatory command property, an alternative command can be defined. * It will be shown and invoked when pressing Alt while opening a menu. */ alt?: string; + /** + * A specific label for this action. If not specified the command label or command id will be used. + */ label?: string + /** + * Icon class(es). If not specified the icon class associated with the specified command + * (i.e. `command.iconClass`) will be used if it exists. + */ icon?: string + /** + * Menu entries are sorted in ascending order based on their `order` strings. If omitted the determined + * label will be used instead. + */ order?: string + /** + * Optional expression which will be evaluated by the {@link ContextKeyService} to determine visibility + * of the action, e.g. `resourceLangId == markdown`. + */ when?: string } @@ -40,8 +61,18 @@ export namespace MenuAction { } } +/** + * Additional options when creating a new submenu. + */ export interface SubMenuOptions { + /** + * The class to use for the submenu icon. + */ iconClass?: string + /** + * Menu entries are sorted in ascending order based on their `order` strings. If omitted the determined + * label will be used instead. + */ order?: string } @@ -57,6 +88,29 @@ export const MenuContribution = Symbol('MenuContribution'); /** * Representation of a menu contribution. + * + * Note that there are also convenience classes which combine multiple contributions into one. + * For example to register a view together with a menu and keybinding you could use + * {@link AbstractViewContribution} instead. + * + * ### Example usage + * + * ```typescript + * import { MenuContribution, MenuModelRegistry, MAIN_MENU_BAR } from '@theia/core'; + * + * @injectable() + * export class NewMenuContribution implements MenuContribution { + * registerMenus(menus: MenuModelRegistry): void { + * const menuPath = [...MAIN_MENU_BAR, '99_mymenu']; + * menus.registerSubmenu(menuPath, 'My Menu'); + * + * menus.registerMenuAction(menuPath, { + * commandId: MyCommand.id, + * label: 'My Action' + * }); + * } + * } + * ``` */ export interface MenuContribution { /** @@ -66,6 +120,11 @@ export interface MenuContribution { registerMenus(menus: MenuModelRegistry): void; } +/** + * The MenuModelRegistry allows to register and unregister menus, submenus and actions + * via strings and {@link MenuAction}s without the need to access the underlying UI + * representation. + */ @injectable() export class MenuModelRegistry { protected readonly root = new CompositeMenuNode(''); @@ -82,16 +141,41 @@ export class MenuModelRegistry { } } + /** + * Adds the given menu action to the menu denoted by the given path. + * + * @returns a disposable which, when called, will remove the menu action again. + */ registerMenuAction(menuPath: MenuPath, item: MenuAction): Disposable { const menuNode = new ActionMenuNode(item, this.commands); return this.registerMenuNode(menuPath, menuNode); } + /** + * Adds the given menu node to the menu denoted by the given path. + * + * @returns a disposable which, when called, will remove the menu node again. + */ registerMenuNode(menuPath: MenuPath, menuNode: MenuNode): Disposable { const parent = this.findGroup(menuPath); return parent.addNode(menuNode); } + /** + * Register a new menu at the given path with the given label. + * (If the menu already exists without a label, iconClass or order this method can be used to set them.) + * + * @param menuPath the path for which a new submenu shall be registered. + * @param label the label to be used for the new submenu. + * @param options optionally allows to set an icon class and specify the order of the new menu. + * + * @returns if the menu was successfully created a disposable will be returned which, + * when called, will remove the menu again. If the menu already existed a no-op disposable + * will be returned. + * + * Note that if the menu already existed and was registered with a different label an error + * will be thrown. + */ registerSubmenu(menuPath: MenuPath, label: string, options?: SubMenuOptions): Disposable { if (menuPath.length === 0) { throw new Error('The sub menu path cannot be empty.'); @@ -123,21 +207,24 @@ export class MenuModelRegistry { } /** - * Unregister menu item from the registry - * - * @param item + * Unregister all menu nodes with the same id as the given menu action. + * + * @param item the item whose id will be used. + * @param menuPath if specified only nodes within the path will be unregistered. */ unregisterMenuAction(item: MenuAction, menuPath?: MenuPath): void; /** - * Unregister menu item from the registry - * - * @param command + * Unregister all menu nodes with the same id as the given command. + * + * @param command the command whose id will be used. + * @param menuPath if specified only nodes within the path will be unregistered. */ unregisterMenuAction(command: Command, menuPath?: MenuPath): void; /** - * Unregister menu item from the registry - * - * @param id + * Unregister all menu nodes with the given id. + * + * @param id the id which shall be removed. + * @param menuPath if specified only nodes within the path will be unregistered. */ unregisterMenuAction(id: string, menuPath?: MenuPath): void; unregisterMenuAction(itemOrCommandOrId: MenuAction | Command | string, menuPath?: MenuPath): void { @@ -156,6 +243,7 @@ export class MenuModelRegistry { /** * Recurse all menus, removing any menus matching the `id`. + * * @param id technical identifier of the `MenuNode`. */ unregisterMenuNode(id: string): void { @@ -191,21 +279,40 @@ export class MenuModelRegistry { return newSub; } + /** + * Returns the menu at the given path. + * + * @param menuPath the path specifying the menu to return. If not given the empty path will be used. + * + * @returns the root menu when `menuPath` is empty. If `menuPath` is not empty the specified menu is + * returned if it exists, otherwise an error is thrown. + */ getMenu(menuPath: MenuPath = []): CompositeMenuNode { return this.findGroup(menuPath); } } +/** + * Base interface of the nodes used in the menu tree structure. + */ export interface MenuNode { + /** + * the optional label for this specific node. + */ readonly label?: string /** - * technical identifier + * technical identifier. */ readonly id: string - + /** + * Menu nodes are sorted in ascending order based on their `sortString`. + */ readonly sortString: string } +/** + * Node representing a (sub)menu in the menu tree structure. + */ export class CompositeMenuNode implements MenuNode { protected readonly _children: MenuNode[] = []; public iconClass?: string; @@ -226,6 +333,11 @@ export class CompositeMenuNode implements MenuNode { return this._children; } + /** + * Inserts the given node at the position indicated by `sortString`. + * + * @returns a disposable which, when called, will remove the given node again. + */ public addNode(node: MenuNode): Disposable { this._children.push(node); this._children.sort((m1, m2) => { @@ -254,6 +366,9 @@ export class CompositeMenuNode implements MenuNode { }; } + /** + * Removes the node with the given id. + */ public removeNode(id: string): void { const node = this._children.find(n => n.id === id); if (node) { @@ -272,11 +387,19 @@ export class CompositeMenuNode implements MenuNode { return this.label !== undefined; } + /** + * Indicates whether the given node is the special `navigation` menu. + */ static isNavigationGroup(node: MenuNode): node is CompositeMenuNode { return node instanceof CompositeMenuNode && node.id === 'navigation'; } } +/** + * Node representing an action in the menu tree structure. + * It's based on {@link MenuAction} for which it tries to determine the + * best label, icon and sortString with the given data. + */ export class ActionMenuNode implements MenuNode { readonly altNode: ActionMenuNode | undefined; diff --git a/packages/core/src/common/message-service-protocol.ts b/packages/core/src/common/message-service-protocol.ts index 277edb66059d8..c1fe120bd0982 100644 --- a/packages/core/src/common/message-service-protocol.ts +++ b/packages/core/src/common/message-service-protocol.ts @@ -28,9 +28,21 @@ export enum MessageType { } export interface Message { + /** + * Type of the message, i.e. error, warning, info, etc. + */ readonly type?: MessageType; + /** + * Message text. + */ readonly text: string; + /** + * Actions offered to the user in the context of the message. + */ readonly actions?: string[]; + /** + * Additional options. + */ readonly options?: MessageOptions; readonly source?: string; } @@ -68,14 +80,36 @@ export interface ProgressMessageOptions extends MessageOptions { } export interface Progress { + /** + * Unique progress id. + */ readonly id: string; + /** + * Update the current progress. + * + * @param update the data to update. + */ readonly report: (update: ProgressUpdate) => void; + /** + * Cancel or complete the current progress. + */ readonly cancel: () => void; + /** + * Result of the progress + * + * @returns a promise which resolves to either 'Cancel', an alternative action or `undefined`. + */ readonly result: Promise; } export interface ProgressUpdate { + /** + * Updated message for the progress. + */ readonly message?: string; + /** + * Updated ratio between steps done so far and total number of steps. + */ readonly work?: { done: number, total: number }; } diff --git a/packages/core/src/common/message-service.ts b/packages/core/src/common/message-service.ts index cb5c86c90ac28..67db473b41fe4 100644 --- a/packages/core/src/common/message-service.ts +++ b/packages/core/src/common/message-service.ts @@ -25,6 +25,25 @@ import { } from './message-service-protocol'; import { CancellationTokenSource } from './cancellation'; +/** + * Service to log and categorize messages, show progress information and offer actions. + * + * The messages are processed by this service and forwarded to an injected {@link MessageClient}. + * Usually this is the {@link NotificationManager} contributed by "@theia/messages" rendering + * messages as notifications in the frontend. + * + * ### Example usage + * + * ```typescript + * @inject(MessageService) + * protected readonly messageService: MessageService; + * + * messageService.warn("Typings not available"); + * + * messageService.error("Could not restore state", ["Rollback", "Ignore"]) + * .then(action => action === "Rollback" && rollback()); + * ``` + */ @injectable() export class MessageService { @@ -32,28 +51,88 @@ export class MessageService { @inject(MessageClient) protected readonly client: MessageClient ) { } + /** + * Logs the message and, if given, offers actions to act on it. + * @param message the message to log. + * @param actions the actions to offer. Can be omitted. + * + * @returns the selected action if there is any, `undefined` when there was no action or none was selected. + */ log(message: string, ...actions: T[]): Promise; + /** + * Logs the message and, if given, offers actions to act on it. + * @param message the message to log. + * @param options additional options. Can be omitted + * @param actions the actions to offer. Can be omitted. + * + * @returns the selected action if there is any, `undefined` when there was no action or none was selected. + */ log(message: string, options?: MessageOptions, ...actions: T[]): Promise; // eslint-disable-next-line @typescript-eslint/no-explicit-any log(message: string, ...args: any[]): Promise { return this.processMessage(MessageType.Log, message, args); } + /** + * Logs the message as "info" and, if given, offers actions to act on it. + * @param message the message to log. + * @param actions the actions to offer. Can be omitted. + * + * @returns the selected action if there is any, `undefined` when there was no action or none was selected. + */ info(message: string, ...actions: T[]): Promise; + /** + * Logs the message as "info" and, if given, offers actions to act on it. + * @param message the message to log. + * @param options additional options. Can be omitted + * @param actions the actions to offer. Can be omitted. + * + * @returns the selected action if there is any, `undefined` when there was no action or none was selected. + */ info(message: string, options?: MessageOptions, ...actions: T[]): Promise; // eslint-disable-next-line @typescript-eslint/no-explicit-any info(message: string, ...args: any[]): Promise { return this.processMessage(MessageType.Info, message, args); } + /** + * Logs the message as "warning" and, if given, offers actions to act on it. + * @param message the message to log. + * @param actions the actions to offer. Can be omitted. + * + * @returns the selected action if there is any, `undefined` when there was no action or none was selected. + */ warn(message: string, ...actions: T[]): Promise; + /** + * Logs the message as "warning" and, if given, offers actions to act on it. + * @param message the message to log. + * @param options additional options. Can be omitted + * @param actions the actions to offer. Can be omitted. + * + * @returns the selected action if there is any, `undefined` when there was no action or none was selected. + */ warn(message: string, options?: MessageOptions, ...actions: T[]): Promise; // eslint-disable-next-line @typescript-eslint/no-explicit-any warn(message: string, ...args: any[]): Promise { return this.processMessage(MessageType.Warning, message, args); } + /** + * Logs the message as "error" and, if given, offers actions to act on it. + * @param message the message to log. + * @param actions the actions to offer. Can be omitted. + * + * @returns the selected action if there is any, `undefined` when there was no action or none was selected. + */ error(message: string, ...actions: T[]): Promise; + /** + * Logs the message as "error" and, if given, offers actions to act on it. + * @param message the message to log. + * @param options additional options. Can be omitted + * @param actions the actions to offer. Can be omitted. + * + * @returns the selected action if there is any, `undefined` when there was no action or none was selected. + */ error(message: string, options?: MessageOptions, ...actions: T[]): Promise; // eslint-disable-next-line @typescript-eslint/no-explicit-any error(message: string, ...args: any[]): Promise { @@ -73,6 +152,44 @@ export class MessageService { return this.client.showMessage({ type, text }); } + /** + * Shows the given message as a progress. + * + * @param message the message to show for the progress. + * If not specified otherwise the message will be cancelable. + * @param onDidCancel an optional callback which will be invoked if the progress indicator was indeed canceled. + * + * @returns a promise resolving to a {@link Progress} object with which the progress can be updated. + * + * ### Example usage + * + * ```typescript + * @inject(MessageService) + * protected readonly messageService: MessageService; + * + * // this will show "Progress" as a cancelable message + * this.messageService.showProgress({text: 'Progress'}); + * + * // this will show "Rolling back" with "Cancel" and an additional "Skip" action + * this.messageService.showProgress({ + * text: `Rolling back`, + * actions: ["Skip"], + * }, + * () => console.log("canceled")) + * .then((progress) => { + * // register if interested in the result (only necessary for custom actions) + * progress.result.then((result) => { + * // will be 'Cancel', 'Skip' or `undefined` + * console.log("result is", result); + * }); + * progress.report({message: "Cleaning references", work: {done: 10, total: 100}}); + * progress.report({message: "Restoring previous state", work: {done: 80, total: 100}}); + * progress.report({message: "Complete", work: {done: 100, total: 100}}); + * // we are done so we can cancel the progress message, note that this will also invoke `onDidCancel` + * progress.cancel(); + * }); + * ``` + */ async showProgress(message: ProgressMessage, onDidCancel?: () => void): Promise { const id = this.newProgressId(); const cancellationSource = new CancellationTokenSource(); diff --git a/packages/core/src/node/backend-application.ts b/packages/core/src/node/backend-application.ts index de13d2790c324..dc284e83e503c 100644 --- a/packages/core/src/node/backend-application.ts +++ b/packages/core/src/node/backend-application.ts @@ -30,9 +30,30 @@ import { AddressInfo } from 'net'; import { ApplicationPackage } from '@theia/application-package'; export const BackendApplicationContribution = Symbol('BackendApplicationContribution'); + +/** + * Entry point for hooking into the backend lifecycle. + */ export interface BackendApplicationContribution { - initialize?(): void; - configure?(app: express.Application): void; + /** + * Called during the initialization of the backend application. + * Use this for functionality which has to run as early as possible. + */ + initialize?(): MaybePromise; + + /** + * Called after the initialization of the backend application is complete. + * Use this to configure the Express app before it is started, for example + * to offer additional endpoints. + * @param app the express application to configure + */ + configure?(app: express.Application): MaybePromise; + + /** + * Called right after the server for the Express app is started. + * Use this to additionally configure the server or as ready-signal for your service. + * @param server the backend server + */ onStart?(server: http.Server | https.Server): MaybePromise; /**