diff --git a/src/vs/workbench/api/browser/mainThreadOutputService.ts b/src/vs/workbench/api/browser/mainThreadOutputService.ts index 48ee212e65ccf..2c82719f4e87b 100644 --- a/src/vs/workbench/api/browser/mainThreadOutputService.ts +++ b/src/vs/workbench/api/browser/mainThreadOutputService.ts @@ -42,12 +42,12 @@ export class MainThreadOutputService extends Disposable implements MainThreadOut setVisibleChannel(); } - public async $register(label: string, log: boolean, file: UriComponents, extensionId: string): Promise { + public async $register(label: string, log: boolean, file: UriComponents, languageId: string, extensionId: string): Promise { const idCounter = (MainThreadOutputService._extensionIdPool.get(extensionId) || 0) + 1; MainThreadOutputService._extensionIdPool.set(extensionId, idCounter); const id = `extension-output-${extensionId}-#${idCounter}`; - Registry.as(Extensions.OutputChannels).registerChannel({ id, label, file: URI.revive(file), log }); + Registry.as(Extensions.OutputChannels).registerChannel({ id, label, file: URI.revive(file), log, languageId }); 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 5aa55934e2360..ba2cc271a3e53 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -649,8 +649,11 @@ 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): vscode.OutputChannel { - return extHostOutputService.createOutputChannel(name, extension); + createOutputChannel(name: string, languageId?: string): vscode.OutputChannel { + if (languageId) { + checkProposedApiEnabled(extension, 'outputChannelLanguage'); + } + return extHostOutputService.createOutputChannel(name, languageId, 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 9de647699aa6c..352e2b8c95a18 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -410,7 +410,7 @@ export interface MainThreadMessageServiceShape extends IDisposable { } export interface MainThreadOutputServiceShape extends IDisposable { - $register(label: string, log: boolean, file: UriComponents, extensionId: string): Promise; + $register(label: string, log: boolean, file: UriComponents, 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 186031d7205f5..12e6570161e44 100644 --- a/src/vs/workbench/api/common/extHostOutput.ts +++ b/src/vs/workbench/api/common/extHostOutput.ts @@ -17,6 +17,7 @@ import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitData import { IExtHostFileSystemInfo } from 'vs/workbench/api/common/extHostFileSystemInfo'; 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 { @@ -117,12 +118,15 @@ export class ExtHostOutputService implements ExtHostOutputServiceShape { } } - createOutputChannel(name: string, extension: IExtensionDescription): vscode.OutputChannel { + createOutputChannel(name: string, languageId: string | undefined, extension: IExtensionDescription): vscode.OutputChannel { name = name.trim(); if (!name) { throw new Error('illegal argument `name`. must not be falsy'); } - const extHostOutputChannel = this.doCreateOutputChannel(name, extension); + if (isString(languageId) && !languageId.trim()) { + throw new Error('illegal argument `languageId`. must not be empty'); + } + const extHostOutputChannel = this.doCreateOutputChannel(name, languageId, extension); extHostOutputChannel.then(channel => { this.channels.set(channel.id, channel); channel.visible = channel.id === this.visibleChannelId; @@ -130,11 +134,11 @@ export class ExtHostOutputService implements ExtHostOutputServiceShape { return this.createExtHostOutputChannel(name, extHostOutputChannel); } - private async doCreateOutputChannel(name: string, extension: IExtensionDescription): Promise { + 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 logger = this.loggerService.createLogger(file, { always: true, donotRotate: true, donotUseFormatters: true }); - const id = await this.proxy.$register(name, false, file, extension.identifier.value); + const id = await this.proxy.$register(name, false, file, languageId, extension.identifier.value); return new ExtHostOutputChannel(id, name, logger, this.proxy); } diff --git a/src/vs/workbench/contrib/output/browser/outputServices.ts b/src/vs/workbench/contrib/output/browser/outputServices.ts index e666f230abd2d..2002fd6402555 100644 --- a/src/vs/workbench/contrib/output/browser/outputServices.ts +++ b/src/vs/workbench/contrib/output/browser/outputServices.ts @@ -20,6 +20,7 @@ import { IOutputChannelModel } from 'vs/workbench/contrib/output/common/outputCh import { IViewsService } from 'vs/workbench/common/views'; import { OutputViewPane } from 'vs/workbench/contrib/output/browser/outputView'; import { IOutputChannelModelService } from 'vs/workbench/contrib/output/common/outputChannelModelService'; +import { ILanguageService } from 'vs/editor/common/languages/language'; const OUTPUT_ACTIVE_CHANNEL_KEY = 'output.activechannel'; @@ -33,13 +34,14 @@ class OutputChannel extends Disposable implements IOutputChannel { constructor( readonly outputChannelDescriptor: IOutputChannelDescriptor, - @IOutputChannelModelService outputChannelModelService: IOutputChannelModelService + @IOutputChannelModelService outputChannelModelService: IOutputChannelModelService, + @ILanguageService languageService: ILanguageService, ) { super(); this.id = outputChannelDescriptor.id; this.label = outputChannelDescriptor.label; this.uri = URI.from({ scheme: OUTPUT_SCHEME, path: this.id }); - this.model = this._register(outputChannelModelService.createOutputChannelModel(this.id, this.uri, outputChannelDescriptor.log ? LOG_MIME : OUTPUT_MIME, outputChannelDescriptor.file)); + this.model = this._register(outputChannelModelService.createOutputChannelModel(this.id, this.uri, outputChannelDescriptor.languageId ? languageService.createById(outputChannelDescriptor.languageId) : languageService.createByMimeType(outputChannelDescriptor.log ? LOG_MIME : OUTPUT_MIME), outputChannelDescriptor.file)); } append(output: string): void { @@ -196,7 +198,8 @@ export class LogContentProvider { constructor( @IOutputService private readonly outputService: IOutputService, - @IOutputChannelModelService private readonly outputChannelModelService: IOutputChannelModelService + @IOutputChannelModelService private readonly outputChannelModelService: IOutputChannelModelService, + @ILanguageService private readonly languageService: ILanguageService ) { } @@ -217,7 +220,7 @@ export class LogContentProvider { const channelDisposables: IDisposable[] = []; const outputChannelDescriptor = this.outputService.getChannelDescriptors().filter(({ id }) => id === channelId)[0]; if (outputChannelDescriptor && outputChannelDescriptor.file) { - channelModel = this.outputChannelModelService.createOutputChannelModel(channelId, resource, outputChannelDescriptor.log ? LOG_MIME : OUTPUT_MIME, outputChannelDescriptor.file); + channelModel = this.outputChannelModelService.createOutputChannelModel(channelId, resource, outputChannelDescriptor.languageId ? this.languageService.createById(outputChannelDescriptor.languageId) : this.languageService.createByMimeType(outputChannelDescriptor.log ? LOG_MIME : OUTPUT_MIME), outputChannelDescriptor.file); channelModel.onDispose(() => dispose(channelDisposables), channelDisposables); this.channelModels.set(channelId, channelModel); } diff --git a/src/vs/workbench/contrib/output/common/outputChannelModel.ts b/src/vs/workbench/contrib/output/common/outputChannelModel.ts index 13220e676f18f..daedaaaff8331 100644 --- a/src/vs/workbench/contrib/output/common/outputChannelModel.ts +++ b/src/vs/workbench/contrib/output/common/outputChannelModel.ts @@ -12,7 +12,7 @@ import { URI } from 'vs/base/common/uri'; import { Promises, ThrottledDelayer } from 'vs/base/common/async'; import { IFileService } from 'vs/platform/files/common/files'; import { IModelService } from 'vs/editor/common/services/model'; -import { ILanguageService } from 'vs/editor/common/languages/language'; +import { ILanguageSelection } from 'vs/editor/common/languages/language'; import { Disposable, toDisposable, IDisposable, dispose, MutableDisposable } from 'vs/base/common/lifecycle'; import { isNumber } from 'vs/base/common/types'; import { EditOperation, ISingleEditOperation } from 'vs/editor/common/core/editOperation'; @@ -106,11 +106,10 @@ export class FileOutputChannelModel extends Disposable implements IOutputChannel constructor( private readonly modelUri: URI, - private readonly mimeType: 'text/x-code-log-output' | 'text/x-code-output', + private readonly language: ILanguageSelection, private readonly file: URI, @IFileService private readonly fileService: IFileService, @IModelService private readonly modelService: IModelService, - @ILanguageService private readonly languageService: ILanguageService, @ILogService logService: ILogService, @IEditorWorkerService private readonly editorWorkerService: IEditorWorkerService, ) { @@ -163,7 +162,7 @@ export class FileOutputChannelModel extends Disposable implements IOutputChannel if (this.model) { this.model.setValue(content); } else { - this.model = this.modelService.createModel(content, this.languageService.createByMimeType(this.mimeType), this.modelUri); + this.model = this.modelService.createModel(content, this.language, this.modelUri); this.fileHandler.watch(this.etag); const disposable = this.model.onWillDispose(() => { this.cancelModelUpdate(); @@ -324,16 +323,15 @@ class OutputChannelBackedByFile extends FileOutputChannelModel implements IOutpu constructor( id: string, modelUri: URI, - mimeType: 'text/x-code-log-output' | 'text/x-code-output', + language: ILanguageSelection, file: URI, @IFileService fileService: IFileService, @IModelService modelService: IModelService, - @ILanguageService languageService: ILanguageService, @ILoggerService loggerService: ILoggerService, @ILogService logService: ILogService, @IEditorWorkerService editorWorkerService: IEditorWorkerService ) { - super(modelUri, mimeType, file, fileService, modelService, languageService, logService, editorWorkerService); + super(modelUri, language, file, fileService, modelService, logService, editorWorkerService); // Donot rotate to check for the file reset this.logger = loggerService.createLogger(file, { always: true, donotRotate: true, donotUseFormatters: true }); @@ -371,20 +369,20 @@ export class DelegatedOutputChannelModel extends Disposable implements IOutputCh constructor( id: string, modelUri: URI, - mimeType: 'text/x-code-log-output' | 'text/x-code-output', + language: ILanguageSelection, outputDir: Promise, @IInstantiationService private readonly instantiationService: IInstantiationService, @IFileService private readonly fileService: IFileService, ) { super(); - this.outputChannelModel = this.createOutputChannelModel(id, modelUri, mimeType, outputDir); + this.outputChannelModel = this.createOutputChannelModel(id, modelUri, language, outputDir); } - private async createOutputChannelModel(id: string, modelUri: URI, mimeType: 'text/x-code-log-output' | 'text/x-code-output', outputDirPromise: Promise): Promise { + private async createOutputChannelModel(id: string, modelUri: URI, language: ILanguageSelection, outputDirPromise: Promise): Promise { const outputDir = await outputDirPromise; const file = resources.joinPath(outputDir, `${id.replace(/[\\/:\*\?"<>\|]/g, '')}.log`); await this.fileService.createFile(file); - const outputChannelModel = this._register(this.instantiationService.createInstance(OutputChannelBackedByFile, id, modelUri, mimeType, file)); + const outputChannelModel = this._register(this.instantiationService.createInstance(OutputChannelBackedByFile, id, modelUri, language, file)); this._register(outputChannelModel.onDispose(() => this._onDispose.fire())); return outputChannelModel; } diff --git a/src/vs/workbench/contrib/output/common/outputChannelModelService.ts b/src/vs/workbench/contrib/output/common/outputChannelModelService.ts index e003de09770ba..114b99fb652f1 100644 --- a/src/vs/workbench/contrib/output/common/outputChannelModelService.ts +++ b/src/vs/workbench/contrib/output/common/outputChannelModelService.ts @@ -11,13 +11,14 @@ import { toLocalISOString } from 'vs/base/common/date'; import { dirname, joinPath } from 'vs/base/common/resources'; import { DelegatedOutputChannelModel, FileOutputChannelModel, IOutputChannelModel } from 'vs/workbench/contrib/output/common/outputChannelModel'; import { URI } from 'vs/base/common/uri'; +import { ILanguageSelection } from 'vs/editor/common/languages/language'; export const IOutputChannelModelService = createDecorator('outputChannelModelService'); export interface IOutputChannelModelService { readonly _serviceBrand: undefined; - createOutputChannelModel(id: string, modelUri: URI, mimeType: 'text/x-code-log-output' | 'text/x-code-output', file?: URI): IOutputChannelModel; + createOutputChannelModel(id: string, modelUri: URI, language: ILanguageSelection, file?: URI): IOutputChannelModel; } @@ -31,8 +32,8 @@ export abstract class AbstractOutputChannelModelService { @IInstantiationService protected readonly instantiationService: IInstantiationService ) { } - createOutputChannelModel(id: string, modelUri: URI, mimeType: 'text/x-code-log-output' | 'text/x-code-output', file?: URI): IOutputChannelModel { - return file ? this.instantiationService.createInstance(FileOutputChannelModel, modelUri, mimeType, file) : this.instantiationService.createInstance(DelegatedOutputChannelModel, id, modelUri, mimeType, this.outputDir); + createOutputChannelModel(id: string, modelUri: URI, language: ILanguageSelection, file?: URI): IOutputChannelModel { + return file ? this.instantiationService.createInstance(FileOutputChannelModel, modelUri, language, file) : this.instantiationService.createInstance(DelegatedOutputChannelModel, id, modelUri, language, this.outputDir); } private _outputDir: Promise | null = null; diff --git a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts index 9c8edf4ddc6d4..caf03166e4918 100644 --- a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts +++ b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts @@ -41,6 +41,7 @@ export const allApiProposals = Object.freeze({ notebookLiveShare: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookLiveShare.d.ts', notebookMessaging: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookMessaging.d.ts', notebookMime: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookMime.d.ts', + outputChannelLanguage: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.outputChannelLanguage.d.ts', portsAttributes: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.portsAttributes.d.ts', quickPickSortByLabel: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.quickPickSortByLabel.d.ts', resolvers: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.resolvers.d.ts', diff --git a/src/vs/workbench/services/output/common/output.ts b/src/vs/workbench/services/output/common/output.ts index 1a0833dd8f1b9..f12fa430d7a12 100644 --- a/src/vs/workbench/services/output/common/output.ts +++ b/src/vs/workbench/services/output/common/output.ts @@ -15,6 +15,7 @@ export interface IOutputChannelDescriptor { id: string; label: string; log: boolean; + languageId?: string; file?: URI; } diff --git a/src/vscode-dts/vscode.proposed.outputChannelLanguage.d.ts b/src/vscode-dts/vscode.proposed.outputChannelLanguage.d.ts new file mode 100644 index 0000000000000..1e863abc4fa7a --- /dev/null +++ b/src/vscode-dts/vscode.proposed.outputChannelLanguage.d.ts @@ -0,0 +1,20 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// https://github.com/microsoft/vscode/issues/19561 + +declare module 'vscode' { + + export namespace window { + + /** + * 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 languageId The identifier of the language associated with the channel. + */ + export function createOutputChannel(name: string, languageId?: string): OutputChannel; + } +}