Implemented match case and whole wrd (#8904)

* Implemented match case and whole wrd

* removed unused ref

* renamed test method

* escape \

* refactored search

* added more tests

* updated tests appying match case and whole word to spcl characters

* regex update

* spcl to special

* non-capturing group added to the regex

* test
This commit is contained in:
Maddy
2020-01-23 07:28:12 -08:00
committed by GitHub
parent 76967d9467
commit 5ccc0fd97b
5 changed files with 146 additions and 39 deletions

View File

@@ -430,7 +430,7 @@ export interface INotebookFindModel {
findPrevious(): Promise<NotebookRange>; findPrevious(): Promise<NotebookRange>;
/** search the notebook model for the given exp up to maxMatch occurances */ /** search the notebook model for the given exp up to maxMatch occurances */
find(exp: string, maxMatches?: number): Promise<NotebookRange>; find(exp: string, matchCase?: boolean, wholeWord?: boolean, maxMatches?: number): Promise<NotebookRange>;
/** clear the results of the find */ /** clear the results of the find */
clearFind(): void; clearFind(): void;

View File

@@ -282,12 +282,12 @@ export class NotebookEditor extends BaseEditor implements IFindNotebookControlle
} }
} }
if (e.searchString) { if (e.searchString || e.matchCase || e.wholeWord) {
this._findDecorations.clearDecorations(); this._findDecorations.clearDecorations();
if (this._notebookModel) { if (this._notebookModel) {
if (this._findState.searchString) { if (this._findState.searchString) {
let findScope = this._findDecorations.getFindScope(); let findScope = this._findDecorations.getFindScope();
if (this._findState.searchString === this.notebookFindModel.findExpression && findScope !== null) { if (this._findState.searchString === this.notebookFindModel.findExpression && findScope !== null && !e.matchCase && !e.wholeWord) {
if (findScope) { if (findScope) {
this._updateFinderMatchState(); this._updateFinderMatchState();
this._findState.changeMatchInfo( this._findState.changeMatchInfo(
@@ -300,7 +300,7 @@ export class NotebookEditor extends BaseEditor implements IFindNotebookControlle
} else { } else {
this.notebookInput.notebookFindModel.clearDecorations(); this.notebookInput.notebookFindModel.clearDecorations();
this.notebookFindModel.findExpression = this._findState.searchString; this.notebookFindModel.findExpression = this._findState.searchString;
this.notebookInput.notebookFindModel.find(this._findState.searchString, NOTEBOOK_MAX_MATCHES).then(findRange => { this.notebookInput.notebookFindModel.find(this._findState.searchString, this._findState.matchCase, this._findState.wholeWord, NOTEBOOK_MAX_MATCHES).then(findRange => {
if (findRange) { if (findRange) {
this.updatePosition(findRange); this.updatePosition(findRange);
} else if (this.notebookFindModel.findMatches.length > 0) { } else if (this.notebookFindModel.findMatches.length > 0) {

View File

@@ -490,13 +490,13 @@ export class NotebookFindModel extends Disposable implements INotebookFindModel
} }
} }
find(exp: string, maxMatches?: number): Promise<NotebookRange> { find(exp: string, matchCase?: boolean, wholeWord?: boolean, maxMatches?: number): Promise<NotebookRange> {
this._findArray = new Array<NotebookRange>(); this._findArray = new Array<NotebookRange>();
this._onFindCountChange.fire(this._findArray.length); this._onFindCountChange.fire(this._findArray.length);
if (exp) { if (exp) {
for (let i = 0; i < this.notebookModel.cells.length; i++) { for (let i = 0; i < this.notebookModel.cells.length; i++) {
const item = this.notebookModel.cells[i]; const item = this.notebookModel.cells[i];
const result = this.searchFn(item, exp, maxMatches); const result = this.searchFn(item, exp, matchCase, wholeWord, maxMatches);
if (result) { if (result) {
this._findArray.push(...result); this._findArray.push(...result);
this._onFindCountChange.fire(this._findArray.length); this._onFindCountChange.fire(this._findArray.length);
@@ -523,39 +523,61 @@ export class NotebookFindModel extends Disposable implements INotebookFindModel
return this.findArray; return this.findArray;
} }
private searchFn(cell: ICellModel, exp: string, maxMatches?: number): NotebookRange[] { private searchFn(cell: ICellModel, exp: string, matchCase: boolean = false, wholeWord: boolean = false, maxMatches?: number): NotebookRange[] {
let findResults: NotebookRange[] = []; let findResults: NotebookRange[] = [];
let cellVal = cell.cellType === 'markdown' ? this.cleanUpCellSource(cell.source) : cell.source; let cellVal = cell.cellType === 'markdown' ? this.cleanUpCellSource(cell.source) : cell.source;
let index: number;
let start: number;
let end: number;
if (cellVal) { if (cellVal) {
if (typeof cellVal === 'string') { if (typeof cellVal === 'string') {
index = 0; let findStartResults = this.search(cellVal, exp, matchCase, wholeWord, maxMatches);
while (cellVal.substr(index).toLocaleLowerCase().indexOf(exp.toLocaleLowerCase()) > -1) { findStartResults.forEach(start => {
start = cellVal.substr(index).toLocaleLowerCase().indexOf(exp.toLocaleLowerCase()) + index; let range = new NotebookRange(cell, 0, start, 0, start + exp.length);
end = start + exp.length; findResults.push(range);
let range = new NotebookRange(cell, 0, start, 0, end); });
findResults = findResults.concat(range);
index = end;
}
} else { } else {
for (let j = 0; j < cellVal.length; j++) { for (let j = 0; j < cellVal.length; j++) {
index = 0;
let cellValFormatted = cell.cellType === 'markdown' ? this.cleanMarkdownLinks(cellVal[j]) : cellVal[j]; let cellValFormatted = cell.cellType === 'markdown' ? this.cleanMarkdownLinks(cellVal[j]) : cellVal[j];
while (cellValFormatted.substr(index).toLocaleLowerCase().indexOf(exp.toLocaleLowerCase()) > -1) { let findStartResults = this.search(cellValFormatted, exp, matchCase, wholeWord, maxMatches - findResults.length);
start = cellValFormatted.substr(index).toLocaleLowerCase().indexOf(exp.toLocaleLowerCase()) + index + 1; findStartResults.forEach(start => {
end = start + exp.length;
// lineNumber: j+1 since notebook editors aren't zero indexed. // lineNumber: j+1 since notebook editors aren't zero indexed.
let range = new NotebookRange(cell, j + 1, start, j + 1, end); let range = new NotebookRange(cell, j + 1, start, j + 1, start + exp.length);
findResults = findResults.concat(range); findResults.push(range);
index = end; });
} }
if (findResults.length >= maxMatches) { }
}
return findResults;
}
// escape the special characters in a regex string
escapeRegExp(text: string): string {
return text.replace(/[-[\]{}()*+!<=:?.\/\\^$|#\s,]/g, '\\$&');
}
search(input: string, exp: string, matchCase: boolean = false, wholeWord: boolean = false, maxMatches?: number): number[] {
let index: number = 0;
let start: number;
let findResults: number[] = [];
if (!matchCase) {
input = input.toLocaleLowerCase();
exp = exp.toLocaleLowerCase();
}
let searchText: string = input.substr(index);
while (findResults.length < maxMatches && searchText.indexOf(exp) > -1) {
if (wholeWord) {
// word with no special characters around \\bword\\b, word that begins or ends with special character \\sword\\s
let wholeWordRegex = new RegExp(`(?:\\b|\\s)${this.escapeRegExp(exp)}(?:\\b|\\s)`);
start = searchText.search(wholeWordRegex) + 1;
if (start < 1) {
break; break;
} }
} else {
start = searchText.indexOf(exp) + index + 1;
} }
} findResults.push(start);
index = start + exp.length;
searchText = input.substr(index - 1);
} }
return findResults; return findResults;
} }

View File

@@ -32,12 +32,12 @@ import { NotebookEditorContentManager } from 'sql/workbench/contrib/notebook/bro
let expectedNotebookContent: nb.INotebookContents = { let expectedNotebookContent: nb.INotebookContents = {
cells: [{ cells: [{
cell_type: CellTypes.Code, cell_type: CellTypes.Code,
source: 'insert into t1 values (c1, c2) \ninsert into markdown values (*hello worls*)', source: ['insert into t1 values (c1, c2) ', 'INSERT into markdown values (*hello world*)'],
metadata: { language: 'python' }, metadata: { language: 'python' },
execution_count: 1 execution_count: 1
}, { }, {
cell_type: CellTypes.Markdown, cell_type: CellTypes.Markdown,
source: 'I am *markdown*', source: ['I am *markdown insertImportant*'],
metadata: { language: 'python' }, metadata: { language: 'python' },
execution_count: 1 execution_count: 1
}], }],
@@ -110,23 +110,23 @@ suite('Notebook Find Model', function (): void {
test('Should find results in the notebook', async function (): Promise<void> { test('Should find results in the notebook', async function (): Promise<void> {
//initialize find //initialize find
let notebookFindModel = new NotebookFindModel(model); let notebookFindModel = new NotebookFindModel(model);
await notebookFindModel.find('markdown', max_find_count); await notebookFindModel.find('markdown', false, false, max_find_count);
assert(notebookFindModel.findMatches, new Error('Find in notebook failed.')); assert(notebookFindModel.findMatches, 'Find in notebook failed.');
assert.equal(notebookFindModel.findMatches.length, 2, 'Find couldnt find all occurances'); assert.equal(notebookFindModel.findMatches.length, 2, 'Find couldnt find all occurances');
}); });
test('Should not find results in the notebook', async function (): Promise<void> { test('Should not find results in the notebook', async function (): Promise<void> {
//initialize find //initialize find
let notebookFindModel = new NotebookFindModel(model); let notebookFindModel = new NotebookFindModel(model);
await notebookFindModel.find('notFound', max_find_count); await notebookFindModel.find('notFound', false, false, max_find_count);
assert.equal(notebookFindModel.findMatches.length, 0, 'Find failed'); assert.equal(notebookFindModel.findMatches.length, 0, 'Find failed');
}); });
test('Should match find result ranges', async function (): Promise<void> { test('Should match find result ranges', async function (): Promise<void> {
let notebookFindModel = new NotebookFindModel(model); let notebookFindModel = new NotebookFindModel(model);
await notebookFindModel.find('markdown', max_find_count); await notebookFindModel.find('markdown', false, false, max_find_count);
let expectedFindRange1 = new NotebookRange(model.cells[0], 2, 13, 2, 21); let expectedFindRange1 = new NotebookRange(model.cells[0], 2, 13, 2, 21);
assert.deepEqual(notebookFindModel.findMatches[0].range, expectedFindRange1, 'Find in markdown range is wrong :\n' + JSON.stringify(expectedFindRange1) + '\n ' + JSON.stringify(notebookFindModel.findMatches[0].range)); assert.deepEqual(notebookFindModel.findMatches[0].range, expectedFindRange1, 'Find in markdown range is wrong :\n' + JSON.stringify(expectedFindRange1) + '\n ' + JSON.stringify(notebookFindModel.findMatches[0].range));
@@ -155,7 +155,7 @@ suite('Notebook Find Model', function (): void {
await initNotebookModel(markdownContent); await initNotebookModel(markdownContent);
let notebookFindModel = new NotebookFindModel(model); let notebookFindModel = new NotebookFindModel(model);
await notebookFindModel.find('best', max_find_count); await notebookFindModel.find('best', false, false, max_find_count);
assert.equal(notebookFindModel.findMatches.length, 1, 'Find failed on markdown link'); assert.equal(notebookFindModel.findMatches.length, 1, 'Find failed on markdown link');
@@ -184,11 +184,97 @@ suite('Notebook Find Model', function (): void {
await initNotebookModel(codeContent); await initNotebookModel(codeContent);
//initialize find //initialize find
let notebookFindModel = new NotebookFindModel(model); let notebookFindModel = new NotebookFindModel(model);
await notebookFindModel.find('x', max_find_count); await notebookFindModel.find('x', false, false, max_find_count);
assert.equal(notebookFindModel.findMatches.length, 3, 'Find failed'); assert.equal(notebookFindModel.findMatches.length, 3, 'Find failed');
}); });
test('Should find results correctly with & without matching case selection', async function (): Promise<void> {
//initialize find
let notebookFindModel = new NotebookFindModel(model);
await notebookFindModel.find('insert', false, false, max_find_count);
assert(notebookFindModel.findMatches, 'Find in notebook failed.');
assert.equal(notebookFindModel.findMatches.length, 3, 'Find couldnt find all occurances');
await notebookFindModel.find('insert', true, false, max_find_count);
assert.equal(notebookFindModel.findMatches.length, 2, 'Find failed to apply match case while searching');
});
test('Should find results with matching whole word in the notebook', async function (): Promise<void> {
//initialize find
let notebookFindModel = new NotebookFindModel(model);
await notebookFindModel.find('insert', true, true, max_find_count);
assert.equal(notebookFindModel.findMatches.length, 1, 'Find failed to apply whole word filter while searching');
});
test('Should find special characters in the search term without problems', async function (): Promise<void> {
let codeContent: nb.INotebookContents = {
cells: [{
cell_type: CellTypes.Code,
source: ['import x', 'x.init()', '//am just adding a bunch of {special} <characters> !!!!$}'],
metadata: { language: 'python' },
execution_count: 1
}],
metadata: {
kernelspec: {
name: 'python',
language: 'python'
}
},
nbformat: 4,
nbformat_minor: 5
};
await initNotebookModel(codeContent);
//initialize find
let notebookFindModel = new NotebookFindModel(model);
// test for string with special character
await notebookFindModel.find('{special}', true, true, max_find_count);
assert.equal(notebookFindModel.findMatches.length, 1, 'Find failed for search term with special character');
// test for only special character !!
await notebookFindModel.find('!!', false, false, max_find_count);
assert.equal(notebookFindModel.findMatches.length, 2, 'Find failed for special character');
// test for only special character combination
await notebookFindModel.find('!!!$}', false, false, max_find_count);
assert.equal(notebookFindModel.findMatches.length, 1, 'Find failed for special character combination');
});
test('Should find // characters in the search term correctly', async function (): Promise<void> {
let codeContent: nb.INotebookContents = {
cells: [{
cell_type: CellTypes.Code,
source: ['import x', 'x.init()', '//am just adding a bunch of {special} <characters> !test$}'],
metadata: { language: 'python' },
execution_count: 1
}],
metadata: {
kernelspec: {
name: 'python',
language: 'python'
}
},
nbformat: 4,
nbformat_minor: 5
};
await initNotebookModel(codeContent);
//initialize find
let notebookFindModel = new NotebookFindModel(model);
await notebookFindModel.find('/', true, false, max_find_count);
assert.equal(notebookFindModel.findMatches.length, 2, 'Find failed to find number of / occurrences');
await notebookFindModel.find('//', true, false, max_find_count);
assert.equal(notebookFindModel.findMatches.length, 1, 'Find failed to find number of // occurrences');
await notebookFindModel.find('//', true, true, max_find_count);
assert.equal(notebookFindModel.findMatches.length, 0, 'Find failed to apply match whole word for //');
});
async function initNotebookModel(contents: nb.INotebookContents): Promise<void> { async function initNotebookModel(contents: nb.INotebookContents): Promise<void> {
let mockContentManager = TypeMoq.Mock.ofType(NotebookEditorContentManager); let mockContentManager = TypeMoq.Mock.ofType(NotebookEditorContentManager);
mockContentManager.setup(c => c.loadContent()).returns(() => Promise.resolve(contents)); mockContentManager.setup(c => c.loadContent()).returns(() => Promise.resolve(contents));
@@ -200,4 +286,3 @@ suite('Notebook Find Model', function (): void {
} }
}); });

View File

@@ -135,7 +135,7 @@ export class NotebookFindModelStub implements INotebookFindModel {
findPrevious(): Promise<NotebookRange> { findPrevious(): Promise<NotebookRange> {
throw new Error('Method not implemented.'); throw new Error('Method not implemented.');
} }
find(exp: string, maxMatches?: number): Promise<NotebookRange> { find(exp: string, matchCase?: boolean, wholeWord?: boolean, maxMatches?: number): Promise<NotebookRange> {
throw new Error('Method not implemented.'); throw new Error('Method not implemented.');
} }
clearFind(): void { clearFind(): void {