diff --git a/src/sql/base/browser/ui/table/tableDataView.ts b/src/sql/base/browser/ui/table/tableDataView.ts index 667a7f8bd0..116e2a7fb9 100644 --- a/src/sql/base/browser/ui/table/tableDataView.ts +++ b/src/sql/base/browser/ui/table/tableDataView.ts @@ -3,9 +3,6 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Observable } from 'rxjs/Observable'; -import { Observer } from 'rxjs/Observer'; - import { Event, Emitter } from 'vs/base/common/event'; import * as types from 'vs/base/common/types'; import { compare as stringCompare } from 'vs/base/common/strings'; @@ -50,7 +47,6 @@ export class TableDataView implements IDisposableData //Used when filtering is enabled, _allData holds the complete set of data. private _allData: Array; private _findArray: Array; - private _findObs: Observable | undefined; private _findIndex: number; private _filterEnabled: boolean; @@ -78,6 +74,7 @@ export class TableDataView implements IDisposableData this._data = new Array(); } + // @todo @anthonydresser 5/1/19 theres a lot we could do by just accepting a regex as a exp rather than accepting a full find function this._sortFn = _sortFn ? _sortFn : defaultSort; this._filterFn = _filterFn ? _filterFn : (dataToFilter) => dataToFilter; @@ -155,7 +152,7 @@ export class TableDataView implements IDisposableData this._onRowCountChange.fire(this.getLength()); } - find(exp: string, maxMatches: number = 0): Thenable { + find(exp: string, maxMatches?: number): Promise { if (!this._findFn) { return Promise.reject(new Error('no find function provided')); } @@ -163,35 +160,45 @@ export class TableDataView implements IDisposableData this._findIndex = 0; this._onFindCountChange.fire(this._findArray.length); if (exp) { - this._findObs = Observable.create((observer: Observer) => { - for (let i = 0; i < this._data.length; i++) { - let item = this._data[i]; - let result = this._findFn!(item, exp); - if (result) { - result.forEach(pos => { - let index = { col: pos, row: i }; - this._findArray.push(index); - observer.next(index); - this._onFindCountChange.fire(this._findArray.length); - }); - if (maxMatches > 0 && this._findArray.length > maxMatches) { - break; - } - } - } - }); - return this._findObs!.take(1).toPromise().then(() => { - return this._findArray[this._findIndex]; + return new Promise((resolve) => { + const disp = this.onFindCountChange(e => { + resolve(this._findArray[e - 1]); + disp.dispose(); + }); + this._startSearch(exp, maxMatches); }); } else { return Promise.reject(new Error('no expression')); } } + private _startSearch(exp: string, maxMatches: number = 0): void { + for (let i = 0; i < this._data.length; i++) { + const item = this._data[i]; + const result = this._findFn!(item, exp); + let breakout = false; + if (result) { + for (let j = 0; j < result.length; j++) { + const pos = result[j]; + const index = { col: pos, row: i }; + this._findArray.push(index); + this._onFindCountChange.fire(this._findArray.length); + if (maxMatches > 0 && this._findArray.length === maxMatches) { + breakout = true; + break; + } + } + } + + if (breakout) { + break; + } + } + } + clearFind() { this._findArray = new Array(); this._findIndex = 0; - this._findObs = undefined; this._onFindCountChange.fire(this._findArray.length); } @@ -242,6 +249,5 @@ export class TableDataView implements IDisposableData this._data = []; this._allData = []; this._findArray = []; - this._findObs = undefined; } } diff --git a/src/sqltest/base/browser/ui/table/tableDataView.test.ts b/src/sql/base/test/browser/ui/table/tableDataView.test.ts similarity index 54% rename from src/sqltest/base/browser/ui/table/tableDataView.test.ts rename to src/sql/base/test/browser/ui/table/tableDataView.test.ts index 24a6af5dd8..cc389895f1 100644 --- a/src/sqltest/base/browser/ui/table/tableDataView.test.ts +++ b/src/sql/base/test/browser/ui/table/tableDataView.test.ts @@ -3,12 +3,10 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -'use strict'; - import * as assert from 'assert'; import { TableDataView } from 'sql/base/browser/ui/table/tableDataView'; -suite('TableDataView Tests', () => { +suite('TableDataView', () => { test('Data can be filtered and filter can be cleared', () => { const rowCount = 10; const columnCount = 5; @@ -77,10 +75,108 @@ suite('TableDataView Tests', () => { obj.clearFilter(); verify(2, rowCount + additionalRowCount + additionalRowCount, rowCount + additionalRowCount + additionalRowCount, 4, 'calling clearFilter() multiple times', false); }); + + test('Search can find items', async () => { + const rowCount = 10; + const columnCount = 5; + const originalData = populateData(rowCount, columnCount); + + const searchFn = (val: { [x: string]: string }, exp: string): Array => { + const ret = new Array(); + for (let i = 0; i < columnCount; i++) { + const colVal = val[getColumnName(i)]; + if (colVal && colVal.toLocaleLowerCase().includes(exp.toLocaleLowerCase())) { + ret.push(i); + } + } + return ret; + }; + + const dataView = new TableDataView(originalData, searchFn); + + let findValue = await dataView.find('row 2'); + assert.deepEqual(findValue, { row: 2, col: 0 }); + findValue = await dataView.findNext(); + assert.deepEqual(findValue, { row: 2, col: 1 }); + findValue = await dataView.findNext(); + assert.deepEqual(findValue, { row: 2, col: 2 }); + findValue = await dataView.findNext(); + assert.deepEqual(findValue, { row: 2, col: 3 }); + findValue = await dataView.findNext(); + assert.deepEqual(findValue, { row: 2, col: 4 }); + // find will loop around once it reaches the end + findValue = await dataView.findNext(); + assert.deepEqual(findValue, { row: 2, col: 0 }); + }); + + test('Search fails correctly', async () => { + const rowCount = 10; + const columnCount = 5; + const originalData = populateData(rowCount, columnCount); + + const searchFn = (val: { [x: string]: string }, exp: string): Array => { + const ret = new Array(); + for (let i = 0; i < columnCount; i++) { + const colVal = val[getColumnName(i)]; + if (colVal && colVal.toLocaleLowerCase().includes(exp.toLocaleLowerCase())) { + ret.push(i); + } + } + return ret; + }; + + const dataView = new TableDataView(originalData, searchFn); + + try { + // we haven't started a search so we should throw + await dataView.findNext(); + assert.fail(); + } catch (e) { + + } + + await dataView.find('row 2'); + dataView.clearFind(); + + try { + // we cleared the search and haven't started a new search so we should throw + await dataView.findNext(); + assert.fail(); + } catch (e) { + + } + }); + + test('Search respects max finds', async () => { + const rowCount = 10; + const columnCount = 5; + const originalData = populateData(rowCount, columnCount); + + const searchFn = (val: { [x: string]: string }, exp: string): Array => { + const ret = new Array(); + for (let i = 0; i < columnCount; i++) { + const colVal = val[getColumnName(i)]; + if (colVal && colVal.toLocaleLowerCase().includes(exp.toLocaleLowerCase())) { + ret.push(i); + } + } + return ret; + }; + + const dataView = new TableDataView(originalData, searchFn); + + let findValue = await dataView.find('row 2', 2); + assert.deepEqual(findValue, { row: 2, col: 0 }); + findValue = await dataView.findNext(); + assert.deepEqual(findValue, { row: 2, col: 1 }); + // find will loop around once it reaches the end + findValue = await dataView.findNext(); + assert.deepEqual(findValue, { row: 2, col: 0 }); + }); }); function populateData(row: number, column: number): any[] { - let data = []; + let data: Array<{ [key: string]: string }> = []; for (let i: number = 0; i < row; i++) { let row = {}; for (let j: number = 0; j < column; j++) { @@ -97,4 +193,4 @@ function getColumnName(index: number): string { function getCellValue(row: number, column: number): string { return `row ${row} column ${column}`; -} \ No newline at end of file +}