1+ /*---------------------------------------------------------------------------------------------
2+ * Copyright (c) Microsoft Corporation. All rights reserved.
3+ * Licensed under the MIT License. See License.txt in the project root for license information.
4+ *--------------------------------------------------------------------------------------------*/
5+
6+ import * as nls from 'vs/nls' ;
7+ import { Event , Emitter } from 'vs/base/common/event' ;
8+ import { URI } from 'vs/base/common/uri' ;
9+ import { IDisposable , dispose , Disposable } from 'vs/base/common/lifecycle' ;
10+ import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation' ;
11+ import { IStorageService , StorageScope } from 'vs/platform/storage/common/storage' ;
12+ import { Registry } from 'vs/platform/registry/common/platform' ;
13+ import { EditorOptions } from 'vs/workbench/common/editor' ;
14+ 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' ;
15+ import { OutputPanel } from 'vs/workbench/contrib/output/browser/outputPanel' ;
16+ import { IPanelService } from 'vs/workbench/services/panel/common/panelService' ;
17+ import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace' ;
18+ import { OutputLinkProvider } from 'vs/workbench/contrib/output/common/outputLinkProvider' ;
19+ import { ITextModelService , ITextModelContentProvider } from 'vs/editor/common/services/resolverService' ;
20+ import { ITextModel } from 'vs/editor/common/model' ;
21+ import { IPanel } from 'vs/workbench/common/panel' ;
22+ import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput' ;
23+ import { IEnvironmentService } from 'vs/platform/environment/common/environment' ;
24+ import { IWindowService } from 'vs/platform/windows/common/windows' ;
25+ import { ILogService } from 'vs/platform/log/common/log' ;
26+ import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle' ;
27+ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey' ;
28+ import { CancellationToken } from 'vs/base/common/cancellation' ;
29+ import { IOutputChannelModel , IOutputChannelModelService } from 'vs/workbench/services/output/common/outputChannelModel' ;
30+
31+ const OUTPUT_ACTIVE_CHANNEL_KEY = 'output.activechannel' ;
32+
33+ class OutputChannel extends Disposable implements IOutputChannel {
34+
35+ scrollLock : boolean = false ;
36+ readonly model : IOutputChannelModel ;
37+ readonly id : string ;
38+ readonly label : string ;
39+
40+ constructor (
41+ readonly outputChannelDescriptor : IOutputChannelDescriptor ,
42+ @IOutputChannelModelService outputChannelModelService : IOutputChannelModelService
43+ ) {
44+ super ( ) ;
45+ this . id = outputChannelDescriptor . id ;
46+ this . label = outputChannelDescriptor . label ;
47+ this . model = this . _register ( outputChannelModelService . createOutputChannelModel ( this . id , URI . from ( { scheme : OUTPUT_SCHEME , path : this . id } ) , outputChannelDescriptor . log ? LOG_MIME : OUTPUT_MIME , outputChannelDescriptor . file ) ) ;
48+ }
49+
50+ append ( output : string ) : void {
51+ this . model . append ( output ) ;
52+ }
53+
54+ update ( ) : void {
55+ this . model . update ( ) ;
56+ }
57+
58+ clear ( till ?: number ) : void {
59+ this . model . clear ( till ) ;
60+ }
61+ }
62+
63+ export class OutputService extends Disposable implements IOutputService , ITextModelContentProvider {
64+
65+ public _serviceBrand : any ;
66+
67+ private channels : Map < string , OutputChannel > = new Map < string , OutputChannel > ( ) ;
68+ private activeChannelIdInStorage : string ;
69+ private activeChannel : OutputChannel | null ;
70+
71+ private readonly _onActiveOutputChannel = new Emitter < string > ( ) ;
72+ readonly onActiveOutputChannel : Event < string > = this . _onActiveOutputChannel . event ;
73+
74+ private _outputPanel : OutputPanel ;
75+
76+ constructor (
77+ @IStorageService private readonly storageService : IStorageService ,
78+ @IInstantiationService private readonly instantiationService : IInstantiationService ,
79+ @IPanelService private readonly panelService : IPanelService ,
80+ @IWorkspaceContextService contextService : IWorkspaceContextService ,
81+ @ITextModelService textModelResolverService : ITextModelService ,
82+ @IEnvironmentService environmentService : IEnvironmentService ,
83+ @IWindowService windowService : IWindowService ,
84+ @ILogService private readonly logService : ILogService ,
85+ @ILifecycleService private readonly lifecycleService : ILifecycleService ,
86+ @IContextKeyService private readonly contextKeyService : IContextKeyService ,
87+ ) {
88+ super ( ) ;
89+ this . activeChannelIdInStorage = this . storageService . get ( OUTPUT_ACTIVE_CHANNEL_KEY , StorageScope . WORKSPACE , '' ) ;
90+
91+ // Register as text model content provider for output
92+ textModelResolverService . registerTextModelContentProvider ( OUTPUT_SCHEME , this ) ;
93+ instantiationService . createInstance ( OutputLinkProvider ) ;
94+
95+ // Create output channels for already registered channels
96+ const registry = Registry . as < IOutputChannelRegistry > ( Extensions . OutputChannels ) ;
97+ for ( const channelIdentifier of registry . getChannels ( ) ) {
98+ this . onDidRegisterChannel ( channelIdentifier . id ) ;
99+ }
100+ this . _register ( registry . onDidRegisterChannel ( this . onDidRegisterChannel , this ) ) ;
101+
102+ this . _register ( panelService . onDidPanelOpen ( ( { panel, focus } ) => this . onDidPanelOpen ( panel , ! focus ) , this ) ) ;
103+ this . _register ( panelService . onDidPanelClose ( this . onDidPanelClose , this ) ) ;
104+
105+ // Set active channel to first channel if not set
106+ if ( ! this . activeChannel ) {
107+ const channels = this . getChannelDescriptors ( ) ;
108+ this . activeChannel = channels && channels . length > 0 ? this . getChannel ( channels [ 0 ] . id ) : null ;
109+ }
110+
111+ this . _register ( this . lifecycleService . onShutdown ( ( ) => this . dispose ( ) ) ) ;
112+ this . _register ( this . storageService . onWillSaveState ( ( ) => this . saveState ( ) ) ) ;
113+ }
114+
115+ provideTextContent ( resource : URI ) : Promise < ITextModel > | null {
116+ const channel = < OutputChannel > this . getChannel ( resource . path ) ;
117+ if ( channel ) {
118+ return channel . model . loadModel ( ) ;
119+ }
120+ return null ;
121+ }
122+
123+ showChannel ( id : string , preserveFocus ?: boolean ) : Promise < void > {
124+ const channel = this . getChannel ( id ) ;
125+ if ( ! channel || this . isChannelShown ( channel ) ) {
126+ if ( this . _outputPanel && ! preserveFocus ) {
127+ this . _outputPanel . focus ( ) ;
128+ }
129+ return Promise . resolve ( undefined ) ;
130+ }
131+
132+ this . activeChannel = channel ;
133+ let promise : Promise < void > ;
134+ if ( this . isPanelShown ( ) ) {
135+ promise = this . doShowChannel ( channel , ! ! preserveFocus ) ;
136+ } else {
137+ this . panelService . openPanel ( OUTPUT_PANEL_ID ) ;
138+ promise = this . doShowChannel ( this . activeChannel , ! ! preserveFocus ) ;
139+ }
140+ return promise . then ( ( ) => this . _onActiveOutputChannel . fire ( id ) ) ;
141+ }
142+
143+ getChannel ( id : string ) : OutputChannel | null {
144+ return this . channels . get ( id ) || null ;
145+ }
146+
147+ getChannelDescriptors ( ) : IOutputChannelDescriptor [ ] {
148+ return Registry . as < IOutputChannelRegistry > ( Extensions . OutputChannels ) . getChannels ( ) ;
149+ }
150+
151+ getActiveChannel ( ) : IOutputChannel | null {
152+ return this . activeChannel ;
153+ }
154+
155+ private onDidRegisterChannel ( channelId : string ) : void {
156+ const channel = this . createChannel ( channelId ) ;
157+ this . channels . set ( channelId , channel ) ;
158+ if ( this . activeChannelIdInStorage === channelId ) {
159+ this . activeChannel = channel ;
160+ this . onDidPanelOpen ( this . panelService . getActivePanel ( ) , true )
161+ . then ( ( ) => this . _onActiveOutputChannel . fire ( channelId ) ) ;
162+ }
163+ }
164+
165+ private onDidPanelOpen ( panel : IPanel | null , preserveFocus : boolean ) : Promise < void > {
166+ if ( panel && panel . getId ( ) === OUTPUT_PANEL_ID ) {
167+ this . _outputPanel = < OutputPanel > this . panelService . getActivePanel ( ) ;
168+ if ( this . activeChannel ) {
169+ return this . doShowChannel ( this . activeChannel , preserveFocus ) ;
170+ }
171+ }
172+ return Promise . resolve ( undefined ) ;
173+ }
174+
175+ private onDidPanelClose ( panel : IPanel ) : void {
176+ if ( this . _outputPanel && panel . getId ( ) === OUTPUT_PANEL_ID ) {
177+ CONTEXT_ACTIVE_LOG_OUTPUT . bindTo ( this . contextKeyService ) . set ( false ) ;
178+ this . _outputPanel . clearInput ( ) ;
179+ }
180+ }
181+
182+ private createChannel ( id : string ) : OutputChannel {
183+ const channelDisposables : IDisposable [ ] = [ ] ;
184+ const channel = this . instantiateChannel ( id ) ;
185+ channel . model . onDidAppendedContent ( ( ) => {
186+ if ( ! channel . scrollLock ) {
187+ const panel = this . panelService . getActivePanel ( ) ;
188+ if ( panel && panel . getId ( ) === OUTPUT_PANEL_ID && this . isChannelShown ( channel ) ) {
189+ let outputPanel = < OutputPanel > panel ;
190+ outputPanel . revealLastLine ( ) ;
191+ }
192+ }
193+ } , channelDisposables ) ;
194+ channel . model . onDispose ( ( ) => {
195+ if ( this . activeChannel === channel ) {
196+ const channels = this . getChannelDescriptors ( ) ;
197+ const channel = channels . length ? this . getChannel ( channels [ 0 ] . id ) : null ;
198+ if ( channel && this . isPanelShown ( ) ) {
199+ this . showChannel ( channel . id , true ) ;
200+ } else {
201+ this . activeChannel = channel ;
202+ if ( this . activeChannel ) {
203+ this . _onActiveOutputChannel . fire ( this . activeChannel . id ) ;
204+ }
205+ }
206+ }
207+ Registry . as < IOutputChannelRegistry > ( Extensions . OutputChannels ) . removeChannel ( id ) ;
208+ dispose ( channelDisposables ) ;
209+ } , channelDisposables ) ;
210+
211+ return channel ;
212+ }
213+
214+ private instantiateChannel ( id : string ) : OutputChannel {
215+ const channelData = Registry . as < IOutputChannelRegistry > ( Extensions . OutputChannels ) . getChannel ( id ) ;
216+ if ( ! channelData ) {
217+ this . logService . error ( `Channel '${ id } ' is not registered yet` ) ;
218+ throw new Error ( `Channel '${ id } ' is not registered yet` ) ;
219+ }
220+ return this . instantiationService . createInstance ( OutputChannel , channelData ) ;
221+ }
222+
223+ private doShowChannel ( channel : OutputChannel , preserveFocus : boolean ) : Promise < void > {
224+ if ( this . _outputPanel ) {
225+ CONTEXT_ACTIVE_LOG_OUTPUT . bindTo ( this . contextKeyService ) . set ( ! ! channel . outputChannelDescriptor . file && channel . outputChannelDescriptor . log ) ;
226+ return this . _outputPanel . setInput ( this . createInput ( channel ) , EditorOptions . create ( { preserveFocus } ) , CancellationToken . None )
227+ . then ( ( ) => {
228+ if ( ! preserveFocus ) {
229+ this . _outputPanel . focus ( ) ;
230+ }
231+ } ) ;
232+ }
233+ return Promise . resolve ( undefined ) ;
234+ }
235+
236+ private isChannelShown ( channel : IOutputChannel ) : boolean {
237+ return this . isPanelShown ( ) && this . activeChannel === channel ;
238+ }
239+
240+ private isPanelShown ( ) : boolean {
241+ const panel = this . panelService . getActivePanel ( ) ;
242+ return ! ! panel && panel . getId ( ) === OUTPUT_PANEL_ID ;
243+ }
244+
245+ private createInput ( channel : IOutputChannel ) : ResourceEditorInput {
246+ const resource = URI . from ( { scheme : OUTPUT_SCHEME , path : channel . id } ) ;
247+ return this . instantiationService . createInstance ( ResourceEditorInput , nls . localize ( 'output' , "{0} - Output" , channel . label ) , nls . localize ( 'channel' , "Output channel for '{0}'" , channel . label ) , resource ) ;
248+ }
249+
250+ private saveState ( ) : void {
251+ if ( this . activeChannel ) {
252+ this . storageService . store ( OUTPUT_ACTIVE_CHANNEL_KEY , this . activeChannel . id , StorageScope . WORKSPACE ) ;
253+ }
254+ }
255+ }
256+
257+ export class LogContentProvider {
258+
259+ private channelModels : Map < string , IOutputChannelModel > = new Map < string , IOutputChannelModel > ( ) ;
260+
261+ constructor (
262+ @IOutputService private readonly outputService : IOutputService ,
263+ @IOutputChannelModelService private readonly outputChannelModelService : IOutputChannelModelService
264+ ) {
265+ }
266+
267+ provideTextContent ( resource : URI ) : Promise < ITextModel > | null {
268+ if ( resource . scheme === LOG_SCHEME ) {
269+ let channelModel = this . getChannelModel ( resource ) ;
270+ if ( channelModel ) {
271+ return channelModel . loadModel ( ) ;
272+ }
273+ }
274+ return null ;
275+ }
276+
277+ private getChannelModel ( resource : URI ) : IOutputChannelModel | undefined {
278+ const channelId = resource . path ;
279+ let channelModel = this . channelModels . get ( channelId ) ;
280+ if ( ! channelModel ) {
281+ const channelDisposables : IDisposable [ ] = [ ] ;
282+ const outputChannelDescriptor = this . outputService . getChannelDescriptors ( ) . filter ( ( { id } ) => id === channelId ) [ 0 ] ;
283+ if ( outputChannelDescriptor && outputChannelDescriptor . file ) {
284+ channelModel = this . outputChannelModelService . createOutputChannelModel ( channelId , resource , outputChannelDescriptor . log ? LOG_MIME : OUTPUT_MIME , outputChannelDescriptor . file ) ;
285+ channelModel . onDispose ( ( ) => dispose ( channelDisposables ) , channelDisposables ) ;
286+ this . channelModels . set ( channelId , channelModel ) ;
287+ }
288+ }
289+ return channelModel ;
290+ }
291+ }
0 commit comments