diff --git a/extensions/github-authentication/package.json b/extensions/github-authentication/package.json index d9ccdc42d4c..35a95e4354d 100644 --- a/extensions/github-authentication/package.json +++ b/extensions/github-authentication/package.json @@ -48,6 +48,9 @@ } } }, + "enabledApiProposals": [ + "extensionLog" + ], "aiKey": "0c6ae279ed8443289764825290e4f9e2-1a736e7c-1324-4338-be46-fc2a58ae4d14-7255", "main": "./out/extension.js", "browser": "./dist/browser/extension.js", diff --git a/extensions/github-authentication/src/common/logger.ts b/extensions/github-authentication/src/common/logger.ts index 235d7c8b973..84225bd707f 100644 --- a/extensions/github-authentication/src/common/logger.ts +++ b/extensions/github-authentication/src/common/logger.ts @@ -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; } diff --git a/extensions/github-authentication/tsconfig.json b/extensions/github-authentication/tsconfig.json index 5e4713e9f3b..1188d9a05f1 100644 --- a/extensions/github-authentication/tsconfig.json +++ b/extensions/github-authentication/tsconfig.json @@ -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" ] } diff --git a/extensions/microsoft-authentication/package.json b/extensions/microsoft-authentication/package.json index b40c7bb17b5..07f803a5930 100644 --- a/extensions/microsoft-authentication/package.json +++ b/extensions/microsoft-authentication/package.json @@ -16,7 +16,8 @@ "onAuthenticationRequest:microsoft" ], "enabledApiProposals": [ - "idToken" + "idToken", + "extensionLog" ], "capabilities": { "virtualWorkspaces": true, diff --git a/extensions/microsoft-authentication/src/logger.ts b/extensions/microsoft-authentication/src/logger.ts index db142875255..92b5876e1b4 100644 --- a/extensions/microsoft-authentication/src/logger.ts +++ b/extensions/microsoft-authentication/src/logger.ts @@ -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; diff --git a/extensions/microsoft-authentication/tsconfig.json b/extensions/microsoft-authentication/tsconfig.json index 4b9d06d1847..7f930878523 100644 --- a/extensions/microsoft-authentication/tsconfig.json +++ b/extensions/microsoft-authentication/tsconfig.json @@ -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" ] } diff --git a/src/vs/workbench/api/browser/mainThreadOutputService.ts b/src/vs/workbench/api/browser/mainThreadOutputService.ts index 39e110e3f22..2767324ed23 100644 --- a/src/vs/workbench/api/browser/mainThreadOutputService.ts +++ b/src/vs/workbench/api/browser/mainThreadOutputService.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 { + public async $register(label: string, file: UriComponents, log: boolean, languageId: string | undefined, extensionId: string): Promise { const idCounter = (MainThreadOutputService._extensionIdPool.get(extensionId) || 0) + 1; MainThreadOutputService._extensionIdPool.set(extensionId, idCounter); const id = `extension-output-${extensionId}-#${idCounter}-${label}`; - Registry.as(Extensions.OutputChannels).registerChannel({ id, label, file: URI.revive(file), log, languageId }); + Registry.as(Extensions.OutputChannels).registerChannel({ id, label, file: URI.revive(file), log, languageId, extensionId }); this._register(toDisposable(() => this.$dispose(id))); return id; } diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 00fabf7976e..d91f2831cd6 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -703,8 +703,8 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I withProgress(options: vscode.ProgressOptions, task: (progress: vscode.Progress<{ message?: string; worked?: number }>, token: vscode.CancellationToken) => Thenable) { 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); diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index f4532aa46a7..4d64d0b672f 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -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; + $register(label: string, file: UriComponents, log: boolean, languageId: string | undefined, extensionId: string): Promise; $update(channelId: string, mode: OutputChannelUpdateMode, till?: number): Promise; $reveal(channelId: string, preserveFocus: boolean): Promise; $close(channelId: string): Promise; diff --git a/src/vs/workbench/api/common/extHostOutput.ts b/src/vs/workbench/api/common/extHostOutput.ts index 5c013fa2534..1b6f01999be 100644 --- a/src/vs/workbench/api/common/extHostOutput.ts +++ b/src/vs/workbench/api/common/extHostOutput.ts @@ -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 | undefined; private namePool: number = 1; - private readonly channels: Map = new Map(); + private readonly channels = new Map(); 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, >extHostOutputChannel) : this.createExtHostOutputChannel(name, >extHostOutputChannel); } private async doCreateOutputChannel(name: string, languageId: string | undefined, extension: IExtensionDescription): Promise { - 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 { + private async doCreateLogOutputChannel(name: string, extension: IExtensionDescription): Promise { + 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 { 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): vscode.OutputChannel { @@ -188,6 +223,54 @@ export class ExtHostOutputService implements ExtHostOutputServiceShape { } }; } + + private createExtHostLogOutputChannel(name: string, channelPromise: Promise): 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 { } diff --git a/src/vs/workbench/contrib/output/browser/output.contribution.ts b/src/vs/workbench/contrib/output/browser/output.contribution.ts index 75ba5d4622b..d7c89247cde 100644 --- a/src/vs/workbench/contrib/output/browser/output.contribution.ts +++ b/src/vs/workbench/contrib/output/browser/output.contribution.ts @@ -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 { 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); diff --git a/src/vs/workbench/contrib/output/browser/outputView.ts b/src/vs/workbench/contrib/output/browser/outputView.ts index c6909ba0556..dd969c688c3 100644 --- a/src/vs/workbench/contrib/output/browser/outputView.ts +++ b/src/vs/workbench/contrib/output/browser/outputView.ts @@ -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) => { text: label, isDisabled: (index === separatorIndex ? true : false) }), Math.max(0, selected)); + this.setOptions(this.selectionOptionItems, Math.max(0, selected)); } } diff --git a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts index 7663f4786a2..42255af3d4e 100644 --- a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts +++ b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts @@ -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', diff --git a/src/vs/workbench/services/output/common/output.ts b/src/vs/workbench/services/output/common/output.ts index 1d96dcd198a..93698acaa1d 100644 --- a/src/vs/workbench/services/output/common/output.ts +++ b/src/vs/workbench/services/output/common/output.ts @@ -160,6 +160,7 @@ export interface IOutputChannelDescriptor { log: boolean; languageId?: string; file?: URI; + extensionId?: string; } export interface IFileOutputChannelDescriptor extends IOutputChannelDescriptor { diff --git a/src/vscode-dts/vscode.proposed.extensionLog.d.ts b/src/vscode-dts/vscode.proposed.extensionLog.d.ts new file mode 100644 index 00000000000..9122f0963a3 --- /dev/null +++ b/src/vscode-dts/vscode.proposed.extensionLog.d.ts @@ -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; + } + +}