From 6be142022080c2c32451afe99f8758cb7c63c52d Mon Sep 17 00:00:00 2001 From: Alan Ren Date: Sat, 30 Jul 2022 14:29:32 -0700 Subject: [PATCH] do not run query in multi-selection mode (#20213) * do not run query in multi-selection mode * fix test * fix more tests --- .../contrib/query/browser/queryActions.ts | 8 ++++++++ .../contrib/query/browser/queryEditor.ts | 8 ++++++++ .../query/test/browser/queryActions.test.ts | 19 ++++++++++++------- .../query/test/browser/queryEditor.test.ts | 8 ++++---- 4 files changed, 32 insertions(+), 11 deletions(-) diff --git a/src/sql/workbench/contrib/query/browser/queryActions.ts b/src/sql/workbench/contrib/query/browser/queryActions.ts index ce745767cb..1fc85d820b 100644 --- a/src/sql/workbench/contrib/query/browser/queryActions.ts +++ b/src/sql/workbench/contrib/query/browser/queryActions.ts @@ -200,6 +200,7 @@ export class RunQueryAction extends QueryTaskbarAction { editor: QueryEditor, @IQueryModelService protected readonly queryModelService: IQueryModelService, @IConnectionManagementService connectionManagementService: IConnectionManagementService, + @INotificationService private readonly notificationService: INotificationService, @ICommandService private readonly commandService?: ICommandService ) { super(connectionManagementService, editor, RunQueryAction.ID, RunQueryAction.EnabledClass); @@ -238,6 +239,13 @@ export class RunQueryAction extends QueryTaskbarAction { if (this.isConnected(editor)) { // Hide IntelliSense suggestions list when running query to match SSMS behavior this.commandService?.executeCommand('hideSuggestWidget'); + // Do not execute when there are multiple selections in the editor until it can be properly handled. + // Otherwise only the first selection will be executed and cause unexpected issues. + if (editor.getSelections()?.length > 1) { + this.notificationService.error(nls.localize('query.multiSelectionNotSupported', "Running query is not supported when the editor is in multiple selection mode.")); + return true; + } + // if the selection isn't empty then execute the selection // otherwise, either run the statement or the script depending on parameter let selection = editor.getSelection(false); diff --git a/src/sql/workbench/contrib/query/browser/queryEditor.ts b/src/sql/workbench/contrib/query/browser/queryEditor.ts index 233e4ff50c..faa6726196 100644 --- a/src/sql/workbench/contrib/query/browser/queryEditor.ts +++ b/src/sql/workbench/contrib/query/browser/queryEditor.ts @@ -563,6 +563,14 @@ export class QueryEditor extends EditorPane { return true; } + /** + * Returns the underlying SQL editor's text selections. Returns undefined if there + * is no selected text. + */ + public getSelections(): IRange[] | undefined { + return this.currentTextEditor?.getControl()?.getSelections(); + } + /** * Returns the underlying SQL editor's text selection in a 0-indexed format. Returns undefined if there * is no selected text. diff --git a/src/sql/workbench/contrib/query/test/browser/queryActions.test.ts b/src/sql/workbench/contrib/query/test/browser/queryActions.test.ts index 31778eb65b..c51302925c 100644 --- a/src/sql/workbench/contrib/query/test/browser/queryActions.test.ts +++ b/src/sql/workbench/contrib/query/test/browser/queryActions.test.ts @@ -59,6 +59,7 @@ suite('SQL QueryAction Tests', () => { editor.setup(x => x.getSelection()).returns(() => undefined); editor.setup(x => x.getSelection(false)).returns(() => undefined); editor.setup(x => x.isSelectionEmpty()).returns(() => false); + editor.setup(x => x.getSelections()).returns(() => [undefined]); configurationService = TypeMoq.Mock.ofInstance({ getValue: () => undefined, onDidChangeConfiguration: () => undefined @@ -89,7 +90,7 @@ suite('SQL QueryAction Tests', () => { test('setClass sets child CSS class correctly', () => { // If I create a RunQueryAction - let queryAction: QueryTaskbarAction = new RunQueryAction(undefined, undefined, undefined); + let queryAction: QueryTaskbarAction = new RunQueryAction(undefined, undefined, undefined, undefined); // "class should automatically get set to include the base class and the RunQueryAction class let className = RunQueryAction.EnabledClass; @@ -111,7 +112,7 @@ suite('SQL QueryAction Tests', () => { editor.setup(x => x.input).returns(() => testQueryInput.object); // If I create a QueryTaskbarAction and I pass a non-connected editor to _getConnectedQueryEditorUri - let queryAction: QueryTaskbarAction = new RunQueryAction(undefined, undefined, connectionManagementService.object); + let queryAction: QueryTaskbarAction = new RunQueryAction(undefined, undefined, connectionManagementService.object, undefined); let connected: boolean = queryAction.isConnected(editor.object); // I should get an unconnected state @@ -146,7 +147,7 @@ suite('SQL QueryAction Tests', () => { queryModelService.setup(x => x.runQuery(TypeMoq.It.isAny(), undefined, TypeMoq.It.isAny())); // If I call run on RunQueryAction when I am not connected - let queryAction: RunQueryAction = new RunQueryAction(editor.object, queryModelService.object, connectionManagementService.object); + let queryAction: RunQueryAction = new RunQueryAction(editor.object, queryModelService.object, connectionManagementService.object, undefined); isConnected = false; calledRunQueryOnInput = false; await queryAction.run(); @@ -208,9 +209,10 @@ suite('SQL QueryAction Tests', () => { queryEditor.setup(x => x.getSelection()).returns(() => undefined); queryEditor.setup(x => x.getSelection(false)).returns(() => undefined); queryEditor.setup(x => x.isSelectionEmpty()).returns(() => isSelectionEmpty); + queryEditor.setup(x => x.getSelections()).returns(() => [undefined]); // If I call run on RunQueryAction when I have a non empty selection - let queryAction: RunQueryAction = new RunQueryAction(queryEditor.object, queryModelService.object, connectionManagementService.object); + let queryAction: RunQueryAction = new RunQueryAction(queryEditor.object, queryModelService.object, connectionManagementService.object, undefined); isSelectionEmpty = false; await queryAction.run(); @@ -271,6 +273,9 @@ suite('SQL QueryAction Tests', () => { queryEditor.setup(x => x.getSelection(TypeMoq.It.isAny())).returns(() => { return selectionToReturnInGetSelection; }); + queryEditor.setup(x => x.getSelections()).returns(() => { + return [undefined]; + }); // ... Mock "isConnected" in ConnectionManagementService connectionManagementService.setup(x => x.isConnected(TypeMoq.It.isAnyString())).returns(() => isConnected); @@ -284,7 +289,7 @@ suite('SQL QueryAction Tests', () => { /// End Setup Test /// ////// If I call run on RunQueryAction while disconnected and with an undefined selection - let queryAction: RunQueryAction = new RunQueryAction(queryEditor.object, undefined, connectionManagementService.object); + let queryAction: RunQueryAction = new RunQueryAction(queryEditor.object, undefined, connectionManagementService.object, undefined); isConnected = false; selectionToReturnInGetSelection = undefined; await queryAction.run(); @@ -608,7 +613,7 @@ suite('SQL QueryAction Tests', () => { queryModelService.setup(x => x.runQueryStatement(TypeMoq.It.isAny(), TypeMoq.It.isAny())); // Calling runCurrent with no open connection - let queryAction: RunQueryAction = new RunQueryAction(queryEditor.object, queryModelService.object, connectionManagementService.object); + let queryAction: RunQueryAction = new RunQueryAction(queryEditor.object, queryModelService.object, connectionManagementService.object, undefined); calledRunQueryStatementOnInput = false; await queryAction.runCurrent(); @@ -665,7 +670,7 @@ suite('SQL QueryAction Tests', () => { queryModelService.setup(x => x.runQuery(TypeMoq.It.isAny(), undefined, TypeMoq.It.isAny())); queryModelService.setup(x => x.runQueryStatement(TypeMoq.It.isAny(), TypeMoq.It.isAny())); - let queryAction: RunQueryAction = new RunQueryAction(queryEditor.object, queryModelService.object, connectionManagementService.object); + let queryAction: RunQueryAction = new RunQueryAction(queryEditor.object, queryModelService.object, connectionManagementService.object, undefined); // setting up queryEditor with only a cursor. This case should call runQueryStatement queryEditor.setup(x => x.getSelection(false)).returns(() => { return predefinedCursorSelection; }); diff --git a/src/sql/workbench/contrib/query/test/browser/queryEditor.test.ts b/src/sql/workbench/contrib/query/test/browser/queryEditor.test.ts index 9dabc9a52a..ef862bdd71 100644 --- a/src/sql/workbench/contrib/query/test/browser/queryEditor.test.ts +++ b/src/sql/workbench/contrib/query/test/browser/queryEditor.test.ts @@ -58,7 +58,7 @@ suite('SQL QueryEditor Tests', () => { return new Promise((resolve) => resolve(mockEditor)); }); instantiationService.setup(x => x.createInstance(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns((input) => { - return new Promise((resolve) => resolve(new RunQueryAction(undefined, undefined, undefined))); + return new Promise((resolve) => resolve(new RunQueryAction(undefined, undefined, undefined, undefined))); }); // Setup hook to capture calls to create the listDatabase action instantiationService.setup(x => x.createInstance(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns((classDef, editor, action) => { @@ -68,7 +68,7 @@ suite('SQL QueryEditor Tests', () => { } } // Default - return new RunQueryAction(undefined, undefined, undefined); + return new RunQueryAction(undefined, undefined, undefined, undefined); }); // Mock EditorDescriptorService to give us a mock editor description @@ -285,7 +285,7 @@ suite('SQL QueryEditor Tests', () => { queryActionInstantiationService.setup(x => x.createInstance(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns((input) => { // Default - return new RunQueryAction(undefined, undefined, undefined); + return new RunQueryAction(undefined, undefined, undefined, undefined); }); // Setup hook to capture calls to create the listDatabase action @@ -296,7 +296,7 @@ suite('SQL QueryEditor Tests', () => { return item; } // Default - return new RunQueryAction(undefined, undefined, undefined); + return new RunQueryAction(undefined, undefined, undefined, undefined); }); const workbenchinstantiationService = workbenchInstantiationService();