diff --git a/build/lib/i18n.resources.json b/build/lib/i18n.resources.json index 0e4c15c74083ce7758d4d169f68b6268f059869a..93cf72c39c98424f7503f1fbadbdca5c941eae5e 100644 --- a/build/lib/i18n.resources.json +++ b/build/lib/i18n.resources.json @@ -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 diff --git a/src/vs/workbench/api/electron-browser/mainThreadOutputService.ts b/src/vs/workbench/api/electron-browser/mainThreadOutputService.ts index c85633089f0fdb41f55781f5471b8de27c5ac466..905dc07490de89e0f2832c3ba1b0154fb0eebd73 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadOutputService.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadOutputService.ts @@ -45,7 +45,8 @@ export class MainThreadOutputService implements MainThreadOutputServiceShape { } public $reveal(channelId: string, label: string, preserveFocus: boolean): TPromise { - this._getChannel(channelId, label).show(preserveFocus); + const channel = this._getChannel(channelId, label); + this._outputService.showChannel(channel.id, preserveFocus); return undefined; } diff --git a/src/vs/workbench/electron-browser/actions.ts b/src/vs/workbench/electron-browser/actions.ts index 76d1c2d8e3a3799df22945a58afd7e0f7850ece6..de3a3ee1dd79f0814107acac885923a382ffd151 100644 --- a/src/vs/workbench/electron-browser/actions.ts +++ b/src/vs/workbench/electron-browser/actions.ts @@ -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 { - 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 { - 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 { - 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 diff --git a/src/vs/workbench/electron-browser/main.contribution.ts b/src/vs/workbench/electron-browser/main.contribution.ts index 24565c1ea2d9dc50ed6ab48b324c35485a87be4d..3c9ed0fa55695c8901f3cbd2bb1d36b658b35bcc 100644 --- a/src/vs/workbench/electron-browser/main.contribution.ts +++ b/src/vs/workbench/electron-browser/main.contribution.ts @@ -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(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); diff --git a/src/vs/workbench/parts/logs/common/logConstants.ts b/src/vs/workbench/parts/logs/common/logConstants.ts new file mode 100644 index 0000000000000000000000000000000000000000..08ac75b8e9698d478a68a371c707f6a5f0ff4f29 --- /dev/null +++ b/src/vs/workbench/parts/logs/common/logConstants.ts @@ -0,0 +1,9 @@ +/*--------------------------------------------------------------------------------------------- + * 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 diff --git a/src/vs/workbench/parts/logs/electron-browser/logs.contribution.ts b/src/vs/workbench/parts/logs/electron-browser/logs.contribution.ts new file mode 100644 index 0000000000000000000000000000000000000000..a6f3683ab5461f1b3ed8a3ec506d973a12df85bd --- /dev/null +++ b/src/vs/workbench/parts/logs/electron-browser/logs.contribution.ts @@ -0,0 +1,44 @@ +/*--------------------------------------------------------------------------------------------- + * 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 = 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(WorkbenchExtensions.Workbench).registerWorkbenchContribution(LogOutputChannels, LifecyclePhase.Restoring); + +const workbenchActionsRegistry = Registry.as(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 diff --git a/src/vs/workbench/parts/logs/electron-browser/logsActions.ts b/src/vs/workbench/parts/logs/electron-browser/logsActions.ts new file mode 100644 index 0000000000000000000000000000000000000000..74fee6a2e6f4df55bcdf0d94a3076506cc3a5db0 --- /dev/null +++ b/src/vs/workbench/parts/logs/electron-browser/logsActions.ts @@ -0,0 +1,93 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { + 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 { + 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 { + 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 diff --git a/src/vs/workbench/parts/output/browser/outputActions.ts b/src/vs/workbench/parts/output/browser/outputActions.ts index 8fdbcd9698f5fa0229eb74652d5d241369de5829..51daf903872b62039a16f5c6b0e9be78d9e01bbc 100644 --- a/src/vs/workbench/parts/output/browser/outputActions.ts +++ b/src/vs/workbench/parts/output/browser/outputActions.ts @@ -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 { - 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 = 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; diff --git a/src/vs/workbench/parts/output/browser/outputServices.ts b/src/vs/workbench/parts/output/browser/outputServices.ts index f89599c5d41f1bbcba86492ba5f97d6104fa590b..36a1f677632d5041095d42888cf536cc247cae4f 100644 --- a/src/vs/workbench/parts/output/browser/outputServices.ts +++ b/src/vs/workbench/parts/output/browser/outputServices.ts @@ -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 = new Emitter(); + readonly onDidChange: Event = this._onDidChange.event; + + protected _onDispose: Emitter = new Emitter(); + readonly onDispose: Event = 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 { return TPromise.as(null); } + hide(): void { } + append(output: string) { /** noop */ } + getOutputDelta(previousDelta?: IOutputDelta): TPromise { 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 { + 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; + 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 { + 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 { + 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 { + 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 = new Map(); - private channels: Map = new Map(); - + private channels: Map = new Map(); private activeChannelId: string; - private _onOutput: Emitter; - private _onOutputChannel: Emitter; - private _onActiveOutputChannel: Emitter; + private _onOutput: Emitter = new Emitter(); + readonly onOutput: Event = this._onOutput.event; + + private _onActiveOutputChannel: Emitter = new Emitter(); + readonly onActiveOutputChannel: Event = 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(); - this._onOutputChannel = new Emitter(); - this._onActiveOutputChannel = new Emitter(); - 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 { - return this._onOutput.event; + (Registry.as(OutputExt.OutputChannels)).onDidRegisterChannel(channel => this.showActiveChannel(channel)); } - public get onOutputChannel(): Event { - return this._onOutputChannel.event; - } + showChannel(id: string, preserveFocus?: boolean): TPromise { + const panel = this.panelService.getActivePanel(); + if (this.activeChannelId === id && panel && panel.getId() === OUTPUT_PANEL_ID) { + return TPromise.as(null); + } + + if (this.activeChannelId) { + const activeChannel = this.getChannel(this.activeChannelId); + if (activeChannel) { + activeChannel.hide(); + } + } - public get onActiveOutputChannel(): Event { - return this._onActiveOutputChannel.event; + this.activeChannelId = id; + const activeChannel = 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(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(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(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 { - const panel = this.panelService.getActivePanel(); - if (this.activeChannelId === channelId && panel && panel.getId() === OUTPUT_PANEL_ID) { - return TPromise.as(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 = 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(); - (panel).revealLastLine(); - } + if (!this.channelIdsWithScrollLock.has(channel)) { + // reveal last line + const panel = this.panelService.getActivePanel(); + (panel).revealLastLine(); + } + }); } private isVisible(channel: string): boolean { @@ -365,15 +468,16 @@ class OutputContentProvider implements ITextModelContentProvider { } public provideTextContent(resource: URI): TPromise { - 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 = 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 { diff --git a/src/vs/workbench/parts/output/common/output.ts b/src/vs/workbench/parts/output/common/output.ts index b4066e6e6bef81b613d955057bc4c39cd2b03913..6638949b4931863a4b74de08781ee38eead7a90e 100644 --- a/src/vs/workbench/parts/output/common/output.ts +++ b/src/vs/workbench/parts/output/common/output.ts @@ -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; + showChannel(id: string, preserveFocus?: boolean): TPromise; /** - * Allows to register on a output channel being added or removed + * Allows to register on Output events. */ - onOutputChannel: Event; + onOutput: Event; /** * 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; - /** * 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; + readonly onDidRemoveChannel: Event; + /** * 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(); - public registerChannel(id: string, label: string): void { + private _onDidRegisterChannel: Emitter = new Emitter(); + readonly onDidRegisterChannel: Event = this._onDidRegisterChannel.event; + + private _onDidRemoveChannel: Emitter = new Emitter(); + readonly onDidRemoveChannel: Event = 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); } } diff --git a/src/vs/workbench/parts/quickopen/browser/viewPickerHandler.ts b/src/vs/workbench/parts/quickopen/browser/viewPickerHandler.ts index 6b499c85130bb5da56cbb2fc10415456d7222aff..bcfefc9f74f8b3a3796b528c5cd900c8914a97c2 100644 --- a/src/vs/workbench/parts/quickopen/browser/viewPickerHandler.ts +++ b/src/vs/workbench/parts/quickopen/browser/viewPickerHandler.ts @@ -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); }); diff --git a/src/vs/workbench/parts/tasks/electron-browser/task.contribution.ts b/src/vs/workbench/parts/tasks/electron-browser/task.contribution.ts index 3f1d2c26a6b93765ff8bd492288efd489d6d1dc9..56180505b60a83ade7f44bdbf8de9e35f9073568 100644 --- a/src/vs/workbench/parts/tasks/electron-browser/task.contribution.ts +++ b/src/vs/workbench/parts/tasks/electron-browser/task.contribution.ts @@ -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); } } diff --git a/src/vs/workbench/parts/tasks/electron-browser/terminalTaskSystem.ts b/src/vs/workbench/parts/tasks/electron-browser/terminalTaskSystem.ts index 55f852198c0c0081d60ca0f3b2e51591ab4744c3..e6c5d2bc254fad4e39b2912904c7424dd589fe21 100644 --- a/src/vs/workbench/parts/tasks/electron-browser/terminalTaskSystem.ts +++ b/src/vs/workbench/parts/tasks/electron-browser/terminalTaskSystem.ts @@ -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 { diff --git a/src/vs/workbench/parts/tasks/node/processTaskSystem.ts b/src/vs/workbench/parts/tasks/node/processTaskSystem.ts index 0d96212034ebfb872c3ff51b1fb8a1b6864efd43..70321d0cc641e50279fc32410c4a24033e46314f 100644 --- a/src/vs/workbench/parts/tasks/node/processTaskSystem.ts +++ b/src/vs/workbench/parts/tasks/node/processTaskSystem.ts @@ -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 { diff --git a/src/vs/workbench/workbench.main.ts b/src/vs/workbench/workbench.main.ts index 8b75deae37f611d9abe4c41ad2f8af9a234ac0f9..cfe23986359d5e1803d6069ef8c5b856179727fe 100644 --- a/src/vs/workbench/workbench.main.ts +++ b/src/vs/workbench/workbench.main.ts @@ -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';