提交 a4b49166 编写于 作者: S Sandeep Somavarapu

Fix #69110

上级 4bfec9c8
......@@ -27,6 +27,7 @@
"./vs/workbench/services/files/node/watcher/**/*",
"./vs/workbench/services/themes/**/*.ts",
"./vs/workbench/services/bulkEdit/**/*.ts",
"./vs/workbench/services/output/**/*.ts",
"./vs/workbench/services/progress/**/*.ts",
"./vs/workbench/services/preferences/**/*.ts",
"./vs/workbench/services/timer/**/*.ts",
......@@ -313,9 +314,8 @@
"./vs/workbench/contrib/output/common/output.ts",
"./vs/workbench/contrib/output/common/outputLinkComputer.ts",
"./vs/workbench/contrib/output/common/outputLinkProvider.ts",
"./vs/workbench/contrib/output/electron-browser/output.contribution.ts",
"./vs/workbench/contrib/output/electron-browser/outputServices.ts",
"./vs/workbench/contrib/output/node/outputAppender.ts",
"./vs/workbench/contrib/output/browser/output.contribution.ts",
"./vs/workbench/contrib/output/browser/outputServices.ts",
"./vs/workbench/contrib/preferences/browser/preferencesActions.ts",
"./vs/workbench/contrib/preferences/browser/preferencesWidgets.ts",
"./vs/workbench/contrib/preferences/browser/settingsLayout.ts",
......
......@@ -7,7 +7,7 @@ import { MainContext, MainThreadOutputServiceShape, IMainContext, ExtHostOutputS
import * as vscode from 'vscode';
import { URI } from 'vs/base/common/uri';
import { join } from 'vs/base/common/path';
import { OutputAppender } from 'vs/workbench/contrib/output/node/outputAppender';
import { OutputAppender } from 'vs/workbench/services/output/node/outputAppender';
import { toLocalISOString } from 'vs/base/common/date';
import { Event, Emitter } from 'vs/base/common/event';
import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle';
......
......@@ -10,7 +10,7 @@ import { Registry } from 'vs/platform/registry/common/platform';
import { MenuId, MenuRegistry, SyncActionDescriptor, registerAction } from 'vs/platform/actions/common/actions';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions';
import { OutputService, LogContentProvider } from 'vs/workbench/contrib/output/electron-browser/outputServices';
import { OutputService, LogContentProvider } from 'vs/workbench/contrib/output/browser/outputServices';
import { ToggleOutputAction, ClearOutputAction, OpenLogOutputFile, ShowLogsOutputChannelAction, OpenOutputLogFileAction } from 'vs/workbench/contrib/output/browser/outputActions';
import { OUTPUT_MODE_ID, OUTPUT_MIME, OUTPUT_PANEL_ID, IOutputService, CONTEXT_IN_OUTPUT, LOG_SCHEME, LOG_MODE_ID, LOG_MIME, CONTEXT_ACTIVE_LOG_OUTPUT } from 'vs/workbench/contrib/output/common/output';
import { PanelRegistry, Extensions, PanelDescriptor } from 'vs/workbench/browser/panel';
......
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as nls from 'vs/nls';
import { Event, Emitter } from 'vs/base/common/event';
import { URI } from 'vs/base/common/uri';
import { IDisposable, dispose, Disposable } from 'vs/base/common/lifecycle';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { Registry } from 'vs/platform/registry/common/platform';
import { EditorOptions } from 'vs/workbench/common/editor';
import { IOutputChannelDescriptor, IOutputChannel, IOutputService, Extensions, OUTPUT_PANEL_ID, IOutputChannelRegistry, OUTPUT_SCHEME, LOG_SCHEME, CONTEXT_ACTIVE_LOG_OUTPUT, LOG_MIME, OUTPUT_MIME } from 'vs/workbench/contrib/output/common/output';
import { OutputPanel } from 'vs/workbench/contrib/output/browser/outputPanel';
import { IPanelService } from 'vs/workbench/services/panel/common/panelService';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { OutputLinkProvider } from 'vs/workbench/contrib/output/common/outputLinkProvider';
import { ITextModelService, ITextModelContentProvider } from 'vs/editor/common/services/resolverService';
import { ITextModel } from 'vs/editor/common/model';
import { IPanel } from 'vs/workbench/common/panel';
import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IWindowService } from 'vs/platform/windows/common/windows';
import { ILogService } from 'vs/platform/log/common/log';
import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { CancellationToken } from 'vs/base/common/cancellation';
import { IOutputChannelModel, IOutputChannelModelService } from 'vs/workbench/services/output/common/outputChannelModel';
const OUTPUT_ACTIVE_CHANNEL_KEY = 'output.activechannel';
class OutputChannel extends Disposable implements IOutputChannel {
scrollLock: boolean = false;
readonly model: IOutputChannelModel;
readonly id: string;
readonly label: string;
constructor(
readonly outputChannelDescriptor: IOutputChannelDescriptor,
@IOutputChannelModelService outputChannelModelService: IOutputChannelModelService
) {
super();
this.id = outputChannelDescriptor.id;
this.label = outputChannelDescriptor.label;
this.model = this._register(outputChannelModelService.createOutputChannelModel(this.id, URI.from({ scheme: OUTPUT_SCHEME, path: this.id }), outputChannelDescriptor.log ? LOG_MIME : OUTPUT_MIME, outputChannelDescriptor.file));
}
append(output: string): void {
this.model.append(output);
}
update(): void {
this.model.update();
}
clear(till?: number): void {
this.model.clear(till);
}
}
export class OutputService extends Disposable implements IOutputService, ITextModelContentProvider {
public _serviceBrand: any;
private channels: Map<string, OutputChannel> = new Map<string, OutputChannel>();
private activeChannelIdInStorage: string;
private activeChannel: OutputChannel | null;
private readonly _onActiveOutputChannel = new Emitter<string>();
readonly onActiveOutputChannel: Event<string> = this._onActiveOutputChannel.event;
private _outputPanel: OutputPanel;
constructor(
@IStorageService private readonly storageService: IStorageService,
@IInstantiationService private readonly instantiationService: IInstantiationService,
@IPanelService private readonly panelService: IPanelService,
@IWorkspaceContextService contextService: IWorkspaceContextService,
@ITextModelService textModelResolverService: ITextModelService,
@IEnvironmentService environmentService: IEnvironmentService,
@IWindowService windowService: IWindowService,
@ILogService private readonly logService: ILogService,
@ILifecycleService private readonly lifecycleService: ILifecycleService,
@IContextKeyService private readonly contextKeyService: IContextKeyService,
) {
super();
this.activeChannelIdInStorage = this.storageService.get(OUTPUT_ACTIVE_CHANNEL_KEY, StorageScope.WORKSPACE, '');
// Register as text model content provider for output
textModelResolverService.registerTextModelContentProvider(OUTPUT_SCHEME, this);
instantiationService.createInstance(OutputLinkProvider);
// Create output channels for already registered channels
const registry = Registry.as<IOutputChannelRegistry>(Extensions.OutputChannels);
for (const channelIdentifier of registry.getChannels()) {
this.onDidRegisterChannel(channelIdentifier.id);
}
this._register(registry.onDidRegisterChannel(this.onDidRegisterChannel, this));
this._register(panelService.onDidPanelOpen(({ panel, focus }) => this.onDidPanelOpen(panel, !focus), this));
this._register(panelService.onDidPanelClose(this.onDidPanelClose, this));
// Set active channel to first channel if not set
if (!this.activeChannel) {
const channels = this.getChannelDescriptors();
this.activeChannel = channels && channels.length > 0 ? this.getChannel(channels[0].id) : null;
}
this._register(this.lifecycleService.onShutdown(() => this.dispose()));
this._register(this.storageService.onWillSaveState(() => this.saveState()));
}
provideTextContent(resource: URI): Promise<ITextModel> | null {
const channel = <OutputChannel>this.getChannel(resource.path);
if (channel) {
return channel.model.loadModel();
}
return null;
}
showChannel(id: string, preserveFocus?: boolean): Promise<void> {
const channel = this.getChannel(id);
if (!channel || this.isChannelShown(channel)) {
if (this._outputPanel && !preserveFocus) {
this._outputPanel.focus();
}
return Promise.resolve(undefined);
}
this.activeChannel = channel;
let promise: Promise<void>;
if (this.isPanelShown()) {
promise = this.doShowChannel(channel, !!preserveFocus);
} else {
this.panelService.openPanel(OUTPUT_PANEL_ID);
promise = this.doShowChannel(this.activeChannel, !!preserveFocus);
}
return promise.then(() => this._onActiveOutputChannel.fire(id));
}
getChannel(id: string): OutputChannel | null {
return this.channels.get(id) || null;
}
getChannelDescriptors(): IOutputChannelDescriptor[] {
return Registry.as<IOutputChannelRegistry>(Extensions.OutputChannels).getChannels();
}
getActiveChannel(): IOutputChannel | null {
return this.activeChannel;
}
private onDidRegisterChannel(channelId: string): void {
const channel = this.createChannel(channelId);
this.channels.set(channelId, channel);
if (this.activeChannelIdInStorage === channelId) {
this.activeChannel = channel;
this.onDidPanelOpen(this.panelService.getActivePanel(), true)
.then(() => this._onActiveOutputChannel.fire(channelId));
}
}
private onDidPanelOpen(panel: IPanel | null, preserveFocus: boolean): Promise<void> {
if (panel && panel.getId() === OUTPUT_PANEL_ID) {
this._outputPanel = <OutputPanel>this.panelService.getActivePanel();
if (this.activeChannel) {
return this.doShowChannel(this.activeChannel, preserveFocus);
}
}
return Promise.resolve(undefined);
}
private onDidPanelClose(panel: IPanel): void {
if (this._outputPanel && panel.getId() === OUTPUT_PANEL_ID) {
CONTEXT_ACTIVE_LOG_OUTPUT.bindTo(this.contextKeyService).set(false);
this._outputPanel.clearInput();
}
}
private createChannel(id: string): OutputChannel {
const channelDisposables: IDisposable[] = [];
const channel = this.instantiateChannel(id);
channel.model.onDidAppendedContent(() => {
if (!channel.scrollLock) {
const panel = this.panelService.getActivePanel();
if (panel && panel.getId() === OUTPUT_PANEL_ID && this.isChannelShown(channel)) {
let outputPanel = <OutputPanel>panel;
outputPanel.revealLastLine();
}
}
}, channelDisposables);
channel.model.onDispose(() => {
if (this.activeChannel === channel) {
const channels = this.getChannelDescriptors();
const channel = channels.length ? this.getChannel(channels[0].id) : null;
if (channel && this.isPanelShown()) {
this.showChannel(channel.id, true);
} else {
this.activeChannel = channel;
if (this.activeChannel) {
this._onActiveOutputChannel.fire(this.activeChannel.id);
}
}
}
Registry.as<IOutputChannelRegistry>(Extensions.OutputChannels).removeChannel(id);
dispose(channelDisposables);
}, channelDisposables);
return channel;
}
private instantiateChannel(id: string): OutputChannel {
const channelData = Registry.as<IOutputChannelRegistry>(Extensions.OutputChannels).getChannel(id);
if (!channelData) {
this.logService.error(`Channel '${id}' is not registered yet`);
throw new Error(`Channel '${id}' is not registered yet`);
}
return this.instantiationService.createInstance(OutputChannel, channelData);
}
private doShowChannel(channel: OutputChannel, preserveFocus: boolean): Promise<void> {
if (this._outputPanel) {
CONTEXT_ACTIVE_LOG_OUTPUT.bindTo(this.contextKeyService).set(!!channel.outputChannelDescriptor.file && channel.outputChannelDescriptor.log);
return this._outputPanel.setInput(this.createInput(channel), EditorOptions.create({ preserveFocus }), CancellationToken.None)
.then(() => {
if (!preserveFocus) {
this._outputPanel.focus();
}
});
}
return Promise.resolve(undefined);
}
private isChannelShown(channel: IOutputChannel): boolean {
return this.isPanelShown() && this.activeChannel === channel;
}
private isPanelShown(): boolean {
const panel = this.panelService.getActivePanel();
return !!panel && panel.getId() === OUTPUT_PANEL_ID;
}
private createInput(channel: IOutputChannel): ResourceEditorInput {
const resource = URI.from({ scheme: OUTPUT_SCHEME, path: channel.id });
return this.instantiationService.createInstance(ResourceEditorInput, nls.localize('output', "{0} - Output", channel.label), nls.localize('channel', "Output channel for '{0}'", channel.label), resource);
}
private saveState(): void {
if (this.activeChannel) {
this.storageService.store(OUTPUT_ACTIVE_CHANNEL_KEY, this.activeChannel.id, StorageScope.WORKSPACE);
}
}
}
export class LogContentProvider {
private channelModels: Map<string, IOutputChannelModel> = new Map<string, IOutputChannelModel>();
constructor(
@IOutputService private readonly outputService: IOutputService,
@IOutputChannelModelService private readonly outputChannelModelService: IOutputChannelModelService
) {
}
provideTextContent(resource: URI): Promise<ITextModel> | null {
if (resource.scheme === LOG_SCHEME) {
let channelModel = this.getChannelModel(resource);
if (channelModel) {
return channelModel.loadModel();
}
}
return null;
}
private getChannelModel(resource: URI): IOutputChannelModel | undefined {
const channelId = resource.path;
let channelModel = this.channelModels.get(channelId);
if (!channelModel) {
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.onDispose(() => dispose(channelDisposables), channelDisposables);
this.channelModels.set(channelId, channelModel);
}
}
return channelModel;
}
}
\ No newline at end of file
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IOutputChannelModelService, AsbtractOutputChannelModelService } from 'vs/workbench/services/output/common/outputChannelModel';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
export class OutputChannelModelService extends AsbtractOutputChannelModelService implements IOutputChannelModelService {
_serviceBrand: any;
}
registerSingleton(IOutputChannelModelService, OutputChannelModelService);
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import * as extfs from 'vs/base/node/extfs';
import { dirname, join } from 'vs/base/common/path';
import { ITextModel } from 'vs/editor/common/model';
import { URI } from 'vs/base/common/uri';
import { ThrottledDelayer } from 'vs/base/common/async';
import { IFileService } from 'vs/platform/files/common/files';
import { IModelService } from 'vs/editor/common/services/modelService';
import { IModeService } from 'vs/editor/common/services/modeService';
import { toDisposable, IDisposable } from 'vs/base/common/lifecycle';
import { ILogService } from 'vs/platform/log/common/log';
import { IOutputChannelModel, AbstractFileOutputChannelModel, IOutputChannelModelService, AsbtractOutputChannelModelService } from 'vs/workbench/services/output/common/outputChannelModel';
import { OutputAppender } from 'vs/workbench/services/output/node/outputAppender';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IWindowService } from 'vs/platform/windows/common/windows';
import { toLocalISOString } from 'vs/base/common/date';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
let watchingOutputDir = false;
let callbacks: ((eventType: string, fileName?: string) => void)[] = [];
function watchOutputDirectory(outputDir: string, logService: ILogService, onChange: (eventType: string, fileName: string) => void): IDisposable {
callbacks.push(onChange);
if (!watchingOutputDir) {
const watcherDisposable = extfs.watch(outputDir, (eventType, fileName) => {
for (const callback of callbacks) {
callback(eventType, fileName);
}
}, (error: string) => {
logService.error(error);
});
watchingOutputDir = true;
return toDisposable(() => {
callbacks = [];
watcherDisposable.dispose();
});
}
return toDisposable(() => { });
}
class OutputChannelBackedByFile extends AbstractFileOutputChannelModel implements IOutputChannelModel {
private appender: OutputAppender;
private appendedMessage: string;
private loadingFromFileInProgress: boolean;
private resettingDelayer: ThrottledDelayer<void>;
private readonly rotatingFilePath: string;
constructor(
id: string,
modelUri: URI,
mimeType: string,
@IWindowService windowService: IWindowService,
@IEnvironmentService environmentService: IEnvironmentService,
@IFileService fileService: IFileService,
@IModelService modelService: IModelService,
@IModeService modeService: IModeService,
@ILogService logService: ILogService
) {
const outputDir = join(environmentService.logsPath, `output_${windowService.getCurrentWindowId()}_${toLocalISOString(new Date()).replace(/-|:|\.\d+Z$/g, '')}`);
super(modelUri, mimeType, URI.file(join(outputDir, `${id}.log`)), fileService, modelService, modeService);
this.appendedMessage = '';
this.loadingFromFileInProgress = false;
// Use one rotating file to check for main file reset
this.appender = new OutputAppender(id, this.file.fsPath);
this.rotatingFilePath = `${id}.1.log`;
this._register(watchOutputDirectory(dirname(this.file.fsPath), logService, (eventType, file) => this.onFileChangedInOutputDirector(eventType, file)));
this.resettingDelayer = new ThrottledDelayer<void>(50);
}
append(message: string): void {
// update end offset always as message is read
this.endOffset = this.endOffset + Buffer.from(message).byteLength;
if (this.loadingFromFileInProgress) {
this.appendedMessage += message;
} else {
this.write(message);
if (this.model) {
this.appendedMessage += message;
if (!this.modelUpdater.isScheduled()) {
this.modelUpdater.schedule();
}
}
}
}
clear(till?: number): void {
super.clear(till);
this.appendedMessage = '';
}
loadModel(): Promise<ITextModel> {
this.loadingFromFileInProgress = true;
if (this.modelUpdater.isScheduled()) {
this.modelUpdater.cancel();
}
this.appendedMessage = '';
return this.loadFile()
.then(content => {
if (this.endOffset !== this.startOffset + Buffer.from(content).byteLength) {
// Queue content is not written into the file
// Flush it and load file again
this.flush();
return this.loadFile();
}
return content;
})
.then(content => {
if (this.appendedMessage) {
this.write(this.appendedMessage);
this.appendedMessage = '';
}
this.loadingFromFileInProgress = false;
return this.createModel(content);
});
}
private resetModel(): Promise<void> {
this.startOffset = 0;
this.endOffset = 0;
if (this.model) {
return this.loadModel().then(() => undefined);
}
return Promise.resolve(undefined);
}
private loadFile(): Promise<string> {
return this.fileService.resolveContent(this.file, { position: this.startOffset, encoding: 'utf8' })
.then(content => this.appendedMessage ? content.value + this.appendedMessage : content.value);
}
protected updateModel(): void {
if (this.model && this.appendedMessage) {
this.appendToModel(this.appendedMessage);
this.appendedMessage = '';
}
}
private onFileChangedInOutputDirector(eventType: string, fileName?: string): void {
// Check if rotating file has changed. It changes only when the main file exceeds its limit.
if (this.rotatingFilePath === fileName) {
this.resettingDelayer.trigger(() => this.resetModel());
}
}
private write(content: string): void {
this.appender.append(content);
}
private flush(): void {
this.appender.flush();
}
}
export class OutputChannelModelService extends AsbtractOutputChannelModelService implements IOutputChannelModelService {
_serviceBrand: any;
constructor(
@IInstantiationService instantiationService: IInstantiationService,
@ILogService private readonly logService: ILogService,
@ITelemetryService private readonly telemetryService: ITelemetryService
) {
super(instantiationService);
}
createOutputChannelModel(id: string, modelUri: URI, mimeType: string, file?: URI): IOutputChannelModel {
if (!file) {
try {
return this.instantiationService.createInstance(OutputChannelBackedByFile, id, modelUri, mimeType);
} catch (e) {
// Do not crash if spdlog rotating logger cannot be loaded (workaround for https://github.com/Microsoft/vscode/issues/47883)
this.logService.error(e);
/* __GDPR__
"output.channel.creation.error" : {}
*/
this.telemetryService.publicLog('output.channel.creation.error');
}
}
return super.createOutputChannelModel(id, modelUri, mimeType, file);
}
}
registerSingleton(IOutputChannelModelService, OutputChannelModelService);
......@@ -69,6 +69,7 @@ import 'vs/workbench/services/progress/browser/progressService2';
import 'vs/workbench/services/editor/browser/codeEditorService';
import 'vs/workbench/services/broadcast/electron-browser/broadcastService';
import 'vs/workbench/services/preferences/browser/preferencesService';
import 'vs/workbench/services/output/node/outputChannelModelService';
import 'vs/workbench/services/configuration/node/jsonEditingService';
import 'vs/workbench/services/textmodelResolver/common/textModelResolverService';
import 'vs/workbench/services/textfile/common/textFileService';
......@@ -155,7 +156,7 @@ import 'vs/workbench/contrib/extensions/browser/extensionsQuickOpen';
import 'vs/workbench/contrib/extensions/electron-browser/extensionsViewlet';
// Output Panel
import 'vs/workbench/contrib/output/electron-browser/output.contribution';
import 'vs/workbench/contrib/output/browser/output.contribution';
import 'vs/workbench/contrib/output/browser/outputPanel';
// Terminal
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册