Show current connection info in the status bar (#551)

This commit is contained in:
Matt Irvine
2018-01-26 14:31:49 -08:00
committed by GitHub
parent f640bda802
commit 9a1ac20710
5 changed files with 58 additions and 102 deletions

View File

@@ -4,51 +4,29 @@
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import { $, append, show, hide } from 'vs/base/browser/dom'; import { $, append, show, hide } from 'vs/base/browser/dom';
import { IDisposable, combinedDisposable } from 'vs/base/common/lifecycle'; import { IDisposable, combinedDisposable } from 'vs/base/common/lifecycle';
import URI from 'vs/base/common/uri';
import { IEditorInput } from 'vs/platform/editor/common/editor';
import { IStatusbarItem } from 'vs/workbench/browser/parts/statusbar/statusbar'; import { IStatusbarItem } from 'vs/workbench/browser/parts/statusbar/statusbar';
import { IEditorCloseEvent } from 'vs/workbench/common/editor';
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService'; import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService';
import { IConnectionManagementService, IConnectionParams } from 'sql/parts/connection/common/connectionManagement'; import { IConnectionManagementService } from 'sql/parts/connection/common/connectionManagement';
import { ConnectionStatusManager } from 'sql/parts/connection/common/connectionStatusManager';
import { ICapabilitiesService } from 'sql/services/capabilities/capabilitiesService'; import { ICapabilitiesService } from 'sql/services/capabilities/capabilitiesService';
import { QueryInput } from 'sql/parts/query/common/queryInput';
import { IConnectionProfile } from 'sql/parts/connection/common/interfaces'; import { IConnectionProfile } from 'sql/parts/connection/common/interfaces';
import * as WorkbenchUtils from 'sql/workbench/common/sqlWorkbenchUtils'; import { IObjectExplorerService } from 'sql/parts/registeredServer/common/objectExplorerService';
import * as TaskUtilities from 'sql/workbench/common/taskUtilities';
enum ConnectionActivityStatus { // Connection status bar showing the current global connection
Connected,
Disconnected
}
// Contains connection status for each editor
class ConnectionStatusEditor {
public connectionActivityStatus: ConnectionActivityStatus;
public connectionProfile: IConnectionProfile;
constructor() {
this.connectionActivityStatus = ConnectionActivityStatus.Disconnected;
}
}
// Connection status bar for editor
export class ConnectionStatusbarItem implements IStatusbarItem { export class ConnectionStatusbarItem implements IStatusbarItem {
private _element: HTMLElement; private _element: HTMLElement;
private _connectionElement: HTMLElement; private _connectionElement: HTMLElement;
private _connectionStatusEditors: { [connectionUri: string]: ConnectionStatusEditor };
private _toDispose: IDisposable[]; private _toDispose: IDisposable[];
private _connectionStatusManager: ConnectionStatusManager;
constructor( constructor(
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService, @IConnectionManagementService private _connectionManagementService: IConnectionManagementService,
@IEditorGroupService private _editorGroupService: IEditorGroupService, @IEditorGroupService private _editorGroupService: IEditorGroupService,
@IWorkbenchEditorService private _editorService: IWorkbenchEditorService, @IWorkbenchEditorService private _editorService: IWorkbenchEditorService,
@ICapabilitiesService private _capabilitiesService: ICapabilitiesService, @ICapabilitiesService private _capabilitiesService: ICapabilitiesService,
@IObjectExplorerService private _objectExplorerService: IObjectExplorerService,
) { ) {
this._connectionStatusEditors = {};
this._connectionStatusManager = new ConnectionStatusManager(this._capabilitiesService);
} }
public render(container: HTMLElement): IDisposable { public render(container: HTMLElement): IDisposable {
@@ -58,80 +36,25 @@ export class ConnectionStatusbarItem implements IStatusbarItem {
this._toDispose = []; this._toDispose = [];
this._toDispose.push( this._toDispose.push(
this._connectionManagementService.onConnect((connectionUri: IConnectionParams) => this._onConnect(connectionUri)), this._connectionManagementService.onConnect(() => this._updateStatus()),
this._connectionManagementService.onConnectionChanged((connectionUri: IConnectionParams) => this._onConnect(connectionUri)), this._connectionManagementService.onConnectionChanged(() => this._updateStatus()),
this._connectionManagementService.onDisconnect((connectionUri: IConnectionParams) => this._onDisconnect(connectionUri)), this._connectionManagementService.onDisconnect(() => this._updateStatus()),
this._editorGroupService.onEditorsChanged(() => this._onEditorsChanged()), this._editorGroupService.onEditorsChanged(() => this._updateStatus()),
this._editorGroupService.getStacksModel().onEditorClosed(event => this._onEditorClosed(event)) this._editorGroupService.getStacksModel().onEditorClosed(() => this._updateStatus()),
this._objectExplorerService.onSelectionOrFocusChange(() => this._updateStatus())
); );
return combinedDisposable(this._toDispose); return combinedDisposable(this._toDispose);
} }
private _onEditorClosed(event: IEditorCloseEvent): void { // Update the connection status shown in the bar
let uri = WorkbenchUtils.getEditorUri(event.editor); private _updateStatus(): void {
if (uri && uri in this._connectionStatusEditors) { let activeConnection = TaskUtilities.getCurrentGlobalConnection(this._objectExplorerService, this._connectionManagementService, this._editorService);
this._updateStatus(uri, ConnectionActivityStatus.Disconnected, undefined); if (activeConnection) {
delete this._connectionStatusEditors[uri]; this._setConnectionText(activeConnection);
}
}
private _onEditorsChanged(): void {
let activeEditor = this._editorService.getActiveEditor();
if (activeEditor) {
let uri = WorkbenchUtils.getEditorUri(activeEditor.input);
// Show active editor's query status
if (uri && uri in this._connectionStatusEditors) {
this._showStatus(uri);
} else {
hide(this._connectionElement);
}
} else {
hide(this._connectionElement);
}
}
private _onConnect(connectionParams: IConnectionParams): void {
if (!this._connectionStatusManager.isDefaultTypeUri(connectionParams.connectionUri)) {
this._updateStatus(connectionParams.connectionUri, ConnectionActivityStatus.Connected, connectionParams.connectionProfile);
}
}
private _onDisconnect(connectionUri: IConnectionParams): void {
if (!this._connectionStatusManager.isDefaultTypeUri(connectionUri.connectionUri)) {
this._updateStatus(connectionUri.connectionUri, ConnectionActivityStatus.Disconnected, undefined);
}
}
// Update connection status for the editor
private _updateStatus(uri: string, newStatus: ConnectionActivityStatus, connectionProfile: IConnectionProfile) {
if (uri) {
if (!(uri in this._connectionStatusEditors)) {
this._connectionStatusEditors[uri] = new ConnectionStatusEditor();
}
this._connectionStatusEditors[uri].connectionActivityStatus = newStatus;
this._connectionStatusEditors[uri].connectionProfile = connectionProfile;
this._showStatus(uri);
}
}
// Show/hide query status for active editor
private _showStatus(uri: string): void {
let activeEditor = this._editorService.getActiveEditor();
if (activeEditor) {
let currentUri = WorkbenchUtils.getEditorUri(activeEditor.input);
if (uri === currentUri) {
switch (this._connectionStatusEditors[uri].connectionActivityStatus) {
case ConnectionActivityStatus.Connected:
this._setConnectionText(this._connectionStatusEditors[uri].connectionProfile);
show(this._connectionElement); show(this._connectionElement);
break; } else {
case ConnectionActivityStatus.Disconnected:
hide(this._connectionElement); hide(this._connectionElement);
break;
}
}
} }
} }

View File

@@ -61,6 +61,8 @@ export interface IObjectExplorerService {
getSelectedProfileAndDatabase(): { profile: ConnectionProfile, databaseName: string }; getSelectedProfileAndDatabase(): { profile: ConnectionProfile, databaseName: string };
isFocused(): boolean; isFocused(): boolean;
onSelectionOrFocusChange: Event<void>;
} }
interface SessionStatus { interface SessionStatus {
@@ -94,6 +96,8 @@ export class ObjectExplorerService implements IObjectExplorerService {
private _serverTreeView: ServerTreeView; private _serverTreeView: ServerTreeView;
private _onSelectionOrFocusChange: Emitter<void>;
constructor( constructor(
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService, @IConnectionManagementService private _connectionManagementService: IConnectionManagementService,
@ITelemetryService private _telemetryService: ITelemetryService @ITelemetryService private _telemetryService: ITelemetryService
@@ -102,12 +106,20 @@ export class ObjectExplorerService implements IObjectExplorerService {
this._activeObjectExplorerNodes = {}; this._activeObjectExplorerNodes = {};
this._sessions = {}; this._sessions = {};
this._providers = {}; this._providers = {};
this._onSelectionOrFocusChange = new Emitter<void>();
} }
public get onUpdateObjectExplorerNodes(): Event<ObjectExplorerNodeEventArgs> { public get onUpdateObjectExplorerNodes(): Event<ObjectExplorerNodeEventArgs> {
return this._onUpdateObjectExplorerNodes.event; return this._onUpdateObjectExplorerNodes.event;
} }
/**
* Event fired when the selection or focus of Object Explorer changes
*/
public get onSelectionOrFocusChange(): Event<void> {
return this._onSelectionOrFocusChange.event;
}
public updateObjectExplorerNodes(connection: IConnectionProfile): Promise<void> { public updateObjectExplorerNodes(connection: IConnectionProfile): Promise<void> {
return this._connectionManagementService.addSavedPassword(connection).then(withPassword => { return this._connectionManagementService.addSavedPassword(connection).then(withPassword => {
let connectionProfile = ConnectionProfile.convertToConnectionProfile( let connectionProfile = ConnectionProfile.convertToConnectionProfile(
@@ -370,6 +382,7 @@ export class ObjectExplorerService implements IObjectExplorerService {
throw new Error('The object explorer server tree view is already registered'); throw new Error('The object explorer server tree view is already registered');
} }
this._serverTreeView = view; this._serverTreeView = view;
this._serverTreeView.onSelectionOrFocusChange(() => this._onSelectionOrFocusChange.fire());
} }
/** /**

View File

@@ -27,6 +27,7 @@ import { IConnectionProfile } from 'sql/parts/connection/common/interfaces';
import { ICapabilitiesService } from 'sql/services/capabilities/capabilitiesService'; import { ICapabilitiesService } from 'sql/services/capabilities/capabilitiesService';
import { Button } from 'sql/base/browser/ui/button/button'; import { Button } from 'sql/base/browser/ui/button/button';
import { attachButtonStyler } from 'sql/common/theme/styler'; import { attachButtonStyler } from 'sql/common/theme/styler';
import Event, { Emitter } from 'vs/base/common/event';
const $ = builder.$; const $ = builder.$;
@@ -41,6 +42,7 @@ export class ServerTreeView {
private _activeConnectionsFilterAction: ActiveConnectionsFilterAction; private _activeConnectionsFilterAction: ActiveConnectionsFilterAction;
private _tree: ITree; private _tree: ITree;
private _toDispose: IDisposable[] = []; private _toDispose: IDisposable[] = [];
private _onSelectionOrFocusChange: Emitter<void>;
constructor( constructor(
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService, @IConnectionManagementService private _connectionManagementService: IConnectionManagementService,
@@ -56,6 +58,7 @@ export class ServerTreeView {
ActiveConnectionsFilterAction.LABEL, ActiveConnectionsFilterAction.LABEL,
this); this);
this._treeSelectionHandler = this._instantiationService.createInstance(TreeSelectionHandler); this._treeSelectionHandler = this._instantiationService.createInstance(TreeSelectionHandler);
this._onSelectionOrFocusChange = new Emitter();
} }
/** /**
@@ -64,6 +67,14 @@ export class ServerTreeView {
public get activeConnectionsFilterAction(): ActiveConnectionsFilterAction { public get activeConnectionsFilterAction(): ActiveConnectionsFilterAction {
return this._activeConnectionsFilterAction; return this._activeConnectionsFilterAction;
} }
/**
* Event fired when the tree's selection or focus changes
*/
public get onSelectionOrFocusChange(): Event<void> {
return this._onSelectionOrFocusChange.event;
}
/** /**
* Render the view body * Render the view body
*/ */
@@ -87,6 +98,8 @@ export class ServerTreeView {
this._tree = TreeCreationUtils.createRegisteredServersTree(container, this._instantiationService); this._tree = TreeCreationUtils.createRegisteredServersTree(container, this._instantiationService);
//this._tree.setInput(undefined); //this._tree.setInput(undefined);
this._toDispose.push(this._tree.addListener('selection', (event) => this.onSelected(event))); this._toDispose.push(this._tree.addListener('selection', (event) => this.onSelected(event)));
this._toDispose.push(this._tree.onDOMBlur(() => this._onSelectionOrFocusChange.fire()));
this._toDispose.push(this._tree.onDOMFocus(() => this._onSelectionOrFocusChange.fire()));
// Theme styler // Theme styler
this._toDispose.push(attachListStyler(this._tree, this._themeService)); this._toDispose.push(attachListStyler(this._tree, this._themeService));
@@ -385,7 +398,8 @@ export class ServerTreeView {
} }
private onSelected(event: any): void { private onSelected(event: any): void {
this._treeSelectionHandler.onTreeSelect(event, this._tree, this._connectionManagementService, this._objectExplorerService); this._treeSelectionHandler.onTreeSelect(event, this._tree, this._connectionManagementService, this._objectExplorerService, () => this._onSelectionOrFocusChange.fire());
this._onSelectionOrFocusChange.fire();
} }
/** /**

View File

@@ -41,7 +41,7 @@ export class TreeSelectionHandler {
/** /**
* Handle selection of tree element * Handle selection of tree element
*/ */
public onTreeSelect(event: any, tree: ITree, connectionManagementService: IConnectionManagementService, objectExplorerService: IObjectExplorerService) { public onTreeSelect(event: any, tree: ITree, connectionManagementService: IConnectionManagementService, objectExplorerService: IObjectExplorerService, connectionCompleteCallback: () => void) {
if (this.isMouseEvent(event)) { if (this.isMouseEvent(event)) {
this._clicks++; this._clicks++;
} }
@@ -60,7 +60,7 @@ export class TreeSelectionHandler {
// don't send tree update events while dragging // don't send tree update events while dragging
if (!TreeUpdateUtils.isInDragAndDrop) { if (!TreeUpdateUtils.isInDragAndDrop) {
let isDoubleClick = this._clicks > 1; let isDoubleClick = this._clicks > 1;
this.handleTreeItemSelected(connectionManagementService, objectExplorerService, isDoubleClick, isKeyboard, selection, tree); this.handleTreeItemSelected(connectionManagementService, objectExplorerService, isDoubleClick, isKeyboard, selection, tree, connectionCompleteCallback);
} }
this._clicks = 0; this._clicks = 0;
this._doubleClickTimeoutId = -1; this._doubleClickTimeoutId = -1;
@@ -74,8 +74,10 @@ export class TreeSelectionHandler {
* @param isDoubleClick * @param isDoubleClick
* @param isKeyboard * @param isKeyboard
* @param selection * @param selection
* @param tree
* @param connectionCompleteCallback A function that gets called after a connection is established due to the selection, if needed
*/ */
private handleTreeItemSelected(connectionManagementService: IConnectionManagementService, objectExplorerService: IObjectExplorerService, isDoubleClick: boolean, isKeyboard: boolean, selection: any[], tree: ITree): void { private handleTreeItemSelected(connectionManagementService: IConnectionManagementService, objectExplorerService: IObjectExplorerService, isDoubleClick: boolean, isKeyboard: boolean, selection: any[], tree: ITree, connectionCompleteCallback: () => void): void {
let connectionProfile: ConnectionProfile = undefined; let connectionProfile: ConnectionProfile = undefined;
let options: IConnectionCompletionOptions = { let options: IConnectionCompletionOptions = {
params: undefined, params: undefined,
@@ -94,6 +96,9 @@ export class TreeSelectionHandler {
if (!sessionCreated) { if (!sessionCreated) {
this.onTreeActionStateChange(false); this.onTreeActionStateChange(false);
} }
if (connectionCompleteCallback) {
connectionCompleteCallback();
}
}, error => { }, error => {
this.onTreeActionStateChange(false); this.onTreeActionStateChange(false);
}); });

View File

@@ -18,6 +18,7 @@ import * as TypeMoq from 'typemoq';
import * as assert from 'assert'; import * as assert from 'assert';
import { ServerTreeView } from 'sql/parts/registeredServer/viewlet/serverTreeView'; import { ServerTreeView } from 'sql/parts/registeredServer/viewlet/serverTreeView';
import { ConnectionOptionSpecialType } from 'sql/workbench/api/common/sqlExtHostTypes'; import { ConnectionOptionSpecialType } from 'sql/workbench/api/common/sqlExtHostTypes';
import Event from 'vs/base/common/event';
suite('SQL Object Explorer Service tests', () => { suite('SQL Object Explorer Service tests', () => {
var sqlOEProvider: TypeMoq.Mock<ObjectExplorerProviderTestService>; var sqlOEProvider: TypeMoq.Mock<ObjectExplorerProviderTestService>;
@@ -451,7 +452,7 @@ suite('SQL Object Explorer Service tests', () => {
}); });
test('getSelectedProfileAndDatabase returns the profile if it is selected', () => { test('getSelectedProfileAndDatabase returns the profile if it is selected', () => {
let serverTreeView = TypeMoq.Mock.ofInstance({ getSelection: () => undefined } as ServerTreeView); let serverTreeView = TypeMoq.Mock.ofInstance({ getSelection: () => undefined, onSelectionOrFocusChange: Event.None } as ServerTreeView);
serverTreeView.setup(x => x.getSelection()).returns(() => [connection]); serverTreeView.setup(x => x.getSelection()).returns(() => [connection]);
objectExplorerService.registerServerTreeView(serverTreeView.object); objectExplorerService.registerServerTreeView(serverTreeView.object);
@@ -461,7 +462,7 @@ suite('SQL Object Explorer Service tests', () => {
}); });
test('getSelectedProfileAndDatabase returns the profile but no database if children of a server are selected', () => { test('getSelectedProfileAndDatabase returns the profile but no database if children of a server are selected', () => {
let serverTreeView = TypeMoq.Mock.ofInstance({ getSelection: () => undefined } as ServerTreeView); let serverTreeView = TypeMoq.Mock.ofInstance({ getSelection: () => undefined, onSelectionOrFocusChange: Event.None } as ServerTreeView);
let databaseNode = new TreeNode(NodeType.Folder, 'Folder1', false, 'testServerName\\Folder1', '', '', undefined, undefined); let databaseNode = new TreeNode(NodeType.Folder, 'Folder1', false, 'testServerName\\Folder1', '', '', undefined, undefined);
databaseNode.connection = connection; databaseNode.connection = connection;
serverTreeView.setup(x => x.getSelection()).returns(() => [databaseNode]); serverTreeView.setup(x => x.getSelection()).returns(() => [databaseNode]);
@@ -473,7 +474,7 @@ suite('SQL Object Explorer Service tests', () => {
}); });
test('getSelectedProfileAndDatabase returns the profile and database if children of a database node are selected', () => { test('getSelectedProfileAndDatabase returns the profile and database if children of a database node are selected', () => {
let serverTreeView = TypeMoq.Mock.ofInstance({ getSelection: () => undefined } as ServerTreeView); let serverTreeView = TypeMoq.Mock.ofInstance({ getSelection: () => undefined, onSelectionOrFocusChange: Event.None } as ServerTreeView);
let databaseMetadata = { let databaseMetadata = {
metadataType: 0, metadataType: 0,
metadataTypeName: 'Database', metadataTypeName: 'Database',
@@ -495,7 +496,7 @@ suite('SQL Object Explorer Service tests', () => {
}); });
test('getSelectedProfileAndDatabase returns undefined when there is no selection', () => { test('getSelectedProfileAndDatabase returns undefined when there is no selection', () => {
let serverTreeView = TypeMoq.Mock.ofInstance({ getSelection: () => undefined } as ServerTreeView); let serverTreeView = TypeMoq.Mock.ofInstance({ getSelection: () => undefined, onSelectionOrFocusChange: Event.None } as ServerTreeView);
serverTreeView.setup(x => x.getSelection()).returns(() => []); serverTreeView.setup(x => x.getSelection()).returns(() => []);
objectExplorerService.registerServerTreeView(serverTreeView.object); objectExplorerService.registerServerTreeView(serverTreeView.object);