mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 10:58:30 -05:00
Support isDirty flag for model view editors and begin plumb through of save support (#2547)
* Add dirty and save support to model view * Add issue # for a TODO
This commit is contained in:
@@ -3,16 +3,50 @@
|
|||||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
import * as sqlops from 'sqlops';
|
||||||
|
|
||||||
import { TPromise } from 'vs/base/common/winjs.base';
|
import { TPromise } from 'vs/base/common/winjs.base';
|
||||||
import { IEditorModel } from 'vs/platform/editor/common/editor';
|
import { IEditorModel } from 'vs/platform/editor/common/editor';
|
||||||
import { EditorInput } from 'vs/workbench/common/editor';
|
import { EditorInput, EditorModel, ConfirmResult } from 'vs/workbench/common/editor';
|
||||||
import * as DOM from 'vs/base/browser/dom';
|
import * as DOM from 'vs/base/browser/dom';
|
||||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||||
import { IPartService, Parts } from 'vs/workbench/services/part/common/partService';
|
import { IPartService, Parts } from 'vs/workbench/services/part/common/partService';
|
||||||
|
|
||||||
import { DialogPane } from 'sql/platform/dialog/dialogPane';
|
import { DialogPane } from 'sql/platform/dialog/dialogPane';
|
||||||
|
import { Emitter, Event } from 'vs/base/common/event';
|
||||||
|
|
||||||
import * as sqlops from 'sqlops';
|
export type ModeViewSaveHandler = (handle: number) => Thenable<boolean>;
|
||||||
|
|
||||||
|
export class ModelViewInputModel extends EditorModel {
|
||||||
|
private dirty: boolean;
|
||||||
|
private readonly _onDidChangeDirty: Emitter<void> = this._register(new Emitter<void>());
|
||||||
|
get onDidChangeDirty(): Event<void> { return this._onDidChangeDirty.event; }
|
||||||
|
|
||||||
|
constructor(public readonly modelViewId, private readonly handle: number, private saveHandler?: ModeViewSaveHandler) {
|
||||||
|
super();
|
||||||
|
this.dirty = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
get isDirty(): boolean {
|
||||||
|
return this.dirty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public setDirty(dirty: boolean): void {
|
||||||
|
if (this.dirty === dirty) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.dirty = dirty;
|
||||||
|
this._onDidChangeDirty.fire();
|
||||||
|
}
|
||||||
|
|
||||||
|
save(): TPromise<boolean> {
|
||||||
|
if (this.saveHandler) {
|
||||||
|
return TPromise.wrap(this.saveHandler(this.handle));
|
||||||
|
}
|
||||||
|
return TPromise.wrap(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
export class ModelViewInput extends EditorInput {
|
export class ModelViewInput extends EditorInput {
|
||||||
|
|
||||||
public static ID: string = 'workbench.editorinputs.ModelViewEditorInput';
|
public static ID: string = 'workbench.editorinputs.ModelViewEditorInput';
|
||||||
@@ -20,14 +54,15 @@ export class ModelViewInput extends EditorInput {
|
|||||||
private _dialogPaneContainer: HTMLElement;
|
private _dialogPaneContainer: HTMLElement;
|
||||||
private _dialogPane: DialogPane;
|
private _dialogPane: DialogPane;
|
||||||
|
|
||||||
constructor(private _title: string, private _modelViewId: string,
|
constructor(private _title: string, private _model: ModelViewInputModel,
|
||||||
private _options: sqlops.ModelViewEditorOptions,
|
private _options: sqlops.ModelViewEditorOptions,
|
||||||
@IInstantiationService private _instantiationService: IInstantiationService,
|
@IInstantiationService private _instantiationService: IInstantiationService,
|
||||||
@IPartService private readonly _partService: IPartService
|
@IPartService private readonly _partService: IPartService
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
|
this._model.onDidChangeDirty(() => this._onDidChangeDirty.fire());
|
||||||
this._container = document.createElement('div');
|
this._container = document.createElement('div');
|
||||||
this._container.id = `modelView-${_modelViewId}`;
|
this._container.id = `modelView-${_model.modelViewId}`;
|
||||||
this._partService.getContainer(Parts.EDITOR_PART).appendChild(this._container);
|
this._partService.getContainer(Parts.EDITOR_PART).appendChild(this._container);
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -37,7 +72,7 @@ export class ModelViewInput extends EditorInput {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public get modelViewId(): string {
|
public get modelViewId(): string {
|
||||||
return this._modelViewId;
|
return this._model.modelViewId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getTypeId(): string {
|
public getTypeId(): string {
|
||||||
@@ -85,6 +120,31 @@ export class ModelViewInput extends EditorInput {
|
|||||||
return this._options;
|
return this._options;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An editor that is dirty will be asked to be saved once it closes.
|
||||||
|
*/
|
||||||
|
isDirty(): boolean {
|
||||||
|
return this._model.isDirty;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subclasses should bring up a proper dialog for the user if the editor is dirty and return the result.
|
||||||
|
*/
|
||||||
|
confirmSave(): TPromise<ConfirmResult> {
|
||||||
|
// TODO #2530 support save on close / confirm save. This is significantly more work
|
||||||
|
// as we need to either integrate with textFileService (seems like this isn't viable)
|
||||||
|
// or register our own complimentary service that handles the lifecycle operations such
|
||||||
|
// as close all, auto save etc.
|
||||||
|
return TPromise.wrap(ConfirmResult.DONT_SAVE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves the editor if it is dirty. Subclasses return a promise with a boolean indicating the success of the operation.
|
||||||
|
*/
|
||||||
|
save(): TPromise<boolean> {
|
||||||
|
return this._model.save();
|
||||||
|
}
|
||||||
|
|
||||||
public dispose(): void {
|
public dispose(): void {
|
||||||
if (this._dialogPane) {
|
if (this._dialogPane) {
|
||||||
this._dialogPane.dispose();
|
this._dialogPane.dispose();
|
||||||
@@ -93,6 +153,9 @@ export class ModelViewInput extends EditorInput {
|
|||||||
this._container.remove();
|
this._container.remove();
|
||||||
this._container = undefined;
|
this._container = undefined;
|
||||||
}
|
}
|
||||||
|
if (this._model) {
|
||||||
|
this._model.dispose();
|
||||||
|
}
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
16
src/sql/sqlops.proposed.d.ts
vendored
16
src/sql/sqlops.proposed.d.ts
vendored
@@ -1115,11 +1115,22 @@ declare module 'sqlops' {
|
|||||||
export function createModelViewEditor(title: string, options?: ModelViewEditorOptions): ModelViewEditor;
|
export function createModelViewEditor(title: string, options?: ModelViewEditorOptions): ModelViewEditor;
|
||||||
|
|
||||||
export interface ModelViewEditor extends window.modelviewdialog.ModelViewPanel {
|
export interface ModelViewEditor extends window.modelviewdialog.ModelViewPanel {
|
||||||
|
/**
|
||||||
|
* `true` if there are unpersisted changes.
|
||||||
|
* This is editable to support extensions updating the dirty status.
|
||||||
|
*/
|
||||||
|
isDirty: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Opens the editor
|
* Opens the editor
|
||||||
*/
|
*/
|
||||||
openEditor(position?: vscode.ViewColumn): Thenable<void>;
|
openEditor(position?: vscode.ViewColumn): Thenable<void>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a save handler for this editor. This will be called if [supportsSave](#ModelViewEditorOptions.supportsSave)
|
||||||
|
* is set to true and the editor is marked as dirty
|
||||||
|
*/
|
||||||
|
registerSaveHandler(handler: () => Thenable<boolean>);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1128,6 +1139,11 @@ declare module 'sqlops' {
|
|||||||
* Should the model view editor's context be kept around even when the editor is no longer visible? It is false by default
|
* Should the model view editor's context be kept around even when the editor is no longer visible? It is false by default
|
||||||
*/
|
*/
|
||||||
readonly retainContextWhenHidden?: boolean;
|
readonly retainContextWhenHidden?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Does this model view editor support save?
|
||||||
|
*/
|
||||||
|
readonly supportsSave?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum DataProviderType {
|
export enum DataProviderType {
|
||||||
|
|||||||
@@ -75,6 +75,9 @@ class ModelViewPanelImpl implements sqlops.window.modelviewdialog.ModelViewPanel
|
|||||||
}
|
}
|
||||||
|
|
||||||
class ModelViewEditorImpl extends ModelViewPanelImpl implements sqlops.workspace.ModelViewEditor {
|
class ModelViewEditorImpl extends ModelViewPanelImpl implements sqlops.workspace.ModelViewEditor {
|
||||||
|
private _isDirty: boolean;
|
||||||
|
private _saveHandler: () => Thenable<boolean>;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
extHostModelViewDialog: ExtHostModelViewDialog,
|
extHostModelViewDialog: ExtHostModelViewDialog,
|
||||||
extHostModelView: ExtHostModelViewShape,
|
extHostModelView: ExtHostModelViewShape,
|
||||||
@@ -84,10 +87,32 @@ class ModelViewEditorImpl extends ModelViewPanelImpl implements sqlops.workspace
|
|||||||
private _options: sqlops.ModelViewEditorOptions
|
private _options: sqlops.ModelViewEditorOptions
|
||||||
) {
|
) {
|
||||||
super('modelViewEditor', extHostModelViewDialog, extHostModelView, extensionLocation);
|
super('modelViewEditor', extHostModelViewDialog, extHostModelView, extensionLocation);
|
||||||
|
this._isDirty = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public openEditor(position?: vscode.ViewColumn): Thenable<void> {
|
public openEditor(position?: vscode.ViewColumn): Thenable<void> {
|
||||||
return this._proxy.$openEditor(this._modelViewId, this._title, this._options, position);
|
return this._proxy.$openEditor(this.handle, this._modelViewId, this._title, this._options, position);
|
||||||
|
}
|
||||||
|
|
||||||
|
public get isDirty(): boolean {
|
||||||
|
return this._isDirty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public set isDirty(value: boolean) {
|
||||||
|
this._isDirty = value;
|
||||||
|
this._proxy.$setDirty(this.handle, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
registerSaveHandler(handler: () => Thenable<boolean>) {
|
||||||
|
this._saveHandler = handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
public handleSave(): Thenable<boolean> {
|
||||||
|
if (this._saveHandler) {
|
||||||
|
return Promise.resolve(this._saveHandler());
|
||||||
|
} else {
|
||||||
|
return Promise.resolve(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -470,6 +495,11 @@ export class ExtHostModelViewDialog implements ExtHostModelViewDialogShape {
|
|||||||
return dialog.validateClose();
|
return dialog.validateClose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public $handleSave(handle: number): Thenable<boolean> {
|
||||||
|
let editor = this._objectsByHandle.get(handle) as ModelViewEditorImpl;
|
||||||
|
return editor.handleSave();
|
||||||
|
}
|
||||||
|
|
||||||
public openDialog(dialog: sqlops.window.modelviewdialog.Dialog): void {
|
public openDialog(dialog: sqlops.window.modelviewdialog.Dialog): void {
|
||||||
let handle = this.getHandle(dialog);
|
let handle = this.getHandle(dialog);
|
||||||
this.updateDialogContent(dialog);
|
this.updateDialogContent(dialog);
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||||
import { IEditorOptions } from 'vs/platform/editor/common/editor';
|
import { IEditorOptions } from 'vs/platform/editor/common/editor';
|
||||||
|
import { IEditor } from 'vs/workbench/common/editor';
|
||||||
import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers';
|
import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers';
|
||||||
import { IExtHostContext } from 'vs/workbench/api/node/extHost.protocol';
|
import { IExtHostContext } from 'vs/workbench/api/node/extHost.protocol';
|
||||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||||
@@ -14,7 +15,7 @@ import { MainThreadModelViewDialogShape, SqlMainContext, ExtHostModelViewDialogS
|
|||||||
import { Dialog, DialogTab, DialogButton, WizardPage, Wizard } from 'sql/platform/dialog/dialogTypes';
|
import { Dialog, DialogTab, DialogButton, WizardPage, Wizard } from 'sql/platform/dialog/dialogTypes';
|
||||||
import { CustomDialogService } from 'sql/platform/dialog/customDialogService';
|
import { CustomDialogService } from 'sql/platform/dialog/customDialogService';
|
||||||
import { IModelViewDialogDetails, IModelViewTabDetails, IModelViewButtonDetails, IModelViewWizardPageDetails, IModelViewWizardDetails } from 'sql/workbench/api/common/sqlExtHostTypes';
|
import { IModelViewDialogDetails, IModelViewTabDetails, IModelViewButtonDetails, IModelViewWizardPageDetails, IModelViewWizardDetails } from 'sql/workbench/api/common/sqlExtHostTypes';
|
||||||
import { ModelViewInput } from 'sql/parts/modelComponents/modelEditor/modelViewInput';
|
import { ModelViewInput, ModelViewInputModel, ModeViewSaveHandler } from 'sql/parts/modelComponents/modelEditor/modelViewInput';
|
||||||
|
|
||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
import * as sqlops from 'sqlops';
|
import * as sqlops from 'sqlops';
|
||||||
@@ -28,6 +29,7 @@ export class MainThreadModelViewDialog implements MainThreadModelViewDialogShape
|
|||||||
private readonly _wizardPages = new Map<number, WizardPage>();
|
private readonly _wizardPages = new Map<number, WizardPage>();
|
||||||
private readonly _wizardPageHandles = new Map<WizardPage, number>();
|
private readonly _wizardPageHandles = new Map<WizardPage, number>();
|
||||||
private readonly _wizards = new Map<number, Wizard>();
|
private readonly _wizards = new Map<number, Wizard>();
|
||||||
|
private readonly _editorInputModels = new Map<number, ModelViewInputModel>();
|
||||||
private _dialogService: CustomDialogService;
|
private _dialogService: CustomDialogService;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@@ -43,15 +45,18 @@ export class MainThreadModelViewDialog implements MainThreadModelViewDialogShape
|
|||||||
throw new Error('Method not implemented.');
|
throw new Error('Method not implemented.');
|
||||||
}
|
}
|
||||||
|
|
||||||
public $openEditor(modelViewId: string, title: string, options?: sqlops.ModelViewEditorOptions, position?: vscode.ViewColumn): Thenable<void> {
|
public $openEditor(handle: number, modelViewId: string, title: string, options?: sqlops.ModelViewEditorOptions, position?: vscode.ViewColumn): Thenable<void> {
|
||||||
return new Promise<void>((resolve, reject) => {
|
return new Promise<void>((resolve, reject) => {
|
||||||
let input = this._instatiationService.createInstance(ModelViewInput, title, modelViewId, options);
|
let saveHandler: ModeViewSaveHandler = options && options.supportsSave ? (h) => this.handleSave(h) : undefined;
|
||||||
|
let model = new ModelViewInputModel(modelViewId, handle, saveHandler);
|
||||||
|
let input = this._instatiationService.createInstance(ModelViewInput, title, model, options);
|
||||||
let editorOptions = {
|
let editorOptions = {
|
||||||
preserveFocus: true,
|
preserveFocus: true,
|
||||||
pinned: true
|
pinned: true
|
||||||
};
|
};
|
||||||
|
|
||||||
this._editorService.openEditor(input, editorOptions, position as any).then(() => {
|
this._editorService.openEditor(input, editorOptions, position as any).then((editor) => {
|
||||||
|
this._editorInputModels.set(handle, model);
|
||||||
resolve();
|
resolve();
|
||||||
}, error => {
|
}, error => {
|
||||||
reject(error);
|
reject(error);
|
||||||
@@ -59,6 +64,10 @@ export class MainThreadModelViewDialog implements MainThreadModelViewDialogShape
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private handleSave(handle: number): Thenable<boolean> {
|
||||||
|
return this._proxy.$handleSave(handle);
|
||||||
|
}
|
||||||
|
|
||||||
public $openDialog(handle: number): Thenable<void> {
|
public $openDialog(handle: number): Thenable<void> {
|
||||||
let dialog = this.getDialog(handle);
|
let dialog = this.getDialog(handle);
|
||||||
this._dialogService.showDialog(dialog);
|
this._dialogService.showDialog(dialog);
|
||||||
@@ -213,6 +222,21 @@ export class MainThreadModelViewDialog implements MainThreadModelViewDialogShape
|
|||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$setDirty(handle: number, isDirty: boolean): void {
|
||||||
|
let model = this.getEditor(handle);
|
||||||
|
if (model) {
|
||||||
|
model.setDirty(isDirty);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private getEditor(handle: number): ModelViewInputModel {
|
||||||
|
let model = this._editorInputModels.get(handle);
|
||||||
|
if (!model) {
|
||||||
|
throw new Error('No editor matching the given handle');
|
||||||
|
}
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
|
||||||
private getDialog(handle: number): Dialog {
|
private getDialog(handle: number): Dialog {
|
||||||
let dialog = this._dialogs.get(handle);
|
let dialog = this._dialogs.get(handle);
|
||||||
if (!dialog) {
|
if (!dialog) {
|
||||||
|
|||||||
@@ -672,10 +672,11 @@ export interface ExtHostModelViewDialogShape {
|
|||||||
$updateWizardPageInfo(handle: number, pageHandles: number[], currentPageIndex: number): void;
|
$updateWizardPageInfo(handle: number, pageHandles: number[], currentPageIndex: number): void;
|
||||||
$validateNavigation(handle: number, info: sqlops.window.modelviewdialog.WizardPageChangeInfo): Thenable<boolean>;
|
$validateNavigation(handle: number, info: sqlops.window.modelviewdialog.WizardPageChangeInfo): Thenable<boolean>;
|
||||||
$validateDialogClose(handle: number): Thenable<boolean>;
|
$validateDialogClose(handle: number): Thenable<boolean>;
|
||||||
|
$handleSave(handle: number): Thenable<boolean>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MainThreadModelViewDialogShape extends IDisposable {
|
export interface MainThreadModelViewDialogShape extends IDisposable {
|
||||||
$openEditor(modelViewId: string, title: string, options?: sqlops.ModelViewEditorOptions, position?: vscode.ViewColumn): Thenable<void>;
|
$openEditor(handle: number, modelViewId: string, title: string, options?: sqlops.ModelViewEditorOptions, position?: vscode.ViewColumn): Thenable<void>;
|
||||||
$openDialog(handle: number): Thenable<void>;
|
$openDialog(handle: number): Thenable<void>;
|
||||||
$closeDialog(handle: number): Thenable<void>;
|
$closeDialog(handle: number): Thenable<void>;
|
||||||
$setDialogDetails(handle: number, details: IModelViewDialogDetails): Thenable<void>;
|
$setDialogDetails(handle: number, details: IModelViewDialogDetails): Thenable<void>;
|
||||||
@@ -688,6 +689,7 @@ export interface MainThreadModelViewDialogShape extends IDisposable {
|
|||||||
$addWizardPage(wizardHandle: number, pageHandle: number, pageIndex: number): Thenable<void>;
|
$addWizardPage(wizardHandle: number, pageHandle: number, pageIndex: number): Thenable<void>;
|
||||||
$removeWizardPage(wizardHandle: number, pageIndex: number): Thenable<void>;
|
$removeWizardPage(wizardHandle: number, pageIndex: number): Thenable<void>;
|
||||||
$setWizardPage(wizardHandle: number, pageIndex: number): Thenable<void>;
|
$setWizardPage(wizardHandle: number, pageIndex: number): Thenable<void>;
|
||||||
|
$setDirty(handle: number, isDirty: boolean): void;
|
||||||
}
|
}
|
||||||
export interface ExtHostQueryEditorShape {
|
export interface ExtHostQueryEditorShape {
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user