diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f7e5301e7605..aff2b70e2ee4a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,10 @@ ## History - [Previous Changelogs](https://github.com/eclipse-theia/theia/tree/master/doc/changelogs/) -## v1.37.0 0 - + +## v1.37.0 - + +- [plugin] implemented the VS Code `LogOutputChannel` API [#12017](https://github.com/eclipse-theia/theia/pull/12429) [Breaking Changes:](#breaking_changes_1.37.0) - [core] Inject core preference into `DockPanelRenderer` constructor [12360](https://github.com/eclipse-theia/theia/pull/12360) diff --git a/packages/plugin-ext/src/common/plugin-api-rpc.ts b/packages/plugin-ext/src/common/plugin-api-rpc.ts index e375511feb536..e1f3c90e1d1e4 100644 --- a/packages/plugin-ext/src/common/plugin-api-rpc.ts +++ b/packages/plugin-ext/src/common/plugin-api-rpc.ts @@ -286,7 +286,8 @@ export interface TerminalServiceExt { getEnvironmentVariableCollection(extensionIdentifier: string): theia.EnvironmentVariableCollection; } export interface OutputChannelRegistryExt { - createOutputChannel(name: string, pluginInfo: PluginInfo): theia.OutputChannel + createOutputChannel(name: string, pluginInfo: PluginInfo): theia.OutputChannel, + createOutputChannel(name: string, pluginInfo: PluginInfo, options?: { log: true }): theia.LogOutputChannel } export interface ConnectionMain { diff --git a/packages/plugin-ext/src/plugin/output-channel-registry.ts b/packages/plugin-ext/src/plugin/output-channel-registry.ts index 2b1c6b41b1b30..282977c6c07c0 100644 --- a/packages/plugin-ext/src/plugin/output-channel-registry.ts +++ b/packages/plugin-ext/src/plugin/output-channel-registry.ts @@ -13,27 +13,39 @@ // // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 // ***************************************************************************** -import { - PLUGIN_RPC_CONTEXT as Ext, OutputChannelRegistryMain, PluginInfo, OutputChannelRegistryExt -} from '../common/plugin-api-rpc'; -import { RPCProtocol } from '../common/rpc-protocol'; + import * as theia from '@theia/plugin'; +import { PLUGIN_RPC_CONTEXT as Ext, OutputChannelRegistryExt, OutputChannelRegistryMain, PluginInfo } from '../common/plugin-api-rpc'; +import { RPCProtocol } from '../common/rpc-protocol'; +import { LogOutputChannelImpl } from './output-channel/logoutput-channel'; import { OutputChannelImpl } from './output-channel/output-channel-item'; export class OutputChannelRegistryExtImpl implements OutputChannelRegistryExt { - proxy: OutputChannelRegistryMain; + private proxy: OutputChannelRegistryMain; constructor(rpc: RPCProtocol) { this.proxy = rpc.getProxy(Ext.OUTPUT_CHANNEL_REGISTRY_MAIN); } - createOutputChannel(name: string, pluginInfo: PluginInfo): theia.OutputChannel { + createOutputChannel(name: string, pluginInfo: PluginInfo): theia.OutputChannel; + createOutputChannel(name: string, pluginInfo: PluginInfo, options?: { log: true; }): theia.LogOutputChannel; + createOutputChannel(name: string, pluginInfo: PluginInfo, options?: { log: true; }): theia.OutputChannel | theia.LogOutputChannel { name = name.trim(); if (!name) { throw new Error('illegal argument \'name\'. must not be falsy'); - } else { - return new OutputChannelImpl(name, this.proxy, pluginInfo); } + const isLogOutput = options && typeof options === 'object' && options.log; + return isLogOutput + ? this.doCreateLogOutputChannel(name, pluginInfo) + : this.doCreateOutputChannel(name, pluginInfo); + } + + private doCreateOutputChannel(name: string, pluginInfo: PluginInfo): OutputChannelImpl { + return new OutputChannelImpl(name, this.proxy, pluginInfo); + } + + private doCreateLogOutputChannel(name: string, pluginInfo: PluginInfo): LogOutputChannelImpl { + return new LogOutputChannelImpl(name, this.proxy, pluginInfo); } } diff --git a/packages/plugin-ext/src/plugin/output-channel/logoutput-channel.ts b/packages/plugin-ext/src/plugin/output-channel/logoutput-channel.ts new file mode 100644 index 0000000000000..6b6c932e27b17 --- /dev/null +++ b/packages/plugin-ext/src/plugin/output-channel/logoutput-channel.ts @@ -0,0 +1,80 @@ +// ***************************************************************************** +// Copyright (C) 2023 STMicroelectronics and others. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License v. 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0. +// +// This Source Code may also be made available under the following Secondary +// Licenses when the conditions for such availability set forth in the Eclipse +// Public License v. 2.0 are satisfied: GNU General Public License, version 2 +// with the GNU Classpath Exception which is available at +// https://www.gnu.org/software/classpath/license.html. +// +// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 +// ***************************************************************************** + +import { AbstractMessageLogger, DEFAULT_LOG_LEVEL, LogLevel } from '@theia/monaco-editor-core/esm/vs/platform/log/common/log'; +import * as theia from '@theia/plugin'; + +import { OutputChannelRegistryMain, PluginInfo } from '../../common/plugin-api-rpc'; +import { toLogLevel } from '../type-converters'; + +export class LogOutputChannelImpl extends AbstractMessageLogger implements theia.LogOutputChannel { + + private _disposed: boolean = false; + get disposed(): boolean { return this._disposed; } + + override onDidChangeLogLevel: theia.Event; + + constructor(readonly name: string, protected proxy: OutputChannelRegistryMain, protected readonly pluginInfo: PluginInfo) { + super(); + this.setLevel(DEFAULT_LOG_LEVEL); + } + + get logLevel(): theia.LogLevel { + return toLogLevel(this.getLevel()); + } + + append(value: string): void { + this.info(value); + } + + appendLine(value: string): void { + this.append(value + '\n'); + } + + replace(value: string): void { + this.info(value); + this.proxy.$append(this.name, value, this.pluginInfo); + } + + clear(): void { + this.proxy.$clear(this.name); + } + + show(columnOrPreserveFocus?: theia.ViewColumn | boolean, preserveFocus?: boolean): void { + this.proxy.$reveal(this.name, !!(typeof columnOrPreserveFocus === 'boolean' ? columnOrPreserveFocus : preserveFocus)); + } + + hide(): void { + this.proxy.$close(this.name); + } + + protected log(level: LogLevel, message: string): void { + const now = new Date(Date.now()); + const eol = message.endsWith('\n') ? '' : '\n'; + const logMessage = `${now.toISOString()} [${LogLevel[level]}] ${message}${eol}`; + this.proxy.$append(this.name, logMessage, this.pluginInfo); + } + + override dispose(): void { + super.dispose(); + + if (!this._disposed) { + this.proxy.$dispose(this.name); + this._disposed = true; + } + } + +} diff --git a/packages/plugin-ext/src/plugin/plugin-context.ts b/packages/plugin-ext/src/plugin/plugin-context.ts index 18f9e27010ed3..f46484054da23 100644 --- a/packages/plugin-ext/src/plugin/plugin-context.ts +++ b/packages/plugin-ext/src/plugin/plugin-context.ts @@ -481,8 +481,8 @@ export function createAPIFactory( return statusBarMessageRegistryExt.createStatusBarItem(alignment, priority, id); }, - createOutputChannel(name: string): theia.OutputChannel { - return outputChannelRegistryExt.createOutputChannel(name, pluginToPluginInfo(plugin)); + createOutputChannel(name: string, options?: { log: true }): any { + return outputChannelRegistryExt.createOutputChannel(name, pluginToPluginInfo(plugin), options); }, createWebviewPanel(viewType: string, title: string, diff --git a/packages/plugin-ext/src/plugin/type-converters.ts b/packages/plugin-ext/src/plugin/type-converters.ts index 29d8fe90c3329..f918efde7a875 100644 --- a/packages/plugin-ext/src/plugin/type-converters.ts +++ b/packages/plugin-ext/src/plugin/type-converters.ts @@ -29,6 +29,7 @@ import { UriComponents } from '../common/uri-components'; import { isReadonlyArray } from '../common/arrays'; import { MarkdownString as MarkdownStringDTO } from '@theia/core/lib/common/markdown-rendering'; import { isObject } from '@theia/core/lib/common'; +import { LogLevel as MonacoLogLevel } from '@theia/monaco-editor-core/esm/vs/platform/log/common/log'; const SIDE_GROUP = -2; const ACTIVE_GROUP = -1; @@ -1385,3 +1386,16 @@ export namespace DataTransfer { return dataTransfer; } } + +export function toLogLevel(logLevel: MonacoLogLevel): theia.LogLevel { + switch (logLevel) { + case MonacoLogLevel.Trace: return types.LogLevel.Trace; + case MonacoLogLevel.Debug: return types.LogLevel.Debug; + case MonacoLogLevel.Info: return types.LogLevel.Info; + case MonacoLogLevel.Warning: return types.LogLevel.Warning; + case MonacoLogLevel.Error: return types.LogLevel.Error; + case MonacoLogLevel.Critical: return types.LogLevel.Error /* the plugin API's max LogLevel is Error */; + case MonacoLogLevel.Off: return types.LogLevel.Off; + default: throw new Error(`Invalid log level ${logLevel}`); + } +} diff --git a/packages/plugin-ext/src/plugin/types-impl.ts b/packages/plugin-ext/src/plugin/types-impl.ts index d74d6b911700c..8da9451f4ca10 100644 --- a/packages/plugin-ext/src/plugin/types-impl.ts +++ b/packages/plugin-ext/src/plugin/types-impl.ts @@ -2734,13 +2734,12 @@ export namespace DebugAdapterInlineImplementation { export type DebugAdapterDescriptor = DebugAdapterExecutable | DebugAdapterServer | DebugAdapterNamedPipeServer | DebugAdapterInlineImplementation; export enum LogLevel { + Off = 0, Trace = 1, Debug = 2, Info = 3, Warning = 4, - Error = 5, - Critical = 6, - Off = 7 + Error = 5 } /** diff --git a/packages/plugin/src/theia-proposed.d.ts b/packages/plugin/src/theia-proposed.d.ts index 1b48bf9bb5d6c..d97447cff6a10 100644 --- a/packages/plugin/src/theia-proposed.d.ts +++ b/packages/plugin/src/theia-proposed.d.ts @@ -168,35 +168,6 @@ export module '@theia/plugin' { color?: ThemeColor; } - // #region LogLevel: https://github.com/microsoft/vscode/issues/85992 - - /** - * The severity level of a log message - */ - export enum LogLevel { - Trace = 1, - Debug = 2, - Info = 3, - Warning = 4, - Error = 5, - Critical = 6, - Off = 7 - } - - export namespace env { - /** - * Current logging level. - */ - export const logLevel: LogLevel; - - /** - * An [event](#Event) that fires when the log level has changed. - */ - export const onDidChangeLogLevel: Event; - } - - // #endregion - // #region search in workspace /** * The parameters of a query for text search. diff --git a/packages/plugin/src/theia.d.ts b/packages/plugin/src/theia.d.ts index 8200e21202993..f1821a9a9d0dd 100644 --- a/packages/plugin/src/theia.d.ts +++ b/packages/plugin/src/theia.d.ts @@ -2702,6 +2702,9 @@ export module '@theia/plugin' { /** * An output channel is a container for readonly textual information. + * + * To get an instance of an `OutputChannel` use + * {@link window.createOutputChannel createOutputChannel}. */ export interface OutputChannel { @@ -2765,6 +2768,106 @@ export module '@theia/plugin' { dispose(): void; } + /** + * Log levels + */ + export enum LogLevel { + + /** + * No messages are logged with this level. + */ + Off = 0, + + /** + * All messages are logged with this level. + */ + Trace = 1, + + /** + * Messages with debug and higher log level are logged with this level. + */ + Debug = 2, + + /** + * Messages with info and higher log level are logged with this level. + */ + Info = 3, + + /** + * Messages with warning and higher log level are logged with this level. + */ + Warning = 4, + + /** + * Only error messages are logged with this level. + */ + Error = 5 + } + + /** + * A channel for containing log output. + * + * To get an instance of a `LogOutputChannel` use + * {@link window.createOutputChannel createOutputChannel}. + */ + export interface LogOutputChannel extends OutputChannel { + + /** + * The current log level of the channel. Defaults to {@link env.logLevel editor log level}. + */ + readonly logLevel: LogLevel; + + /** + * An {@link Event} which fires when the log level of the channel changes. + */ + readonly onDidChangeLogLevel: Event; + + /** + * Outputs the given trace message to the channel. Use this method to log verbose information. + * + * The message is only loggeed if the channel is configured to display {@link LogLevel.Trace trace} log level. + * + * @param message trace message to log + */ + trace(message: string, ...args: any[]): void; + + /** + * Outputs the given debug message to the channel. + * + * The message is only loggeed if the channel is configured to display {@link LogLevel.Debug debug} log level or lower. + * + * @param message debug message to log + */ + debug(message: string, ...args: any[]): void; + + /** + * Outputs the given information message to the channel. + * + * The message is only loggeed if the channel is configured to display {@link LogLevel.Info info} log level or lower. + * + * @param message info message to log + */ + info(message: string, ...args: any[]): void; + + /** + * Outputs the given warning message to the channel. + * + * The message is only loggeed if the channel is configured to display {@link LogLevel.Warning warning} log level or lower. + * + * @param message warning message to log + */ + warn(message: string, ...args: any[]): void; + + /** + * Outputs the given error or error message to the channel. + * + * The message is only loggeed if the channel is configured to display {@link LogLevel.Error error} log level or lower. + * + * @param error Error or error message to log + */ + error(error: string | Error, ...args: any[]): void; + } + /** * Options to configure the behaviour of a file open dialog. * @@ -5250,6 +5353,15 @@ export module '@theia/plugin' { */ export function createOutputChannel(name: string): OutputChannel; + /** + * Creates a new {@link OutputChannel output channel} with the given name. + * If options are given, creates a new {@link OutputChannel output channel} with the given name. + * + * @param name Human-readable string which will be used to represent the channel in the UI. + * @param options optional; Options for the log output channel. + */ + export function createOutputChannel(name: string, options?: { log: true }): LogOutputChannel; + /** * Create new terminal. * @param name - terminal name to display on the UI. @@ -7527,6 +7639,15 @@ export module '@theia/plugin' { */ export function asExternalUri(target: Uri): Thenable; + /** + * The current log level of the editor. + */ + export const logLevel: LogLevel; + + /** + * An {@link Event} which fires when the log level of the editor changes. + */ + export const onDidChangeLogLevel: Event; } /**