mirror of
https://github.com/microsoft/vscode.git
synced 2026-10-05 15:36:22 +01:00
1 parent
c1c0b65e28
commit
693de57e87
9 files changed
+347
-154
No files matched your search
@@ -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
|
||||
]
|
||||
});
|
||||
|
||||
@@ -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<any> {
|
||||
return null;
|
||||
}
|
||||
|
||||
public collectPropertyCompletions(resource: string, location: JSONPath, currentWord: string, addValue: boolean, isLast: boolean, result: CompletionsCollector): Thenable<any> {
|
||||
return null;
|
||||
}
|
||||
|
||||
public collectValueCompletions(resource: string, location: JSONPath, currentKey: string, result: CompletionsCollector): Thenable<any> {
|
||||
return null;
|
||||
}
|
||||
|
||||
public getInfoContribution(resource: string, location: JSONPath): Thenable<MarkedString[]> {
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -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('');
|
||||
}
|
||||
@@ -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');
|
||||
});
|
||||
});
|
||||
@@ -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<string>('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;
|
||||
|
||||
@@ -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'],
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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<boolean>('window.showFullPath').value;
|
||||
|
||||
if (update && currentShowPath !== this.showFullPath) {
|
||||
this.updateWindowTitle(this.editorService.getActiveEditorInput());
|
||||
}
|
||||
|
||||
const excludesConfig = getExcludes(this.configurationService.getConfiguration<ISearchConfiguration>());
|
||||
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 = [];
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in new issue
Block a user