mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-17 11:01:37 -05:00
Add tab coloring by server group (#383)
This commit is contained in:
2
.vscode/launch.json
vendored
2
.vscode/launch.json
vendored
@@ -95,7 +95,7 @@
|
|||||||
"name": "Unit Tests",
|
"name": "Unit Tests",
|
||||||
"protocol": "inspector",
|
"protocol": "inspector",
|
||||||
"program": "${workspaceFolder}/node_modules/mocha/bin/_mocha",
|
"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": {
|
"windows": {
|
||||||
"runtimeExecutable": "${workspaceFolder}/.build/electron/sqlops.exe"
|
"runtimeExecutable": "${workspaceFolder}/.build/electron/sqlops.exe"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -220,6 +220,8 @@ export interface IConnectionManagementService {
|
|||||||
|
|
||||||
canChangeConnectionConfig(profile: ConnectionProfile, newGroupID: string): boolean;
|
canChangeConnectionConfig(profile: ConnectionProfile, newGroupID: string): boolean;
|
||||||
|
|
||||||
|
getTabColorForUri(uri: string): string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends a notification that the language flavor for a given URI has changed.
|
* Sends a notification that the language flavor for a given URI has changed.
|
||||||
* For SQL, this would be the specific SQL implementation being used.
|
* For SQL, this would be the specific SQL implementation being used.
|
||||||
|
|||||||
@@ -148,6 +148,11 @@ export class ConnectionManagementService implements IConnectionManagementService
|
|||||||
|
|
||||||
this.disposables.push(this._onAddConnectionProfile);
|
this.disposables.push(this._onAddConnectionProfile);
|
||||||
this.disposables.push(this._onDeleteConnectionProfile);
|
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
|
// Event Emitters
|
||||||
@@ -1219,6 +1224,7 @@ export class ConnectionManagementService implements IConnectionManagementService
|
|||||||
public editGroup(group: ConnectionProfileGroup): Promise<any> {
|
public editGroup(group: ConnectionProfileGroup): Promise<any> {
|
||||||
return new Promise<string>((resolve, reject) => {
|
return new Promise<string>((resolve, reject) => {
|
||||||
this._connectionStore.editGroup(group).then(groupId => {
|
this._connectionStore.editGroup(group).then(groupId => {
|
||||||
|
this.refreshEditorTitles();
|
||||||
this._onAddConnectionProfile.fire();
|
this._onAddConnectionProfile.fire();
|
||||||
resolve(null);
|
resolve(null);
|
||||||
}).catch(err => {
|
}).catch(err => {
|
||||||
@@ -1323,4 +1329,25 @@ export class ConnectionManagementService implements IConnectionManagementService
|
|||||||
}
|
}
|
||||||
return Promise.reject('The given URI is not currently connected');
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ import { ConfigurationEditingService } from 'vs/workbench/services/configuration
|
|||||||
import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration';
|
import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration';
|
||||||
import { ICapabilitiesService } from 'sql/services/capabilities/capabilitiesService';
|
import { ICapabilitiesService } from 'sql/services/capabilities/capabilitiesService';
|
||||||
import * as data from 'data';
|
import * as data from 'data';
|
||||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
|
||||||
|
|
||||||
const MAX_CONNECTIONS_DEFAULT = 25;
|
const MAX_CONNECTIONS_DEFAULT = 25;
|
||||||
|
|
||||||
@@ -490,6 +489,11 @@ export class ConnectionStore {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getGroupFromId(groupId: string): IConnectionProfileGroup {
|
||||||
|
let groups = this._connectionConfig.getAllGroups();
|
||||||
|
return groups.find(group => group.id === groupId);
|
||||||
|
}
|
||||||
|
|
||||||
private getMaxRecentConnectionsCount(): number {
|
private getMaxRecentConnectionsCount(): number {
|
||||||
let config = this._workspaceConfigurationService.getConfiguration(Constants.sqlConfigSectionName);
|
let config = this._workspaceConfigurationService.getConfiguration(Constants.sqlConfigSectionName);
|
||||||
|
|
||||||
|
|||||||
@@ -167,4 +167,8 @@ export class DashboardInput extends EditorInput {
|
|||||||
&& profile1.authenticationType === profile2.authenticationType
|
&& profile1.authenticationType === profile2.authenticationType
|
||||||
&& profile1.groupFullName === profile2.groupFullName;
|
&& profile1.groupFullName === profile2.groupFullName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public get tabColor(): string {
|
||||||
|
return this._connectionService.getTabColorForUri(this.uri);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -180,4 +180,8 @@ export class EditDataInput extends EditorInput implements IConnectableInput {
|
|||||||
super.close();
|
super.close();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public get tabColor(): string {
|
||||||
|
return this._connectionManagementService.getTabColorForUri(this.uri);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -240,6 +240,11 @@ let registryProperties = {
|
|||||||
'description': localize('sql.showBatchTime', '[Optional] Should execution time be shown for individual batches'),
|
'description': localize('sql.showBatchTime', '[Optional] Should execution time be shown for individual batches'),
|
||||||
'default': false
|
'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': {
|
'mssql.intelliSense.enableIntelliSense': {
|
||||||
'type': 'boolean',
|
'type': 'boolean',
|
||||||
'default': true,
|
'default': true,
|
||||||
|
|||||||
@@ -252,4 +252,11 @@ export class QueryInput extends EditorInput implements IEncodingSupport, IConnec
|
|||||||
this._currentEventCallbacks = dispose(this._currentEventCallbacks);
|
this._currentEventCallbacks = dispose(this._currentEventCallbacks);
|
||||||
this._currentEventCallbacks = callbacks;
|
this._currentEventCallbacks = callbacks;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the color that should be displayed
|
||||||
|
*/
|
||||||
|
public get tabColor(): string {
|
||||||
|
return this._connectionManagementService.getTabColorForUri(this.uri);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -32,6 +32,7 @@ import { WorkspaceConfigurationTestService } from 'sqltest/stubs/workspaceConfig
|
|||||||
|
|
||||||
import * as assert from 'assert';
|
import * as assert from 'assert';
|
||||||
import * as TypeMoq from 'typemoq';
|
import * as TypeMoq from 'typemoq';
|
||||||
|
import { IConnectionProfileGroup } from 'sql/parts/connection/common/connectionProfileGroup';
|
||||||
|
|
||||||
suite('SQL ConnectionManagementService tests', () => {
|
suite('SQL ConnectionManagementService tests', () => {
|
||||||
|
|
||||||
@@ -210,7 +211,7 @@ suite('SQL ConnectionManagementService tests', () => {
|
|||||||
let connectionToUse = connection ? connection : connectionProfile;
|
let connectionToUse = connection ? connection : connectionProfile;
|
||||||
return new Promise<IConnectionResult>((resolve, reject) => {
|
return new Promise<IConnectionResult>((resolve, reject) => {
|
||||||
let id = connectionToUse.getOptionsKey();
|
let id = connectionToUse.getOptionsKey();
|
||||||
let defaultUri = 'connection://' + (id ? id : connection.serverName + ':' + connection.databaseName);
|
let defaultUri = 'connection://' + (id ? id : connectionToUse.serverName + ':' + connectionToUse.databaseName);
|
||||||
connectionManagementService.onConnectionRequestSent(() => {
|
connectionManagementService.onConnectionRequestSent(() => {
|
||||||
let info: data.ConnectionInfoSummary = {
|
let info: data.ConnectionInfoSummary = {
|
||||||
connectionId: error ? undefined : 'id',
|
connectionId: error ? undefined : 'id',
|
||||||
@@ -290,7 +291,7 @@ suite('SQL ConnectionManagementService tests', () => {
|
|||||||
}).catch(err => {
|
}).catch(err => {
|
||||||
done(err);
|
done(err);
|
||||||
});
|
});
|
||||||
});
|
}, err => done(err));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('connect should save profile given options with saveProfile set to true', done => {
|
test('connect should save profile given options with saveProfile set to true', done => {
|
||||||
@@ -764,4 +765,29 @@ suite('SQL ConnectionManagementService tests', () => {
|
|||||||
}
|
}
|
||||||
}, err => done(err));
|
}, 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));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
@@ -18,7 +18,7 @@ import { CapabilitiesService } from 'sql/services/capabilities/capabilitiesServi
|
|||||||
import * as data from 'data';
|
import * as data from 'data';
|
||||||
import { ConnectionProfile } from 'sql/parts/connection/common/connectionProfile';
|
import { ConnectionProfile } from 'sql/parts/connection/common/connectionProfile';
|
||||||
import { Emitter } from 'vs/base/common/event';
|
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', () => {
|
suite('SQL ConnectionStore tests', () => {
|
||||||
let defaultNamedProfile: IConnectionProfile;
|
let defaultNamedProfile: IConnectionProfile;
|
||||||
@@ -93,7 +93,7 @@ suite('SQL ConnectionStore tests', () => {
|
|||||||
getInstalled: () => {
|
getInstalled: () => {
|
||||||
return Promise.resolve([]);
|
return Promise.resolve([]);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
capabilitiesService = TypeMoq.Mock.ofType(CapabilitiesService, TypeMoq.MockBehavior.Loose, extensionManagementServiceMock, {});
|
capabilitiesService = TypeMoq.Mock.ofType(CapabilitiesService, TypeMoq.MockBehavior.Loose, extensionManagementServiceMock, {});
|
||||||
let capabilities: data.DataProtocolServerCapabilities[] = [];
|
let capabilities: data.DataProtocolServerCapabilities[] = [];
|
||||||
@@ -462,4 +462,33 @@ suite('SQL ConnectionStore tests', () => {
|
|||||||
currentList = connectionStore.getConnectionsFromMemento(mementoKey);
|
currentList = connectionStore.getConnectionsFromMemento(mementoKey);
|
||||||
assert.equal(currentList.length, 3, 'Adding same connection with group /');
|
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');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
@@ -237,4 +237,8 @@ export class TestConnectionManagementService implements IConnectionManagementSer
|
|||||||
rebuildIntelliSenseCache(uri: string): Thenable<void> {
|
rebuildIntelliSenseCache(uri: string): Thenable<void> {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getTabColorForUri(uri: string): string {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -87,6 +87,8 @@ export interface IEditorGroupsControl {
|
|||||||
|
|
||||||
getRatio(): number[];
|
getRatio(): number[];
|
||||||
|
|
||||||
|
// {{SQL CARBON EDIT}} -- Allow editor titles to be refreshed to support tab coloring
|
||||||
|
refreshTitles(): void;
|
||||||
|
|
||||||
dispose(): 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 {
|
public dispose(): void {
|
||||||
super.dispose();
|
super.dispose();
|
||||||
|
|
||||||
|
|||||||
@@ -1359,6 +1359,11 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupService
|
|||||||
return sizes;
|
return sizes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// {{SQL CARBON EDIT}} -- Allow editor titles to be refreshed to support tab coloring
|
||||||
|
public refreshEditorTitles(): void {
|
||||||
|
this.editorGroupsControl.refreshTitles();
|
||||||
|
}
|
||||||
|
|
||||||
public shutdown(): void {
|
public shutdown(): void {
|
||||||
|
|
||||||
// Persist UI State
|
// Persist UI State
|
||||||
|
|||||||
@@ -39,12 +39,15 @@ import { ScrollbarVisibility } from 'vs/base/common/scrollable';
|
|||||||
import { extractResources } from 'vs/base/browser/dnd';
|
import { extractResources } from 'vs/base/browser/dnd';
|
||||||
import { getOrSet } from 'vs/base/common/map';
|
import { getOrSet } from 'vs/base/common/map';
|
||||||
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
|
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 { 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 { activeContrastBorder, contrastBorder } from 'vs/platform/theme/common/colorRegistry';
|
||||||
import { IFileService } from 'vs/platform/files/common/files';
|
import { IFileService } from 'vs/platform/files/common/files';
|
||||||
import { IWorkspacesService } from 'vs/platform/workspaces/common/workspaces';
|
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 {
|
interface IEditorInputLabel {
|
||||||
name: string;
|
name: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
@@ -330,6 +333,20 @@ export class TabsTitleControl extends TitleControl {
|
|||||||
} else {
|
} else {
|
||||||
DOM.removeClass(tabContainer, 'dirty');
|
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 = '';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user