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;
}
/**