From 25ab324045ff3904877518525ddf009ba6009f67 Mon Sep 17 00:00:00 2001 From: Nina Doschek Date: Fri, 24 Feb 2023 13:31:19 +0100 Subject: [PATCH 1/4] plugin: Support LogOutputChannel - Support LogOutputChannel - Support LogLevel - Support in namespace/env: logLevel & onDidChangeLogLevel - Remark: Needs further extension of application to fully support all aspects of a LogOutputViewChannel (i.e. developer logger service, logging to file, extension of output view UI) Resolves #12017 Contributed on behalf of STMicroelectronics. Signed-off-by: Nina Doschek --- CHANGELOG.md | 5 +- .../plugin-ext/src/common/plugin-api-rpc.ts | 3 +- .../src/plugin/output-channel-registry.ts | 28 ++-- .../output-channel/logoutput-channel.ts | 80 ++++++++++++ .../plugin-ext/src/plugin/plugin-context.ts | 4 +- .../plugin-ext/src/plugin/type-converters.ts | 14 ++ packages/plugin-ext/src/plugin/types-impl.ts | 5 +- packages/plugin/src/theia-proposed.d.ts | 29 ----- packages/plugin/src/theia.d.ts | 121 ++++++++++++++++++ 9 files changed, 245 insertions(+), 44 deletions(-) create mode 100644 packages/plugin-ext/src/plugin/output-channel/logoutput-channel.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 5527fdb773840..9f11586c96672 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 ec76a50ae104a..3386ad722c224 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..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 0960cefc490c8..43f9a89568cce 100644 --- a/packages/plugin-ext/src/plugin/plugin-context.ts +++ b/packages/plugin-ext/src/plugin/plugin-context.ts @@ -483,8 +483,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 bc44dd4cb22b6..80c1bc0b8510d 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; } /** From 6ce5618925a112dbdc4833164f0764b4ca11f8cb Mon Sep 17 00:00:00 2001 From: Nina Doschek Date: Thu, 20 Apr 2023 13:55:20 +0200 Subject: [PATCH 2/4] Address review comments - Ensure options to identify LogOutputChannel is never optional - LogOutputChannelImpl extends OutputChannelImpl instead of vscode's AbstractMessageLogger - Remove obsolete type converter again - Update Changelog Contributed on behalf of STMicroelectronics. Signed-off-by: Nina Doschek --- CHANGELOG.md | 1 + .../plugin-ext/src/common/plugin-api-rpc.ts | 2 +- .../src/plugin/output-channel-registry.ts | 7 +- .../output-channel/log-output-channel.ts | 132 ++++++++++++++++++ .../output-channel/logoutput-channel.ts | 80 ----------- .../output-channel/output-channel-item.ts | 2 +- .../plugin-ext/src/plugin/plugin-context.ts | 4 +- .../plugin-ext/src/plugin/type-converters.ts | 14 -- packages/plugin/src/theia.d.ts | 7 +- 9 files changed, 145 insertions(+), 104 deletions(-) create mode 100644 packages/plugin-ext/src/plugin/output-channel/log-output-channel.ts delete mode 100644 packages/plugin-ext/src/plugin/output-channel/logoutput-channel.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f11586c96672..a4eae4c5629ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ [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] removed enum `LogLevel` and namespace `env` from `plugin/src/theia-proposed.d.ts` [#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 3386ad722c224..d9d540ed93903 100644 --- a/packages/plugin-ext/src/common/plugin-api-rpc.ts +++ b/packages/plugin-ext/src/common/plugin-api-rpc.ts @@ -288,7 +288,7 @@ export interface TerminalServiceExt { } export interface OutputChannelRegistryExt { 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.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 282977c6c07c0..67b21982072fa 100644 --- a/packages/plugin-ext/src/plugin/output-channel-registry.ts +++ b/packages/plugin-ext/src/plugin/output-channel-registry.ts @@ -17,7 +17,8 @@ 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 { 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 { @@ -29,13 +30,13 @@ export class OutputChannelRegistryExtImpl implements OutputChannelRegistryExt { } 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.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'); } - const isLogOutput = options && typeof options === 'object' && options.log; + const isLogOutput = options && isObject(options); return isLogOutput ? this.doCreateLogOutputChannel(name, pluginInfo) : this.doCreateOutputChannel(name, 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..befa3225aed94 --- /dev/null +++ b/packages/plugin-ext/src/plugin/output-channel/log-output-channel.ts @@ -0,0 +1,132 @@ +// ***************************************************************************** +// 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'; + +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(override readonly name: string, protected override readonly proxy: OutputChannelRegistryMain, protected override readonly 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 { + this.info(value); + } + + override appendLine(value: string): void { + this.append(value + '\n'); + } + + protected log(level: theia.LogLevel, message: string): void { + 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); + } + + override dispose(): void { + super.dispose(); + this.onDidChangeLogLevelEmitter.dispose(); + } + + // begin + // copied from vscode: https://github.com/Microsoft/vscode/blob/main/src/vs/platform/log/common/log.ts + /*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + private checkLogLevel(level: theia.LogLevel): boolean { + return this.logLevel <= level; + } + + trace(message: string, ...args: any[]): void { + if (this.checkLogLevel(LogLevel.Trace)) { + this.log(LogLevel.Trace, this.format([message, ...args])); + } + } + + debug(message: string, ...args: any[]): void { + if (this.checkLogLevel(LogLevel.Debug)) { + this.log(LogLevel.Debug, this.format([message, ...args])); + } + } + + info(message: string, ...args: any[]): void { + if (this.checkLogLevel(LogLevel.Info)) { + this.log(LogLevel.Info, this.format([message, ...args])); + } + } + + warn(message: string, ...args: any[]): void { + if (this.checkLogLevel(LogLevel.Warning)) { + this.log(LogLevel.Warning, this.format([message, ...args])); + } + } + + error(message: string | Error, ...args: any[]): void { + if (this.checkLogLevel(LogLevel.Error)) { + if (message instanceof Error) { + const array = Array.prototype.slice.call(arguments) as unknown[]; + array[0] = message.stack; + this.log(LogLevel.Error, this.format(array)); + } else { + this.log(LogLevel.Error, this.format([message, ...args])); + } + } + } + + private format(args: any): string { + let result = ''; + + for (let i = 0; i < args.length; i++) { + let a = args[i]; + + if (typeof a === 'object') { + try { + a = JSON.stringify(a); + } catch (e) { } + } + + result += (i > 0 ? ' ' : '') + a; + } + + return result; + } + // end + +} diff --git a/packages/plugin-ext/src/plugin/output-channel/logoutput-channel.ts b/packages/plugin-ext/src/plugin/output-channel/logoutput-channel.ts deleted file mode 100644 index 6b6c932e27b17..0000000000000 --- a/packages/plugin-ext/src/plugin/output-channel/logoutput-channel.ts +++ /dev/null @@ -1,80 +0,0 @@ -// ***************************************************************************** -// 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/output-channel/output-channel-item.ts b/packages/plugin-ext/src/plugin/output-channel/output-channel-item.ts index cdb97f86e9947..5f01f2f485f78 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 { diff --git a/packages/plugin-ext/src/plugin/plugin-context.ts b/packages/plugin-ext/src/plugin/plugin-context.ts index 43f9a89568cce..628e0c529eab8 100644 --- a/packages/plugin-ext/src/plugin/plugin-context.ts +++ b/packages/plugin-ext/src/plugin/plugin-context.ts @@ -484,7 +484,9 @@ export function createAPIFactory( return statusBarMessageRegistryExt.createStatusBarItem(alignment, priority, id); }, createOutputChannel(name: string, options?: { log: true }): any { - return outputChannelRegistryExt.createOutputChannel(name, pluginToPluginInfo(plugin), options); + 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/type-converters.ts b/packages/plugin-ext/src/plugin/type-converters.ts index f918efde7a875..29d8fe90c3329 100644 --- a/packages/plugin-ext/src/plugin/type-converters.ts +++ b/packages/plugin-ext/src/plugin/type-converters.ts @@ -29,7 +29,6 @@ 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; @@ -1386,16 +1385,3 @@ 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/src/theia.d.ts b/packages/plugin/src/theia.d.ts index 80c1bc0b8510d..7bcf35b74f469 100644 --- a/packages/plugin/src/theia.d.ts +++ b/packages/plugin/src/theia.d.ts @@ -5354,13 +5354,12 @@ 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. + * 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 optional; Options for the log output channel. + * @param options Options for the log output channel. */ - export function createOutputChannel(name: string, options?: { log: true }): LogOutputChannel; + export function createOutputChannel(name: string, options: { log: true }): LogOutputChannel; /** * Create new terminal. From c9302304d9345f6f5ad3f61e97195badd51abcc4 Mon Sep 17 00:00:00 2001 From: Nina Doschek Date: Fri, 21 Apr 2023 10:59:00 +0200 Subject: [PATCH 3/4] Address review comments #2 - Ensure LogOutputview is validated before logging or appending content - Simplify processing of error objects - Simplify formatting of arguments in log messages - Update Changelog Contributed on behalf of STMicroelectronics. Signed-off-by: Nina Doschek --- CHANGELOG.md | 5 +- .../src/plugin/output-channel-registry.ts | 2 +- .../output-channel/log-output-channel.ts | 76 +++++++------------ .../output-channel/output-channel-item.ts | 2 +- 4 files changed, 31 insertions(+), 54 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a4eae4c5629ab..8c0efb89d82d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,12 +6,13 @@ ## v1.37.0 - -- [plugin] implemented the VS Code `LogOutputChannel` API [#12017](https://github.com/eclipse-theia/theia/pull/12429) +- [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] removed enum `LogLevel` and namespace `env` from `plugin/src/theia-proposed.d.ts` [#12017](https://github.com/eclipse-theia/theia/pull/12429) +- [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/plugin/output-channel-registry.ts b/packages/plugin-ext/src/plugin/output-channel-registry.ts index 67b21982072fa..4849ab7db9604 100644 --- a/packages/plugin-ext/src/plugin/output-channel-registry.ts +++ b/packages/plugin-ext/src/plugin/output-channel-registry.ts @@ -23,7 +23,7 @@ import { OutputChannelImpl } from './output-channel/output-channel-item'; export class OutputChannelRegistryExtImpl implements OutputChannelRegistryExt { - private proxy: OutputChannelRegistryMain; + proxy: OutputChannelRegistryMain; constructor(rpc: RPCProtocol) { this.proxy = rpc.getProxy(Ext.OUTPUT_CHANNEL_REGISTRY_MAIN); 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 index befa3225aed94..fe40112d8544e 100644 --- a/packages/plugin-ext/src/plugin/output-channel/log-output-channel.ts +++ b/packages/plugin-ext/src/plugin/output-channel/log-output-channel.ts @@ -21,6 +21,7 @@ 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 { @@ -45,88 +46,63 @@ export class LogOutputChannelImpl extends OutputChannelImpl implements theia.Log } override append(value: string): void { + super.validate(); this.info(value); } override appendLine(value: string): void { + super.validate(); this.append(value + '\n'); } - protected log(level: theia.LogLevel, message: string): void { - 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); - } - override dispose(): void { super.dispose(); this.onDidChangeLogLevelEmitter.dispose(); } - // begin - // copied from vscode: https://github.com/Microsoft/vscode/blob/main/src/vs/platform/log/common/log.ts - /*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ + 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 { - if (this.checkLogLevel(LogLevel.Trace)) { - this.log(LogLevel.Trace, this.format([message, ...args])); - } + this.log(LogLevel.Trace, this.format(message, args)); } debug(message: string, ...args: any[]): void { - if (this.checkLogLevel(LogLevel.Debug)) { - this.log(LogLevel.Debug, this.format([message, ...args])); - } + this.log(LogLevel.Debug, this.format(message, args)); } info(message: string, ...args: any[]): void { - if (this.checkLogLevel(LogLevel.Info)) { - this.log(LogLevel.Info, this.format([message, ...args])); - } + this.log(LogLevel.Info, this.format(message, args)); } warn(message: string, ...args: any[]): void { - if (this.checkLogLevel(LogLevel.Warning)) { - this.log(LogLevel.Warning, this.format([message, ...args])); - } + this.log(LogLevel.Warning, this.format(message, args)); } - error(message: string | Error, ...args: any[]): void { - if (this.checkLogLevel(LogLevel.Error)) { - if (message instanceof Error) { - const array = Array.prototype.slice.call(arguments) as unknown[]; - array[0] = message.stack; - this.log(LogLevel.Error, this.format(array)); - } else { - this.log(LogLevel.Error, 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(args: any): string { - let result = ''; - - for (let i = 0; i < args.length; i++) { - let a = args[i]; - - if (typeof a === 'object') { - try { - a = JSON.stringify(a); - } catch (e) { } - } - - result += (i > 0 ? ' ' : '') + a; + 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 result; + return message; } - // end } 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 5f01f2f485f78..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 @@ -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'); } From a9067c8cb984e6b273d433e4b04d37c0be6ccfff Mon Sep 17 00:00:00 2001 From: Nina Doschek Date: Fri, 21 Apr 2023 14:16:44 +0200 Subject: [PATCH 4/4] Address review comments #3 Contributed on behalf of STMicroelectronics. Signed-off-by: Nina Doschek --- .../plugin-ext/src/plugin/output-channel/log-output-channel.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index fe40112d8544e..d12a0c1c88225 100644 --- a/packages/plugin-ext/src/plugin/output-channel/log-output-channel.ts +++ b/packages/plugin-ext/src/plugin/output-channel/log-output-channel.ts @@ -29,7 +29,7 @@ export class LogOutputChannelImpl extends OutputChannelImpl implements theia.Log readonly onDidChangeLogLevel: theia.Event = this.onDidChangeLogLevelEmitter.event; public logLevel: theia.LogLevel; - constructor(override readonly name: string, protected override readonly proxy: OutputChannelRegistryMain, protected override readonly pluginInfo: PluginInfo) { + constructor(name: string, proxy: OutputChannelRegistryMain, pluginInfo: PluginInfo) { super(name, proxy, pluginInfo); this.setLogLevel(LogLevel.Info); }