Add tab coloring by server group (#383)

This commit is contained in:
Matt Irvine
2017-12-20 18:21:48 -08:00
committed by GitHub
parent 3ffafbe5bc
commit b1b3a92717
14 changed files with 152 additions and 8 deletions

4
.vscode/launch.json vendored
View File

@@ -63,7 +63,7 @@
"outFiles": [
"${workspaceFolder}/out/**/*.js"
]
},
},
{
"type": "chrome",
"request": "attach",
@@ -95,7 +95,7 @@
"name": "Unit Tests",
"protocol": "inspector",
"program": "${workspaceFolder}/node_modules/mocha/bin/_mocha",
"runtimeExecutable": "${workspaceFolder}/.build/electron/sqlops.app/Contents/MacOS/Electron",
"runtimeExecutable": "${workspaceFolder}/.build/electron/SQL Operations Studio.app/Contents/MacOS/Electron",
"windows": {
"runtimeExecutable": "${workspaceFolder}/.build/electron/sqlops.exe"
},

View File

@@ -220,6 +220,8 @@ export interface IConnectionManagementService {
canChangeConnectionConfig(profile: ConnectionProfile, newGroupID: string): boolean;
getTabColorForUri(uri: string): string;
/**
* Sends a notification that the language flavor for a given URI has changed.
* For SQL, this would be the specific SQL implementation being used.

View File

@@ -148,6 +148,11 @@ export class ConnectionManagementService implements IConnectionManagementService
this.disposables.push(this._onAddConnectionProfile);
this.disposables.push(this._onDeleteConnectionProfile);
// Refresh editor titles when connections start/end/change to ensure tabs are colored correctly
this.onConnectionChanged(() => this.refreshEditorTitles());
this.onConnect(() => this.refreshEditorTitles());
this.onDisconnect(() => this.refreshEditorTitles());
}
// Event Emitters
@@ -1219,6 +1224,7 @@ export class ConnectionManagementService implements IConnectionManagementService
public editGroup(group: ConnectionProfileGroup): Promise<any> {
return new Promise<string>((resolve, reject) => {
this._connectionStore.editGroup(group).then(groupId => {
this.refreshEditorTitles();
this._onAddConnectionProfile.fire();
resolve(null);
}).catch(err => {
@@ -1323,4 +1329,25 @@ export class ConnectionManagementService implements IConnectionManagementService
}
return Promise.reject('The given URI is not currently connected');
}
public getTabColorForUri(uri: string): string {
if (!WorkbenchUtils.getSqlConfigValue<string>(this._workspaceConfigurationService, 'enableTabColors')) {
return undefined;
}
let connectionProfile = this.getConnectionProfile(uri);
if (!connectionProfile) {
return undefined;
}
let matchingGroup = this._connectionStore.getGroupFromId(connectionProfile.groupId);
if (!matchingGroup) {
return undefined;
}
return matchingGroup.color;
}
private refreshEditorTitles(): void {
if (this._editorGroupService instanceof EditorPart) {
this._editorGroupService.refreshEditorTitles();
}
}
}

View File

@@ -19,7 +19,6 @@ import { ConfigurationEditingService } from 'vs/workbench/services/configuration
import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration';
import { ICapabilitiesService } from 'sql/services/capabilities/capabilitiesService';
import * as data from 'data';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
const MAX_CONNECTIONS_DEFAULT = 25;
@@ -490,6 +489,11 @@ export class ConnectionStore {
return result;
}
public getGroupFromId(groupId: string): IConnectionProfileGroup {
let groups = this._connectionConfig.getAllGroups();
return groups.find(group => group.id === groupId);
}
private getMaxRecentConnectionsCount(): number {
let config = this._workspaceConfigurationService.getConfiguration(Constants.sqlConfigSectionName);

View File

@@ -167,4 +167,8 @@ export class DashboardInput extends EditorInput {
&& profile1.authenticationType === profile2.authenticationType
&& profile1.groupFullName === profile2.groupFullName;
}
public get tabColor(): string {
return this._connectionService.getTabColorForUri(this.uri);
}
}

View File

@@ -180,4 +180,8 @@ export class EditDataInput extends EditorInput implements IConnectableInput {
super.close();
});
}
public get tabColor(): string {
return this._connectionManagementService.getTabColorForUri(this.uri);
}
}

View File

@@ -240,6 +240,11 @@ let registryProperties = {
'description': localize('sql.showBatchTime', '[Optional] Should execution time be shown for individual batches'),
'default': false
},
'sql.enableTabColors': {
'type': 'boolean',
'description': localize('sql.enableTabColors', 'True to color tabs based on the server group of their active connection, false otherwise'),
'default': true
},
'mssql.intelliSense.enableIntelliSense': {
'type': 'boolean',
'default': true,

View File

@@ -252,4 +252,11 @@ export class QueryInput extends EditorInput implements IEncodingSupport, IConnec
this._currentEventCallbacks = dispose(this._currentEventCallbacks);
this._currentEventCallbacks = callbacks;
}
/**
* Get the color that should be displayed
*/
public get tabColor(): string {
return this._connectionManagementService.getTabColorForUri(this.uri);
}
}

View File

@@ -32,6 +32,7 @@ import { WorkspaceConfigurationTestService } from 'sqltest/stubs/workspaceConfig
import * as assert from 'assert';
import * as TypeMoq from 'typemoq';
import { IConnectionProfileGroup } from 'sql/parts/connection/common/connectionProfileGroup';
suite('SQL ConnectionManagementService tests', () => {
@@ -210,7 +211,7 @@ suite('SQL ConnectionManagementService tests', () => {
let connectionToUse = connection ? connection : connectionProfile;
return new Promise<IConnectionResult>((resolve, reject) => {
let id = connectionToUse.getOptionsKey();
let defaultUri = 'connection://' + (id ? id : connection.serverName + ':' + connection.databaseName);
let defaultUri = 'connection://' + (id ? id : connectionToUse.serverName + ':' + connectionToUse.databaseName);
connectionManagementService.onConnectionRequestSent(() => {
let info: data.ConnectionInfoSummary = {
connectionId: error ? undefined : 'id',
@@ -290,7 +291,7 @@ suite('SQL ConnectionManagementService tests', () => {
}).catch(err => {
done(err);
});
});
}, err => done(err));
});
test('connect should save profile given options with saveProfile set to true', done => {
@@ -764,4 +765,29 @@ suite('SQL ConnectionManagementService tests', () => {
}
}, err => done(err));
});
test('getTabColorForUri returns undefined when there is no connection for the given URI', () => {
let connectionManagementService = createConnectionManagementService();
let color = connectionManagementService.getTabColorForUri('invalidUri');
assert.equal(color, undefined);
});
test('getTabColorForUri returns the group color corresponding to the connection for a URI', done => {
// Set up the connection store to give back a group for the expected connection profile
configResult['enableTabColors'] = true;
let expectedColor = 'red';
connectionStore.setup(x => x.getGroupFromId(connectionProfile.groupId)).returns(() => <IConnectionProfileGroup> {
color: expectedColor
});
let uri = 'testUri';
connect(uri).then(() => {
try {
let tabColor = connectionManagementService.getTabColorForUri(uri);
assert.equal(tabColor, expectedColor);
done();
} catch (e) {
done(e);
}
}, err => done(err));
});
});

View File

@@ -18,7 +18,7 @@ import { CapabilitiesService } from 'sql/services/capabilities/capabilitiesServi
import * as data from 'data';
import { ConnectionProfile } from 'sql/parts/connection/common/connectionProfile';
import { Emitter } from 'vs/base/common/event';
import { IConnectionProfileGroup } from 'sql/parts/connection/common/connectionProfileGroup';
import { ConnectionProfileGroup, IConnectionProfileGroup } from 'sql/parts/connection/common/connectionProfileGroup';
suite('SQL ConnectionStore tests', () => {
let defaultNamedProfile: IConnectionProfile;
@@ -93,7 +93,7 @@ suite('SQL ConnectionStore tests', () => {
getInstalled: () => {
return Promise.resolve([]);
}
}
};
capabilitiesService = TypeMoq.Mock.ofType(CapabilitiesService, TypeMoq.MockBehavior.Loose, extensionManagementServiceMock, {});
let capabilities: data.DataProtocolServerCapabilities[] = [];
@@ -462,4 +462,33 @@ suite('SQL ConnectionStore tests', () => {
currentList = connectionStore.getConnectionsFromMemento(mementoKey);
assert.equal(currentList.length, 3, 'Adding same connection with group /');
});
test('getGroupFromId returns undefined when there is no group with the given ID', () => {
let connectionStore = new ConnectionStore(storageServiceMock.object, context.object, undefined, workspaceConfigurationServiceMock.object,
credentialStore.object, capabilitiesService.object, connectionConfig.object);
let group = connectionStore.getGroupFromId('invalidId');
assert.equal(group, undefined, 'Returned group was not undefined when there was no group with the given ID');
});
test('getGroupFromId returns the group that has the given ID', () => {
// Set up the server groups with an additional group that contains a child group
let groups: IConnectionProfileGroup[] = connectionConfig.object.getAllGroups();
let parentGroupId = 'parentGroup';
let childGroupId = 'childGroup';
let parentGroup = new ConnectionProfileGroup(parentGroupId, undefined, parentGroupId, '', '');
let childGroup = new ConnectionProfileGroup(childGroupId, parentGroup, childGroupId, '', '');
groups.push(parentGroup, childGroup);
let newConnectionConfig = TypeMoq.Mock.ofType(ConnectionConfig);
newConnectionConfig.setup(x => x.getAllGroups()).returns(() => groups);
let connectionStore = new ConnectionStore(storageServiceMock.object, context.object, undefined, workspaceConfigurationServiceMock.object,
credentialStore.object, capabilitiesService.object, newConnectionConfig.object);
// If I look up the parent group using its ID, then I get back the correct group
let actualGroup = connectionStore.getGroupFromId(parentGroupId);
assert.equal(actualGroup.id, parentGroupId, 'Did not get the parent group when looking it up with its ID');
// If I look up the child group using its ID, then I get back the correct group
actualGroup = connectionStore.getGroupFromId(childGroupId);
assert.equal(actualGroup.id, childGroupId, 'Did not get the child group when looking it up with its ID');
});
});

View File

@@ -237,4 +237,8 @@ export class TestConnectionManagementService implements IConnectionManagementSer
rebuildIntelliSenseCache(uri: string): Thenable<void> {
return undefined;
}
getTabColorForUri(uri: string): string {
return undefined;
}
}

