@@ -18,31 +18,178 @@ import * as errors from 'vs/base/common/errors';
1818import { IContextMenuService } from 'vs/platform/contextview/browser/contextView' ;
1919import { StandardMouseEvent } from 'vs/base/browser/mouseEvent' ;
2020import { IAction , Action } from 'vs/base/common/actions' ;
21+ import { IConfigurationService } from 'vs/platform/configuration/common/configuration' ;
22+ import { IIntegrityService } from 'vs/platform/integrity/common/integrity' ;
23+ import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService' ;
24+ import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService' ;
25+ import { IDisposable , dispose } from 'vs/base/common/lifecycle' ;
26+ import { isMacintosh , isLinux } from 'vs/base/common/platform' ;
27+ import nls = require( 'vs/nls' ) ;
28+ import * as labels from 'vs/base/common/labels' ;
29+ import { EditorInput , toResource } from 'vs/workbench/common/editor' ;
30+ import { IEnvironmentService } from 'vs/platform/environment/common/environment' ;
31+ import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace' ;
2132
2233export class TitlebarPart extends Part implements ITitleService {
2334
2435 public _serviceBrand : any ;
2536
37+ private static NLS_UNSUPPORTED = nls . localize ( 'patchedWindowTitle' , "[Unsupported]" ) ;
38+ private static NLS_EXTENSION_HOST = nls . localize ( 'devExtensionWindowTitlePrefix' , "[Extension Development Host]" ) ;
39+ private static TITLE_DIRTY = '\u25cf ' ;
40+ private static TITLE_SEPARATOR = ' - ' ;
41+
2642 private titleContainer : Builder ;
2743 private title : Builder ;
2844 private pendingTitle : string ;
2945 private initialTitleFontSize : number ;
3046 private representedFileName : string ;
3147
48+ private titleTemplate : string ;
49+ private isPure : boolean ;
50+ private activeEditorListeners : IDisposable [ ] ;
51+ private workspacePath : string ;
52+
3253 constructor (
3354 id : string ,
3455 @IContextMenuService private contextMenuService : IContextMenuService ,
3556 @IWindowService private windowService : IWindowService ,
36- @IWindowsService private windowsService : IWindowsService
57+ @IConfigurationService private configurationService : IConfigurationService ,
58+ @IWindowsService private windowsService : IWindowsService ,
59+ @IWorkbenchEditorService private editorService : IWorkbenchEditorService ,
60+ @IEditorGroupService private editorGroupService : IEditorGroupService ,
61+ @IIntegrityService private integrityService : IIntegrityService ,
62+ @IEnvironmentService private environmentService : IEnvironmentService ,
63+ @IWorkspaceContextService private contextService : IWorkspaceContextService
3764 ) {
3865 super ( id , { hasTitle : false } ) ;
3966
67+ this . isPure = true ;
68+ this . activeEditorListeners = [ ] ;
69+ this . workspacePath = contextService . hasWorkspace ( ) ? this . tildify ( labels . getPathLabel ( contextService . getWorkspace ( ) . resource ) ) : '' ;
70+
71+ this . init ( ) ;
72+
4073 this . registerListeners ( ) ;
4174 }
4275
76+ private init ( ) : void {
77+
78+ // Read initial config
79+ this . onConfigurationChanged ( ) ;
80+
81+ // Initial window title
82+ this . setTitle ( this . getWindowTitle ( ) ) ;
83+
84+ // Integrity for window title
85+ this . integrityService . isPure ( ) . then ( r => {
86+ if ( ! r . isPure ) {
87+ this . isPure = false ;
88+ this . setTitle ( this . getWindowTitle ( ) ) ;
89+ }
90+ } ) ;
91+ }
92+
4393 private registerListeners ( ) : void {
4494 this . toUnbind . push ( DOM . addDisposableListener ( window , DOM . EventType . BLUR , ( ) => { if ( this . titleContainer ) { this . titleContainer . addClass ( 'blurred' ) ; } } ) ) ;
4595 this . toUnbind . push ( DOM . addDisposableListener ( window , DOM . EventType . FOCUS , ( ) => { if ( this . titleContainer ) { this . titleContainer . removeClass ( 'blurred' ) ; } } ) ) ;
96+ this . toUnbind . push ( this . configurationService . onDidUpdateConfiguration ( ( ) => this . onConfigurationChanged ( true ) ) ) ;
97+ this . toUnbind . push ( this . editorGroupService . onEditorsChanged ( ( ) => this . onEditorsChanged ( ) ) ) ;
98+ }
99+
100+ private onConfigurationChanged ( update ?: boolean ) : void {
101+ const currentTitleTemplate = this . titleTemplate ;
102+ this . titleTemplate = this . configurationService . lookup < string > ( 'window.title' ) . value ;
103+
104+ if ( update && currentTitleTemplate !== this . titleTemplate ) {
105+ this . setTitle ( this . getWindowTitle ( ) ) ;
106+ }
107+ }
108+
109+ private onEditorsChanged ( ) : void {
110+
111+ // Dispose old listeners
112+ dispose ( this . activeEditorListeners ) ;
113+ this . activeEditorListeners = [ ] ;
114+
115+ const activeEditor = this . editorService . getActiveEditor ( ) ;
116+ const activeInput = activeEditor ? activeEditor . input : void 0 ;
117+
118+ // Calculate New Window Title
119+ this . setTitle ( this . getWindowTitle ( ) ) ;
120+
121+ // Apply listener for dirty and label changes
122+ if ( activeInput instanceof EditorInput ) {
123+ this . activeEditorListeners . push ( activeInput . onDidChangeDirty ( ( ) => {
124+ this . setTitle ( this . getWindowTitle ( ) ) ;
125+ } ) ) ;
126+
127+ this . activeEditorListeners . push ( activeInput . onDidChangeLabel ( ( ) => {
128+ this . setTitle ( this . getWindowTitle ( ) ) ;
129+ } ) ) ;
130+ }
131+ }
132+
133+ private getWindowTitle ( ) : string {
134+ let title = this . doGetWindowTitle ( ) ;
135+ if ( ! title ) {
136+ title = this . environmentService . appNameLong ;
137+ }
138+
139+ if ( ! this . isPure ) {
140+ title = `${ title } ${ TitlebarPart . NLS_UNSUPPORTED } ` ;
141+ }
142+
143+ // Extension Development Host gets a special title to identify itself
144+ if ( this . environmentService . isExtensionDevelopment ) {
145+ title = `${ TitlebarPart . NLS_EXTENSION_HOST } - ${ title } ` ;
146+ }
147+
148+ return title ;
149+ }
150+
151+ /**
152+ * Possible template values:
153+ *
154+ * {activeEditorName}: e.g. myFile.txt
155+ * {activeFilePath}: e.g. /Users/Development/myProject/myFile.txt
156+ * {rootName}: e.g. myProject
157+ * {rootPath}: e.g. /Users/Development/myProject
158+ * {appName}: e.g. VS Code
159+ * {dirty}: indiactor
160+ * {separator}: conditional separator
161+ */
162+ private doGetWindowTitle ( ) : string {
163+ const input = this . editorService . getActiveEditorInput ( ) ;
164+ const workspace = this . contextService . getWorkspace ( ) ;
165+ const file = toResource ( input , { filter : 'file' } ) ;
166+
167+ // Variables
168+ const activeEditorName = input ? input . getName ( ) : '' ;
169+ const activeFilePath = file ? this . tildify ( labels . getPathLabel ( file ) ) : '' ;
170+ const rootName = workspace ? workspace . name : '' ;
171+ const rootPath = workspace ? this . workspacePath : '' ;
172+ const dirty = input && input . isDirty ( ) ? TitlebarPart . TITLE_DIRTY : '' ;
173+ const appName = this . environmentService . appNameLong ;
174+ const separator = TitlebarPart . TITLE_SEPARATOR ;
175+
176+ return labels . template ( this . titleTemplate , {
177+ activeEditorName,
178+ activeFilePath,
179+ rootName,
180+ rootPath,
181+ dirty,
182+ appName,
183+ separator : { label : separator }
184+ } ) ;
185+ }
186+
187+ private tildify ( path : string ) : string {
188+ if ( path && ( isMacintosh || isLinux ) && path . indexOf ( this . environmentService . userHome ) === 0 ) {
189+ path = `~${ path . substr ( this . environmentService . userHome . length ) } ` ;
190+ }
191+
192+ return path ;
46193 }
47194
48195 public createContentArea ( parent : Builder ) : Builder {
@@ -127,7 +274,7 @@ export class TitlebarPart extends Part implements ITitleService {
127274 return actions ;
128275 }
129276
130- public updateTitle ( title : string ) : void {
277+ public setTitle ( title : string ) : void {
131278
132279 // Always set the native window title to identify us properly to the OS
133280 window . document . title = title ;
0 commit comments