diff --git a/packages/plugin-ext/src/api/plugin-api.ts b/packages/plugin-ext/src/api/plugin-api.ts index 24cb723f0bc42..7eed430390744 100644 --- a/packages/plugin-ext/src/api/plugin-api.ts +++ b/packages/plugin-ext/src/api/plugin-api.ts @@ -398,6 +398,14 @@ export interface PreferenceRegistryExt { $acceptConfigurationChanged(data: { [key: string]: any }, eventData: PreferenceChange): void; } +export interface OutputChannelRegistryMain { + $append(channelName: string, value: string): PromiseLike; + $clear(channelName: string): PromiseLike; + $dispose(channelName: string): PromiseLike; + $reveal(channelName: string, preserveFocus: boolean): PromiseLike; + $close(channelName: string): PromiseLike; +} + export const PLUGIN_RPC_CONTEXT = { COMMAND_REGISTRY_MAIN: >createProxyIdentifier('CommandRegistryMain'), QUICK_OPEN_MAIN: createProxyIdentifier('QuickOpenMain'), @@ -406,7 +414,8 @@ export const PLUGIN_RPC_CONTEXT = { DOCUMENTS_MAIN: createProxyIdentifier('DocumentsMain'), STATUS_BAR_MESSAGE_REGISTRY_MAIN: >createProxyIdentifier('StatusBarMessageRegistryMain'), ENV_MAIN: createProxyIdentifier('EnvMain'), - PREFERENCE_REGISTRY_MAIN: createProxyIdentifier('PreferenceRegistryMain') + PREFERENCE_REGISTRY_MAIN: createProxyIdentifier('PreferenceRegistryMain'), + OUTPUT_CHANNEL_REGISTRY_MAIN: >createProxyIdentifier('OutputChannelRegistryMain') }; export const MAIN_RPC_CONTEXT = { diff --git a/packages/plugin-ext/src/main/browser/main-context.ts b/packages/plugin-ext/src/main/browser/main-context.ts index 91517884126f8..e6bfd461d5f97 100644 --- a/packages/plugin-ext/src/main/browser/main-context.ts +++ b/packages/plugin-ext/src/main/browser/main-context.ts @@ -4,7 +4,6 @@ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 */ - import { interfaces } from 'inversify'; import { CommandRegistryMainImpl } from './command-registry-main'; import { PreferenceRegistryMainImpl } from './preference-registry-main'; @@ -16,6 +15,7 @@ import { WindowStateMain } from './window-state-main'; import { StatusBarMessageRegistryMainImpl } from './status-bar-message-registry-main'; import { EnvMainImpl } from './env-main'; import { EditorsAndDocumentsMain } from './editors-and-documents-main'; +import {OutputChannelRegistryMainImpl} from "./output-channel-registry-main"; export function setUpPluginApi(rpc: RPCProtocol, container: interfaces.Container): void { const commandRegistryMain = new CommandRegistryMainImpl(rpc, container); @@ -43,4 +43,7 @@ export function setUpPluginApi(rpc: RPCProtocol, container: interfaces.Container const envMain = new EnvMainImpl(rpc, container); rpc.set(PLUGIN_RPC_CONTEXT.ENV_MAIN, envMain); + + const outputChannelRegistryMain = new OutputChannelRegistryMainImpl(container); + rpc.set(PLUGIN_RPC_CONTEXT.OUTPUT_CHANNEL_REGISTRY_MAIN, outputChannelRegistryMain); } diff --git a/packages/plugin-ext/src/main/browser/output-channel-registry-main.ts b/packages/plugin-ext/src/main/browser/output-channel-registry-main.ts new file mode 100644 index 0000000000000..b881bfd57046b --- /dev/null +++ b/packages/plugin-ext/src/main/browser/output-channel-registry-main.ts @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2018 Red Hat, Inc. and others. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + */ +import {interfaces} from 'inversify'; +import {OUTPUT_WIDGET_KIND} from '@theia/output/lib/browser/output-widget'; +import {OutputChannel, OutputChannelManager} from '@theia/output/lib/common/output-channel'; +import {OutputChannelRegistryMain} from '../../api/plugin-api'; + +export class OutputChannelRegistryMainImpl implements OutputChannelRegistryMain { + private delegate: OutputChannelManager; + + private channels: Map = new Map(); + + constructor(container: interfaces.Container) { + this.delegate = container.get(OutputChannelManager); + } + + $append(channelName: string, value: string): PromiseLike { + const outputChannel = this.getChannels(channelName); + if (outputChannel) { + outputChannel.append(value); + } + + return Promise.resolve(); + } + + $clear(channelName: string): PromiseLike { + const outputChannel = this.getChannels(channelName); + if (outputChannel) { + outputChannel.clear(); + } + + return Promise.resolve(); + } + + $dispose(channelName: string): PromiseLike { + this.delegate.deleteChannel(channelName); + if (this.channels.has(channelName)) { + this.channels.delete(channelName); + } + + return Promise.resolve(); + } + + $reveal(channelName: string, preserveFocus: boolean): PromiseLike { + const outputChannel = this.getChannels(channelName); + if (outputChannel) { + outputChannel.setVisibility(true); + if (!preserveFocus) { + this.setOutputChannelFocus(); + } + } + + return Promise.resolve(); + } + + $close(channelName: string): PromiseLike { + const outputChannel = this.getChannels(channelName); + if (outputChannel) { + outputChannel.setVisibility(false); + } + + return Promise.resolve(); + } + + private getChannels(channelName: string): OutputChannel | undefined { + let outputChannel: OutputChannel | undefined; + if (this.channels.has(channelName)) { + outputChannel = this.channels.get(channelName); + } else { + outputChannel = this.delegate.getChannel(channelName); + this.channels.set(channelName, outputChannel); + } + + return outputChannel; + } + + private setOutputChannelFocus(): void { + const outputWidget = document.getElementById(OUTPUT_WIDGET_KIND); + if (outputWidget) { + outputWidget.focus(); + } + } +} diff --git a/packages/plugin-ext/src/plugin/output-channel-registry.ts b/packages/plugin-ext/src/plugin/output-channel-registry.ts new file mode 100644 index 0000000000000..e25fb1b65ad21 --- /dev/null +++ b/packages/plugin-ext/src/plugin/output-channel-registry.ts @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2018 Red Hat, Inc. and others. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + */ +import { + PLUGIN_RPC_CONTEXT as Ext, OutputChannelRegistryMain +} from '../api/plugin-api'; +import { RPCProtocol } from '../api/rpc-protocol'; +import * as theia from '@theia/plugin'; +import { OutputChannelImpl } from './output-channel/output-channel-item'; + +export class OutputChannelRegistryExt { + + proxy: OutputChannelRegistryMain; + + constructor(rpc: RPCProtocol) { + this.proxy = rpc.getProxy(Ext.OUTPUT_CHANNEL_REGISTRY_MAIN); + } + + createOutputChannel(name: string): theia.OutputChannel { + name = name.trim(); + if (!name) { + throw new Error('illegal argument \'name\'. must not be falsy'); + } else { + return new OutputChannelImpl(name, this.proxy); + } + } +} diff --git a/packages/plugin-ext/src/plugin/output-channel/output-channel-item.ts b/packages/plugin-ext/src/plugin/output-channel/output-channel-item.ts new file mode 100644 index 0000000000000..5fb8d1b65b3e3 --- /dev/null +++ b/packages/plugin-ext/src/plugin/output-channel/output-channel-item.ts @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2018 Red Hat, Inc. and others. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + */ +import * as theia from '@theia/plugin'; +import {OutputChannelRegistryMain} from '../../api/plugin-api'; + +export class OutputChannelImpl implements theia.OutputChannel { + + private disposed: boolean; + + constructor(readonly name: string, private proxy: OutputChannelRegistryMain) { + } + + dispose(): void { + if (!this.disposed) { + this.proxy.$dispose(this.name).then(() => { + this.disposed = true; + }); + } + } + + append(value: string): void { + this.validate(); + this.proxy.$append(this.name, value); + } + + appendLine(value: string): void { + this.validate(); + this.append(value + '\n'); + } + + clear(): void { + this.validate(); + this.proxy.$clear(this.name); + } + + show(preserveFocus: boolean | undefined): void { + this.validate(); + this.proxy.$reveal(this.name, !!preserveFocus); + } + + hide(): void { + this.validate(); + this.proxy.$close(this.name); + } + + private validate(): void { + if (this.disposed) { + throw new Error('Channel has been closed'); + } + } +} diff --git a/packages/plugin-ext/src/plugin/plugin-context.ts b/packages/plugin-ext/src/plugin/plugin-context.ts index 888216b7fd162..77a8135edbd3d 100644 --- a/packages/plugin-ext/src/plugin/plugin-context.ts +++ b/packages/plugin-ext/src/plugin/plugin-context.ts @@ -42,18 +42,20 @@ import Uri from 'vscode-uri'; import { TextEditorCursorStyle } from '../common/editor-options'; import { PreferenceRegistryExtImpl } from './preference-registry'; import URI from 'vscode-uri'; +import { OutputChannelRegistryExt } from './output-channel-registry'; export function createAPI(rpc: RPCProtocol): typeof theia { const commandRegistryExt = rpc.set(MAIN_RPC_CONTEXT.COMMAND_REGISTRY_EXT, new CommandRegistryImpl(rpc)); const quickOpenExt = rpc.set(MAIN_RPC_CONTEXT.QUICK_OPEN_EXT, new QuickOpenExtImpl(rpc)); const messageRegistryExt = new MessageRegistryExt(rpc); - const windowStateExt = rpc.set(MAIN_RPC_CONTEXT.WINDOW_STATE_EXT, new WindowStateExtImpl(rpc)); + const windowStateExt = rpc.set(MAIN_RPC_CONTEXT.WINDOW_STATE_EXT, new WindowStateExtImpl()); const editorsAndDocuments = rpc.set(MAIN_RPC_CONTEXT.EDITORS_AND_DOCUMENTS_EXT, new EditorsAndDocumentsExtImpl(rpc)); const editors = rpc.set(MAIN_RPC_CONTEXT.TEXT_EDITORS_EXT, new TextEditorsExtImpl(rpc, editorsAndDocuments)); const documents = rpc.set(MAIN_RPC_CONTEXT.DOCUMENTS_EXT, new DocumentsExtImpl(rpc, editorsAndDocuments)); const statusBarMessageRegistryExt = new StatusBarMessageRegistryExt(rpc); const envExt = rpc.set(MAIN_RPC_CONTEXT.ENV_EXT, new EnvExtImpl(rpc)); const preferenceRegistryExt = rpc.set(MAIN_RPC_CONTEXT.PREFERENCE_REGISTRY_EXT, new PreferenceRegistryExtImpl(rpc)); + const outputChannelRegistryExt = new OutputChannelRegistryExt(rpc); const commands: typeof theia.commands = { // tslint:disable-next-line:no-any @@ -134,6 +136,9 @@ export function createAPI(rpc: RPCProtocol): typeof theia { createStatusBarItem(alignment?: theia.StatusBarAlignment, priority?: number): theia.StatusBarItem { return statusBarMessageRegistryExt.createStatusBarItem(alignment, priority); }, + createOutputChannel(name: string): theia.OutputChannel { + return outputChannelRegistryExt.createOutputChannel(name); + }, get state(): theia.WindowState { return windowStateExt.getWindowState(); diff --git a/packages/plugin-ext/src/plugin/window-state.ts b/packages/plugin-ext/src/plugin/window-state.ts index 929f6efb00911..d06eccac15910 100644 --- a/packages/plugin-ext/src/plugin/window-state.ts +++ b/packages/plugin-ext/src/plugin/window-state.ts @@ -7,7 +7,6 @@ import { WindowState } from "@theia/plugin"; import { WindowStateExt } from "../api/plugin-api"; -import { RPCProtocol } from "../api/rpc-protocol"; import { Event, Emitter } from "@theia/core/lib/common/event"; export class WindowStateExtImpl implements WindowStateExt { @@ -17,7 +16,7 @@ export class WindowStateExtImpl implements WindowStateExt { private windowStateChangedEmitter = new Emitter(); public readonly onDidChangeWindowState: Event = this.windowStateChangedEmitter.event; - constructor(rpc: RPCProtocol) { + constructor() { this.windowStateCached = { focused: true }; // supposed tab is active on start } diff --git a/packages/plugin/API.md b/packages/plugin/API.md index 9f4a66d4f01b8..572039c6ad7b1 100644 --- a/packages/plugin/API.md +++ b/packages/plugin/API.md @@ -119,6 +119,15 @@ Simple example that show a status bar message with statusBarItem: item.text = 'test status bar item'; item.show(); ``` +#### Output channel API + + It is possible to show a container for readonly textual information: + +```typescript + const channel = theia.window.createOutputChannel('test channel'); + channel.appendLine('test output'); + +``` #### Environment API diff --git a/packages/plugin/src/theia.d.ts b/packages/plugin/src/theia.d.ts index 4a6a7da7833c7..8902157325a57 100644 --- a/packages/plugin/src/theia.d.ts +++ b/packages/plugin/src/theia.d.ts @@ -1644,6 +1644,54 @@ declare module '@theia/plugin' { readonly focused: boolean; } + /** + * An output channel is a container for readonly textual information. + */ + export interface OutputChannel { + + /** + * The name of this output channel. + */ + readonly name: string; + + /** + * Append the given value to the channel. + * + * @param value + */ + append(value: string): void; + + /** + * Append the given value and a line feed character + * to the channel. + * + * @param value + */ + appendLine(value: string): void; + + /** + * Removes all output from the channel. + */ + clear(): void; + + /** + * Reveal this channel in the UI. + * + * @param preserveFocus When 'true' the channel will not take focus. + */ + show(preserveFocus?: boolean): void; + + /** + * Hide this channel from the UI. + */ + hide(): void; + + /** + * Dispose and free associated resources. + */ + dispose(): void; + } + /** * Common namespace for dealing with window and editor, showing messages and user input. */ @@ -1890,6 +1938,12 @@ declare module '@theia/plugin' { */ export function createStatusBarItem(alignment?: StatusBarAlignment, priority?: number): StatusBarItem; + /** + * Create a new [output channel](#OutputChannel) with the given name. + * + * @param name String which will be used to represent the channel in the UI. + */ + export function createOutputChannel(name: string): OutputChannel; } /**