diff --git a/extensions/mssql/package.json b/extensions/mssql/package.json index 98d71cb4fb..de1129b243 100644 --- a/extensions/mssql/package.json +++ b/extensions/mssql/package.json @@ -658,7 +658,7 @@ } }, "dependencies": { - "dataprotocol-client": "github:Microsoft/sqlops-dataprotocolclient#0.1.5", + "dataprotocol-client": "github:Microsoft/sqlops-dataprotocolclient#0.1.7", "opener": "^1.4.3", "service-downloader": "github:anthonydresser/service-downloader#0.1.2", "vscode-extension-telemetry": "^0.0.15" diff --git a/extensions/mssql/src/config.json b/extensions/mssql/src/config.json index bb7354927b..5229251f99 100644 --- a/extensions/mssql/src/config.json +++ b/extensions/mssql/src/config.json @@ -1,6 +1,6 @@ { "downloadUrl": "https://github.com/Microsoft/sqltoolsservice/releases/download/v{#version#}/microsoft.sqltools.servicelayer-{#fileName#}", - "version": "1.4.0-alpha.30", + "version": "1.4.0-alpha.31", "downloadFileNames": { "Windows_86": "win-x86-netcoreapp2.1.zip", "Windows_64": "win-x64-netcoreapp2.1.zip", diff --git a/src/sql/media/icons/common-icons.css b/src/sql/media/icons/common-icons.css index 1a93df7c1a..d4573c0ca1 100644 --- a/src/sql/media/icons/common-icons.css +++ b/src/sql/media/icons/common-icons.css @@ -131,6 +131,14 @@ background: url("filter_inverse.svg") center center no-repeat !important; } +.vs .icon.filterLabel { + background-image: url("filter.svg"); +} + +.vs-dark .icon.filterLabel, +.hc-black .icon.filterLabel { + background-image: url("filter_inverse.svg"); +} .vs .icon.warning-badge, .vs-dark .icon.warning-badge, diff --git a/src/sql/parts/editData/common/editDataInput.ts b/src/sql/parts/editData/common/editDataInput.ts index aadc7cb428..5c5f4c214a 100644 --- a/src/sql/parts/editData/common/editDataInput.ts +++ b/src/sql/parts/editData/common/editDataInput.ts @@ -4,19 +4,21 @@ *--------------------------------------------------------------------------------------------*/ import { TPromise } from 'vs/base/common/winjs.base'; -import { EditorInput, EditorModel } from 'vs/workbench/common/editor'; +import { EditorInput, EditorModel, ConfirmResult, EncodingMode } from 'vs/workbench/common/editor'; import { IConnectionManagementService, IConnectableInput, INewConnectionParams } from 'sql/parts/connection/common/connectionManagement'; import { IQueryModelService } from 'sql/parts/query/execution/queryModel'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import Event, { Emitter } from 'vs/base/common/event'; -import { EditSessionReadyParams } from 'sqlops'; +import { EditSessionReadyParams, ISelectionData } from 'sqlops'; import URI from 'vs/base/common/uri'; import nls = require('vs/nls'); import { INotificationService } from 'vs/platform/notification/common/notification'; import Severity from 'vs/base/common/severity'; +import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput'; +import { EditDataResultsInput } from 'sql/parts/editData/common/editDataResultsInput'; /** - * Input for the EditDataEditor. This input is simply a wrapper around a QueryResultsInput for the QueryResultsEditor + * Input for the EditDataEditor. */ export class EditDataInput extends EditorInput implements IConnectableInput { public static ID: string = 'workbench.editorinputs.editDataInput'; @@ -25,15 +27,23 @@ export class EditDataInput extends EditorInput implements IConnectableInput { private _editorContainer: HTMLElement; private _updateTaskbar: Emitter; private _editorInitializing: Emitter; - private _showTableView: Emitter; + private _showResultsEditor: Emitter; private _refreshButtonEnabled: boolean; private _stopButtonEnabled: boolean; private _setup: boolean; private _toDispose: IDisposable[]; private _rowLimit: number; private _objectType: string; + private _css: HTMLStyleElement; + private _useQueryFilter: boolean; - constructor(private _uri: URI, private _schemaName, private _tableName, + constructor( + private _uri: URI, + private _schemaName, + private _tableName, + private _sql: UntitledEditorInput, + private _queryString: string, + private _results: EditDataResultsInput, @IConnectionManagementService private _connectionManagementService: IConnectionManagementService, @IQueryModelService private _queryModelService: IQueryModelService, @INotificationService private notificationService: INotificationService @@ -42,12 +52,20 @@ export class EditDataInput extends EditorInput implements IConnectableInput { this._visible = false; this._hasBootstrapped = false; this._updateTaskbar = new Emitter(); - this._showTableView = new Emitter(); + this._showResultsEditor = new Emitter(); this._editorInitializing = new Emitter(); this._setup = false; this._stopButtonEnabled = false; this._refreshButtonEnabled = false; this._toDispose = []; + this._useQueryFilter = false; + + // re-emit sql editor events through this editor if it exists + if (this._sql) { + this._toDispose.push(this._sql.onDidChangeDirty(() => this._onDidChangeDirty.fire())); + this._sql.disableSaving(); + } + this.disableSaving(); //TODO determine is this is a table or a view this._objectType = 'TABLE'; @@ -79,27 +97,45 @@ export class EditDataInput extends EditorInput implements IConnectableInput { public get tableName(): string { return this._tableName; } public get schemaName(): string { return this._schemaName; } public get uri(): string { return this._uri.toString(); } - public get updateTaskbar(): Event { return this._updateTaskbar.event; } - public get editorInitializing(): Event { return this._editorInitializing.event; } - public get showTableView(): Event { return this._showTableView.event; } + public get sql(): UntitledEditorInput { return this._sql; } + public get results(): EditDataResultsInput { return this._results; } + public getResultsInputResource(): string { return this._results.uri; } + public get updateTaskbarEvent(): Event { return this._updateTaskbar.event; } + public get editorInitializingEvent(): Event { return this._editorInitializing.event; } + public get showResultsEditorEvent(): Event { return this._showResultsEditor.event; } public get stopButtonEnabled(): boolean { return this._stopButtonEnabled; } public get refreshButtonEnabled(): boolean { return this._refreshButtonEnabled; } public get container(): HTMLElement { return this._editorContainer; } public get hasBootstrapped(): boolean { return this._hasBootstrapped; } - public get visible(): boolean { return this._visible; } public get setup(): boolean { return this._setup; } public get rowLimit(): number { return this._rowLimit; } public get objectType(): string { return this._objectType; } + public showResultsEditor(): void { this._showResultsEditor.fire(); } + public isDirty(): boolean { return false; } + public save(): TPromise { return TPromise.as(false); } + public confirmSave(): TPromise { return TPromise.wrap(ConfirmResult.DONT_SAVE); } public getTypeId(): string { return EditDataInput.ID; } - public setVisibleTrue(): void { this._visible = true; } public setBootstrappedTrue(): void { this._hasBootstrapped = true; } public getResource(): URI { return this._uri; } - public getName(): string { return this._uri.path; } public supportsSplitEditor(): boolean { return false; } public setupComplete() { this._setup = true; } - public set container(container: HTMLElement) { - this._disposeContainer(); - this._editorContainer = container; + public get queryString(): string { + return this._queryString; + } + public set queryString(queryString: string) { + this._queryString = queryString; + } + public get css(): HTMLStyleElement { + return this._css; + } + public set css(css: HTMLStyleElement) { + this._css = css; + } + public get queryPaneEnabled(): boolean { + return this._useQueryFilter; + } + public set queryPaneEnabled(useQueryFilter: boolean) { + this._useQueryFilter = useQueryFilter; } // State Update Callbacks @@ -111,13 +147,9 @@ export class EditDataInput extends EditorInput implements IConnectableInput { } public initEditEnd(result: EditSessionReadyParams): void { - if (result.success) { - this._refreshButtonEnabled = true; - this._stopButtonEnabled = false; - } else { - this._refreshButtonEnabled = false; - this._stopButtonEnabled = false; - + this._refreshButtonEnabled = true; + this._stopButtonEnabled = false; + if (!result.success) { this.notificationService.notify({ severity: Severity.Error, message: result.message @@ -142,8 +174,16 @@ export class EditDataInput extends EditorInput implements IConnectableInput { } public onConnectSuccess(params?: INewConnectionParams): void { - this._queryModelService.initializeEdit(this.uri, this.schemaName, this.tableName, this._objectType, this._rowLimit); - this._showTableView.fire(this); + let rowLimit: number = undefined; + let queryString: string = undefined; + if (this._useQueryFilter) { + queryString = this._queryString; + } else { + rowLimit = this._rowLimit; + } + + this._queryModelService.initializeEdit(this.uri, this.schemaName, this.tableName, this._objectType, rowLimit, queryString); + this.showResultsEditor(); } public onDisconnect(): void { @@ -157,27 +197,19 @@ export class EditDataInput extends EditorInput implements IConnectableInput { // Boiler Plate Functions public matches(otherInput: any): boolean { if (otherInput instanceof EditDataInput) { - return (this.uri === otherInput.uri); + return this._sql.matches(otherInput.sql); } - return false; - } - - public resolve(refresh?: boolean): TPromise { - return TPromise.as(null); + return this._sql.matches(otherInput); } public dispose(): void { + this._queryModelService.disposeQuery(this.uri); + this._sql.dispose(); + this._results.dispose(); this._toDispose = dispose(this._toDispose); - this._disposeContainer(); - super.dispose(); - } - private _disposeContainer() { - if (this._editorContainer && this._editorContainer.parentElement) { - this._editorContainer.parentElement.removeChild(this._editorContainer); - this._editorContainer = null; - } + super.dispose(); } public close(): void { @@ -186,11 +218,22 @@ export class EditDataInput extends EditorInput implements IConnectableInput { return this._connectionManagementService.disconnectEditor(this, true); }).then(() => { this.dispose(); - super.close(); }); } public get tabColor(): string { return this._connectionManagementService.getTabColorForUri(this.uri); } + + public get onDidModelChangeContent(): Event { return this._sql.onDidModelChangeContent; } + public get onDidModelChangeEncoding(): Event { return this._sql.onDidModelChangeEncoding; } + public resolve(refresh?: boolean): TPromise { return this._sql.resolve(); } + public getEncoding(): string { return this._sql.getEncoding(); } + public suggestFileName(): string { return this._sql.suggestFileName(); } + public getName(): string { return this._sql.getName(); } + public get hasAssociatedFilePath(): boolean { return this._sql.hasAssociatedFilePath; } + + public setEncoding(encoding: string, mode: EncodingMode /* ignored, we only have Encode */): void { + this._sql.setEncoding(encoding, mode); + } } diff --git a/src/sql/parts/editData/common/editDataResultsInput.ts b/src/sql/parts/editData/common/editDataResultsInput.ts new file mode 100644 index 0000000000..7d2e2597ea --- /dev/null +++ b/src/sql/parts/editData/common/editDataResultsInput.ts @@ -0,0 +1,105 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import { localize } from 'vs/nls'; +import { TPromise } from 'vs/base/common/winjs.base'; +import { EditorInput } from 'vs/workbench/common/editor'; + +/** + * Input for the EditDataResultsEditor. This input helps with logic for the viewing and editing of + * data in the results grid. + */ +export class EditDataResultsInput extends EditorInput { + + // Tracks if the editor that holds this input should be visible (i.e. true if a query has been run) + private _visible: boolean; + + // Tracks if the editor has holds this input has has bootstrapped angular yet + private _hasBootstrapped: boolean; + + // Holds the HTML content for the editor when the editor discards this input and loads another + private _editorContainer: HTMLElement; + public css: HTMLStyleElement; + + constructor(private _uri: string) { + super(); + this._visible = false; + this._hasBootstrapped = false; + } + + getTypeId(): string { + return EditDataResultsInput.ID; + } + + matches(other: any): boolean { + if (other instanceof EditDataResultsInput) { + return (other._uri === this._uri); + } + + return false; + } + + resolve(refresh?: boolean): TPromise { + return TPromise.as(null); + } + + supportsSplitEditor(): boolean { + return false; + } + + public setBootstrappedTrue(): void { + this._hasBootstrapped = true; + } + + public dispose(): void { + this._disposeContainer(); + super.dispose(); + } + + private _disposeContainer() { + if (!this._editorContainer) { + return; + } + + let parentContainer = this._editorContainer.parentNode; + if (parentContainer) { + parentContainer.removeChild(this._editorContainer); + this._editorContainer = null; + } + } + + //// Properties + + static get ID() { + return 'workbench.editorinputs.editDataResultsInput'; + } + + set container(container: HTMLElement) { + this._disposeContainer(); + this._editorContainer = container; + } + + get container(): HTMLElement { + return this._editorContainer; + } + + get hasBootstrapped(): boolean { + return this._hasBootstrapped; + } + + get visible(): boolean { + return this._visible; + } + + set visible(visible: boolean) { + this._visible = visible; + } + + get uri(): string { + return this._uri; + } +} \ No newline at end of file diff --git a/src/sql/parts/editData/editor/editDataEditor.ts b/src/sql/parts/editData/editor/editDataEditor.ts index 11c7138c0c..a34989f440 100644 --- a/src/sql/parts/editData/editor/editDataEditor.ts +++ b/src/sql/parts/editData/editor/editDataEditor.ts @@ -6,13 +6,14 @@ import 'vs/css!sql/parts/query/editor/media/queryEditor'; import { TPromise } from 'vs/base/common/winjs.base'; +import * as strings from 'vs/base/common/strings'; import * as DOM from 'vs/base/browser/dom'; import * as nls from 'vs/nls'; -import { Builder, Dimension } from 'vs/base/browser/builder'; +import { Builder, Dimension, withElementById } from 'vs/base/browser/builder'; -import { EditorOptions } from 'vs/workbench/common/editor'; +import { EditorOptions, EditorInput } from 'vs/workbench/common/editor'; import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; -import { Position } from 'vs/platform/editor/common/editor'; +import { Position, IEditorControl, IEditor } from 'vs/platform/editor/common/editor'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IThemeService } from 'vs/platform/theme/common/themeService'; @@ -21,6 +22,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { EditDataInput } from 'sql/parts/editData/common/editDataInput'; import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; +import * as queryContext from 'sql/parts/query/common/queryContext'; import { Taskbar, ITaskbarContent } from 'sql/base/browser/ui/taskbar/taskbar'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IActionItem } from 'vs/base/browser/ui/actionbar/actionbar'; @@ -29,13 +31,22 @@ import { IQueryModelService } from 'sql/parts/query/execution/queryModel'; import { IEditorDescriptorService } from 'sql/parts/query/editor/editorDescriptorService'; import { IConnectionManagementService } from 'sql/parts/connection/common/connectionManagement'; import { - RefreshTableAction, StopRefreshTableAction, - ChangeMaxRowsAction, ChangeMaxRowsActionItem + RefreshTableAction, StopRefreshTableAction, ChangeMaxRowsAction, ChangeMaxRowsActionItem, ShowQueryPaneAction } from 'sql/parts/editData/execution/editDataActions'; import { EditDataModule } from 'sql/parts/grid/views/editData/editData.module'; import { IBootstrapService } from 'sql/services/bootstrap/bootstrapService'; import { EDITDATA_SELECTOR } from 'sql/parts/grid/views/editData/editData.component'; import { EditDataComponentParams } from 'sql/services/bootstrap/bootstrapParams'; +import { TextResourceEditor } from 'vs/workbench/browser/parts/editor/textResourceEditor'; +import { CodeEditor } from 'vs/editor/browser/codeEditor'; +import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { ISelectionData } from 'sqlops'; +import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput'; +import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService'; +import { IFlexibleSash, VerticalFlexibleSash, HorizontalFlexibleSash } from 'sql/parts/query/views/flexibleSash'; +import { Orientation } from 'vs/base/browser/ui/sash/sash'; +import { EditDataResultsEditor } from 'sql/parts/editData/editor/editDataResultsEditor'; +import { EditDataResultsInput } from 'sql/parts/editData/common/editDataResultsInput'; /** * Editor that hosts an action bar and a resultSetInput for an edit data session @@ -44,17 +55,34 @@ export class EditDataEditor extends BaseEditor { public static ID: string = 'workbench.editor.editDataEditor'; + // The height of the tabs above the editor + private readonly _tabHeight: number = 35; + + // The minimum width/height of the editors hosted in the QueryEditor + private readonly _minEditorSize: number = 220; + + private _sash: IFlexibleSash; private _dimension: Dimension; - private _container: HTMLElement; + + private _resultsEditor: EditDataResultsEditor; + private _resultsEditorContainer: HTMLElement; + + private _sqlEditor: TextResourceEditor; + private _sqlEditorContainer: HTMLElement; + private _taskbar: Taskbar; private _taskbarContainer: HTMLElement; private _changeMaxRowsActionItem: ChangeMaxRowsActionItem; private _stopRefreshTableAction: StopRefreshTableAction; private _refreshTableAction: RefreshTableAction; private _changeMaxRowsAction: ChangeMaxRowsAction; + private _showQueryPaneAction: ShowQueryPaneAction; private _spinnerElement: HTMLElement; private _initialized: boolean = false; + private _queryEditorVisible: IContextKey; + private hideQueryResultsView = false; + constructor( @ITelemetryService _telemetryService: ITelemetryService, @IThemeService themeService: IThemeService, @@ -62,19 +90,61 @@ export class EditDataEditor extends BaseEditor { @IWorkbenchEditorService private _editorService: IWorkbenchEditorService, @IContextMenuService private _contextMenuService: IContextMenuService, @IQueryModelService private _queryModelService: IQueryModelService, + @IEditorDescriptorService private _editorDescriptorService: IEditorDescriptorService, + @IEditorGroupService private _editorGroupService: IEditorGroupService, + @IContextKeyService contextKeyService: IContextKeyService, @IConnectionManagementService private _connectionManagementService: IConnectionManagementService, @IBootstrapService private _bootstrapService: IBootstrapService ) { super(EditDataEditor.ID, _telemetryService, themeService); + + if (contextKeyService) { + this._queryEditorVisible = queryContext.QueryEditorVisibleContext.bindTo(contextKeyService); + } } // PUBLIC METHODS //////////////////////////////////////////////////////////// // Getters and Setters public get editDataInput(): EditDataInput { return this.input; } - public get uri(): string { return this.input ? this.editDataInput.uri.toString() : undefined; } public get tableName(): string { return this.editDataInput.tableName; } + public get uri(): string { return this.input ? this.editDataInput.uri.toString() : undefined; } + public set resultsEditorVisibility(isVisible: boolean) { + let input: EditDataInput = this.input; + input.results.visible = isVisible; + } + /** + * Changes the position of the editor. + */ + public changePosition(position: Position): void { + if (this._resultsEditor) { + this._resultsEditor.changePosition(position); + } + if (this._sqlEditor) { + this._sqlEditor.changePosition(position); + } + super.changePosition(position); + } + + /** + * Called to indicate to the editor that the input should be cleared and resources associated with the + * input should be freed. + */ + public clearInput(): void { + if (this._resultsEditor) { + this._resultsEditor.clearInput(); + } + if (this._sqlEditor) { + this._sqlEditor.clearInput(); + } + this._disposeEditors(); + super.clearInput(); + } + + public close(): void { + this.editDataInput.close(); + } /** * Called to create the editor in the parent builder. @@ -83,9 +153,82 @@ export class EditDataEditor extends BaseEditor { const parentElement = parent.getHTMLElement(); DOM.addClass(parentElement, 'side-by-side-editor'); this._createTaskbar(parentElement); - this._container = document.createElement('div'); - this._container.style.height = 'calc(100% - 28px)'; - DOM.append(parentElement, this._container); + } + + public dispose(): void { + this._disposeEditors(); + super.dispose(); + } + + /** + * Sets focus on this editor. Specifically, it sets the focus on the hosted text editor. + */ + public focus(): void { + if (this._sqlEditor) { + this._sqlEditor.focus(); + } + } + + public getControl(): IEditorControl { + if (this._sqlEditor) { + return this._sqlEditor.getControl(); + } + return null; + } + + public getEditorText(): string { + if (this._sqlEditor && this._sqlEditor.getControl()) { + let control = this._sqlEditor.getControl(); + let codeEditor: CodeEditor = control; + + if (codeEditor) { + let value = codeEditor.getModel().getValue(); + if (value !== undefined && value.length > 0) { + return value; + } + } + } + return ''; + } + + /** + * Hide the spinner element to show that something was happening, hidden by default + */ + public hideSpinner(): void { + this._spinnerElement.style.visibility = 'hidden'; + } + + /** + * Updates the internal variable keeping track of the editor's size, and re-calculates the sash position. + * To be called when the container of this editor changes size. + */ + public layout(dimension: Dimension): void { + this._dimension = dimension; + + if (this._sash) { + this._setSashDimension(); + this._sash.layout(); + } + + this._doLayout(); + this._resizeGridContents(); + } + + /** + * Sets this editor and the sub-editors to visible. + */ + public setEditorVisible(visible: boolean, position: Position): void { + if (this._resultsEditor) { + this._resultsEditor.setVisible(visible, position); + } + if (this._sqlEditor) { + this._sqlEditor.setVisible(visible, position); + } + + super.setEditorVisible(visible, position); + + // Note: must update after calling super.setEditorVisible so that the accurate count is handled + this._updateQueryEditorVisible(visible); } /** @@ -95,9 +238,9 @@ export class EditDataEditor extends BaseEditor { let oldInput = this.input; if (!newInput.setup) { this._initialized = false; - this._register(newInput.updateTaskbar((owner) => this._updateTaskbar(owner))); - this._register(newInput.editorInitializing((initializing) => this.onEditorInitializingChanged(initializing))); - this._register(newInput.showTableView(() => this._showTableView())); + this._register(newInput.updateTaskbarEvent((owner) => this._updateTaskbar(owner))); + this._register(newInput.editorInitializingEvent((initializing) => this._onEditorInitializingChanged(initializing))); + this._register(newInput.showResultsEditorEvent(() => this._showResultsEditor())); newInput.onRowDropDownSet(this._changeMaxRowsActionItem.defaultRowCount); newInput.setupComplete(); } @@ -106,15 +249,6 @@ export class EditDataEditor extends BaseEditor { .then(() => this._updateInput(oldInput, newInput, options)); } - private onEditorInitializingChanged(initializing: boolean): void { - if (initializing) { - this.showSpinner(); - } else { - this._initialized = true; - this.hideSpinner(); - } - } - /** * Show the spinner element that shows something is happening, hidden by default */ @@ -126,92 +260,74 @@ export class EditDataEditor extends BaseEditor { }, 200); } - /** - * Hide the spinner element to show that something was happening, hidden by default - */ - public hideSpinner(): void { - this._spinnerElement.style.visibility = 'hidden'; - } - - /** - * Sets this editor and the sub-editors to visible. - */ - public setEditorVisible(visible: boolean, position: Position): void { - super.setEditorVisible(visible, position); - } - - /** - * Changes the position of the editor. - */ - public changePosition(position: Position): void { - super.changePosition(position); - } - - /** - * Called to indicate to the editor that the input should be cleared and resources associated with the - * input should be freed. - */ - public clearInput(): void { - this._disposeEditors(); - super.clearInput(); - } - - /** - * Updates the internal variable keeping track of the editor's size, and re-calculates the sash position. - * To be called when the container of this editor changes size. - */ - public layout(dimension: Dimension): void { - this._dimension = dimension; - let input: EditDataInput = this.input; - if (input) { - let uri: string = input.uri; - if (uri) { - this._queryModelService.resizeResultsets(uri); - } - } - } - - public dispose(): void { - this._disposeEditors(); - super.dispose(); - } - - public close(): void { - this.input.close(); - } - - /** - * Returns true if the results table for the current edit data session is visible - * Public for testing only. - */ - public _isResultsEditorVisible(): boolean { - if (!this.editDataInput) { - return false; - } - return this.editDataInput.visible; - } - - /** - * Makes visible the results table for the current edit data session - */ - private _showTableView(): void { - if (this._isResultsEditorVisible()) { + public toggleResultsEditorVisibility(): void { + let input = this.input; + let hideResults = this.hideQueryResultsView; + this.hideQueryResultsView = !this.hideQueryResultsView; + if (!input.results) { return; } - - this._createTableViewContainer(); - this._setTableViewVisible(); - this.setInput(this.editDataInput, this.options); + this.resultsEditorVisibility = hideResults; + this._doLayout(); } // PRIVATE METHODS //////////////////////////////////////////////////////////// - private _updateTaskbar(owner: EditDataInput): void { - // Update the taskbar if the owner of this call is being presented - if (owner.matches(this.editDataInput)) { - this._refreshTableAction.enabled = owner.refreshButtonEnabled; - this._stopRefreshTableAction.enabled = owner.stopButtonEnabled; - this._changeMaxRowsActionItem.setCurrentOptionIndex = owner.rowLimit; + private _createEditor(editorInput: EditorInput, container: HTMLElement): TPromise { + const descriptor = this._editorDescriptorService.getEditor(editorInput); + if (!descriptor) { + return TPromise.wrapError(new Error(strings.format('Can not find a registered editor for the input {0}', editorInput))); } + + let editor = descriptor.instantiate(this._instantiationService); + editor.create(new Builder(container)); + editor.setVisible(this.isVisible(), this.position); + return TPromise.as(editor); + } + + /** + * Appends the HTML for the EditDataResultsEditor to the EditDataEditor. If the HTML has not yet been + * created, it creates it and appends it. If it has already been created, it locates it and + * appends it. + */ + private _createResultsEditorContainer() { + this._createSash(); + + const parentElement = this.getContainer().getHTMLElement(); + let input = this.input; + + if (!input.results.container) { + this._resultsEditorContainer = DOM.append(parentElement, DOM.$('.editDataContainer-horizontal')); + this._resultsEditorContainer.style.position = 'absolute'; + + input.results.container = this._resultsEditorContainer; + } else { + this._resultsEditorContainer = DOM.append(parentElement, input.results.container); + } + } + + /** + * Creates the sash with the requested orientation and registers sash callbacks + */ + private _createSash(): void { + if (!this._sash) { + let parentElement: HTMLElement = this.getContainer().getHTMLElement(); + + this._sash = this._register(new HorizontalFlexibleSash(parentElement, this._minEditorSize)); + this._setSashDimension(); + + this._register(this._sash.onPositionChange(position => this._doLayout())); + } + + this._sash.show(); + } + + /** + * Appends the HTML for the SQL editor. Creates new HTML every time. + */ + private _createSqlEditorContainer() { + const parentElement = this.getContainer().getHTMLElement(); + this._sqlEditorContainer = DOM.append(parentElement, DOM.$('.details-editor-container')); + this._sqlEditorContainer.style.position = 'absolute'; } private _createTaskbar(parentElement: HTMLElement): void { @@ -222,22 +338,25 @@ export class EditDataEditor extends BaseEditor { }); // Create Actions for the toolbar - this._stopRefreshTableAction = this._instantiationService.createInstance(StopRefreshTableAction, this); this._refreshTableAction = this._instantiationService.createInstance(RefreshTableAction, this); + this._stopRefreshTableAction = this._instantiationService.createInstance(StopRefreshTableAction, this); this._changeMaxRowsAction = this._instantiationService.createInstance(ChangeMaxRowsAction, this); + this._showQueryPaneAction = this._instantiationService.createInstance(ShowQueryPaneAction, this); // Create HTML Elements for the taskbar let separator = Taskbar.createTaskbarSeparator(); - let textSeperator = Taskbar.createTaskbarText(nls.localize('maxRowTaskbar', 'Max Rows:')); + let textSeparator = Taskbar.createTaskbarText(nls.localize('maxRowTaskbar', 'Max Rows:')); this._spinnerElement = Taskbar.createTaskbarSpinner(); + // Set the content in the order we desire let content: ITaskbarContent[] = [ - { action: this._stopRefreshTableAction }, { action: this._refreshTableAction }, + { action: this._stopRefreshTableAction }, { element: separator }, - { element: textSeperator }, + { element: textSeparator }, { action: this._changeMaxRowsAction }, + { action: this._showQueryPaneAction }, { element: this._spinnerElement } ]; this._taskbar.setContent(content); @@ -258,32 +377,181 @@ export class EditDataEditor extends BaseEditor { return null; } - /** - * Handles setting input for this editor. If this new input does not match the old input (e.g. a new file - * has been opened with the same editor, or we are opening the editor for the first time). - */ - private _updateInput(oldInput: EditDataInput, newInput: EditDataInput, options?: EditorOptions): TPromise { - let returnValue: TPromise; - - if (!newInput.matches(oldInput)) { - this._disposeEditors(); - - if (this._isResultsEditorVisible()) { - this._createTableViewContainer(); - let uri: string = newInput.uri; - if (uri) { - this._queryModelService.refreshResultsets(uri); - } - } - - returnValue = this._setNewInput(newInput, options); - } else { - this._setNewInput(newInput, options); - returnValue = TPromise.as(null); + private _disposeEditors(): void { + if (this._sqlEditor) { + this._sqlEditor.dispose(); + this._sqlEditor = null; + } + if (this._resultsEditor) { + this._resultsEditor.dispose(); + this._resultsEditor = null; } - this._updateTaskbar(newInput); - return returnValue; + let thisEditorParent: HTMLElement = this.getContainer().getHTMLElement(); + + if (this._sqlEditorContainer) { + let sqlEditorParent: HTMLElement = this._sqlEditorContainer.parentElement; + if (sqlEditorParent && sqlEditorParent === thisEditorParent) { + this._sqlEditorContainer.parentElement.removeChild(this._sqlEditorContainer); + } + this._sqlEditorContainer = null; + } + + if (this._resultsEditorContainer) { + let resultsEditorParent: HTMLElement = this._resultsEditorContainer.parentElement; + if (resultsEditorParent && resultsEditorParent === thisEditorParent) { + this._resultsEditorContainer.parentElement.removeChild(this._resultsEditorContainer); + } + this._resultsEditorContainer = null; + this.hideQueryResultsView = false; + } + } + + private _doLayout(skipResizeGridContent: boolean = false): void { + if (!this._isResultsEditorVisible() && this._sqlEditor) { + this._doLayoutSql(); + return; + } + if (!this._sqlEditor || !this._resultsEditor || !this._dimension || !this._sash) { + return; + } + + this._doLayoutHorizontal(); + + if (!skipResizeGridContent) { + this._resizeGridContents(); + } + } + + private _doLayoutHorizontal(): void { + let splitPointTop: number = this._sash.getSplitPoint(); + let parent: ClientRect = this.getContainer().getHTMLElement().getBoundingClientRect(); + + let sqlEditorHeight: number; + let sqlEditorTop: number; + let resultsEditorHeight: number; + let resultsEditorTop: number; + + let editorTopOffset = parent.top + this._getTaskBarHeight(); + + this._resultsEditorContainer.hidden = false; + + let titleBar = withElementById('workbench.parts.titlebar'); + if (this.queryPaneEnabled()) { + this._sqlEditorContainer.hidden = false; + + sqlEditorTop = editorTopOffset; + sqlEditorHeight = splitPointTop - sqlEditorTop; + + resultsEditorTop = splitPointTop; + resultsEditorHeight = parent.bottom - resultsEditorTop; + + if (titleBar) { + sqlEditorHeight += DOM.getContentHeight(titleBar.getHTMLElement()); + } + } else { + this._sqlEditorContainer.hidden = true; + + sqlEditorTop = editorTopOffset; + sqlEditorHeight = 0; + + resultsEditorTop = editorTopOffset; + resultsEditorHeight = parent.bottom - resultsEditorTop; + + if (titleBar) { + resultsEditorHeight += DOM.getContentHeight(titleBar.getHTMLElement()); + } + } + + this._sqlEditorContainer.style.height = `${sqlEditorHeight}px`; + this._sqlEditorContainer.style.width = `${this._dimension.width}px`; + this._sqlEditorContainer.style.top = `${sqlEditorTop}px`; + + this._resultsEditorContainer.style.height = `${resultsEditorHeight}px`; + this._resultsEditorContainer.style.width = `${this._dimension.width}px`; + this._resultsEditorContainer.style.top = `${resultsEditorTop}px`; + + this._sqlEditor.layout(new Dimension(this._dimension.width, sqlEditorHeight)); + this._resultsEditor.layout(new Dimension(this._dimension.width, resultsEditorHeight)); + } + + private _doLayoutSql() { + if (this._resultsEditorContainer) { + this._resultsEditorContainer.style.width = '0px'; + this._resultsEditorContainer.style.height = '0px'; + this._resultsEditorContainer.style.left = '0px'; + this._resultsEditorContainer.hidden = true; + } + + if (this._dimension) { + let sqlEditorHeight: number; + + if (this.queryPaneEnabled()) { + this._sqlEditorContainer.hidden = false; + sqlEditorHeight = this._dimension.height - this._getTaskBarHeight(); + } else { + this._sqlEditorContainer.hidden = true; + sqlEditorHeight = 0; + } + + this._sqlEditorContainer.style.height = `${sqlEditorHeight}px`; + this._sqlEditorContainer.style.width = `${this._dimension.width}px`; + + this._sqlEditor.layout(new Dimension(this._dimension.width, sqlEditorHeight)); + } + } + + private _getTaskBarHeight(): number { + let taskBarElement = this._taskbar.getContainer().getHTMLElement(); + return DOM.getContentHeight(taskBarElement); + } + + /** + * Returns true if the results table for the current edit data session is visible + * Public for testing only. + */ + private _isResultsEditorVisible(): boolean { + let input: EditDataInput = this.input; + + if (!input) { + return false; + } + return input.results.visible; + } + + private _onEditorInitializingChanged(initializing: boolean): void { + if (initializing) { + this.showSpinner(); + } else { + this._initialized = true; + this.hideSpinner(); + } + } + + /** + * Sets input for the results editor after it has been created. + */ + private _onResultsEditorCreated(resultsEditor: EditDataResultsEditor, resultsInput: EditDataResultsInput, options: EditorOptions): TPromise { + this._resultsEditor = resultsEditor; + return this._resultsEditor.setInput(resultsInput, options); + } + + /** + * Sets input for the SQL editor after it has been created. + */ + private _onSqlEditorCreated(sqlEditor: TextResourceEditor, sqlInput: UntitledEditorInput, options: EditorOptions): TPromise { + this._sqlEditor = sqlEditor; + return this._sqlEditor.setInput(sqlInput, options); + } + + private _resizeGridContents(): void { + if (this._isResultsEditorVisible()) { + let queryInput: EditDataInput = this.input; + let uri: string = queryInput.uri; + if (uri) { + this._queryModelService.resizeResultsets(uri); + } + } } /** @@ -291,68 +559,177 @@ export class EditDataEditor extends BaseEditor { * - Opened for the first time * - Opened with a new EditDataInput */ - private _setNewInput(newInput: EditDataInput, options?: EditorOptions): TPromise { + private _setNewInput(newInput: EditDataInput, options?: EditorOptions): TPromise { + + // Promises that will ensure proper ordering of editor creation logic + let createEditors: () => TPromise; + let onEditorsCreated: (result) => TPromise; + + // If both editors exist, create joined promises - one for each editor if (this._isResultsEditorVisible()) { - // If both editors exist, create a joined promise and wait for both editors to be created - return this._bootstrapAngularAndResults(newInput, options); - } - - return TPromise.as(undefined); - } - /** - * Appends the HTML for the edit data table view - */ - private _createTableViewContainer() { - if (!this.editDataInput.container) { - this.editDataInput.container = DOM.append(this._container, DOM.$('.editDataContainer')); - this.editDataInput.container.style.height = '100%'; - } else { - DOM.append(this._container, this.editDataInput.container); - } - } - - private _disposeEditors(): void { - if (this._container) { - new Builder(this._container).clearChildren(); - } - } - - /** - * Load the angular components and record for this input that we have done so - */ - private _bootstrapAngularAndResults(input: EditDataInput, options: EditorOptions): TPromise { - super.setInput(input, options); - if (!input.hasBootstrapped) { - let uri = this.editDataInput.uri; - - // Pass the correct DataService to the new angular component - let dataService = this._queryModelService.getDataService(uri); - if (!dataService) { - throw new Error('DataService not found for URI: ' + uri); - } - - // Mark that we have bootstrapped - input.setBootstrappedTrue(); - - // Get the bootstrap params and perform the bootstrap - // Note: pass in input so on disposal this is cleaned up. - // Otherwise many components will be left around and be subscribed - // to events from the backing data service - const parent = this.editDataInput.container; - let params: EditDataComponentParams = { - dataService: dataService + createEditors = () => { + return TPromise.join([ + this._createEditor(newInput.results, this._resultsEditorContainer), + this._createEditor(newInput.sql, this._sqlEditorContainer) + ]); + }; + onEditorsCreated = (result: IEditor[]) => { + return TPromise.join([ + this._onResultsEditorCreated(result[0], newInput.results, options), + this._onSqlEditorCreated(result[1], newInput.sql, options) + ]); + }; + + // If only the sql editor exists, create a promise and wait for the sql editor to be created + } else { + createEditors = () => { + return this._createEditor(newInput.sql, this._sqlEditorContainer); + }; + onEditorsCreated = (result: TextResourceEditor) => { + return this._onSqlEditorCreated(result, newInput.sql, options); }; - this._bootstrapService.bootstrap( - EditDataModule, - parent, - EDITDATA_SELECTOR, - params, - this.editDataInput); } - return TPromise.wrap(null); + + // Create a promise to re render the layout after the editor creation logic + let doLayout: () => TPromise = () => { + this._doLayout(); + return TPromise.as(undefined); + }; + + // Run all three steps synchronously + return createEditors() + .then(onEditorsCreated) + .then(doLayout); } - private _setTableViewVisible(): void { - this.editDataInput.setVisibleTrue(); + private _setSashDimension(): void { + if (!this._dimension) { + return; + } + this._sash.setDimenesion(this._dimension); + } + + /** + * Makes visible the results table for the current edit data session + */ + private _showResultsEditor(): void { + if (this._isResultsEditorVisible()) { + return; + } + + this._editorGroupService.pinEditor(this.position, this.input); + + let input = this.input; + this._createResultsEditorContainer(); + + this._createEditor(input.results, this._resultsEditorContainer) + .then(result => { + this._onResultsEditorCreated(result, input.results, this.options); + this.resultsEditorVisibility = true; + this.hideQueryResultsView = false; + this._doLayout(true); + }); + } + + /** + * Handles setting input for this editor. If this new input does not match the old input (e.g. a new file + * has been opened with the same editor, or we are opening the editor for the first time). + */ + private _updateInput(oldInput: EditDataInput, newInput: EditDataInput, options?: EditorOptions): TPromise { + + if (this._sqlEditor) { + this._sqlEditor.clearInput(); + } + + if (oldInput) { + this._disposeEditors(); + } + + this._createSqlEditorContainer(); + if (this._isResultsEditorVisible()) { + this._createResultsEditorContainer(); + + let uri: string = newInput.uri; + if (uri) { + this._queryModelService.refreshResultsets(uri); + } + } + + if (this._sash) { + if (this._isResultsEditorVisible()) { + this._sash.show(); + } else { + this._sash.hide(); + } + } + + this._updateTaskbar(newInput); + return this._setNewInput(newInput, options); + } + + private _updateQueryEditorVisible(currentEditorIsVisible: boolean): void { + if (this._queryEditorVisible) { + let visible = currentEditorIsVisible; + if (!currentEditorIsVisible) { + // Current editor is closing but still tracked as visible. Check if any other editor is visible + const candidates = [...this._editorService.getVisibleEditors()].filter(e => { + if (e && e.getId) { + return e.getId() === EditDataEditor.ID; + } + return false; + }); + // Note: require 2 or more candidates since current is closing but still + // counted as visible + visible = candidates.length > 1; + } + this._queryEditorVisible.set(visible); + } + } + + private _updateTaskbar(owner: EditDataInput): void { + // Update the taskbar if the owner of this call is being presented + if (owner.matches(this.editDataInput)) { + this._refreshTableAction.enabled = owner.refreshButtonEnabled; + this._stopRefreshTableAction.enabled = owner.stopButtonEnabled; + this._changeMaxRowsActionItem.setCurrentOptionIndex = owner.rowLimit; + this._showQueryPaneAction.queryPaneEnabled = owner.queryPaneEnabled; + } + } + + /** + * Calls the run method of this editor's RunQueryAction + */ + public runQuery(): void { + this._refreshTableAction.run(); + } + + /** + * Calls the run method of this editor's CancelQueryAction + */ + public cancelQuery(): void { + this._stopRefreshTableAction.run(); + } + + public toggleQueryPane(): void { + this.editDataInput.queryPaneEnabled = !this.queryPaneEnabled(); + if (this.queryPaneEnabled()) { + this._showQueryEditor(); + } else { + this._hideQueryEditor(); + } + this._doLayout(false); + } + + private _showQueryEditor(): void { + this._sqlEditorContainer.hidden = false; + this._changeMaxRowsActionItem.disable(); + } + private _hideQueryEditor(): void { + this._sqlEditorContainer.hidden = true; + this._changeMaxRowsActionItem.enable(); + } + + public queryPaneEnabled(): boolean { + return this.editDataInput.queryPaneEnabled; } } diff --git a/src/sql/parts/editData/editor/editDataResultsEditor.ts b/src/sql/parts/editData/editor/editDataResultsEditor.ts new file mode 100644 index 0000000000..66484bea34 --- /dev/null +++ b/src/sql/parts/editData/editor/editDataResultsEditor.ts @@ -0,0 +1,113 @@ +import { Dimension, Builder } from 'vs/base/browser/builder'; +import { EditorOptions } from 'vs/workbench/common/editor'; +import { TPromise } from 'vs/base/common/winjs.base'; +import { getZoomLevel } from 'vs/base/browser/browser'; +import { Configuration } from 'vs/editor/browser/config/configuration'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { IQueryModelService } from 'sql/parts/query/execution/queryModel'; +import { IBootstrapService } from 'sql/services/bootstrap/bootstrapService'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { BareResultsGridInfo } from 'sql/parts/query/editor/queryResultsEditor'; +import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; +import * as dom from 'vs/base/browser/dom'; +import * as types from 'vs/base/common/types'; +import { EditDataComponentParams } from 'sql/services/bootstrap/bootstrapParams'; +import { EditDataModule } from 'sql/parts/grid/views/editData/editData.module'; +import { EDITDATA_SELECTOR } from 'sql/parts/grid/views/editData/editData.component'; +import { EditDataResultsInput } from 'sql/parts/editData/common/editDataResultsInput'; + +export class EditDataResultsEditor extends BaseEditor { + + public static ID: string = 'workbench.editor.editDataResultsEditor'; + public static AngularSelectorString: string = 'slickgrid-container.slickgridContainer'; + protected _input: EditDataResultsInput; + protected _rawOptions: BareResultsGridInfo; + + constructor( + @ITelemetryService telemetryService: ITelemetryService, + @IThemeService themeService: IThemeService, + @IQueryModelService private _queryModelService: IQueryModelService, + @IBootstrapService private _bootstrapService: IBootstrapService, + @IConfigurationService private _configurationService: IConfigurationService + ) { + super(EditDataResultsEditor.ID, telemetryService, themeService); + this._rawOptions = BareResultsGridInfo.createFromRawSettings(this._configurationService.getValue('resultsGrid'), getZoomLevel()); + this._configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration('resultsGrid')) { + this._rawOptions = BareResultsGridInfo.createFromRawSettings(this._configurationService.getValue('resultsGrid'), getZoomLevel()); + this._applySettings(); + } + }); + } + + public get input(): EditDataResultsInput { + return this._input; + } + + public createEditor(parent: Builder): void { + } + + public dispose(): void { + super.dispose(); + } + + public layout(dimension: Dimension): void { + } + + public setInput(input: EditDataResultsInput, options: EditorOptions): TPromise { + super.setInput(input, options); + this._applySettings(); + if (!input.hasBootstrapped) { + this._bootstrapAngular(); + } + return TPromise.wrap(null); + } + + private _applySettings() { + if (this.input && this.input.container) { + Configuration.applyFontInfoSlow(this.getContainer().getHTMLElement(), this._rawOptions); + if (!this.input.css) { + this.input.css = dom.createStyleSheet(this.input.container); + } + let cssRuleText = ''; + if (types.isNumber(this._rawOptions.cellPadding)) { + cssRuleText = this._rawOptions.cellPadding + 'px'; + } else { + cssRuleText = this._rawOptions.cellPadding.join('px ') + 'px;'; + } + let content = `.grid .slick-cell { padding: ${cssRuleText}; }`; + this.input.css.innerHTML = content; + } + } + + /** + * Load the angular components and record for this input that we have done so + */ + private _bootstrapAngular(): void { + let input = this.input; + let uri = input.uri; + + // Pass the correct DataService to the new angular component + let dataService = this._queryModelService.getDataService(uri); + if (!dataService) { + throw new Error('DataService not found for URI: ' + uri); + } + + // Mark that we have bootstrapped + input.setBootstrappedTrue(); + + // Get the bootstrap params and perform the bootstrap + // Note: pass in input so on disposal this is cleaned up. + // Otherwise many components will be left around and be subscribed + // to events from the backing data service + const parent = input.container; + let params: EditDataComponentParams = { dataService: dataService }; + this._bootstrapService.bootstrap( + EditDataModule, + parent, + EDITDATA_SELECTOR, + params, + input); + } +} \ No newline at end of file diff --git a/src/sql/parts/editData/execution/editDataActions.ts b/src/sql/parts/editData/execution/editDataActions.ts index 49474532c9..773fca2e88 100644 --- a/src/sql/parts/editData/execution/editDataActions.ts +++ b/src/sql/parts/editData/execution/editDataActions.ts @@ -7,7 +7,7 @@ import { Action, IActionItem, IActionRunner } from 'vs/base/common/actions'; import { TPromise } from 'vs/base/common/winjs.base'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { IQueryModelService } from 'sql/parts/query/execution/queryModel'; -import { SelectBox } from 'vs/base/browser/ui/selectBox/selectBox'; +import { SelectBox } from 'sql/base/browser/ui/selectBox/selectBox'; import { EventEmitter } from 'sql/base/common/eventEmitter'; import { IConnectionManagementService } from 'sql/parts/connection/common/connectionManagement'; import { EditDataEditor } from 'sql/parts/editData/editor/editDataEditor'; @@ -63,7 +63,7 @@ export abstract class EditDataAction extends Action { * Action class that refreshes the table for an edit data session */ export class RefreshTableAction extends EditDataAction { - private static EnabledClass = 'refresh'; + private static EnabledClass = 'start'; public static ID = 'refreshTableAction'; constructor(editor: EditDataEditor, @@ -72,14 +72,24 @@ export class RefreshTableAction extends EditDataAction { @INotificationService private _notificationService: INotificationService, ) { super(editor, RefreshTableAction.ID, RefreshTableAction.EnabledClass, _connectionManagementService); - this.label = nls.localize('editData.refresh', 'Refresh'); + this.label = nls.localize('editData.run', 'Run'); } public run(): TPromise { if (this.isConnected(this.editor)) { let input = this.editor.editDataInput; + + let rowLimit: number = undefined; + let queryString: string = undefined; + if (input.queryPaneEnabled) { + queryString = input.queryString = this.editor.getEditorText(); + } else { + rowLimit = input.rowLimit; + } + this._queryModelService.disposeEdit(input.uri).then((result) => { - this._queryModelService.initializeEdit(input.uri, input.schemaName, input.tableName, input.objectType, input.rowLimit); + this._queryModelService.initializeEdit(input.uri, input.schemaName, input.tableName, input.objectType, rowLimit, queryString); + input.showResultsEditor(); }, error => { this._notificationService.notify({ severity: Severity.Error, @@ -162,10 +172,12 @@ export class ChangeMaxRowsActionItem extends EventEmitter implements IActionItem this._options = ['200', '1000', '10000']; this._currentOptionsIndex = 0; this.toDispose = []; - this.selectBox = new SelectBox([], -1, contextViewService); + this.selectBox = new SelectBox(this._options, this._options[this._currentOptionsIndex], contextViewService); this._registerListeners(); this._refreshOptions(); this.defaultRowCount = Number(this._options[this._currentOptionsIndex]); + + this.toDispose.push(attachSelectBoxStyler(this.selectBox, _themeService)); } public render(container: HTMLElement): void { @@ -181,6 +193,14 @@ export class ChangeMaxRowsActionItem extends EventEmitter implements IActionItem return true; } + public enable(): void { + this.selectBox.enable(); + } + + public disable(): void { + this.selectBox.disable(); + } + public set setCurrentOptionIndex(selection: number) { this._currentOptionsIndex = this._options.findIndex(x => x === selection.toString()); this._refreshOptions(); @@ -210,3 +230,40 @@ export class ChangeMaxRowsActionItem extends EventEmitter implements IActionItem this.toDispose.push(attachSelectBoxStyler(this.selectBox, this._themeService)); } } + +/** + * Action class that is tied with toggling the Query editor + */ +export class ShowQueryPaneAction extends EditDataAction { + + private static EnabledClass = 'filterLabel'; + public static ID = 'showQueryPaneAction'; + private readonly showSqlLabel = nls.localize('editData.showSql', 'Show SQL Pane'); + private readonly closeSqlLabel = nls.localize('editData.closeSql', 'Close SQL Pane'); + + constructor(editor: EditDataEditor, + @IQueryModelService private _queryModelService: IQueryModelService, + @IConnectionManagementService _connectionManagementService: IConnectionManagementService + ) { + super(editor, ShowQueryPaneAction.ID, ShowQueryPaneAction.EnabledClass, _connectionManagementService); + this.label = this.showSqlLabel; + } + + public set queryPaneEnabled(value: boolean) { + this.updateLabel(value); + } + + private updateLabel(queryPaneEnabled: boolean): void { + if (queryPaneEnabled) { + this.label = this.closeSqlLabel; + } else { + this.label = this.showSqlLabel; + } + } + + public run(): TPromise { + this.editor.toggleQueryPane(); + this.updateLabel(this.editor.queryPaneEnabled()); + return TPromise.as(null); + } +} \ No newline at end of file diff --git a/src/sql/parts/grid/views/editData/editData.component.ts b/src/sql/parts/grid/views/editData/editData.component.ts index c0e22d886d..fe8c4f9fcc 100644 --- a/src/sql/parts/grid/views/editData/editData.component.ts +++ b/src/sql/parts/grid/views/editData/editData.component.ts @@ -22,6 +22,8 @@ import { GridParentComponent } from 'sql/parts/grid/views/gridParentComponent'; import { EditDataGridActionProvider } from 'sql/parts/grid/views/editData/editDataGridActions'; import { error } from 'sql/base/common/log'; import { clone } from 'sql/base/common/objects'; +import { INotificationService } from 'vs/platform/notification/common/notification'; +import Severity from 'vs/base/common/severity'; export const EDITDATA_SELECTOR: string = 'editdata-component'; @@ -40,7 +42,6 @@ export class EditDataComponent extends GridParentComponent implements OnInit, On // All datasets private dataSet: IGridDataSet; private scrollTimeOut: number; - private messagesAdded = false; private scrollEnabled = true; private firstRender = true; private totalElapsedTimeSpan: number; @@ -54,6 +55,8 @@ export class EditDataComponent extends GridParentComponent implements OnInit, On private removingNewRow: boolean; private rowIdMappings: { [gridRowId: number]: number } = {}; + private notificationService: INotificationService; + // Edit Data functions public onActiveCellChanged: (event: { row: number, column: number }) => void; public onCellEditEnd: (event: { row: number, column: number, newValue: any }) => void; @@ -75,6 +78,7 @@ export class EditDataComponent extends GridParentComponent implements OnInit, On let editDataParameters: EditDataComponentParams = this._bootstrapService.getBootstrapParams(this._el.nativeElement.tagName); this.dataService = editDataParameters.dataService; this.actionProvider = this._bootstrapService.instantiationService.createInstance(EditDataGridActionProvider, this.dataService, this.onGridSelectAll(), this.onDeleteRow(), this.onRevertRow()); + this.notificationService = bootstrapService.notificationService; } /** @@ -127,7 +131,6 @@ export class EditDataComponent extends GridParentComponent implements OnInit, On self.renderedDataSets = self.placeHolderDataSets; self.totalElapsedTimeSpan = undefined; self.complete = false; - self.messagesAdded = false; // Hooking up edit functions this.onIsCellEditValid = (row, column, value): boolean => { @@ -302,7 +305,6 @@ export class EditDataComponent extends GridParentComponent implements OnInit, On handleComplete(self: EditDataComponent, event: any): void { self.totalElapsedTimeSpan = event.data; self.complete = true; - self.messagesAdded = true; } handleEditSessionReady(self, event): void { @@ -310,7 +312,12 @@ export class EditDataComponent extends GridParentComponent implements OnInit, On } handleMessage(self: EditDataComponent, event: any): void { - // TODO: what do we do with messages? + if (event.data && event.data.isError) { + self.notificationService.notify({ + severity: Severity.Error, + message: event.data.message + }); + } } handleResultSet(self: EditDataComponent, event: any): void { @@ -360,7 +367,6 @@ export class EditDataComponent extends GridParentComponent implements OnInit, On undefinedDataSet.dataRows = undefined; undefinedDataSet.resized = new EventEmitter(); self.placeHolderDataSets.push(undefinedDataSet); - self.messagesAdded = true; self.onScroll(0); // Setup the state of the selected cell diff --git a/src/sql/parts/objectExplorer/viewlet/objectExplorerActions.ts b/src/sql/parts/objectExplorer/viewlet/objectExplorerActions.ts index 2b4b096ea2..65fcaa2c31 100644 --- a/src/sql/parts/objectExplorer/viewlet/objectExplorerActions.ts +++ b/src/sql/parts/objectExplorer/viewlet/objectExplorerActions.ts @@ -192,9 +192,10 @@ export class OEEditDataAction extends EditDataAction { id: string, label: string, @IQueryEditorService protected _queryEditorService: IQueryEditorService, @IConnectionManagementService protected _connectionManagementService: IConnectionManagementService, + @IScriptingService protected _scriptingService: IScriptingService, @IInstantiationService private _instantiationService: IInstantiationService ) { - super(id, label, _queryEditorService, _connectionManagementService); + super(id, label, _queryEditorService, _connectionManagementService, _scriptingService); } public run(actionContext: any): TPromise { diff --git a/src/sql/parts/query/common/query.contribution.ts b/src/sql/parts/query/common/query.contribution.ts index 59de4386ad..2eb3b40cce 100644 --- a/src/sql/parts/query/common/query.contribution.ts +++ b/src/sql/parts/query/common/query.contribution.ts @@ -33,6 +33,8 @@ import { QueryPlanEditor } from 'sql/parts/queryPlan/queryPlanEditor'; import { QueryPlanInput } from 'sql/parts/queryPlan/queryPlanInput'; import * as Constants from 'sql/parts/query/common/constants'; import { localize } from 'vs/nls'; +import { EditDataResultsEditor } from 'sql/parts/editData/editor/editDataResultsEditor'; +import { EditDataResultsInput } from 'sql/parts/editData/common/editDataResultsInput'; const gridCommandsWeightBonus = 100; // give our commands a little bit more weight over other default list/tree commands @@ -81,6 +83,16 @@ const editDataEditorDescriptor = new EditorDescriptor( Registry.as(EditorExtensions.Editors) .registerEditor(editDataEditorDescriptor, [new SyncDescriptor(EditDataInput)]); +// Editor +const editDataResultsEditorDescriptor = new EditorDescriptor( + EditDataResultsEditor, + EditDataResultsEditor.ID, + 'EditDataResults' +); + +Registry.as(EditorExtensions.Editors) + .registerEditor(editDataResultsEditorDescriptor, [new SyncDescriptor(EditDataResultsInput)]); + let actionRegistry = Registry.as(Extensions.WorkbenchActions); // Query Actions diff --git a/src/sql/parts/query/common/queryEditorService.ts b/src/sql/parts/query/common/queryEditorService.ts index fc67005603..7701b7f1f9 100644 --- a/src/sql/parts/query/common/queryEditorService.ts +++ b/src/sql/parts/query/common/queryEditorService.ts @@ -30,7 +30,7 @@ export interface IQueryEditorService { newQueryPlanEditor(xmlShowPlan: string): Promise; // Creates new edit data session - newEditDataEditor(schemaName: string, tableName: string): Promise; + newEditDataEditor(schemaName: string, tableName: string, queryString: string): Promise; // Clears any QueryEditor data for the given URI held by this service onQueryInputClosed(uri: string): void; diff --git a/src/sql/parts/query/common/queryManagement.ts b/src/sql/parts/query/common/queryManagement.ts index 8b679c3cd8..da34630e2d 100644 --- a/src/sql/parts/query/common/queryManagement.ts +++ b/src/sql/parts/query/common/queryManagement.ts @@ -43,7 +43,7 @@ export interface IQueryManagementService { onEditSessionReady(ownerUri: string, success: boolean, message: string): void; // Edit Data Functions - initializeEdit(ownerUri: string, schemaName: string, objectName: string, objectType: string, rowLimit: number): Thenable; + initializeEdit(ownerUri: string, schemaName: string, objectName: string, objectType: string, rowLimit: number, queryString: string): Thenable; disposeEdit(ownerUri: string): Thenable; updateCell(ownerUri: string, rowId: number, columnId: number, newValue: string): Thenable; commitEdit(ownerUri): Thenable; @@ -68,7 +68,7 @@ export interface IQueryRequestHandler { saveResults(requestParams: sqlops.SaveResultsRequestParams): Thenable; // Edit Data actions - initializeEdit(ownerUri: string, schemaName: string, objectName: string, objectType: string, rowLimit: number): Thenable; + initializeEdit(ownerUri: string, schemaName: string, objectName: string, objectType: string, rowLimit: number, queryString: string): Thenable; disposeEdit(ownerUri: string): Thenable; updateCell(ownerUri: string, rowId: number, columnId: number, newValue: string): Thenable; commitEdit(ownerUri): Thenable; @@ -244,9 +244,9 @@ export class QueryManagementService implements IQueryManagementService { } // Edit Data Functions - public initializeEdit(ownerUri: string, schemaName: string, objectName: string, objectType: string, rowLimit: number): Thenable { + public initializeEdit(ownerUri: string, schemaName: string, objectName: string, objectType: string, rowLimit: number, queryString: string): Thenable { return this._runAction(ownerUri, (runner) => { - return runner.initializeEdit(ownerUri, schemaName, objectName, objectType, rowLimit); + return runner.initializeEdit(ownerUri, schemaName, objectName, objectType, rowLimit, queryString); }); } diff --git a/src/sql/parts/query/execution/keyboardQueryActions.ts b/src/sql/parts/query/execution/keyboardQueryActions.ts index 3471f1ebef..abba8c849c 100644 --- a/src/sql/parts/query/execution/keyboardQueryActions.ts +++ b/src/sql/parts/query/execution/keyboardQueryActions.ts @@ -19,6 +19,7 @@ import { IQueryModelService } from 'sql/parts/query/execution/queryModel'; import * as WorkbenchUtils from 'sql/workbench/common/sqlWorkbenchUtils'; import * as Constants from 'sql/parts/query/common/constants'; import * as ConnectionConstants from 'sql/parts/connection/common/constants'; +import { EditDataEditor } from 'sql/parts/editData/editor/editDataEditor'; const singleQuote = '\''; @@ -98,8 +99,8 @@ export class RunQueryKeyboardAction extends Action { public run(): TPromise { let editor = this._editorService.getActiveEditor(); - if (editor && editor instanceof QueryEditor) { - let queryEditor: QueryEditor = editor; + if (editor && (editor instanceof QueryEditor || editor instanceof EditDataEditor)) { + let queryEditor: QueryEditor | EditDataEditor = editor; queryEditor.runQuery(); } return TPromise.as(null); @@ -174,8 +175,8 @@ export class CancelQueryKeyboardAction extends Action { public run(): TPromise { let editor = this._editorService.getActiveEditor(); - if (editor && editor instanceof QueryEditor) { - let queryEditor: QueryEditor = editor; + if (editor && (editor instanceof QueryEditor || editor instanceof EditDataEditor)) { + let queryEditor: QueryEditor | EditDataEditor = editor; queryEditor.cancelQuery(); } return TPromise.as(null); diff --git a/src/sql/parts/query/execution/queryModel.ts b/src/sql/parts/query/execution/queryModel.ts index 7ea485fbfa..089a450830 100644 --- a/src/sql/parts/query/execution/queryModel.ts +++ b/src/sql/parts/query/execution/queryModel.ts @@ -57,7 +57,7 @@ export interface IQueryModelService { // Edit Data Functions - initializeEdit(ownerUri: string, schemaName: string, objectName: string, objectType: string, rowLimit: number): void; + initializeEdit(ownerUri: string, schemaName: string, objectName: string, objectType: string, rowLimit: number, queryString: string): void; disposeEdit(ownerUri: string): Thenable; updateCell(ownerUri: string, rowId: number, columnId: number, newValue: string): Thenable; commitEdit(ownerUri): Thenable; diff --git a/src/sql/parts/query/execution/queryModelService.ts b/src/sql/parts/query/execution/queryModelService.ts index 4813ef9943..14d521c345 100644 --- a/src/sql/parts/query/execution/queryModelService.ts +++ b/src/sql/parts/query/execution/queryModelService.ts @@ -352,7 +352,7 @@ export class QueryModelService implements IQueryModelService { } // EDIT DATA METHODS ///////////////////////////////////////////////////// - initializeEdit(ownerUri: string, schemaName: string, objectName: string, objectType: string, rowLimit: number): void { + initializeEdit(ownerUri: string, schemaName: string, objectName: string, objectType: string, rowLimit: number, queryString: string): void { // Reuse existing query runner if it exists let queryRunner: QueryRunner; let info: QueryInfo; @@ -368,6 +368,8 @@ export class QueryModelService implements IQueryModelService { queryRunner = existingRunner; } else { + info = new QueryInfo(); + // We do not have a query runner for this editor, so create a new one // and map it to the results uri queryRunner = this._instantiationService.createInstance(QueryRunner, ownerUri, ownerUri); @@ -376,15 +378,21 @@ export class QueryModelService implements IQueryModelService { }); queryRunner.addListener(QREvents.BATCH_START, batch => { let link = undefined; + let messageText = LocalizedConstants.runQueryBatchStartMessage; if (batch.selection) { - link = { - text: strings.format(LocalizedConstants.runQueryBatchStartLine, batch.selection.startLine + 1), - uri: '' - }; + if (info.selectionSnippet) { + // This indicates it's a query string. Do not include line information since it'll be inaccurate, but show some of the + // executed query text + messageText = nls.localize('runQueryStringBatchStartMessage', 'Started executing query "{0}"', info.selectionSnippet); + } else { + link = { + text: strings.format(LocalizedConstants.runQueryBatchStartLine, batch.selection.startLine + 1) + }; + } } let message = { - message: LocalizedConstants.runQueryBatchStartMessage, - batchId: undefined, + message: messageText, + batchId: batch.id, isError: false, time: new Date().toLocaleTimeString(), link: link @@ -407,13 +415,20 @@ export class QueryModelService implements IQueryModelService { this._fireQueryEvent(e.ownerUri, 'editSessionReady'); }); - info = new QueryInfo(); info.queryRunner = queryRunner; info.dataService = this._instantiationService.createInstance(DataService, ownerUri); this._queryInfoMap.set(ownerUri, info); } - queryRunner.initializeEdit(ownerUri, schemaName, objectName, objectType, rowLimit); + if (queryString) { + if (queryString.length < selectionSnippetMaxLen) { + info.selectionSnippet = queryString; + } else { + info.selectionSnippet = queryString.substring(0, selectionSnippetMaxLen - 3) + '...'; + } + } + + queryRunner.initializeEdit(ownerUri, schemaName, objectName, objectType, rowLimit, queryString); } public cancelInitializeEdit(input: QueryRunner | string): void { diff --git a/src/sql/parts/query/execution/queryRunner.ts b/src/sql/parts/query/execution/queryRunner.ts index d63211ded6..84f9f3b9d1 100644 --- a/src/sql/parts/query/execution/queryRunner.ts +++ b/src/sql/parts/query/execution/queryRunner.ts @@ -300,13 +300,13 @@ export default class QueryRunner { /* * Handle a session ready event for Edit Data */ - public initializeEdit(ownerUri: string, schemaName: string, objectName: string, objectType: string, rowLimit: number): Thenable { + public initializeEdit(ownerUri: string, schemaName: string, objectName: string, objectType: string, rowLimit: number, queryString: string): Thenable { // Update internal state to show that we're executing the query this._isExecuting = true; this._totalElapsedMilliseconds = 0; // TODO issue #228 add statusview callbacks here - return this._queryManagementService.initializeEdit(ownerUri, schemaName, objectName, objectType, rowLimit).then(result => { + return this._queryManagementService.initializeEdit(ownerUri, schemaName, objectName, objectType, rowLimit, queryString).then(result => { // The query has started, so lets fire up the result pane this._eventEmitter.emit(EventType.START); this._queryManagementService.registerRunner(this, ownerUri); diff --git a/src/sql/parts/query/services/queryEditorService.ts b/src/sql/parts/query/services/queryEditorService.ts index 8ba4e5f555..7fd2b81cf5 100644 --- a/src/sql/parts/query/services/queryEditorService.ts +++ b/src/sql/parts/query/services/queryEditorService.ts @@ -29,6 +29,7 @@ import paths = require('vs/base/common/paths'); import { isLinux } from 'vs/base/common/platform'; import { Schemas } from 'vs/base/common/network'; import { INotificationService } from 'vs/platform/notification/common/notification'; +import { EditDataResultsInput } from 'sql/parts/editData/common/editDataResultsInput'; const fs = require('fs'); @@ -120,7 +121,7 @@ export class QueryEditorService implements IQueryEditorService { /** * Creates new edit data session */ - public newEditDataEditor(schemaName: string, tableName: string): Promise { + public newEditDataEditor(schemaName: string, tableName: string, sqlContent: string): Promise { return new Promise((resolve, reject) => { try { @@ -129,8 +130,17 @@ export class QueryEditorService implements IQueryEditorService { let filePath = this.createEditDataFileName(objectName); let docUri: URI = URI.from({ scheme: Schemas.untitled, path: filePath }); + // Create a sql document pane with accoutrements + const fileInput = this._untitledEditorService.createOrGet(docUri, 'sql'); + fileInput.resolve().then(m => { + if (sqlContent) { + m.textEditorModel.setValue(sqlContent); + } + }); + // Create an EditDataInput for editing - let editDataInput: EditDataInput = this._instantiationService.createInstance(EditDataInput, docUri, schemaName, tableName); + const resultsInput: EditDataResultsInput = this._instantiationService.createInstance(EditDataResultsInput, docUri.toString()); + let editDataInput: EditDataInput = this._instantiationService.createInstance(EditDataInput, docUri, schemaName, tableName, fileInput, sqlContent, resultsInput); this._editorService.openEditor(editDataInput, { pinned: true }) .then((editor) => { @@ -212,7 +222,7 @@ export class QueryEditorService implements IQueryEditorService { } let uri: URI = QueryEditorService._getEditorChangeUri(editor.input, changingToSql); - if(uri.scheme === Schemas.untitled && editor.input instanceof QueryInput) + if(uri.scheme === Schemas.untitled && (editor.input instanceof QueryInput || editor.input instanceof EditDataInput)) { QueryEditorService.notificationService.notify({ severity: Severity.Error, @@ -299,10 +309,8 @@ export class QueryEditorService implements IQueryEditorService { filePath = editDataFileName(counter); } - // TODO: check if this document name already exists in any open documents tabs - let fileNames: string[] = []; - this._editorGroupService.getStacksModel().groups.map(group => group.getEditors().map(editor => fileNames.push(editor.getName()))); - while (fileNames.find(x => x.toUpperCase() === filePath.toUpperCase())) { + let untitledEditors = this._untitledEditorService.getAll(); + while (untitledEditors.find(x => x.getName().toUpperCase() === filePath.toUpperCase())) { counter++; filePath = editDataFileName(counter); } diff --git a/src/sql/sqlops.d.ts b/src/sql/sqlops.d.ts index 1d00285dd9..aad2ab2721 100644 --- a/src/sql/sqlops.d.ts +++ b/src/sql/sqlops.d.ts @@ -651,7 +651,7 @@ declare module 'sqlops' { createRow(ownerUri: string): Thenable; deleteRow(ownerUri: string, rowId: number): Thenable; disposeEdit(ownerUri: string): Thenable; - initializeEdit(ownerUri: string, schemaName: string, objectName: string, objectType: string, rowLimit: number): Thenable; + initializeEdit(ownerUri: string, schemaName: string, objectName: string, objectType: string, rowLimit: number, queryString: string): Thenable; revertCell(ownerUri: string, rowId: number, columnId: number): Thenable; revertRow(ownerUri: string, rowId: number): Thenable; updateCell(ownerUri: string, rowId: number, columnId: number, newValue: string): Thenable; @@ -879,6 +879,7 @@ declare module 'sqlops' { objectName: string; schemaName: string; objectType: string; + queryString: string; } diff --git a/src/sql/workbench/api/node/extHostDataProtocol.ts b/src/sql/workbench/api/node/extHostDataProtocol.ts index dfbcb56138..4031d415c2 100644 --- a/src/sql/workbench/api/node/extHostDataProtocol.ts +++ b/src/sql/workbench/api/node/extHostDataProtocol.ts @@ -247,8 +247,8 @@ export class ExtHostDataProtocol extends ExtHostDataProtocolShape { return this._resolveProvider(handle).disposeEdit(ownerUri); } - $initializeEdit(handle: number, ownerUri: string, schemaName: string, objectName: string, objectType: string, rowLimit: number): Thenable { - return this._resolveProvider(handle).initializeEdit(ownerUri, schemaName, objectName, objectType, rowLimit); + $initializeEdit(handle: number, ownerUri: string, schemaName: string, objectName: string, objectType: string, rowLimit: number, queryString: string): Thenable { + return this._resolveProvider(handle).initializeEdit(ownerUri, schemaName, objectName, objectType, rowLimit, queryString); } $revertCell(handle: number, ownerUri: string, rowId: number, columnId: number): Thenable { diff --git a/src/sql/workbench/api/node/mainThreadDataProtocol.ts b/src/sql/workbench/api/node/mainThreadDataProtocol.ts index a1e34fa5c2..2407597f0a 100644 --- a/src/sql/workbench/api/node/mainThreadDataProtocol.ts +++ b/src/sql/workbench/api/node/mainThreadDataProtocol.ts @@ -132,8 +132,8 @@ export class MainThreadDataProtocol implements MainThreadDataProtocolShape { return self._serializationService.saveAs(requestParams.resultFormat, requestParams.filePath, undefined, true); } }, - initializeEdit(ownerUri: string, schemaName: string, objectName: string, objectType: string, rowLimit: number): Thenable { - return self._proxy.$initializeEdit(handle, ownerUri, schemaName, objectName, objectType, rowLimit); + initializeEdit(ownerUri: string, schemaName: string, objectName: string, objectType: string, rowLimit: number, queryString: string): Thenable { + return self._proxy.$initializeEdit(handle, ownerUri, schemaName, objectName, objectType, rowLimit, queryString); }, updateCell(ownerUri: string, rowId: number, columnId: number, newValue: string): Thenable { return self._proxy.$updateCell(handle, ownerUri, rowId, columnId, newValue); diff --git a/src/sql/workbench/api/node/sqlExtHost.protocol.ts b/src/sql/workbench/api/node/sqlExtHost.protocol.ts index 79b3616b64..d12dcf9639 100644 --- a/src/sql/workbench/api/node/sqlExtHost.protocol.ts +++ b/src/sql/workbench/api/node/sqlExtHost.protocol.ts @@ -196,7 +196,7 @@ export abstract class ExtHostDataProtocolShape { /** * Initializes a new edit data session for the requested table/view */ - $initializeEdit(handle: number, ownerUri: string, schemaName: string, objectName: string, objectType: string, rowLimit: number): Thenable { throw ni(); } + $initializeEdit(handle: number, ownerUri: string, schemaName: string, objectName: string, objectType: string, rowLimit: number, queryString: string): Thenable { throw ni(); } /** * Reverts any pending changes for the requested cell and returns the original value diff --git a/src/sql/workbench/common/actions.ts b/src/sql/workbench/common/actions.ts index f65cff0ef6..725e5bbb1d 100644 --- a/src/sql/workbench/common/actions.ts +++ b/src/sql/workbench/common/actions.ts @@ -187,19 +187,20 @@ export class EditDataAction extends Action { constructor( id: string, label: string, @IQueryEditorService protected _queryEditorService: IQueryEditorService, - @IConnectionManagementService protected _connectionManagementService: IConnectionManagementService + @IConnectionManagementService protected _connectionManagementService: IConnectionManagementService, + @IScriptingService protected _scriptingService: IScriptingService ) { super(id, label); } public run(actionContext: BaseActionContext): TPromise { return new TPromise((resolve, reject) => { - TaskUtilities.editData( + TaskUtilities.scriptEditSelect( actionContext.profile, - actionContext.object.name, - actionContext.object.schema, + actionContext.object, this._connectionManagementService, - this._queryEditorService + this._queryEditorService, + this._scriptingService ).then( result => { resolve(true); diff --git a/src/sql/workbench/common/taskUtilities.ts b/src/sql/workbench/common/taskUtilities.ts index 19e35477a3..042b32be9e 100644 --- a/src/sql/workbench/common/taskUtilities.ts +++ b/src/sql/workbench/common/taskUtilities.ts @@ -169,19 +169,33 @@ export function scriptSelect(connectionProfile: IConnectionProfile, metadata: sq /** * Opens a new Edit Data session */ -export function editData(connectionProfile: IConnectionProfile, tableName: string, schemaName: string, connectionService: IConnectionManagementService, queryEditorService: IQueryEditorService): Promise { - return new Promise((resolve) => { - queryEditorService.newEditDataEditor(schemaName, tableName).then((owner: EditDataInput) => { - // Connect our editor - let options: IConnectionCompletionOptions = { - params: { connectionType: ConnectionType.editor, runQueryOnCompletion: RunQueryOnConnectionMode.none, input: owner }, - saveTheConnection: false, - showDashboard: false, - showConnectionDialogOnError: true, - showFirewallRuleOnError: true - }; - connectionService.connect(connectionProfile, owner.uri, options).then(() => { - resolve(); +export function scriptEditSelect(connectionProfile: IConnectionProfile, metadata: sqlops.ObjectMetadata, connectionService: IConnectionManagementService, queryEditorService: IQueryEditorService, scriptingService: IScriptingService): Promise { + return new Promise((resolve, reject) => { + connectionService.connectIfNotConnected(connectionProfile).then(connectionResult => { + let paramDetails: sqlops.ScriptingParamDetails = getScriptingParamDetails(connectionService, connectionResult, metadata); + scriptingService.script(connectionResult, metadata, ScriptOperation.Select, paramDetails).then(result => { + if (result.script) { + queryEditorService.newEditDataEditor(metadata.schema, metadata.name, result.script).then((owner: EditDataInput) => { + // Connect our editor + let options: IConnectionCompletionOptions = { + params: { connectionType: ConnectionType.editor, runQueryOnCompletion: RunQueryOnConnectionMode.none, input: owner }, + saveTheConnection: false, + showDashboard: false, + showConnectionDialogOnError: true, + showFirewallRuleOnError: true + }; + connectionService.connect(connectionProfile, owner.uri, options).then(() => { + resolve(); + }); + }).catch(editorError => { + reject(editorError); + }); + } else { + let errMsg: string = nls.localize('scriptSelectNotFound', 'No script was returned when calling select script on object '); + reject(errMsg.concat(metadata.metadataTypeName)); + } + }, scriptError => { + reject(scriptError); }); }); }); diff --git a/src/vs/workbench/common/editor.ts b/src/vs/workbench/common/editor.ts index 4a23a78578..d89939fe4c 100644 --- a/src/vs/workbench/common/editor.ts +++ b/src/vs/workbench/common/editor.ts @@ -131,6 +131,16 @@ export abstract class EditorInput implements IEditorInput { return this._onDispose.event; } + // {{SQL CARBON EDIT}} + // Saving is not supported in the EditData query editor, so this can be overriden in its Input. + private _savingSupported: boolean = true; + public get savingSupported(): boolean { + return this._savingSupported; + } + public disableSaving() { + this._savingSupported = false; + } + /** * Returns the associated resource of this input if any. */ diff --git a/src/vs/workbench/common/editor/untitledEditorInput.ts b/src/vs/workbench/common/editor/untitledEditorInput.ts index 8fc7610ed7..1c02449e25 100644 --- a/src/vs/workbench/common/editor/untitledEditorInput.ts +++ b/src/vs/workbench/common/editor/untitledEditorInput.ts @@ -167,6 +167,11 @@ export class UntitledEditorInput extends EditorInput implements IEncodingSupport } public isDirty(): boolean { + // {{SQL CARBON EDIT}} + if (!this.savingSupported) { + return false; + } + if (this.cachedModel) { return this.cachedModel.isDirty(); } diff --git a/src/vs/workbench/parts/files/electron-browser/fileCommands.ts b/src/vs/workbench/parts/files/electron-browser/fileCommands.ts index cc70448d33..fac49b5928 100644 --- a/src/vs/workbench/parts/files/electron-browser/fileCommands.ts +++ b/src/vs/workbench/parts/files/electron-browser/fileCommands.ts @@ -11,7 +11,7 @@ import { TPromise } from 'vs/base/common/winjs.base'; import * as labels from 'vs/base/common/labels'; import URI from 'vs/base/common/uri'; import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { toResource, IEditorCommandsContext } from 'vs/workbench/common/editor'; +import { toResource, IEditorCommandsContext, EditorInput } from 'vs/workbench/common/editor'; import { IWindowsService } from 'vs/platform/windows/common/windows'; import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; @@ -89,6 +89,11 @@ function save(resource: URI, isSaveAs: boolean, editorService: IWorkbenchEditorS textFileService: ITextFileService, editorGroupService: IEditorGroupService, queryEditorService: IQueryEditorService,): TPromise { if (resource && (fileService.canHandleResource(resource) || resource.scheme === Schemas.untitled)) { + // {{SQL CARBON EDIT}} + let editorInput = editorService.getActiveEditorInput(); + if (editorInput instanceof EditorInput && !(editorInput).savingSupported) { + return TPromise.as(false); + } // Save As (or Save untitled with associated path) if (isSaveAs || resource.scheme === Schemas.untitled) {