Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

plugin: Support LogOutputChannel #12429

Merged
merged 4 commits into from
Apr 21, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

<a name="breaking_changes_1.37.0">[Breaking Changes:](#breaking_changes_1.37.0)</a>
- [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

Expand Down
3 changes: 2 additions & 1 deletion packages/plugin-ext/src/common/plugin-api-rpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
27 changes: 20 additions & 7 deletions packages/plugin-ext/src/plugin/output-channel-registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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);
}
}
108 changes: 108 additions & 0 deletions packages/plugin-ext/src/plugin/output-channel/log-output-channel.ts
Original file line number Diff line number Diff line change
@@ -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<theia.LogLevel> = new Emitter<theia.LogLevel>();
readonly onDidChangeLogLevel: theia.Event<theia.LogLevel> = this.onDidChangeLogLevelEmitter.event;
public logLevel: theia.LogLevel;

constructor(override readonly name: string, protected override readonly proxy: OutputChannelRegistryMain, protected override readonly pluginInfo: PluginInfo) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since name is public on the superclass, do we need the override readonly?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is also readonly in the superclass, so I guess this is okay this way

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But why would you redeclare a variable that is already present in the superclass and visible just fine in the subclass? It's unnecessary for all three constructor params, IMO.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes basically I agree, but as I had to write a custom constructor (to initalize the log level) I have no other choice, as Typescript requires them to be overridden due to the same variable names, see This parameter property must have an 'override' modifier because it overrides a member in base class 'OutputChannelImpl'.ts(4115)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This works for me:

constructor(name: string, proxy: OutputChannelRegistryMain, pluginInfo: PluginInfo) {
        super(name, proxy, pluginInfo);
        this.setLogLevel(LogLevel.Info);
    }

The "readonly" is a property of the implicitly created field, not the constructor parameter.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks I was not aware of that, that works great! I pushed another update for this.

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);
tsmaeder marked this conversation as resolved.
Show resolved Hide resolved
}

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

}
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
tsmaeder marked this conversation as resolved.
Show resolved Hide resolved
}

dispose(): void {
Expand Down Expand Up @@ -65,7 +65,7 @@ export class OutputChannelImpl implements theia.OutputChannel {
this.proxy.$close(this.name);
}

private validate(): void {
protected validate(): void {
tsmaeder marked this conversation as resolved.
Show resolved Hide resolved
if (this.disposed) {
throw new Error('Channel has been closed');
}
Expand Down
6 changes: 4 additions & 2 deletions packages/plugin-ext/src/plugin/plugin-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
5 changes: 2 additions & 3 deletions packages/plugin-ext/src/plugin/types-impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

/**
Expand Down
29 changes: 0 additions & 29 deletions packages/plugin/src/theia-proposed.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
ndoschek marked this conversation as resolved.
Show resolved Hide resolved
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<LogLevel>;
}

// #endregion

// #region search in workspace
/**
* The parameters of a query for text search.
Expand Down
Loading