mirror of
https://github.com/Microsoft/vscode
synced 2024-07-17 02:57:19 +00:00
Support log output channels for extensions (#161249)
* introduce log api in extension context * separate registering output vs log channel * Separate extension log channels in show logs command * add logging error to embedder logger * show extension log in the extension editor * configure log level per extension * change the order of log entries * introduce logger * align with output chanel * revert changes * fixes
This commit is contained in:
parent
82431003f3
commit
35c7ee9d02
|
@ -48,6 +48,9 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"enabledApiProposals": [
|
||||
"extensionLog"
|
||||
],
|
||||
"aiKey": "0c6ae279ed8443289764825290e4f9e2-1a736e7c-1324-4338-be46-fc2a58ae4d14-7255",
|
||||
"main": "./out/extension.js",
|
||||
"browser": "./dist/browser/extension.js",
|
||||
|
|
|
@ -6,53 +6,24 @@
|
|||
import * as vscode from 'vscode';
|
||||
import { AuthProviderType } from '../github';
|
||||
|
||||
type LogLevel = 'Trace' | 'Info' | 'Error';
|
||||
|
||||
export class Log {
|
||||
private output: vscode.OutputChannel;
|
||||
private output: vscode.LogOutputChannel;
|
||||
|
||||
constructor(private readonly type: AuthProviderType) {
|
||||
const friendlyName = this.type === AuthProviderType.github ? 'GitHub' : 'GitHub Enterprise';
|
||||
this.output = vscode.window.createOutputChannel(`${friendlyName} Authentication`);
|
||||
this.output = vscode.window.createOutputChannel(`${friendlyName} Authentication`, { log: true });
|
||||
}
|
||||
|
||||
private data2String(data: any): string {
|
||||
if (data instanceof Error) {
|
||||
return data.stack || data.message;
|
||||
}
|
||||
if (data.success === false && data.message) {
|
||||
return data.message;
|
||||
}
|
||||
return data.toString();
|
||||
public trace(message: string): void {
|
||||
this.output.trace(message);
|
||||
}
|
||||
|
||||
public trace(message: string, data?: any): void {
|
||||
this.logLevel('Trace', message, data);
|
||||
public info(message: string): void {
|
||||
this.output.info(message);
|
||||
}
|
||||
|
||||
public info(message: string, data?: any): void {
|
||||
this.logLevel('Info', message, data);
|
||||
public error(message: string): void {
|
||||
this.output.error(message);
|
||||
}
|
||||
|
||||
public error(message: string, data?: any): void {
|
||||
this.logLevel('Error', message, data);
|
||||
}
|
||||
|
||||
public logLevel(level: LogLevel, message: string, data?: any): void {
|
||||
this.output.appendLine(`[${level} - ${this.now()}] ${message}`);
|
||||
if (data) {
|
||||
this.output.appendLine(this.data2String(data));
|
||||
}
|
||||
}
|
||||
|
||||
private now(): string {
|
||||
const now = new Date();
|
||||
return padLeft(now.getUTCHours() + '', 2, '0')
|
||||
+ ':' + padLeft(now.getMinutes() + '', 2, '0')
|
||||
+ ':' + padLeft(now.getUTCSeconds() + '', 2, '0') + '.' + now.getMilliseconds();
|
||||
}
|
||||
}
|
||||
|
||||
function padLeft(s: string, n: number, pad = ' ') {
|
||||
return pad.repeat(Math.max(0, n - s.length)) + s;
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
},
|
||||
"include": [
|
||||
"src/**/*",
|
||||
"../../src/vscode-dts/vscode.d.ts"
|
||||
"../../src/vscode-dts/vscode.d.ts",
|
||||
"../../src/vscode-dts/vscode.proposed.extensionLog.d.ts"
|
||||
]
|
||||
}
|
||||
|
|
|
@ -16,7 +16,8 @@
|
|||
"onAuthenticationRequest:microsoft"
|
||||
],
|
||||
"enabledApiProposals": [
|
||||
"idToken"
|
||||
"idToken",
|
||||
"extensionLog"
|
||||
],
|
||||
"capabilities": {
|
||||
"virtualWorkspaces": true,
|
||||
|
|
|
@ -5,55 +5,5 @@
|
|||
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
type LogLevel = 'Trace' | 'Info' | 'Error';
|
||||
|
||||
class Log {
|
||||
private output: vscode.OutputChannel;
|
||||
|
||||
constructor() {
|
||||
this.output = vscode.window.createOutputChannel('Microsoft Authentication');
|
||||
}
|
||||
|
||||
private data2String(data: any): string {
|
||||
if (data instanceof Error) {
|
||||
return data.stack || data.message;
|
||||
}
|
||||
if (data.success === false && data.message) {
|
||||
return data.message;
|
||||
}
|
||||
return data.toString();
|
||||
}
|
||||
|
||||
public trace(message: string, data?: any): void {
|
||||
this.logLevel('Trace', message, data);
|
||||
}
|
||||
|
||||
public info(message: string, data?: any): void {
|
||||
this.logLevel('Info', message, data);
|
||||
}
|
||||
|
||||
public error(message: string, data?: any): void {
|
||||
this.logLevel('Error', message, data);
|
||||
}
|
||||
|
||||
public logLevel(level: LogLevel, message: string, data?: any): void {
|
||||
this.output.appendLine(`[${level} - ${this.now()}] ${message}`);
|
||||
if (data) {
|
||||
this.output.appendLine(this.data2String(data));
|
||||
}
|
||||
}
|
||||
|
||||
private now(): string {
|
||||
const now = new Date();
|
||||
return padLeft(now.getUTCHours() + '', 2, '0')
|
||||
+ ':' + padLeft(now.getMinutes() + '', 2, '0')
|
||||
+ ':' + padLeft(now.getUTCSeconds() + '', 2, '0') + '.' + now.getMilliseconds();
|
||||
}
|
||||
}
|
||||
|
||||
function padLeft(s: string, n: number, pad = ' ') {
|
||||
return pad.repeat(Math.max(0, n - s.length)) + s;
|
||||
}
|
||||
|
||||
const Logger = new Log();
|
||||
const Logger = vscode.window.createOutputChannel('Microsoft Authentication', { log: true });
|
||||
export default Logger;
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
"include": [
|
||||
"src/**/*",
|
||||
"../../src/vscode-dts/vscode.d.ts",
|
||||
"../../src/vscode-dts/vscode.proposed.idToken.d.ts"
|
||||
"../../src/vscode-dts/vscode.proposed.idToken.d.ts",
|
||||
"../../src/vscode-dts/vscode.proposed.extensionLog.d.ts"
|
||||
]
|
||||
}
|
||||
|
|
|
@ -41,12 +41,12 @@ export class MainThreadOutputService extends Disposable implements MainThreadOut
|
|||
setVisibleChannel();
|
||||
}
|
||||
|
||||
public async $register(label: string, log: boolean, file: UriComponents, languageId: string, extensionId: string): Promise<string> {
|
||||
public async $register(label: string, file: UriComponents, log: boolean, languageId: string | undefined, extensionId: string): Promise<string> {
|
||||
const idCounter = (MainThreadOutputService._extensionIdPool.get(extensionId) || 0) + 1;
|
||||
MainThreadOutputService._extensionIdPool.set(extensionId, idCounter);
|
||||
const id = `extension-output-${extensionId}-#${idCounter}-${label}`;
|
||||
|
||||
Registry.as<IOutputChannelRegistry>(Extensions.OutputChannels).registerChannel({ id, label, file: URI.revive(file), log, languageId });
|
||||
Registry.as<IOutputChannelRegistry>(Extensions.OutputChannels).registerChannel({ id, label, file: URI.revive(file), log, languageId, extensionId });
|
||||
this._register(toDisposable(() => this.$dispose(id)));
|
||||
return id;
|
||||
}
|
||||
|
|
|
@ -703,8 +703,8 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
|||
withProgress<R>(options: vscode.ProgressOptions, task: (progress: vscode.Progress<{ message?: string; worked?: number }>, token: vscode.CancellationToken) => Thenable<R>) {
|
||||
return extHostProgress.withProgress(extension, options, task);
|
||||
},
|
||||
createOutputChannel(name: string, languageId?: string): vscode.OutputChannel {
|
||||
return extHostOutputService.createOutputChannel(name, languageId, extension);
|
||||
createOutputChannel(name: string, options: string | { log: true } | undefined): any {
|
||||
return extHostOutputService.createOutputChannel(name, options, extension);
|
||||
},
|
||||
createWebviewPanel(viewType: string, title: string, showOptions: vscode.ViewColumn | { viewColumn: vscode.ViewColumn; preserveFocus?: boolean }, options?: vscode.WebviewPanelOptions & vscode.WebviewOptions): vscode.WebviewPanel {
|
||||
return extHostWebviewPanels.createWebviewPanel(extension, viewType, title, showOptions, options);
|
||||
|
|
|
@ -418,7 +418,7 @@ export interface MainThreadMessageServiceShape extends IDisposable {
|
|||
}
|
||||
|
||||
export interface MainThreadOutputServiceShape extends IDisposable {
|
||||
$register(label: string, log: boolean, file: UriComponents, languageId: string | undefined, extensionId: string): Promise<string>;
|
||||
$register(label: string, file: UriComponents, log: boolean, languageId: string | undefined, extensionId: string): Promise<string>;
|
||||
$update(channelId: string, mode: OutputChannelUpdateMode, till?: number): Promise<void>;
|
||||
$reveal(channelId: string, preserveFocus: boolean): Promise<void>;
|
||||
$close(channelId: string): Promise<void>;
|
||||
|
|
|
@ -6,11 +6,10 @@
|
|||
import { MainContext, MainThreadOutputServiceShape, ExtHostOutputServiceShape } from './extHost.protocol';
|
||||
import type * as vscode from 'vscode';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
|
||||
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
import { ILogger, ILoggerService } from 'vs/platform/log/common/log';
|
||||
import { AbstractMessageLogger, ILogger, ILoggerService, log, LogLevel } from 'vs/platform/log/common/log';
|
||||
import { OutputChannelUpdateMode } from 'vs/workbench/services/output/common/output';
|
||||
import { IExtHostConsumerFileSystem } from 'vs/workbench/api/common/extHostFileSystemConsumer';
|
||||
import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService';
|
||||
|
@ -19,23 +18,63 @@ import { toLocalISOString } from 'vs/base/common/date';
|
|||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
import { isString } from 'vs/base/common/types';
|
||||
|
||||
export class ExtHostOutputChannel extends Disposable implements vscode.OutputChannel {
|
||||
|
||||
private offset: number = 0;
|
||||
public visible: boolean = false;
|
||||
export class ExtHostLogOutputChannel extends AbstractMessageLogger implements vscode.LogOutputChannel {
|
||||
|
||||
private _disposed: boolean = false;
|
||||
get disposed(): boolean { return this._disposed; }
|
||||
|
||||
public visible: boolean = false;
|
||||
|
||||
constructor(
|
||||
readonly id: string, readonly name: string,
|
||||
private readonly logger: ILogger,
|
||||
private readonly proxy: MainThreadOutputServiceShape
|
||||
protected readonly logger: ILogger,
|
||||
protected readonly proxy: MainThreadOutputServiceShape,
|
||||
readonly extension: IExtensionDescription,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
appendLine(value: string): void {
|
||||
this.info(value);
|
||||
}
|
||||
|
||||
show(preserveFocus?: boolean): void {
|
||||
this.proxy.$reveal(this.id, !!preserveFocus);
|
||||
}
|
||||
|
||||
hide(): void {
|
||||
this.proxy.$close(this.id);
|
||||
}
|
||||
|
||||
protected log(level: LogLevel, message: string): void {
|
||||
log(this.logger, level, message);
|
||||
}
|
||||
|
||||
override dispose(): void {
|
||||
super.dispose();
|
||||
|
||||
if (!this._disposed) {
|
||||
this.proxy.$dispose(this.id);
|
||||
this._disposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class ExtHostOutputChannel extends ExtHostLogOutputChannel implements vscode.OutputChannel {
|
||||
|
||||
private offset: number = 0;
|
||||
|
||||
constructor(
|
||||
id: string, name: string,
|
||||
logger: ILogger,
|
||||
proxy: MainThreadOutputServiceShape,
|
||||
extension: IExtensionDescription
|
||||
) {
|
||||
super(id, name, logger, proxy, extension);
|
||||
}
|
||||
|
||||
override appendLine(value: string): void {
|
||||
this.append(value + '\n');
|
||||
}
|
||||
|
||||
|
@ -62,13 +101,9 @@ export class ExtHostOutputChannel extends Disposable implements vscode.OutputCha
|
|||
}
|
||||
}
|
||||
|
||||
show(columnOrPreserveFocus?: vscode.ViewColumn | boolean, preserveFocus?: boolean): void {
|
||||
override show(columnOrPreserveFocus?: vscode.ViewColumn | boolean, preserveFocus?: boolean): void {
|
||||
this.logger.flush();
|
||||
this.proxy.$reveal(this.id, !!(typeof columnOrPreserveFocus === 'boolean' ? columnOrPreserveFocus : preserveFocus));
|
||||
}
|
||||
|
||||
hide(): void {
|
||||
this.proxy.$close(this.id);
|
||||
super.show(!!(typeof columnOrPreserveFocus === 'boolean' ? columnOrPreserveFocus : preserveFocus));
|
||||
}
|
||||
|
||||
private write(value: string): void {
|
||||
|
@ -76,15 +111,6 @@ export class ExtHostOutputChannel extends Disposable implements vscode.OutputCha
|
|||
this.logger.info(value);
|
||||
}
|
||||
|
||||
override dispose(): void {
|
||||
super.dispose();
|
||||
|
||||
if (!this._disposed) {
|
||||
this.proxy.$dispose(this.id);
|
||||
this._disposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class ExtHostOutputService implements ExtHostOutputServiceShape {
|
||||
|
@ -97,7 +123,7 @@ export class ExtHostOutputService implements ExtHostOutputServiceShape {
|
|||
private outputDirectoryPromise: Thenable<URI> | undefined;
|
||||
private namePool: number = 1;
|
||||
|
||||
private readonly channels: Map<string, ExtHostOutputChannel> = new Map<string, ExtHostOutputChannel>();
|
||||
private readonly channels = new Map<string, ExtHostLogOutputChannel | ExtHostOutputChannel>();
|
||||
private visibleChannelId: string | null = null;
|
||||
|
||||
constructor(
|
||||
|
@ -118,35 +144,44 @@ export class ExtHostOutputService implements ExtHostOutputServiceShape {
|
|||
}
|
||||
}
|
||||
|
||||
createOutputChannel(name: string, languageId: string | undefined, extension: IExtensionDescription): vscode.OutputChannel {
|
||||
createOutputChannel(name: string, options: string | { log: true } | undefined, extension: IExtensionDescription): vscode.OutputChannel | vscode.LogOutputChannel {
|
||||
name = name.trim();
|
||||
if (!name) {
|
||||
throw new Error('illegal argument `name`. must not be falsy');
|
||||
}
|
||||
const log = typeof options === 'object' && options.log;
|
||||
const languageId = isString(options) ? options : undefined;
|
||||
if (isString(languageId) && !languageId.trim()) {
|
||||
throw new Error('illegal argument `languageId`. must not be empty');
|
||||
}
|
||||
const extHostOutputChannel = this.doCreateOutputChannel(name, languageId, extension);
|
||||
const extHostOutputChannel = log ? this.doCreateLogOutputChannel(name, extension) : this.doCreateOutputChannel(name, languageId, extension);
|
||||
extHostOutputChannel.then(channel => {
|
||||
this.channels.set(channel.id, channel);
|
||||
channel.visible = channel.id === this.visibleChannelId;
|
||||
});
|
||||
return this.createExtHostOutputChannel(name, extHostOutputChannel);
|
||||
return log ? this.createExtHostLogOutputChannel(name, <Promise<ExtHostOutputChannel>>extHostOutputChannel) : this.createExtHostOutputChannel(name, <Promise<ExtHostOutputChannel>>extHostOutputChannel);
|
||||
}
|
||||
|
||||
private async doCreateOutputChannel(name: string, languageId: string | undefined, extension: IExtensionDescription): Promise<ExtHostOutputChannel> {
|
||||
const outputDir = await this.createOutputDirectory();
|
||||
const file = this.extHostFileSystemInfo.extUri.joinPath(outputDir, `${this.namePool++}-${name.replace(/[\\/:\*\?"<>\|]/g, '')}.log`);
|
||||
const file = await this.createLogFile(name);
|
||||
const logger = this.loggerService.createLogger(file, { always: true, donotRotate: true, donotUseFormatters: true });
|
||||
const id = await this.proxy.$register(name, false, file, languageId, extension.identifier.value);
|
||||
return new ExtHostOutputChannel(id, name, logger, this.proxy);
|
||||
const id = await this.proxy.$register(name, file, false, languageId, extension.identifier.value);
|
||||
return new ExtHostOutputChannel(id, name, logger, this.proxy, extension);
|
||||
}
|
||||
|
||||
private createOutputDirectory(): Thenable<URI> {
|
||||
private async doCreateLogOutputChannel(name: string, extension: IExtensionDescription): Promise<ExtHostLogOutputChannel> {
|
||||
const file = await this.createLogFile(name);
|
||||
const logger = this.loggerService.createLogger(file, { name });
|
||||
const id = await this.proxy.$register(name, file, true, undefined, extension.identifier.value);
|
||||
return new ExtHostLogOutputChannel(id, name, logger, this.proxy, extension);
|
||||
}
|
||||
|
||||
private async createLogFile(name: string): Promise<URI> {
|
||||
if (!this.outputDirectoryPromise) {
|
||||
this.outputDirectoryPromise = this.extHostFileSystem.value.createDirectory(this.outputsLocation).then(() => this.outputsLocation);
|
||||
}
|
||||
return this.outputDirectoryPromise;
|
||||
const outputDir = await this.outputDirectoryPromise;
|
||||
return this.extHostFileSystemInfo.extUri.joinPath(outputDir, `${this.namePool++}-${name.replace(/[\\/:\*\?"<>\|]/g, '')}.log`);
|
||||
}
|
||||
|
||||
private createExtHostOutputChannel(name: string, channelPromise: Promise<ExtHostOutputChannel>): vscode.OutputChannel {
|
||||
|
@ -188,6 +223,54 @@ export class ExtHostOutputService implements ExtHostOutputServiceShape {
|
|||
}
|
||||
};
|
||||
}
|
||||
|
||||
private createExtHostLogOutputChannel(name: string, channelPromise: Promise<ExtHostOutputChannel>): vscode.LogOutputChannel {
|
||||
let disposed = false;
|
||||
const validate = () => {
|
||||
if (disposed) {
|
||||
throw new Error('Channel has been closed');
|
||||
}
|
||||
};
|
||||
return {
|
||||
get name(): string { return name; },
|
||||
appendLine(value: string): void {
|
||||
validate();
|
||||
channelPromise.then(channel => channel.appendLine(value));
|
||||
},
|
||||
trace(value: string): void {
|
||||
validate();
|
||||
channelPromise.then(channel => channel.info(value));
|
||||
},
|
||||
debug(value: string): void {
|
||||
validate();
|
||||
channelPromise.then(channel => channel.debug(value));
|
||||
},
|
||||
info(value: string): void {
|
||||
validate();
|
||||
channelPromise.then(channel => channel.info(value));
|
||||
},
|
||||
warn(value: string): void {
|
||||
validate();
|
||||
channelPromise.then(channel => channel.warn(value));
|
||||
},
|
||||
error(value: Error | string): void {
|
||||
validate();
|
||||
channelPromise.then(channel => channel.error(value));
|
||||
},
|
||||
show(preserveFocus?: boolean): void {
|
||||
validate();
|
||||
channelPromise.then(channel => channel.show(preserveFocus));
|
||||
},
|
||||
hide(): void {
|
||||
validate();
|
||||
channelPromise.then(channel => channel.hide());
|
||||
},
|
||||
dispose(): void {
|
||||
disposed = true;
|
||||
channelPromise.then(channel => channel.dispose());
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export interface IExtHostOutputService extends ExtHostOutputService { }
|
||||
|
|
|
@ -24,7 +24,7 @@ import { ITextModelService } from 'vs/editor/common/services/resolverService';
|
|||
import { ViewContainer, IViewContainersRegistry, ViewContainerLocation, Extensions as ViewContainerExtensions, IViewsRegistry, IViewsService } from 'vs/workbench/common/views';
|
||||
import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer';
|
||||
import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry';
|
||||
import { IQuickPickItem, IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
|
||||
import { IQuickPickItem, IQuickInputService, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { assertIsDefined } from 'vs/base/common/types';
|
||||
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||
|
@ -231,9 +231,26 @@ registerAction2(class extends Action2 {
|
|||
async run(accessor: ServicesAccessor): Promise<void> {
|
||||
const outputService = accessor.get(IOutputService);
|
||||
const quickInputService = accessor.get(IQuickInputService);
|
||||
const entries: { id: string; label: string }[] = outputService.getChannelDescriptors().filter(c => c.file && c.log)
|
||||
.map(({ id, label }) => ({ id, label }));
|
||||
|
||||
const extensionLogs = [], logs = [];
|
||||
for (const channel of outputService.getChannelDescriptors()) {
|
||||
if (channel.log) {
|
||||
if (channel.extensionId) {
|
||||
extensionLogs.push(channel);
|
||||
} else {
|
||||
logs.push(channel);
|
||||
}
|
||||
}
|
||||
}
|
||||
const entries: ({ id: string; label: string } | IQuickPickSeparator)[] = [];
|
||||
for (const { id, label } of logs) {
|
||||
entries.push({ id, label });
|
||||
}
|
||||
if (extensionLogs.length && logs.length) {
|
||||
entries.push({ type: 'separator', label: nls.localize('extensionLogs', "Extension Logs") });
|
||||
}
|
||||
for (const { id, label } of extensionLogs) {
|
||||
entries.push({ id, label });
|
||||
}
|
||||
const entry = await quickInputService.pick(entries, { placeHolder: nls.localize('selectlog', "Select Log") });
|
||||
if (entry) {
|
||||
return outputService.showChannel(entry.id);
|
||||
|
|
|
@ -30,7 +30,6 @@ import { IOpenerService } from 'vs/platform/opener/common/opener';
|
|||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { attachSelectBoxStyler, attachStylerCallback } from 'vs/platform/theme/common/styler';
|
||||
import { ISelectOptionItem } from 'vs/base/browser/ui/selectBox/selectBox';
|
||||
import { groupBy } from 'vs/base/common/arrays';
|
||||
import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme';
|
||||
import { editorBackground, selectBorder } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { SelectActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems';
|
||||
|
@ -271,12 +270,13 @@ export class OutputEditor extends AbstractTextResourceEditor {
|
|||
}
|
||||
}
|
||||
|
||||
type OutputChannelSelectionOptionItem = ISelectOptionItem & { readonly channel?: IOutputChannelDescriptor };
|
||||
|
||||
class SwitchOutputActionViewItem extends SelectActionViewItem {
|
||||
|
||||
private static readonly SEPARATOR = '\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500';
|
||||
|
||||
private outputChannels: IOutputChannelDescriptor[] = [];
|
||||
private logChannels: IOutputChannelDescriptor[] = [];
|
||||
private selectionOptionItems: OutputChannelSelectionOptionItem[] = [];
|
||||
|
||||
constructor(
|
||||
action: IAction,
|
||||
|
@ -304,35 +304,48 @@ class SwitchOutputActionViewItem extends SelectActionViewItem {
|
|||
}
|
||||
|
||||
protected override getActionContext(option: string, index: number): string {
|
||||
const channel = index < this.outputChannels.length ? this.outputChannels[index] : this.logChannels[index - this.outputChannels.length - 1];
|
||||
return channel ? channel.id : option;
|
||||
return this.selectionOptionItems[index]?.channel?.id ?? option;
|
||||
}
|
||||
|
||||
private updateOptions(): void {
|
||||
const groups = groupBy(this.outputService.getChannelDescriptors(), (c1: IOutputChannelDescriptor, c2: IOutputChannelDescriptor) => {
|
||||
if (!c1.log && c2.log) {
|
||||
return -1;
|
||||
const outputChannels = [];
|
||||
const logChannels = [];
|
||||
const extensionLogChannels = [];
|
||||
this.selectionOptionItems = [];
|
||||
for (const descriptor of this.outputService.getChannelDescriptors()) {
|
||||
if (descriptor.log) {
|
||||
if (descriptor.extensionId) {
|
||||
extensionLogChannels.push(descriptor);
|
||||
} else {
|
||||
logChannels.push(descriptor);
|
||||
}
|
||||
} else {
|
||||
outputChannels.push(descriptor);
|
||||
}
|
||||
if (c1.log && !c2.log) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
this.outputChannels = groups[0] || [];
|
||||
this.logChannels = groups[1] || [];
|
||||
const showSeparator = this.outputChannels.length && this.logChannels.length;
|
||||
const separatorIndex = showSeparator ? this.outputChannels.length : -1;
|
||||
const options: string[] = [...this.outputChannels.map(c => c.label), ...(showSeparator ? [SwitchOutputActionViewItem.SEPARATOR] : []), ...this.logChannels.map(c => nls.localize('logChannel', "Log ({0})", c.label))];
|
||||
}
|
||||
|
||||
for (const descriptor of outputChannels) {
|
||||
this.selectionOptionItems.push({ text: descriptor.label, isDisabled: false, channel: descriptor });
|
||||
}
|
||||
if (outputChannels.length && logChannels.length) {
|
||||
this.selectionOptionItems.push({ text: SwitchOutputActionViewItem.SEPARATOR, isDisabled: true });
|
||||
}
|
||||
for (const descriptor of logChannels) {
|
||||
this.selectionOptionItems.push({ text: nls.localize('logChannel', "Log ({0})", descriptor.label), isDisabled: false, channel: descriptor });
|
||||
}
|
||||
if (logChannels.length && extensionLogChannels.length) {
|
||||
this.selectionOptionItems.push({ text: SwitchOutputActionViewItem.SEPARATOR, isDisabled: true });
|
||||
}
|
||||
for (const descriptor of extensionLogChannels) {
|
||||
this.selectionOptionItems.push({ text: nls.localize('logChannel', "Log ({0})", descriptor.label), isDisabled: false, channel: descriptor });
|
||||
}
|
||||
|
||||
let selected = 0;
|
||||
const activeChannel = this.outputService.getActiveChannel();
|
||||
if (activeChannel) {
|
||||
selected = this.outputChannels.map(c => c.id).indexOf(activeChannel.id);
|
||||
if (selected === -1) {
|
||||
const logChannelIndex = this.logChannels.map(c => c.id).indexOf(activeChannel.id);
|
||||
selected = logChannelIndex !== -1 ? separatorIndex + 1 + logChannelIndex : 0;
|
||||
}
|
||||
selected = this.selectionOptionItems.findIndex(item => item.channel?.id === activeChannel.id);
|
||||
}
|
||||
this.setOptions(options.map((label, index) => <ISelectOptionItem>{ text: label, isDisabled: (index === separatorIndex ? true : false) }), Math.max(0, selected));
|
||||
this.setOptions(this.selectionOptionItems, Math.max(0, selected));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -26,6 +26,7 @@ export const allApiProposals = Object.freeze({
|
|||
editSessionIdentityProvider: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.editSessionIdentityProvider.d.ts',
|
||||
editorInsets: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.editorInsets.d.ts',
|
||||
envShellEvent: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.envShellEvent.d.ts',
|
||||
extensionLog: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.extensionLog.d.ts',
|
||||
extensionRuntime: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.extensionRuntime.d.ts',
|
||||
extensionsAny: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.extensionsAny.d.ts',
|
||||
externalUriOpener: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.externalUriOpener.d.ts',
|
||||
|
|
|
@ -160,6 +160,7 @@ export interface IOutputChannelDescriptor {
|
|||
log: boolean;
|
||||
languageId?: string;
|
||||
file?: URI;
|
||||
extensionId?: string;
|
||||
}
|
||||
|
||||
export interface IFileOutputChannelDescriptor extends IOutputChannelDescriptor {
|
||||
|
|
134
src/vscode-dts/vscode.proposed.extensionLog.d.ts
vendored
Normal file
134
src/vscode-dts/vscode.proposed.extensionLog.d.ts
vendored
Normal file
|
@ -0,0 +1,134 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
declare module 'vscode' {
|
||||
|
||||
export interface AbstractOutputChannel {
|
||||
/**
|
||||
* The human-readable name of this output channel.
|
||||
*/
|
||||
readonly name: string;
|
||||
|
||||
/**
|
||||
* Append the given value and a line feed character
|
||||
* to the channel.
|
||||
*
|
||||
* @param value A string, falsy values will be printed.
|
||||
*/
|
||||
appendLine(value: string): void;
|
||||
|
||||
/**
|
||||
* Reveal this channel in the UI.
|
||||
*
|
||||
* @param preserveFocus When `true` the channel will not take focus.
|
||||
*/
|
||||
show(preserveFocus?: boolean): void;
|
||||
|
||||
/**
|
||||
* Hide this channel from the UI.
|
||||
*/
|
||||
hide(): void;
|
||||
|
||||
/**
|
||||
* Dispose and free associated resources.
|
||||
*/
|
||||
dispose(): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 extends AbstractOutputChannel {
|
||||
|
||||
/**
|
||||
* Append the given value to the channel.
|
||||
*
|
||||
* @param value A string, falsy values will not be printed.
|
||||
*/
|
||||
append(value: string): void;
|
||||
|
||||
/**
|
||||
* Replaces all output from the channel with the given value.
|
||||
*
|
||||
* @param value A string, falsy values will not be printed.
|
||||
*/
|
||||
replace(value: string): void;
|
||||
|
||||
/**
|
||||
* Removes all output from the channel.
|
||||
*/
|
||||
clear(): void;
|
||||
|
||||
/**
|
||||
* Reveal this channel in the UI.
|
||||
*
|
||||
* @deprecated Use the overload with just one parameter (`show(preserveFocus?: boolean): void`).
|
||||
*
|
||||
* @param column This argument is **deprecated** and will be ignored.
|
||||
* @param preserveFocus When `true` the channel will not take focus.
|
||||
*/
|
||||
show(column?: ViewColumn, preserveFocus?: boolean): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* A channel for containing log output.
|
||||
*/
|
||||
export interface LogOutputChannel extends AbstractOutputChannel {
|
||||
/**
|
||||
* Log the given trace message to the channel.
|
||||
*
|
||||
* Messages are only printed when the user has enabled trace logging for the extension.
|
||||
*
|
||||
* @param message trace message to log
|
||||
*/
|
||||
trace(message: string): void;
|
||||
/**
|
||||
* Log the given debug message to the channel.
|
||||
*
|
||||
* Messages are only printed when the user has enabled debug logging for the extension.
|
||||
*
|
||||
* @param message debug message to log
|
||||
*/
|
||||
debug(message: string): void;
|
||||
/**
|
||||
* Log the given info message to the channel.
|
||||
*
|
||||
* Messages are only printed when the user has enabled info logging for the extension.
|
||||
*
|
||||
* @param message info message to log
|
||||
*/
|
||||
info(message: string): void;
|
||||
/**
|
||||
* Log the given warning message to the channel.
|
||||
*
|
||||
* Messages are only printed when the user has enabled warn logging for the extension.
|
||||
*
|
||||
* @param message warning message to log
|
||||
*/
|
||||
warn(message: string): void;
|
||||
/**
|
||||
* Log the given error or error message to the channel.
|
||||
*
|
||||
* Messages are only printed when the user has enabled error logging for the extension.
|
||||
*
|
||||
* @param error Error or error message to log
|
||||
*/
|
||||
error(error: string | Error): void;
|
||||
}
|
||||
|
||||
export namespace window {
|
||||
/**
|
||||
* 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: { readonly log: true }): LogOutputChannel;
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in a new issue