View File

@@ -87,6 +87,8 @@ export interface IEditorGroupsControl {
getRatio(): number[];
// {{SQL CARBON EDIT}} -- Allow editor titles to be refreshed to support tab coloring
refreshTitles(): void;
dispose(): void;
}
@@ -2126,6 +2128,14 @@ export class EditorGroupsControl extends Themable implements IEditorGroupsContro
}
}
// {{SQL CARBON EDIT}} -- Allow editor titles to be refreshed to support tab coloring
public refreshTitles(): void {
POSITIONS.forEach(position => {
let titleControl = this.getTitleAreaControl(position);
titleControl.refresh();
});
}
public dispose(): void {
super.dispose();

View File

@@ -1359,6 +1359,11 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupService
return sizes;
}
// {{SQL CARBON EDIT}} -- Allow editor titles to be refreshed to support tab coloring
public refreshEditorTitles(): void {
this.editorGroupsControl.refreshTitles();
}
public shutdown(): void {
// Persist UI State

View File

@@ -39,12 +39,15 @@ import { ScrollbarVisibility } from 'vs/base/common/scrollable';
import { extractResources } from 'vs/base/browser/dnd';
import { getOrSet } from 'vs/base/common/map';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService';
import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector, HIGH_CONTRAST } from 'vs/platform/theme/common/themeService';
import { TAB_INACTIVE_BACKGROUND, TAB_ACTIVE_BACKGROUND, TAB_ACTIVE_FOREGROUND, TAB_INACTIVE_FOREGROUND, TAB_BORDER, EDITOR_DRAG_AND_DROP_BACKGROUND, TAB_UNFOCUSED_ACTIVE_FOREGROUND, TAB_UNFOCUSED_INACTIVE_FOREGROUND, TAB_UNFOCUSED_ACTIVE_BORDER, TAB_ACTIVE_BORDER } from 'vs/workbench/common/theme';
import { activeContrastBorder, contrastBorder } from 'vs/platform/theme/common/colorRegistry';
import { IFileService } from 'vs/platform/files/common/files';
import { IWorkspacesService } from 'vs/platform/workspaces/common/workspaces';
// {{SQL CARBON EDIT}} -- Display the editor's tab color
import { Color } from 'vs/base/common/color';
interface IEditorInputLabel {
name: string;
description?: string;
@@ -330,6 +333,20 @@ export class TabsTitleControl extends TitleControl {
} else {
DOM.removeClass(tabContainer, 'dirty');
}
// {{SQL CARBON EDIT}} -- Display the editor's tab color
let sqlEditor = editor as any;
if (sqlEditor.tabColor && this.themeService.getTheme().type !== HIGH_CONTRAST) {
tabContainer.style.borderTopColor = sqlEditor.tabColor;
tabContainer.style.borderTopWidth = isTabActive ? '2px' : '1px';
let backgroundColor = Color.Format.CSS.parseHex(sqlEditor.tabColor);
if (backgroundColor) {
tabContainer.style.backgroundColor = backgroundColor.transparent(isTabActive ? 0.3 : 0.2).toString();
}
} else {
tabContainer.style.borderTopColor = '';
tabContainer.style.borderTopWidth = '';
}
}
});