提交 77697cc3 编写于 作者: S Sandeep Somavarapu

Log output

- Implement Buffered output channel and file output channel in output services
- Allow to register a file output channel
- Register log files output channels
上级 b5e49206
......@@ -70,6 +70,10 @@
"name": "vs/workbench/parts/markers",
"project": "vscode-workbench"
},
{
"name": "vs/workbench/parts/logs",
"project": "vscode-workbench"
},
{
"name": "vs/workbench/parts/nps",
"project": "vscode-workbench"
......@@ -199,4 +203,4 @@
"project": "vscode-workbench"
}
]
}
}
\ No newline at end of file
......@@ -45,7 +45,8 @@ export class MainThreadOutputService implements MainThreadOutputServiceShape {
}
public $reveal(channelId: string, label: string, preserveFocus: boolean): TPromise<void> {
this._getChannel(channelId, label).show(preserveFocus);
const channel = this._getChannel(channelId, label);
this._outputService.showChannel(channel.id, preserveFocus);
return undefined;
}
......
......@@ -25,7 +25,7 @@ import { IExtensionManagementService, LocalExtensionType, ILocalExtension, IExte
import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration';
import paths = require('vs/base/common/paths');
import { isMacintosh, isLinux, language } from 'vs/base/common/platform';
import { IQuickOpenService, IFilePickOpenEntry, ISeparator, IPickOpenAction, IPickOpenItem, IPickOpenEntry } from 'vs/platform/quickOpen/common/quickOpen';
import { IQuickOpenService, IFilePickOpenEntry, ISeparator, IPickOpenAction, IPickOpenItem } from 'vs/platform/quickOpen/common/quickOpen';
import * as browser from 'vs/base/browser/browser';
import { IIntegrityService } from 'vs/platform/integrity/common/integrity';
import { IEntryRunContext } from 'vs/base/parts/quickopen/common/quickOpen';
......@@ -46,7 +46,6 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
import { IExtensionService, ActivationTimes } from 'vs/platform/extensions/common/extensions';
import { getEntries } from 'vs/base/common/performance';
import { IEditor } from 'vs/platform/editor/common/editor';
import { ILogService, LogLevel } from 'vs/platform/log/common/log';
// --- actions
......@@ -1683,82 +1682,4 @@ export class ConfigureLocaleAction extends Action {
throw new Error(nls.localize('fail.createSettings', "Unable to create '{0}' ({1}).", getPathLabel(file, this.contextService), error));
});
}
}
export class OpenLogsFolderAction extends Action {
static ID = 'workbench.action.openLogsFolder';
static LABEL = nls.localize('openLogsFolder', "Open Logs Folder");
constructor(id: string, label: string,
@IEnvironmentService private environmentService: IEnvironmentService,
@IWindowsService private windowsService: IWindowsService,
) {
super(id, label);
}
run(): TPromise<void> {
return this.windowsService.showItemInFolder(paths.join(this.environmentService.logsPath, 'main.log'));
}
}
export class ShowLogsAction extends Action {
static ID = 'workbench.action.showLogs';
static LABEL = nls.localize('showLogs', "Show Logs...");
constructor(id: string, label: string,
@IEnvironmentService private environmentService: IEnvironmentService,
@IWindowService private windowService: IWindowService,
@IWorkbenchEditorService private editorService: IWorkbenchEditorService,
@IQuickOpenService private quickOpenService: IQuickOpenService
) {
super(id, label);
}
run(): TPromise<void> {
const entries: IPickOpenEntry[] = [
{ id: 'main', label: nls.localize('mainProcess', "Main"), run: () => this.editorService.openEditor({ resource: URI.file(paths.join(this.environmentService.logsPath, 'main.log')) }) },
{ id: 'shared', label: nls.localize('sharedProcess', "Shared"), run: () => this.editorService.openEditor({ resource: URI.file(paths.join(this.environmentService.logsPath, 'sharedprocess.log')) }) },
{ id: 'renderer', label: nls.localize('rendererProcess', "Renderer"), run: () => this.editorService.openEditor({ resource: URI.file(paths.join(this.environmentService.logsPath, `renderer${this.windowService.getCurrentWindowId()}.log`)) }) },
{ id: 'extenshionHost', label: nls.localize('extensionHost', "Extension Host"), run: () => this.editorService.openEditor({ resource: URI.file(paths.join(this.environmentService.logsPath, `exthost${this.windowService.getCurrentWindowId()}.log`)) }) }
];
return this.quickOpenService.pick(entries, { placeHolder: nls.localize('selectProcess', "Select process") }).then(entry => {
if (entry) {
entry.run(null);
}
});
}
}
export class SetLogLevelAction extends Action {
static ID = 'workbench.action.setLogLevel';
static LABEL = nls.localize('setLogLevel', "Set Log Level");
constructor(id: string, label: string,
@IQuickOpenService private quickOpenService: IQuickOpenService,
@ILogService private logService: ILogService
) {
super(id, label);
}
run(): TPromise<void> {
const entries = [
{ label: nls.localize('trace', "Trace"), level: LogLevel.Trace },
{ label: nls.localize('debug', "Debug"), level: LogLevel.Debug },
{ label: nls.localize('info', "Info"), level: LogLevel.Info },
{ label: nls.localize('warn', "Warning"), level: LogLevel.Warning },
{ label: nls.localize('err', "Error"), level: LogLevel.Error },
{ label: nls.localize('critical', "Critical"), level: LogLevel.Critical },
{ label: nls.localize('off', "Off"), level: LogLevel.Off }
];
return this.quickOpenService.pick(entries, { placeHolder: nls.localize('selectLogLevel', "Select log level"), autoFocus: { autoFocusIndex: this.logService.getLevel() } }).then(entry => {
if (entry) {
this.logService.setLevel(entry.level);
}
});
}
}
\ No newline at end of file
......@@ -14,7 +14,7 @@ import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'v
import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actions';
import { KeyMod, KeyChord, KeyCode } from 'vs/base/common/keyCodes';
import { isWindows, isLinux, isMacintosh } from 'vs/base/common/platform';
import { CloseEditorAction, KeybindingsReferenceAction, OpenDocumentationUrlAction, OpenIntroductoryVideosUrlAction, OpenTipsAndTricksUrlAction, ReportIssueAction, ReportPerformanceIssueAction, ZoomResetAction, ZoomOutAction, ZoomInAction, ToggleFullScreenAction, ToggleMenuBarAction, CloseWorkspaceAction, CloseCurrentWindowAction, SwitchWindow, NewWindowAction, CloseMessagesAction, NavigateUpAction, NavigateDownAction, NavigateLeftAction, NavigateRightAction, IncreaseViewSizeAction, DecreaseViewSizeAction, ShowStartupPerformance, ToggleSharedProcessAction, QuickSwitchWindow, QuickOpenRecentAction, inRecentFilesPickerContextKey, ConfigureLocaleAction, ShowLogsAction, OpenLogsFolderAction, SetLogLevelAction } from 'vs/workbench/electron-browser/actions';
import { CloseEditorAction, KeybindingsReferenceAction, OpenDocumentationUrlAction, OpenIntroductoryVideosUrlAction, OpenTipsAndTricksUrlAction, ReportIssueAction, ReportPerformanceIssueAction, ZoomResetAction, ZoomOutAction, ZoomInAction, ToggleFullScreenAction, ToggleMenuBarAction, CloseWorkspaceAction, CloseCurrentWindowAction, SwitchWindow, NewWindowAction, CloseMessagesAction, NavigateUpAction, NavigateDownAction, NavigateLeftAction, NavigateRightAction, IncreaseViewSizeAction, DecreaseViewSizeAction, ShowStartupPerformance, ToggleSharedProcessAction, QuickSwitchWindow, QuickOpenRecentAction, inRecentFilesPickerContextKey, ConfigureLocaleAction } from 'vs/workbench/electron-browser/actions';
import { MessagesVisibleContext } from 'vs/workbench/electron-browser/workbench';
import { IJSONSchema } from 'vs/base/common/jsonSchema';
import { registerCommands } from 'vs/workbench/electron-browser/commands';
......@@ -31,15 +31,12 @@ registerCommands();
const viewCategory = nls.localize('view', "View");
const helpCategory = nls.localize('help', "Help");
const fileCategory = nls.localize('file', "File");
const devCategory = nls.localize('developer', "Developer");
const workbenchActionsRegistry = Registry.as<IWorkbenchActionRegistry>(Extensions.WorkbenchActions);
workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(NewWindowAction, NewWindowAction.ID, NewWindowAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_N }), 'New Window');
workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(CloseCurrentWindowAction, CloseCurrentWindowAction.ID, CloseCurrentWindowAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_W }), 'Close Window');
workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(SwitchWindow, SwitchWindow.ID, SwitchWindow.LABEL, { primary: null, mac: { primary: KeyMod.WinCtrl | KeyCode.KEY_W } }), 'Switch Window...');
workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(QuickSwitchWindow, QuickSwitchWindow.ID, QuickSwitchWindow.LABEL), 'Quick Switch Window...');
workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(ShowLogsAction, ShowLogsAction.ID, ShowLogsAction.LABEL), 'Developer: Show Logs...', devCategory);
workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(OpenLogsFolderAction, OpenLogsFolderAction.ID, OpenLogsFolderAction.LABEL), 'Developer: Open Log Folder', devCategory);
workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(SetLogLevelAction, SetLogLevelAction.ID, SetLogLevelAction.LABEL), 'Developer: Set Log Level', devCategory);
workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(QuickOpenRecentAction, QuickOpenRecentAction.ID, QuickOpenRecentAction.LABEL), 'File: Quick Open Recent...', fileCategory);
......
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export const mainLogChannelId = 'mainLog';
export const sharedLogChannelId = 'sharedLog';
export const rendererLogChannelId = 'rendererLog';
export const extHostLogChannelId = 'extHostLog';
\ 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 * as nls from 'vs/nls';
import { join } from 'path';
import { Registry } from 'vs/platform/registry/common/platform';
import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions';
import { IOutputChannelRegistry, Extensions as OutputExt, } from 'vs/workbench/parts/output/common/output';
import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IWindowService } from 'vs/platform/windows/common/windows';
import { Disposable } from 'vs/base/common/lifecycle';
import URI from 'vs/base/common/uri';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import * as Constants from 'vs/workbench/parts/logs/common/logConstants';
import { IWorkbenchActionRegistry, Extensions as WorkbenchActionExtensions } from 'vs/workbench/common/actions';
import { SyncActionDescriptor } from 'vs/platform/actions/common/actions';
import { ShowLogsAction, OpenLogsFolderAction, SetLogLevelAction } from 'vs/workbench/parts/logs/electron-browser/logsActions';
class LogOutputChannels extends Disposable implements IWorkbenchContribution {
constructor(
@IWindowService private windowService: IWindowService,
@IEnvironmentService private environmentService: IEnvironmentService,
@IInstantiationService instantiationService: IInstantiationService
) {
super();
let outputChannelRegistry = <IOutputChannelRegistry>Registry.as(OutputExt.OutputChannels);
outputChannelRegistry.registerChannel(Constants.mainLogChannelId, nls.localize('mainLog', "Log (Main)"), URI.file(join(this.environmentService.logsPath, `main.log`)));
outputChannelRegistry.registerChannel(Constants.sharedLogChannelId, nls.localize('sharedLog', "Log (Shared)"), URI.file(join(this.environmentService.logsPath, `sharedprocess.log`)));
outputChannelRegistry.registerChannel(Constants.rendererLogChannelId, nls.localize('rendererLog', "Log (Window)"), URI.file(join(this.environmentService.logsPath, `renderer${this.windowService.getCurrentWindowId()}.log`)));
outputChannelRegistry.registerChannel(Constants.extHostLogChannelId, nls.localize('extensionsLog', "Log (Extension Host)"), URI.file(join(this.environmentService.logsPath, `extHost${this.windowService.getCurrentWindowId()}.log`)));
}
}
Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench).registerWorkbenchContribution(LogOutputChannels, LifecyclePhase.Restoring);
const workbenchActionsRegistry = Registry.as<IWorkbenchActionRegistry>(WorkbenchActionExtensions.WorkbenchActions);
const devCategory = nls.localize('developer', "Developer");
workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(ShowLogsAction, ShowLogsAction.ID, ShowLogsAction.LABEL), 'Developer: Show Logs...', devCategory);
workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(OpenLogsFolderAction, OpenLogsFolderAction.ID, OpenLogsFolderAction.LABEL), 'Developer: Open Log Folder', devCategory);
workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(SetLogLevelAction, SetLogLevelAction.ID, SetLogLevelAction.LABEL), 'Developer: Set Log Level', devCategory);
\ 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 * as nls from 'vs/nls';
import { Action } from 'vs/base/common/actions';
import * as paths from 'vs/base/common/paths';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IWindowsService } from 'vs/platform/windows/common/windows';
import { TPromise } from 'vs/base/common/winjs.base';
import { IQuickOpenService, IPickOpenEntry } from 'vs/platform/quickOpen/common/quickOpen';
import { ILogService, LogLevel } from 'vs/platform/log/common/log';
import { IOutputService } from 'vs/workbench/parts/output/common/output';
import * as Constants from 'vs/workbench/parts/logs/common/logConstants';
export class OpenLogsFolderAction extends Action {
static ID = 'workbench.action.openLogsFolder';
static LABEL = nls.localize('openLogsFolder', "Open Logs Folder");
constructor(id: string, label: string,
@IEnvironmentService private environmentService: IEnvironmentService,
@IWindowsService private windowsService: IWindowsService,
) {
super(id, label);
}
run(): TPromise<void> {
return this.windowsService.showItemInFolder(paths.join(this.environmentService.logsPath, 'main.log'));
}
}
export class ShowLogsAction extends Action {
static ID = 'workbench.action.showLogs';
static LABEL = nls.localize('showLogs', "Show Logs...");
constructor(id: string, label: string,
@IQuickOpenService private quickOpenService: IQuickOpenService,
@IOutputService private outputService: IOutputService
) {
super(id, label);
}
run(): TPromise<void> {
const entries: IPickOpenEntry[] = [
{ id: Constants.mainLogChannelId, label: nls.localize('mainProcess', "Main") },
{ id: Constants.sharedLogChannelId, label: nls.localize('sharedProcess', "Shared") },
{ id: Constants.rendererLogChannelId, label: nls.localize('rendererProcess', "Window") },
{ id: Constants.extHostLogChannelId, label: nls.localize('extensionHost', "Extension Host") }
];
return this.quickOpenService.pick(entries, { placeHolder: nls.localize('selectProcess', "Select process") })
.then(entry => {
if (entry) {
return this.outputService.showChannel(entry.id);
}
return null;
});
}
}
export class SetLogLevelAction extends Action {
static ID = 'workbench.action.setLogLevel';
static LABEL = nls.localize('setLogLevel', "Set Log Level");
constructor(id: string, label: string,
@IQuickOpenService private quickOpenService: IQuickOpenService,
@ILogService private logService: ILogService
) {
super(id, label);
}
run(): TPromise<void> {
const entries = [
{ label: nls.localize('trace', "Trace"), level: LogLevel.Trace },
{ label: nls.localize('debug', "Debug"), level: LogLevel.Debug },
{ label: nls.localize('info', "Info"), level: LogLevel.Info },
{ label: nls.localize('warn', "Warning"), level: LogLevel.Warning },
{ label: nls.localize('err', "Error"), level: LogLevel.Error },
{ label: nls.localize('critical', "Critical"), level: LogLevel.Critical },
{ label: nls.localize('off', "Off"), level: LogLevel.Off }
];
return this.quickOpenService.pick(entries, { placeHolder: nls.localize('selectLogLevel', "Select log level"), autoFocus: { autoFocusIndex: this.logService.getLevel() } }).then(entry => {
if (entry) {
this.logService.setLevel(entry.level);
}
});
}
}
\ No newline at end of file
......@@ -7,7 +7,7 @@
import { TPromise } from 'vs/base/common/winjs.base';
import nls = require('vs/nls');
import { IAction, Action } from 'vs/base/common/actions';
import { IOutputService, OUTPUT_PANEL_ID } from 'vs/workbench/parts/output/common/output';
import { IOutputService, OUTPUT_PANEL_ID, IOutputChannelRegistry, Extensions as OutputExt } from 'vs/workbench/parts/output/common/output';
import { SelectActionItem } from 'vs/base/browser/ui/actionbar/actionbar';
import { IPartService } from 'vs/workbench/services/part/common/partService';
import { IPanelService } from 'vs/workbench/services/panel/common/panelService';
......@@ -15,6 +15,7 @@ import { TogglePanelAction } from 'vs/workbench/browser/panel';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { attachSelectBoxStyler } from 'vs/platform/theme/common/styler';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { Registry } from 'vs/platform/registry/common/platform';
export class ToggleOutputAction extends TogglePanelAction {
......@@ -99,7 +100,7 @@ export class SwitchOutputAction extends Action {
}
public run(channelId?: string): TPromise<any> {
return this.outputService.getChannel(channelId).show();
return this.outputService.showChannel(channelId);
}
}
......@@ -112,10 +113,9 @@ export class SwitchOutputActionItem extends SelectActionItem {
) {
super(null, action, [], 0);
this.toDispose.push(this.outputService.onOutputChannel(() => {
const activeChannelIndex = this.getSelected(this.outputService.getActiveChannel().id);
this.setOptions(this.getOptions(), activeChannelIndex);
}));
let outputChannelRegistry = <IOutputChannelRegistry>Registry.as(OutputExt.OutputChannels);
this.toDispose.push(outputChannelRegistry.onDidRegisterChannel(() => this.updateOtions()));
this.toDispose.push(outputChannelRegistry.onDidRemoveChannel(() => this.updateOtions()));
this.toDispose.push(this.outputService.onActiveOutputChannel(activeChannelId => this.setOptions(this.getOptions(), this.getSelected(activeChannelId))));
this.toDispose.push(attachSelectBoxStyler(this.selectBox, themeService));
......@@ -132,6 +132,11 @@ export class SwitchOutputActionItem extends SelectActionItem {
return this.outputService.getChannels().map(c => c.label);
}
private updateOtions(): void {
const activeChannelIndex = this.getSelected(this.outputService.getActiveChannel().id);
this.setOptions(this.getOptions(), activeChannelIndex);
}
private getSelected(outputId: string): number {
if (!outputId) {
return undefined;
......
......@@ -8,13 +8,12 @@ import strings = require('vs/base/common/strings');
import Event, { Emitter } from 'vs/base/common/event';
import { binarySearch } from 'vs/base/common/arrays';
import URI from 'vs/base/common/uri';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { IEditor } from 'vs/platform/editor/common/editor';
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 { IOutputChannelIdentifier, OutputEditors, IOutputEvent, IOutputChannel, IOutputService, IOutputDelta, Extensions, OUTPUT_PANEL_ID, IOutputChannelRegistry, MAX_OUTPUT_LENGTH, OUTPUT_SCHEME, OUTPUT_MIME } from 'vs/workbench/parts/output/common/output';
import { IOutputChannelIdentifier, OutputEditors, IOutputEvent, IOutputChannel, IOutputService, IOutputDelta, Extensions, OUTPUT_PANEL_ID, IOutputChannelRegistry, MAX_OUTPUT_LENGTH, OUTPUT_SCHEME, OUTPUT_MIME, Extensions as OutputExt } from 'vs/workbench/parts/output/common/output';
import { OutputPanel } from 'vs/workbench/parts/output/browser/outputPanel';
import { IPanelService } from 'vs/workbench/services/panel/common/panelService';
import { IModelService } from 'vs/editor/common/services/modelService';
......@@ -26,6 +25,7 @@ import { IModeService } from 'vs/editor/common/services/modeService';
import { RunOnceScheduler } from 'vs/base/common/async';
import { EditOperation } from 'vs/editor/common/core/editOperation';
import { Position } from 'vs/editor/common/core/position';
import { IFileService, FileChangeType } from 'vs/platform/files/common/files';
const OUTPUT_ACTIVE_CHANNEL_KEY = 'output.activechannel';
......@@ -78,18 +78,161 @@ export class BufferedContent {
}
}
abstract class OutputChannel extends Disposable implements IOutputChannel {
protected _onDidChange: Emitter<boolean> = new Emitter<boolean>();
readonly onDidChange: Event<boolean> = this._onDidChange.event;
protected _onDispose: Emitter<void> = new Emitter<void>();
readonly onDispose: Event<void> = this._onDispose.event;
scrollLock: boolean = false;
constructor(private readonly oputChannelIdentifier: IOutputChannelIdentifier, ) {
super();
}
get id(): string {
return this.oputChannelIdentifier.id;
}
get label(): string {
return this.oputChannelIdentifier.label;
}
show(): TPromise<void> { return TPromise.as(null); }
hide(): void { }
append(output: string) { /** noop */ }
getOutputDelta(previousDelta?: IOutputDelta): TPromise<IOutputDelta> { return TPromise.as(null); }
clear(): void { }
dispose(): void {
this._onDispose.fire();
super.dispose();
}
}
class BufferredOutputChannel extends OutputChannel implements IOutputChannel {
private bufferredContent: BufferedContent = new BufferedContent();
append(output: string) {
this.bufferredContent.append(output);
this._onDidChange.fire(false);
}
getOutputDelta(previousDelta?: IOutputDelta): TPromise<IOutputDelta> {
return TPromise.as(this.bufferredContent.getDelta(previousDelta));
}
clear(): void {
this.bufferredContent.clear();
this._onDidChange.fire(true);
}
}
class FileOutputChannel extends OutputChannel implements IOutputChannel {
private readonly file: URI;
private disposables: IDisposable[] = [];
private shown: boolean = false;
private contentResolver: TPromise<string>;
private startOffset: number;
private endOffset: number;
constructor(
outputChannelIdentifier: IOutputChannelIdentifier,
@IFileService private fileService: IFileService
) {
super(outputChannelIdentifier);
this.file = outputChannelIdentifier.file;
this.startOffset = 0;
this.endOffset = 0;
}
show(): TPromise<void> {
if (!this.shown) {
this.shown = true;
this.watch();
}
return this.resolve() as TPromise;
}
hide(): void {
if (this.shown) {
this.shown = false;
this.fileService.unwatchFileChanges(this.file);
this.disposables = dispose(this.disposables);
}
this.contentResolver = null;
}
getOutputDelta(previousDelta?: IOutputDelta): TPromise<IOutputDelta> {
if (!this.shown) {
return TPromise.as(null);
}
return this.resolve()
.then(content => {
const startOffset = previousDelta ? previousDelta.id : this.startOffset;
this.endOffset = content.length;
if (startOffset === this.endOffset) {
return { append: true, id: this.endOffset, value: '' };
}
if (startOffset > 0 && startOffset < this.endOffset) {
const value = content.substring(startOffset, this.endOffset);
return { append: true, value, id: this.endOffset };
}
// replace
return { append: false, value: content, id: this.endOffset };
});
}
clear(): void {
this.startOffset = this.endOffset;
this._onDidChange.fire(true);
}
private resolve(): TPromise<string> {
if (!this.contentResolver) {
this.contentResolver = this.fileService.resolveContent(this.file)
.then(content => content.value);
}
return this.contentResolver;
}
private watch(): void {
this.fileService.watchFileChanges(this.file);
this.disposables.push(this.fileService.onFileChanges(changes => {
if (changes.contains(this.file, FileChangeType.UPDATED)) {
this.contentResolver = null;
this._onDidChange.fire(false);
}
}));
}
dispose(): void {
this.contentResolver = null;
this.disposables = dispose(this.disposables);
this.hide();
super.dispose();
}
}
export class OutputService implements IOutputService {
public _serviceBrand: any;
private receivedOutput: Map<string, BufferedContent> = new Map<string, BufferedContent>();
private channels: Map<string, IOutputChannel> = new Map<string, IOutputChannel>();
private channels: Map<string, OutputChannel> = new Map<string, OutputChannel>();
private activeChannelId: string;
private _onOutput: Emitter<IOutputEvent>;
private _onOutputChannel: Emitter<string>;
private _onActiveOutputChannel: Emitter<string>;
private _onOutput: Emitter<IOutputEvent> = new Emitter<IOutputEvent>();
readonly onOutput: Event<IOutputEvent> = this._onOutput.event;
private _onActiveOutputChannel: Emitter<string> = new Emitter<string>();
readonly onActiveOutputChannel: Event<string> = this._onActiveOutputChannel.event;
private _outputContentProvider: OutputContentProvider;
private _outputPanel: OutputPanel;
......@@ -102,10 +245,6 @@ export class OutputService implements IOutputService {
@IModelService modelService: IModelService,
@ITextModelService textModelResolverService: ITextModelService
) {
this._onOutput = new Emitter<IOutputEvent>();
this._onOutputChannel = new Emitter<string>();
this._onActiveOutputChannel = new Emitter<string>();
const channels = this.getChannels();
this.activeChannelId = this.storageService.get(OUTPUT_ACTIVE_CHANNEL_KEY, StorageScope.WORKSPACE, channels && channels.length > 0 ? channels[0].id : null);
......@@ -115,118 +254,79 @@ export class OutputService implements IOutputService {
// Register as text model content provider for output
textModelResolverService.registerTextModelContentProvider(OUTPUT_SCHEME, this._outputContentProvider);
}
public get onOutput(): Event<IOutputEvent> {
return this._onOutput.event;
(<IOutputChannelRegistry>Registry.as(OutputExt.OutputChannels)).onDidRegisterChannel(channel => this.showActiveChannel(channel));
}
public get onOutputChannel(): Event<string> {
return this._onOutputChannel.event;
}
showChannel(id: string, preserveFocus?: boolean): TPromise<void> {
const panel = this.panelService.getActivePanel();
if (this.activeChannelId === id && panel && panel.getId() === OUTPUT_PANEL_ID) {
return TPromise.as(null);
}
if (this.activeChannelId) {
const activeChannel = <OutputChannel>this.getChannel(this.activeChannelId);
if (activeChannel) {
activeChannel.hide();
}
}
public get onActiveOutputChannel(): Event<string> {
return this._onActiveOutputChannel.event;
this.activeChannelId = id;
const activeChannel = <OutputChannel>this.getChannel(id);
return activeChannel.show()
.then(() => {
this.storageService.store(OUTPUT_ACTIVE_CHANNEL_KEY, this.activeChannelId, StorageScope.WORKSPACE);
this._onActiveOutputChannel.fire(id); // emit event that a new channel is active
return this.panelService.openPanel(OUTPUT_PANEL_ID, !preserveFocus)
.then((outputPanel: OutputPanel) => {
this._outputPanel = outputPanel;
return outputPanel && outputPanel.setInput(OutputEditors.getInstance(this.instantiationService, this.getChannel(this.activeChannelId)), EditorOptions.create({ preserveFocus: preserveFocus }));
});
});
}
public getChannel(id: string): IOutputChannel {
getChannel(id: string): IOutputChannel {
if (!this.channels.has(id)) {
const channelData = Registry.as<IOutputChannelRegistry>(Extensions.OutputChannels).getChannel(id);
const channel = channelData && channelData.file ? this.instantiationService.createInstance(FileOutputChannel, channelData) : this.instantiationService.createInstance(BufferredOutputChannel, { id: id, label: '' });
const self = this;
this.channels.set(id, {
id,
label: channelData ? channelData.label : id,
getOutput(before?: IOutputDelta) {
return self.getOutput(id, before);
},
get scrollLock() {
return self._outputContentProvider.scrollLock(id);
},
set scrollLock(value: boolean) {
self._outputContentProvider.setScrollLock(id, value);
},
append: (output: string) => this.append(id, output),
show: (preserveFocus: boolean) => this.showOutput(id, preserveFocus),
clear: () => this.clearOutput(id),
dispose: () => this.removeOutput(id)
});
}
let disposables = [];
channel.onDidChange(isClear => this._onOutput.fire({ channelId: id, isClear }), disposables);
channel.onDispose(() => {
this.removeOutput(id);
dispose(disposables);
}, disposables);
this.channels.set(id, channel);
}
return this.channels.get(id);
}
public getChannels(): IOutputChannelIdentifier[] {
getChannels(): IOutputChannelIdentifier[] {
return Registry.as<IOutputChannelRegistry>(Extensions.OutputChannels).getChannels();
}
private append(channelId: string, output: string): void {
// Initialize
if (!this.receivedOutput.has(channelId)) {
this.receivedOutput.set(channelId, new BufferedContent());
this._onOutputChannel.fire(channelId); // emit event that we have a new channel
}
// Store
if (output) {
const channel = this.receivedOutput.get(channelId);
channel.append(output);
}
this._onOutput.fire({ channelId: channelId, isClear: false });
}
public getActiveChannel(): IOutputChannel {
getActiveChannel(): IOutputChannel {
return this.getChannel(this.activeChannelId);
}
private getOutput(channelId: string, previousDelta: IOutputDelta): IOutputDelta {
if (this.receivedOutput.has(channelId)) {
return this.receivedOutput.get(channelId).getDelta(previousDelta);
}
return undefined;
}
private clearOutput(channelId: string): void {
if (this.receivedOutput.has(channelId)) {
this.receivedOutput.get(channelId).clear();
this._onOutput.fire({ channelId: channelId, isClear: true });
}
}
private removeOutput(channelId: string): void {
this.receivedOutput.delete(channelId);
Registry.as<IOutputChannelRegistry>(Extensions.OutputChannels).removeChannel(channelId);
if (this.activeChannelId === channelId) {
const channels = this.getChannels();
this.activeChannelId = channels.length ? channels[0].id : undefined;
if (this._outputPanel && this.activeChannelId) {
this._outputPanel.setInput(OutputEditors.getInstance(this.instantiationService, this.getChannel(this.activeChannelId)), EditorOptions.create({ preserveFocus: true }));
if (this._outputPanel && channels.length) {
this.showChannel(channels[0].id);
} else {
this._onActiveOutputChannel.fire(void 0);
}
this._onActiveOutputChannel.fire(this.activeChannelId);
}
this._onOutputChannel.fire(channelId);
}
private showOutput(channelId: string, preserveFocus?: boolean): TPromise<IEditor> {
const panel = this.panelService.getActivePanel();
if (this.activeChannelId === channelId && panel && panel.getId() === OUTPUT_PANEL_ID) {
return TPromise.as(<OutputPanel>panel);
private showActiveChannel(id: string): void {
if (this.activeChannelId === id) {
this.showChannel(id);
}
this.activeChannelId = channelId;
this.storageService.store(OUTPUT_ACTIVE_CHANNEL_KEY, this.activeChannelId, StorageScope.WORKSPACE);
this._onActiveOutputChannel.fire(channelId); // emit event that a new channel is active
return this.panelService.openPanel(OUTPUT_PANEL_ID, !preserveFocus).then((outputPanel: OutputPanel) => {
this._outputPanel = outputPanel;
return outputPanel && outputPanel.setInput(OutputEditors.getInstance(this.instantiationService, this.getChannel(this.activeChannelId)), EditorOptions.create({ preserveFocus: preserveFocus })).
then(() => outputPanel);
});
}
}
......@@ -319,31 +419,34 @@ class OutputContentProvider implements ITextModelContentProvider {
}
const bufferedOutput = this.bufferedOutput.get(channel);
const newOutput = this.outputService.getChannel(channel).getOutput(bufferedOutput);
if (!newOutput) {
model.setValue('');
return;
}
this.bufferedOutput.set(channel, newOutput);
const outputChannel = <OutputChannel>this.outputService.getChannel(channel);
outputChannel.getOutputDelta(bufferedOutput)
.then(newOutput => {
if (!newOutput) {
model.setValue('');
return;
}
this.bufferedOutput.set(channel, newOutput);
// just fill in the full (trimmed) output if we exceed max length
if (!newOutput.append) {
model.setValue(newOutput.value);
}
// just fill in the full (trimmed) output if we exceed max length
if (!newOutput.append) {
model.setValue(newOutput.value);
}
// otherwise append
else {
const lastLine = model.getLineCount();
const lastLineMaxColumn = model.getLineMaxColumn(lastLine);
// otherwise append
else {
const lastLine = model.getLineCount();
const lastLineMaxColumn = model.getLineMaxColumn(lastLine);
model.applyEdits([EditOperation.insert(new Position(lastLine, lastLineMaxColumn), newOutput.value)]);
}
model.applyEdits([EditOperation.insert(new Position(lastLine, lastLineMaxColumn), newOutput.value)]);
}
if (!this.channelIdsWithScrollLock.has(channel)) {
// reveal last line
const panel = this.panelService.getActivePanel();
(<OutputPanel>panel).revealLastLine();
}
if (!this.channelIdsWithScrollLock.has(channel)) {
// reveal last line
const panel = this.panelService.getActivePanel();
(<OutputPanel>panel).revealLastLine();
}
});
}
private isVisible(channel: string): boolean {
......@@ -365,15 +468,16 @@ class OutputContentProvider implements ITextModelContentProvider {
}
public provideTextContent(resource: URI): TPromise<IModel> {
const output = this.outputService.getChannel(resource.fsPath).getOutput();
const content = output ? output.value : '';
let codeEditorModel = this.modelService.getModel(resource);
if (!codeEditorModel) {
codeEditorModel = this.modelService.createModel(content, this.modeService.getOrCreateMode(OUTPUT_MIME), resource);
}
return TPromise.as(codeEditorModel);
const channel = <OutputChannel>this.outputService.getChannel(resource.fsPath);
return channel.getOutputDelta()
.then(output => {
const content = output ? output.value : '';
let codeEditorModel = this.modelService.getModel(resource);
if (!codeEditorModel) {
codeEditorModel = this.modelService.createModel(content, this.modeService.getOrCreateMode(OUTPUT_MIME), resource);
}
return codeEditorModel;
});
}
public dispose(): void {
......
......@@ -5,10 +5,9 @@
'use strict';
import { TPromise } from 'vs/base/common/winjs.base';
import Event from 'vs/base/common/event';
import Event, { Emitter } from 'vs/base/common/event';
import { Registry } from 'vs/platform/registry/common/platform';
import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IEditor } from 'vs/platform/editor/common/editor';
import { RawContextKey } from 'vs/platform/contextkey/common/contextkey';
import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput';
import nls = require('vs/nls');
......@@ -78,14 +77,14 @@ export interface IOutputService {
getActiveChannel(): IOutputChannel;
/**
* Allows to register on Output events.
* Show the channel with the passed id.
*/
onOutput: Event<IOutputEvent>;
showChannel(id: string, preserveFocus?: boolean): TPromise<void>;
/**
* Allows to register on a output channel being added or removed
* Allows to register on Output events.
*/
onOutputChannel: Event<string>;
onOutput: Event<IOutputEvent>;
/**
* Allows to register on active output channel change.
......@@ -121,17 +120,6 @@ export interface IOutputChannel {
*/
append(output: string): void;
/**
* Returns the received output content.
* If a delta is passed, returns only the content that came after the passed delta.
*/
getOutput(previousDelta?: IOutputDelta): IOutputDelta;
/**
* Opens the output for this channel.
*/
show(preserveFocus?: boolean): TPromise<IEditor>;
/**
* Clears all received output for this channel.
*/
......@@ -146,14 +134,18 @@ export interface IOutputChannel {
export interface IOutputChannelIdentifier {
id: string;
label: string;
file?: URI;
}
export interface IOutputChannelRegistry {
readonly onDidRegisterChannel: Event<string>;
readonly onDidRemoveChannel: Event<string>;
/**
* Make an output channel known to the output world.
*/
registerChannel(id: string, name: string): void;
registerChannel(id: string, name: string, file?: URI): void;
/**
* Returns the list of channels known to the output world.
......@@ -174,9 +166,16 @@ export interface IOutputChannelRegistry {
class OutputChannelRegistry implements IOutputChannelRegistry {
private channels = new Map<string, IOutputChannelIdentifier>();
public registerChannel(id: string, label: string): void {
private _onDidRegisterChannel: Emitter<string> = new Emitter<string>();
readonly onDidRegisterChannel: Event<string> = this._onDidRegisterChannel.event;
private _onDidRemoveChannel: Emitter<string> = new Emitter<string>();
readonly onDidRemoveChannel: Event<string> = this._onDidRemoveChannel.event;
public registerChannel(id: string, label: string, file?: URI): void {
if (!this.channels.has(id)) {
this.channels.set(id, { id, label });
this.channels.set(id, { id, label, file });
this._onDidRegisterChannel.fire(id);
}
}
......@@ -192,6 +191,7 @@ class OutputChannelRegistry implements IOutputChannelRegistry {
public removeChannel(id: string): void {
this.channels.delete(id);
this._onDidRemoveChannel.fire(id);
}
}
......
......@@ -161,7 +161,7 @@ export class ViewPickerHandler extends QuickOpenHandler {
const channels = this.outputService.getChannels();
channels.forEach((channel, index) => {
const outputCategory = nls.localize('channels', "Output");
const entry = new ViewEntry(channel.label, outputCategory, () => this.outputService.getChannel(channel.id).show().done(null, errors.onUnexpectedError));
const entry = new ViewEntry(channel.label, outputCategory, () => this.outputService.showChannel(channel.id).done(null, errors.onUnexpectedError));
viewEntries.push(entry);
});
......
......@@ -681,7 +681,7 @@ class TaskService implements ITaskService {
}
private showOutput(): void {
this._outputChannel.show(true);
this.outputService.showChannel(this._outputChannel.id, true);
}
private disposeTaskSystemListeners(): void {
......@@ -1278,7 +1278,7 @@ class TaskService implements ITaskService {
this._outputChannel.append('Error: ');
this._outputChannel.append(error.message);
this._outputChannel.append('\n');
this._outputChannel.show(true);
this.outputService.showChannel(this._outputChannel.id, true);
}
} finally {
if (--counter === 0) {
......@@ -1616,7 +1616,7 @@ class TaskService implements ITaskService {
result = true;
this._outputChannel.append(line + '\n');
});
this._outputChannel.show(true);
this.outputService.showChannel(this._outputChannel.id, true);
}
return result;
}
......@@ -1748,7 +1748,7 @@ class TaskService implements ITaskService {
this.messageService.show(Severity.Error, nls.localize('TaskSystem.unknownError', 'An error has occurred while running a task. See task log for details.'));
}
if (showOutput) {
this._outputChannel.show(true);
this.outputService.showChannel(this._outputChannel.id, true);
}
}
......
......@@ -88,7 +88,7 @@ export class TerminalTaskSystem implements ITaskSystem {
}
protected showOutput(): void {
this.outputChannel.show(true);
this.outputService.showChannel(this.outputChannel.id, true);
}
public run(task: Task, resolver: ITaskResolver, trigger: string = Triggers.command): ITaskExecuteResult {
......
......@@ -417,7 +417,7 @@ export class ProcessTaskSystem implements ITaskSystem {
}
private showOutput(): void {
this.outputChannel.show(true);
this.outputService.showChannel(this.outputChannel.id, true);
}
private clearOutput(): void {
......
......@@ -31,6 +31,7 @@ import 'vs/workbench/browser/actions/toggleZenMode';
import 'vs/workbench/browser/actions/toggleTabsVisibility';
import 'vs/workbench/parts/preferences/electron-browser/preferences.contribution';
import 'vs/workbench/parts/preferences/browser/keybindingsEditorContribution';
import 'vs/workbench/parts/logs/electron-browser/logs.contribution';
import 'vs/workbench/browser/parts/quickopen/quickopen.contribution';
import 'vs/workbench/parts/quickopen/browser/quickopen.contribution';
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册