diff --git a/extensions/json/server/src/jsonServerMain.ts b/extensions/json/server/src/jsonServerMain.ts index 3719674ec66..bb541523362 100644 --- a/extensions/json/server/src/jsonServerMain.ts +++ b/extensions/json/server/src/jsonServerMain.ts @@ -19,6 +19,7 @@ import Strings = require('./utils/strings'); import { JSONDocument, JSONSchema, LanguageSettings, getLanguageService } from 'vscode-json-languageservice'; import { ProjectJSONContribution } from './jsoncontributions/projectJSONContribution'; import { GlobPatternContribution } from './jsoncontributions/globPatternContribution'; +import { WindowTitleContribution } from './jsoncontributions/windowTitleContribution'; import { FileAssociationContribution } from './jsoncontributions/fileAssociationContribution'; import { getLanguageModelCache } from './languageModelCache'; @@ -128,6 +129,7 @@ let languageService = getLanguageService({ contributions: [ new ProjectJSONContribution(), new GlobPatternContribution(), + new WindowTitleContribution(), filesAssociationContribution ] }); diff --git a/extensions/json/server/src/jsoncontributions/windowTitleContribution.ts b/extensions/json/server/src/jsoncontributions/windowTitleContribution.ts new file mode 100644 index 00000000000..d93bf4cd56a --- /dev/null +++ b/extensions/json/server/src/jsoncontributions/windowTitleContribution.ts @@ -0,0 +1,51 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +import { MarkedString } from 'vscode-languageserver'; +import Strings = require('../utils/strings'); +import { JSONWorkerContribution, JSONPath, CompletionsCollector } from 'vscode-json-languageservice'; + +import * as nls from 'vscode-nls'; +const localize = nls.loadMessageBundle(); + +export class WindowTitleContribution implements JSONWorkerContribution { + + constructor() { + } + + private isSettingsFile(resource: string): boolean { + return Strings.endsWith(resource, '/settings.json'); + } + + public collectDefaultCompletions(resource: string, result: CompletionsCollector): Thenable { + return null; + } + + public collectPropertyCompletions(resource: string, location: JSONPath, currentWord: string, addValue: boolean, isLast: boolean, result: CompletionsCollector): Thenable { + return null; + } + + public collectValueCompletions(resource: string, location: JSONPath, currentKey: string, result: CompletionsCollector): Thenable { + return null; + } + + public getInfoContribution(resource: string, location: JSONPath): Thenable { + if (this.isSettingsFile(resource) && location.length === 1 && location[0] === 'window.title') { + return Promise.resolve([ + MarkedString.fromPlainText(localize('windowTitle.description', "Controls the window title based on the active editor. Variables are substituted based on the context:")), + MarkedString.fromPlainText(localize('windowTitle.activeEditorName', "$(activeEditorName): e.g. myFile.txt")), + MarkedString.fromPlainText(localize('windowTitle.activeFilePath', "$(activeFilePath): e.g. /Users/Development/myProject/myFile.txt")), + MarkedString.fromPlainText(localize('windowTitle.rootName', "$(rootName): e.g. myProject")), + MarkedString.fromPlainText(localize('windowTitle.rootPath', "$(rootPath): e.g. /Users/Development/myProject")), + MarkedString.fromPlainText(localize('windowTitle.appName', "$(appName): e.g. VS Code")), + MarkedString.fromPlainText(localize('windowTitle.dirty', "$(dirty): a dirty indicator if the active editor is dirty")), + MarkedString.fromPlainText(localize('windowTitle.separator', "$(separator): a conditional separator (\" - \") that only shows when surrounded by variables with values")) + ]); + } + + return null; + } +} \ No newline at end of file diff --git a/src/vs/base/common/labels.ts b/src/vs/base/common/labels.ts index 71ed33eb30b..5f7bad2ad33 100644 --- a/src/vs/base/common/labels.ts +++ b/src/vs/base/common/labels.ts @@ -134,4 +134,90 @@ export function shorten(paths: string[]): string[] { } return shortenedPaths; +} + +export interface ISeparator { + label: string; +} + +enum Type { + TEXT, + VARIABLE, + SEPARATOR +} + +interface ISegment { + value: string; + type: Type; +} + +/** + * Helper to insert values for specific template variables into the string. E.g. "this $(is) a $(template)" can be + * passed to this function together with an object that maps "is" and "template" to strings to have them replaced. + * @param value string to which templating is applied + * @param values the values of the templates to use + */ +export function template(template: string, values: { [key: string]: string | ISeparator } = Object.create(null)): string { + const segments: ISegment[] = []; + + let inVariable = false; + let char: string; + let curVal = ''; + for (let i = 0; i < template.length; i++) { + char = template[i]; + + // Beginning of variable + if (char === '$' || (inVariable && char === '(')) { + if (curVal) { + segments.push({ value: curVal, type: Type.TEXT }); + } + + curVal = ''; + inVariable = true; + } + + // End of variable + else if (char === ')' && inVariable) { + const resolved = values[curVal]; + + // Variable + if (typeof resolved === 'string') { + if (resolved.length) { + segments.push({ value: resolved, type: Type.VARIABLE }); + } + } + + // Separator + else if (resolved) { + segments.push({ value: resolved.label, type: Type.SEPARATOR }); + } + + curVal = ''; + inVariable = false; + } + + // Text or Variable Name + else { + curVal += char; + } + } + + // Tail + if (curVal && !inVariable) { + segments.push({ value: curVal, type: Type.TEXT }); + } + + return segments.filter((segment, index) => { + + // Only keep separator if we have values to the left and right + if (segment.type === Type.SEPARATOR) { + const left = segments[index - 1]; + const right = segments[index + 1]; + + return [left, right].every(segment => segment && segment.type === Type.VARIABLE && segment.value.length > 0); + } + + // accept any TEXT and VARIABLE + return true; + }).map(segment => segment.value).join(''); } \ No newline at end of file diff --git a/src/vs/base/test/common/labels.test.ts b/src/vs/base/test/common/labels.test.ts index 9aaefe0c32d..1e39de79d13 100644 --- a/src/vs/base/test/common/labels.test.ts +++ b/src/vs/base/test/common/labels.test.ts @@ -98,4 +98,35 @@ suite('Labels', () => { assert.deepEqual(labels.shorten(['a', 'a/b', 'b']), ['a', 'a/b', 'b']); assert.deepEqual(labels.shorten(['', 'a', 'b', 'b/c', 'a/c']), ['', 'a', 'b', 'b/c', 'a/c']); }); + + test('template', function () { + + // simple + assert.strictEqual(labels.template('Foo Bar'), 'Foo Bar'); + assert.strictEqual(labels.template('Foo$()Bar'), 'FooBar'); + assert.strictEqual(labels.template('$FooBar'), ''); + assert.strictEqual(labels.template(')FooBar'), ')FooBar'); + assert.strictEqual(labels.template('Foo $(one) Bar', { one: 'value' }), 'Foo value Bar'); + assert.strictEqual(labels.template('Foo $(one) Bar $(two)', { one: 'value', two: 'other value' }), 'Foo value Bar other value'); + + // conditional separator + assert.strictEqual(labels.template('Foo$(separator)Bar'), 'FooBar'); + assert.strictEqual(labels.template('Foo$(separator)Bar', { separator: { label: ' - ' } }), 'FooBar'); + assert.strictEqual(labels.template('$(separator)Foo$(separator)Bar', { value: 'something', separator: { label: ' - ' } }), 'FooBar'); + assert.strictEqual(labels.template('$(value) Foo$(separator)Bar', { value: 'something', separator: { label: ' - ' } }), 'something FooBar'); + + // // real world example (macOS) + let t = '$(activeEditorName)$(separator)$(rootName)'; + assert.strictEqual(labels.template(t, { activeEditorName: '', rootName: '', separator: { label: ' - ' } }), ''); + assert.strictEqual(labels.template(t, { activeEditorName: '', rootName: 'root', separator: { label: ' - ' } }), 'root'); + assert.strictEqual(labels.template(t, { activeEditorName: 'markdown.txt', rootName: 'root', separator: { label: ' - ' } }), 'markdown.txt - root'); + + // // real world example (other) + t = '$(dirty)$(activeEditorName)$(separator)$(rootName)$(separator)$(appName)'; + assert.strictEqual(labels.template(t, { dirty: '', activeEditorName: '', rootName: '', appName: '', separator: { label: ' - ' } }), ''); + assert.strictEqual(labels.template(t, { dirty: '', activeEditorName: '', rootName: '', appName: 'Visual Studio Code', separator: { label: ' - ' } }), 'Visual Studio Code'); + assert.strictEqual(labels.template(t, { dirty: '', activeEditorName: '', rootName: 'monaco', appName: 'Visual Studio Code', separator: { label: ' - ' } }), 'monaco - Visual Studio Code'); + assert.strictEqual(labels.template(t, { dirty: '', activeEditorName: 'somefile.txt', rootName: 'monaco', appName: 'Visual Studio Code', separator: { label: ' - ' } }), 'somefile.txt - monaco - Visual Studio Code'); + assert.strictEqual(labels.template(t, { dirty: '* ', activeEditorName: 'somefile.txt', rootName: 'monaco', appName: 'Visual Studio Code', separator: { label: ' - ' } }), '* somefile.txt - monaco - Visual Studio Code'); + }); }); \ No newline at end of file diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts index ac2659b74a9..bec6e608239 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts @@ -18,31 +18,178 @@ import * as errors from 'vs/base/common/errors'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; import { IAction, Action } from 'vs/base/common/actions'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IIntegrityService } from 'vs/platform/integrity/common/integrity'; +import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService'; +import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { isMacintosh, isLinux } from 'vs/base/common/platform'; +import nls = require('vs/nls'); +import * as labels from 'vs/base/common/labels'; +import { EditorInput, toResource } from 'vs/workbench/common/editor'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; export class TitlebarPart extends Part implements ITitleService { public _serviceBrand: any; + private static NLS_UNSUPPORTED = nls.localize('patchedWindowTitle', "[Unsupported]"); + private static NLS_EXTENSION_HOST = nls.localize('devExtensionWindowTitlePrefix', "[Extension Development Host]"); + private static TITLE_DIRTY = '\u25cf '; + private static TITLE_SEPARATOR = ' - '; + private titleContainer: Builder; private title: Builder; private pendingTitle: string; private initialTitleFontSize: number; private representedFileName: string; + private titleTemplate: string; + private isPure: boolean; + private activeEditorListeners: IDisposable[]; + private workspacePath: string; + constructor( id: string, @IContextMenuService private contextMenuService: IContextMenuService, @IWindowService private windowService: IWindowService, - @IWindowsService private windowsService: IWindowsService + @IConfigurationService private configurationService: IConfigurationService, + @IWindowsService private windowsService: IWindowsService, + @IWorkbenchEditorService private editorService: IWorkbenchEditorService, + @IEditorGroupService private editorGroupService: IEditorGroupService, + @IIntegrityService private integrityService: IIntegrityService, + @IEnvironmentService private environmentService: IEnvironmentService, + @IWorkspaceContextService private contextService: IWorkspaceContextService ) { super(id, { hasTitle: false }); + this.isPure = true; + this.activeEditorListeners = []; + this.workspacePath = contextService.hasWorkspace() ? this.tildify(labels.getPathLabel(contextService.getWorkspace().resource)) : ''; + + this.init(); + this.registerListeners(); } + private init(): void { + + // Read initial config + this.onConfigurationChanged(); + + // Initial window title + this.setTitle(this.getWindowTitle()); + + // Integrity for window title + this.integrityService.isPure().then(r => { + if (!r.isPure) { + this.isPure = false; + this.setTitle(this.getWindowTitle()); + } + }); + } + private registerListeners(): void { this.toUnbind.push(DOM.addDisposableListener(window, DOM.EventType.BLUR, () => { if (this.titleContainer) { this.titleContainer.addClass('blurred'); } })); this.toUnbind.push(DOM.addDisposableListener(window, DOM.EventType.FOCUS, () => { if (this.titleContainer) { this.titleContainer.removeClass('blurred'); } })); + this.toUnbind.push(this.configurationService.onDidUpdateConfiguration(() => this.onConfigurationChanged(true))); + this.toUnbind.push(this.editorGroupService.onEditorsChanged(() => this.onEditorsChanged())); + } + + private onConfigurationChanged(update?: boolean): void { + const currentTitleTemplate = this.titleTemplate; + this.titleTemplate = this.configurationService.lookup('window.title').value; + + if (update && currentTitleTemplate !== this.titleTemplate) { + this.setTitle(this.getWindowTitle()); + } + } + + private onEditorsChanged(): void { + + // Dispose old listeners + dispose(this.activeEditorListeners); + this.activeEditorListeners = []; + + const activeEditor = this.editorService.getActiveEditor(); + const activeInput = activeEditor ? activeEditor.input : void 0; + + // Calculate New Window Title + this.setTitle(this.getWindowTitle()); + + // Apply listener for dirty and label changes + if (activeInput instanceof EditorInput) { + this.activeEditorListeners.push(activeInput.onDidChangeDirty(() => { + this.setTitle(this.getWindowTitle()); + })); + + this.activeEditorListeners.push(activeInput.onDidChangeLabel(() => { + this.setTitle(this.getWindowTitle()); + })); + } + } + + private getWindowTitle(): string { + let title = this.doGetWindowTitle(); + if (!title) { + title = this.environmentService.appNameLong; + } + + if (!this.isPure) { + title = `${title} ${TitlebarPart.NLS_UNSUPPORTED}`; + } + + // Extension Development Host gets a special title to identify itself + if (this.environmentService.isExtensionDevelopment) { + title = `${TitlebarPart.NLS_EXTENSION_HOST} - ${title}`; + } + + return title; + } + + /** + * Possible template values: + * + * {activeEditorName}: e.g. myFile.txt + * {activeFilePath}: e.g. /Users/Development/myProject/myFile.txt + * {rootName}: e.g. myProject + * {rootPath}: e.g. /Users/Development/myProject + * {appName}: e.g. VS Code + * {dirty}: indiactor + * {separator}: conditional separator + */ + private doGetWindowTitle(): string { + const input = this.editorService.getActiveEditorInput(); + const workspace = this.contextService.getWorkspace(); + const file = toResource(input, { filter: 'file' }); + + // Variables + const activeEditorName = input ? input.getName() : ''; + const activeFilePath = file ? this.tildify(labels.getPathLabel(file)) : ''; + const rootName = workspace ? workspace.name : ''; + const rootPath = workspace ? this.workspacePath : ''; + const dirty = input && input.isDirty() ? TitlebarPart.TITLE_DIRTY : ''; + const appName = this.environmentService.appNameLong; + const separator = TitlebarPart.TITLE_SEPARATOR; + + return labels.template(this.titleTemplate, { + activeEditorName, + activeFilePath, + rootName, + rootPath, + dirty, + appName, + separator: { label: separator } + }); + } + + private tildify(path: string): string { + if (path && (isMacintosh || isLinux) && path.indexOf(this.environmentService.userHome) === 0) { + path = `~${path.substr(this.environmentService.userHome.length)}`; + } + + return path; } public createContentArea(parent: Builder): Builder { @@ -127,7 +274,7 @@ export class TitlebarPart extends Part implements ITitleService { return actions; } - public updateTitle(title: string): void { + public setTitle(title: string): void { // Always set the native window title to identify us properly to the OS window.document.title = title; diff --git a/src/vs/workbench/electron-browser/main.contribution.ts b/src/vs/workbench/electron-browser/main.contribution.ts index 380f37d5ba5..94492772645 100644 --- a/src/vs/workbench/electron-browser/main.contribution.ts +++ b/src/vs/workbench/electron-browser/main.contribution.ts @@ -12,7 +12,7 @@ import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry'; import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actionRegistry'; import { KeyMod, KeyChord, KeyCode } from 'vs/base/common/keyCodes'; -import platform = require('vs/base/common/platform'); +import { isWindows, isLinux, isMacintosh } from 'vs/base/common/platform'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindings } from 'vs/platform/keybinding/common/keybinding'; import { KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry'; @@ -68,7 +68,7 @@ workbenchActionsRegistry.registerWorkbenchAction( workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(CloseMessagesAction, CloseMessagesAction.ID, CloseMessagesAction.LABEL, { primary: KeyCode.Escape, secondary: [KeyMod.Shift | KeyCode.Escape] }, MessagesVisibleContext), 'Close Notification Messages'); workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(CloseEditorAction, CloseEditorAction.ID, CloseEditorAction.LABEL, closeEditorOrWindowKeybindings), 'View: Close Editor', viewCategory); workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(ToggleFullScreenAction, ToggleFullScreenAction.ID, ToggleFullScreenAction.LABEL, { primary: KeyCode.F11, mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.KEY_F } }), 'View: Toggle Full Screen', viewCategory); -if (platform.isWindows || platform.isLinux) { +if (isWindows || isLinux) { workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(ToggleMenuBarAction, ToggleMenuBarAction.ID, ToggleMenuBarAction.LABEL), 'View: Toggle Menu Bar', viewCategory); } @@ -234,10 +234,18 @@ Note that there can still be cases where this setting is ignored (e.g. when usin 'default': 0, 'description': nls.localize('zoomLevel', "Adjust the zoom level of the window. The original size is 0 and each increment above (e.g. 1) or below (e.g. -1) represents zooming 20% larger or smaller. You can also enter decimals to adjust the zoom level with a finer granularity.") }, - 'window.showFullPath': { - 'type': 'boolean', - 'default': false, - 'description': nls.localize('showFullPath', "If enabled, will show the full path of opened files in the window title.") + 'window.title': { + 'type': 'string', + 'default': isMacintosh ? '$(activeEditorName)$(separator)$(rootName)' : '$(dirty)$(activeEditorName)$(separator)$(rootName)$(separator)$(appName)', + 'description': nls.localize('title', + `Controls the window title based on the active editor. Variables are substituted based on the context: +$(activeEditorName): e.g. myFile.txt +$(activeFilePath): e.g. /Users/Development/myProject/myFile.txt +$(rootName): e.g. myProject +$(rootPath): e.g. /Users/Development/myProject +$(appName): e.g. VS Code +$(dirty): a dirty indicator if the active editor is dirty +$(separator): a conditional separator (" - ") that only shows when surrounded by variables with values`) }, 'window.newWindowDimensions': { 'type': 'string', @@ -253,7 +261,7 @@ Note that there can still be cases where this setting is ignored (e.g. when usin }, }; -if (platform.isWindows || platform.isLinux) { +if (isWindows || isLinux) { properties['window.menuBarVisibility'] = { 'type': 'string', 'enum': ['default', 'visible', 'toggle', 'hidden'], @@ -268,7 +276,7 @@ if (platform.isWindows || platform.isLinux) { }; } -if (platform.isWindows) { +if (isWindows) { properties['window.autoDetectHighContrast'] = { 'type': 'boolean', 'default': true, @@ -276,7 +284,7 @@ if (platform.isWindows) { }; } -if (platform.isMacintosh) { +if (isMacintosh) { properties['window.titleBarStyle'] = { 'type': 'string', 'enum': ['native', 'custom'], diff --git a/src/vs/workbench/electron-browser/workbench.ts b/src/vs/workbench/electron-browser/workbench.ts index e5c45481559..922c4512fbc 100644 --- a/src/vs/workbench/electron-browser/workbench.ts +++ b/src/vs/workbench/electron-browser/workbench.ts @@ -449,12 +449,6 @@ export class Workbench implements IPartService { // Menus/Actions serviceCollection.set(IMenuService, new SyncDescriptor(MenuService)); - // Title bar - this.titlebarPart = this.instantiationService.createInstance(TitlebarPart, Identifiers.TITLEBAR_PART); - this.toDispose.push(this.titlebarPart); - this.toShutdown.push(this.titlebarPart); - serviceCollection.set(ITitleService, this.titlebarPart); - // Sidebar part this.sidebarPart = this.instantiationService.createInstance(SidebarPart, Identifiers.SIDEBAR_PART); this.toDispose.push(this.sidebarPart); @@ -484,6 +478,12 @@ export class Workbench implements IPartService { serviceCollection.set(IWorkbenchEditorService, this.editorService); serviceCollection.set(IEditorGroupService, this.editorPart); + // Title bar + this.titlebarPart = this.instantiationService.createInstance(TitlebarPart, Identifiers.TITLEBAR_PART); + this.toDispose.push(this.titlebarPart); + this.toShutdown.push(this.titlebarPart); + serviceCollection.set(ITitleService, this.titlebarPart); + // File Service const fileService = this.instantiationService.createInstance(FileService); serviceCollection.set(IFileService, fileService); diff --git a/src/vs/workbench/services/history/browser/history.ts b/src/vs/workbench/services/history/browser/history.ts index b11839ab752..430fe246d70 100644 --- a/src/vs/workbench/services/history/browser/history.ts +++ b/src/vs/workbench/services/history/browser/history.ts @@ -7,9 +7,6 @@ import { TPromise } from 'vs/base/common/winjs.base'; import errors = require('vs/base/common/errors'); -import platform = require('vs/base/common/platform'); -import nls = require('vs/nls'); -import labels = require('vs/base/common/labels'); import objects = require('vs/base/common/objects'); import URI from 'vs/base/common/uri'; import * as editorCommon from 'vs/editor/common/editorCommon'; @@ -27,9 +24,6 @@ import { Registry } from 'vs/platform/platform'; import { once } from 'vs/base/common/event'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { IIntegrityService } from 'vs/platform/integrity/common/integrity'; -import { ITitleService } from 'vs/workbench/services/title/common/titleService'; import { IWindowService } from 'vs/platform/windows/common/windows'; import { getCodeEditor } from 'vs/editor/common/services/codeEditorService'; import { getExcludes, ISearchConfiguration } from 'vs/platform/search/common/search'; @@ -82,37 +76,18 @@ export abstract class BaseHistoryService { protected toUnbind: IDisposable[]; private activeEditorListeners: IDisposable[]; - private isPure: boolean; - private showFullPath: boolean; protected excludes: ParsedExpression; private lastKnownExcludesConfig: IExpression; - private static NLS_UNSUPPORTED = nls.localize('patchedWindowTitle', "[Unsupported]"); - constructor( protected editorGroupService: IEditorGroupService, protected editorService: IWorkbenchEditorService, protected contextService: IWorkspaceContextService, - private configurationService: IConfigurationService, - private environmentService: IEnvironmentService, - integrityService: IIntegrityService, - private titleService: ITitleService + private configurationService: IConfigurationService ) { this.toUnbind = []; this.activeEditorListeners = []; - this.isPure = true; - - // Window Title - this.titleService.updateTitle(this.getWindowTitle(null)); - - // Integrity - integrityService.isPure().then(r => { - if (!r.isPure) { - this.isPure = false; - this.titleService.updateTitle(this.getWindowTitle(this.editorService.getActiveEditorInput())); - } - }); // Editor Input Changes this.toUnbind.push(this.editorGroupService.onEditorsChanged(() => this.onEditorsChanged())); @@ -123,13 +98,6 @@ export abstract class BaseHistoryService { } private onConfigurationChanged(update?: boolean): void { - const currentShowPath = this.showFullPath; - this.showFullPath = this.configurationService.lookup('window.showFullPath').value; - - if (update && currentShowPath !== this.showFullPath) { - this.updateWindowTitle(this.editorService.getActiveEditorInput()); - } - const excludesConfig = getExcludes(this.configurationService.getConfiguration()); if (!objects.equals(excludesConfig, this.lastKnownExcludesConfig)) { const configChanged = !!this.lastKnownExcludesConfig; @@ -150,21 +118,9 @@ export abstract class BaseHistoryService { this.activeEditorListeners = []; const activeEditor = this.editorService.getActiveEditor(); - const activeInput = activeEditor ? activeEditor.input : void 0; // Propagate to history - this.onEditorEvent(activeEditor); - - // Apply listener for dirty and label changes - if (activeInput instanceof EditorInput) { - this.activeEditorListeners.push(activeInput.onDidChangeDirty(() => { - this.updateWindowTitle(activeInput); // Calculate New Window Title when dirty state changes - })); - - this.activeEditorListeners.push(activeInput.onDidChangeLabel(() => { - this.updateWindowTitle(activeInput); // Calculate New Window Title when label changes - })); - } + this.handleActiveEditorChange(activeEditor); // Apply listener for selection changes if this is a text editor const control = getCodeEditor(activeEditor); @@ -175,97 +131,12 @@ export abstract class BaseHistoryService { } } - private onEditorEvent(editor: IBaseEditor): void { - const input = editor ? editor.input : null; - - // Calculate New Window Title - this.updateWindowTitle(input); - - // Delegate to implementors - this.handleActiveEditorChange(editor); - } - - private updateWindowTitle(input?: IEditorInput): void { - let windowTitle: string = null; - if (input && input.getName()) { - windowTitle = this.getWindowTitle(input); - } else { - windowTitle = this.getWindowTitle(null); - } - - this.titleService.updateTitle(windowTitle); - } - protected abstract handleExcludesChange(): void; protected abstract handleEditorSelectionChangeEvent(editor?: IBaseEditor): void; protected abstract handleActiveEditorChange(editor?: IBaseEditor): void; - protected getWindowTitle(input?: IEditorInput): string { - let title = this.doGetWindowTitle(input); - if (!this.isPure) { - title = `${title} ${BaseHistoryService.NLS_UNSUPPORTED}`; - } - - // Extension Development Host gets a special title to identify itself - if (this.environmentService.isExtensionDevelopment) { - return nls.localize('devExtensionWindowTitle', "[Extension Development Host] - {0}", title); - } - - return title; - } - - private doGetWindowTitle(input?: IEditorInput): string { - const appName = this.environmentService.appNameLong; - - let prefix: string; - const file = toResource(input, { filter: 'file' }); - if (file && this.showFullPath) { - prefix = labels.getPathLabel(file); - if ((platform.isMacintosh || platform.isLinux) && prefix.indexOf(this.environmentService.userHome) === 0) { - prefix = `~${prefix.substr(this.environmentService.userHome.length)}`; - } - } else { - prefix = input && input.getName(); - } - - if (prefix && input) { - if (input.isDirty() && !platform.isMacintosh /* Mac has its own decoration in window */) { - prefix = nls.localize('prefixDecoration', "\u25cf {0}", prefix); - } - } - - const workspace = this.contextService.getWorkspace(); - if (workspace) { - const wsName = workspace.name; - - if (prefix) { - if (platform.isMacintosh) { - return nls.localize('prefixWorkspaceTitleMac', "{0} - {1}", prefix, wsName); // Mac: do not append base title - } - - return nls.localize('prefixWorkspaceTitle', "{0} - {1} - {2}", prefix, wsName, appName); - } - - if (platform.isMacintosh) { - return wsName; // Mac: do not append base title - } - - return nls.localize('workspaceTitle', "{0} - {1}", wsName, appName); - } - - if (prefix) { - if (platform.isMacintosh) { - return prefix; // Mac: do not append base title - } - - return nls.localize('prefixTitle', "{0} - {1}", prefix, appName); - } - - return appName; - } - public dispose(): void { this.toUnbind = dispose(this.toUnbind); } @@ -305,17 +176,14 @@ export class HistoryService extends BaseHistoryService implements IHistoryServic constructor( @IWorkbenchEditorService editorService: IWorkbenchEditorService, @IEditorGroupService editorGroupService: IEditorGroupService, - @IEnvironmentService environmentService: IEnvironmentService, @IWorkspaceContextService contextService: IWorkspaceContextService, @IStorageService private storageService: IStorageService, @IConfigurationService configurationService: IConfigurationService, @ILifecycleService private lifecycleService: ILifecycleService, - @IIntegrityService integrityService: IIntegrityService, - @ITitleService titleService: ITitleService, @IFileService private fileService: IFileService, @IWindowService private windowService: IWindowService ) { - super(editorGroupService, editorService, contextService, configurationService, environmentService, integrityService, titleService); + super(editorGroupService, editorService, contextService, configurationService); this.index = -1; this.stack = []; diff --git a/src/vs/workbench/services/title/common/titleService.ts b/src/vs/workbench/services/title/common/titleService.ts index 2fea00759b7..3a74b1efe28 100644 --- a/src/vs/workbench/services/title/common/titleService.ts +++ b/src/vs/workbench/services/title/common/titleService.ts @@ -12,9 +12,9 @@ export interface ITitleService { _serviceBrand: any; /** - * Update the window title with the given value. + * Set the window title with the given value. */ - updateTitle(title: string): void; + setTitle(title: string): void; /** * Set the represented file name to the title if any.