diff --git a/CHANGELOG.md b/CHANGELOG.md index 5527fdb773840..8c0efb89d82d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,11 +3,16 @@ ## 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) - Contributed on behalf of STMicroelectronics [Breaking Changes:](#breaking_changes_1.37.0) - [core] Inject core preference into `DockPanelRenderer` constructor [12360](https://github.com/eclipse-theia/theia/pull/12360) - [core] Introduced `ScrollableTabBar.updateTabs()` to fully render tabs [12360](https://github.com/eclipse-theia/theia/pull/12360) +- [plugin] `plugin/src/theia-proposed.d.ts`: removed enum `LogLevel` and namespace `env` [#12017](https://github.com/eclipse-theia/theia/pull/12429) +- [plugin-ext] `output-channel-item.ts`: changed visibility from `private` to `protected` for member `proxy` and function `validate()` [#12017](https://github.com/eclipse-theia/theia/pull/12429) ## v1.36.0 0 - 03/30/2023 diff --git a/packages/plugin-ext/src/common/plugin-api-rpc.ts b/packages/plugin-ext/src/common/plugin-api-rpc.ts index ec76a50ae104a..d9d540ed93903 100644 --- a/packages/plugin-ext/src/common/plugin-api-rpc.ts +++ b/packages/plugin-ext/src/common/plugin-api-rpc.ts @@ -287,7 +287,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..4849ab7db9604 100644 --- a/packages/plugin-ext/src/plugin/output-channel-registry.ts +++ b/packages/plugin-ext/src/plugin/output-channel-registry.ts @@ -13,11 +13,12 @@ // // 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 { isObject } from '../common/types'; +import { LogOutputChannelImpl } from './output-channel/log-output-channel'; import { OutputChannelImpl } from './output-channel/output-channel-item'; export class OutputChannelRegistryExtImpl implements OutputChannelRegistryExt { @@ -28,12 +29,24 @@ export class OutputChannelRegistryExtImpl implements OutputChannelRegistryExt { 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 && isObject(options); + 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/log-output-channel.ts b/packages/plugin-ext/src/plugin/output-channel/log-output-channel.ts new file mode 100644 index 0000000000000..d12a0c1c88225 --- /dev/null +++ b/packages/plugin-ext/src/plugin/output-channel/log-output-channel.ts @@ -0,0 +1,108 @@ +// ***************************************************************************** +// 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 +// ***************************************************************************** +/* eslint-disable @typescript-eslint/no-explicit-any */ + +import { Emitter } from '@theia/core/shared/vscode-languageserver-protocol'; +import * as theia from '@theia/plugin'; + +import { OutputChannelRegistryMain, PluginInfo } from '../../common/plugin-api-rpc'; +import { OutputChannelImpl } from './output-channel-item'; +import { LogLevel } from '../types-impl'; +import { isArray, isObject } from '@theia/core'; + +export class LogOutputChannelImpl extends OutputChannelImpl implements theia.LogOutputChannel { + + readonly onDidChangeLogLevelEmitter: Emitter = new Emitter(); + readonly onDidChangeLogLevel: theia.Event = this.onDidChangeLogLevelEmitter.event; + public logLevel: theia.LogLevel; + + constructor(name: string, proxy: OutputChannelRegistryMain, pluginInfo: PluginInfo) { + super(name, proxy, pluginInfo); + this.setLogLevel(LogLevel.Info); + } + + setLogLevel(level: theia.LogLevel): void { + if (this.logLevel !== level) { + this.logLevel = level; + this.onDidChangeLogLevelEmitter.fire(this.logLevel); + } + } + + getLogLevel(): theia.LogLevel { + return this.logLevel; + } + + override append(value: string): void { + super.validate(); + this.info(value); + } + + override appendLine(value: string): void { + super.validate(); + this.append(value + '\n'); + } + + override dispose(): void { + super.dispose(); + this.onDidChangeLogLevelEmitter.dispose(); + } + + protected log(level: theia.LogLevel, message: string): void { + super.validate(); + if (this.checkLogLevel(level)) { + const now = new Date(); + const eol = message.endsWith('\n') ? '' : '\n'; + const logMessage = `${now.toISOString()} [${LogLevel[level]}] ${message}${eol}`; + this.proxy.$append(this.name, logMessage, this.pluginInfo); + } + } + + private checkLogLevel(level: theia.LogLevel): boolean { + return this.logLevel <= level; + } + + trace(message: string, ...args: any[]): void { + this.log(LogLevel.Trace, this.format(message, args)); + } + + debug(message: string, ...args: any[]): void { + this.log(LogLevel.Debug, this.format(message, args)); + } + + info(message: string, ...args: any[]): void { + this.log(LogLevel.Info, this.format(message, args)); + } + + warn(message: string, ...args: any[]): void { + this.log(LogLevel.Warning, this.format(message, args)); + } + + error(errorMsg: string | Error, ...args: any[]): void { + if (errorMsg instanceof Error) { + this.log(LogLevel.Error, this.format(errorMsg.stack || errorMsg.message, args)); + } else { + this.log(LogLevel.Error, this.format(errorMsg, args)); + } + } + + private format(message: string, args: any[]): string { + if (args.length > 0) { + return `${message} ${args.map((arg: any) => isObject(arg) || isArray(arg) ? JSON.stringify(arg) : arg).join(' ')}`; + } + return message; + } + +} 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 index cdb97f86e9947..043ec3e85ffed 100644 --- a/packages/plugin-ext/src/plugin/output-channel/output-channel-item.ts +++ b/packages/plugin-ext/src/plugin/output-channel/output-channel-item.ts @@ -20,7 +20,7 @@ export class OutputChannelImpl implements theia.OutputChannel { private disposed: boolean; - constructor(readonly name: string, private proxy: OutputChannelRegistryMain, private readonly pluginInfo: PluginInfo) { + constructor(readonly name: string, protected readonly proxy: OutputChannelRegistryMain, protected readonly pluginInfo: PluginInfo) { } dispose(): void { @@ -65,7 +65,7 @@ export class OutputChannelImpl implements theia.OutputChannel { this.proxy.$close(this.name); } - private validate(): void { + protected 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 0960cefc490c8..628e0c529eab8 100644 --- a/packages/plugin-ext/src/plugin/plugin-context.ts +++ b/packages/plugin-ext/src/plugin/plugin-context.ts @@ -483,8 +483,10 @@ 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 !options + ? outputChannelRegistryExt.createOutputChannel(name, pluginToPluginInfo(plugin)) + : outputChannelRegistryExt.createOutputChannel(name, pluginToPluginInfo(plugin), options); }, createWebviewPanel(viewType: string, title: string, 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 bc44dd4cb22b6..7bcf35b74f469 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,14 @@ export module '@theia/plugin' { */ export function createOutputChannel(name: string): OutputChannel; + /** + * Creates a new {@link LogOutputChannel log output channel} with the given name. + * + * @param name Human-readable string which will be used to represent the channel in the UI. + * @param options 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 +7638,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; } /**