From 7fc1fd947f4e693c2d577f74c18b6e4a7dddce66 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Thu, 6 Sep 2018 17:45:51 -0700 Subject: [PATCH] Added search support for 'match whole word' --- demo/client.ts | 4 +- demo/index.html | 1 + src/addons/search/SearchHelper.ts | 16 +++++ src/addons/search/search.test.ts | 108 ++++++++++++++++++++++++++---- 4 files changed, 114 insertions(+), 15 deletions(-) diff --git a/demo/client.ts b/demo/client.ts index 5c477ae7ec..91cebf07cd 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -102,7 +102,7 @@ function createTerminal(): void { e.preventDefault(); const searchOptions = { regex: (document.getElementById('regex') as HTMLInputElement).checked, - wholeWord: false, + wholeWord: (document.getElementById('whole-word') as HTMLInputElement).checked, caseSensitive: (document.getElementById('case-sensitive') as HTMLInputElement).checked }; term.findNext(actionElements.findNext.value, searchOptions); @@ -113,7 +113,7 @@ function createTerminal(): void { e.preventDefault(); const searchOptions = { regex: (document.getElementById('regex') as HTMLInputElement).checked, - wholeWord: false, + wholeWord: (document.getElementById('whole-word') as HTMLInputElement).checked, caseSensitive: (document.getElementById('case-sensitive') as HTMLInputElement).checked }; term.findPrevious(actionElements.findPrevious.value, searchOptions); diff --git a/demo/index.html b/demo/index.html index 553b9a8b22..12f7cb9835 100644 --- a/demo/index.html +++ b/demo/index.html @@ -18,6 +18,7 @@

Actions

+

diff --git a/src/addons/search/SearchHelper.ts b/src/addons/search/SearchHelper.ts index f89eb77e6f..d07c6b2ca4 100644 --- a/src/addons/search/SearchHelper.ts +++ b/src/addons/search/SearchHelper.ts @@ -4,6 +4,7 @@ */ import { ISearchHelper, ISearchAddonTerminal, ISearchOptions, ISearchResult } from './Interfaces'; +const nonWordCharacters = ' ~!@#$%^&*()_+`-=[]{}|\;:"\',./<>?'; /** * A class that knows how to search the terminal and how to display the results. @@ -99,6 +100,17 @@ export class SearchHelper implements ISearchHelper { return this._selectResult(result); } + /** + * A found substring is a whole word if it doesn't have an alphanumeric character directly adjacent to it. + * @param searchIndex starting indext of the potential whole word substring + * @param line entire string in which the potential whole word was found + * @param term the substring that starts at searchIndex + */ + private _isWholeWord(searchIndex: number, line: string, term: string): boolean { + return (((searchIndex === 0) || (nonWordCharacters.indexOf(line[searchIndex - 1]) !== -1)) && + (((searchIndex + term.length) === line.length) || (nonWordCharacters.indexOf(line[searchIndex + term.length]) !== -1))); + } + /** * Searches a line for a search term. Takes the provided terminal line and searches the text line, which may contain * subsequent terminal lines if the text is wrapped. If the provided line number is part of a wrapped text line that @@ -136,6 +148,10 @@ export class SearchHelper implements ISearchHelper { y += Math.floor(searchIndex / this._terminal.cols); searchIndex = searchIndex % this._terminal.cols; } + if (searchOptions.wholeWord && !this._isWholeWord(searchIndex, searchStringLine, term)) { + return; + } + const line = this._terminal._core.buffer.lines.get(y); for (let i = 0; i < searchIndex; i++) { diff --git a/src/addons/search/search.test.ts b/src/addons/search/search.test.ts index 38a04df503..ccdebaf34b 100644 --- a/src/addons/search/search.test.ts +++ b/src/addons/search/search.test.ts @@ -70,12 +70,12 @@ describe('search addon', () => { goodbye */ - const hello0 = (term.searchHelper as any)._findInLine('Hello', 0); - const hello1 = (term.searchHelper as any)._findInLine('Hello', 1); - const hello2 = (term.searchHelper as any)._findInLine('Hello', 2); - const hello3 = (term.searchHelper as any)._findInLine('Hello', 3); - const llo = (term.searchHelper as any)._findInLine('llo', 1); - const goodbye = (term.searchHelper as any)._findInLine('goodbye', 2); + const hello0 = term.searchHelper.findInLine('Hello', 0); + const hello1 = term.searchHelper.findInLine('Hello', 1); + const hello2 = term.searchHelper.findInLine('Hello', 2); + const hello3 = term.searchHelper.findInLine('Hello', 3); + const llo = term.searchHelper.findInLine('llo', 1); + const goodbye = term.searchHelper.findInLine('goodbye', 2); expect(hello0).eql({col: 8, row: 0, term: 'Hello'}); expect(hello1).eql(undefined); expect(hello2).eql({col: 2, row: 3, term: 'Hello'}); @@ -130,9 +130,9 @@ describe('search addon', () => { wholeWord: false, caseSensitive: true }; - const hello0 = (term.searchHelper as any)._findInLine('Hello', 0, searchOptions); - const hello1 = (term.searchHelper as any)._findInLine('Hello', 1, searchOptions); - const hello2 = (term.searchHelper as any)._findInLine('Hello', 2, searchOptions); + const hello0 = term.searchHelper.findInLine('Hello', 0, searchOptions); + const hello1 = term.searchHelper.findInLine('Hello', 1, searchOptions); + const hello2 = term.searchHelper.findInLine('Hello', 2, searchOptions); expect(hello0).eql({col: 0, row: 0, term: 'Hello'}); expect(hello1).eql(undefined); expect(hello2).eql({col: 8, row: 2, term: 'Hello'}); @@ -153,14 +153,96 @@ describe('search addon', () => { wholeWord: false, caseSensitive: true }; - const hello0 = (term.searchHelper as any)._findInLine('Hello', 0, searchOptions); - const hello1 = (term.searchHelper as any)._findInLine('Hello$', 0, searchOptions); - const hello2 = (term.searchHelper as any)._findInLine('Hello', 1, searchOptions); - const hello3 = (term.searchHelper as any)._findInLine('Hello$', 1, searchOptions); + const hello0 = term.searchHelper.findInLine('Hello', 0, searchOptions); + const hello1 = term.searchHelper.findInLine('Hello$', 0, searchOptions); + const hello2 = term.searchHelper.findInLine('Hello', 1, searchOptions); + const hello3 = term.searchHelper.findInLine('Hello$', 1, searchOptions); expect(hello0).eql(undefined); expect(hello1).eql(undefined); expect(hello2).eql({col: 0, row: 1, term: 'Hello'}); expect(hello3).eql({col: 5, row: 1, term: 'Hello'}); }); + it('should respect whole-word search option', function(): void { + search.apply(MockTerminal); + const term = new MockTerminal({cols: 20, rows: 5}); + term.core.write('Hello World\r\nWorld Hello\r\nWorldHelloWorld\r\nHelloWorld\r\nWorldHello'); + term.pushWriteData(); + const searchOptions = { + regex: false, + wholeWord: true, + caseSensitive: false + }; + const hello0 = term.searchHelper.findInLine('Hello', 0, searchOptions); + const hello1 = term.searchHelper.findInLine('Hello', 1, searchOptions); + const hello2 = term.searchHelper.findInLine('Hello', 2, searchOptions); + const hello3 = term.searchHelper.findInLine('Hello', 3, searchOptions); + const hello4 = term.searchHelper.findInLine('Hello', 4, searchOptions); + expect(hello0).eql({col: 0, row: 0, term: 'Hello'}); + expect(hello1).eql({col: 6, row: 1, term: 'Hello'}); + expect(hello2).eql(undefined); + expect(hello3).eql(undefined); + expect(hello4).eql(undefined); + }); + it('should respect whole-word + case sensitive search options', function(): void { + search.apply(MockTerminal); + const term = new MockTerminal({cols: 20, rows: 5}); + term.core.write('Hello World\r\nHelloWorld'); + term.pushWriteData(); + const searchOptions = { + regex: false, + wholeWord: true, + caseSensitive: true + }; + const hello0 = term.searchHelper.findInLine('Hello', 0, searchOptions); + const hello1 = term.searchHelper.findInLine('hello', 0, searchOptions); + const hello2 = term.searchHelper.findInLine('Hello', 1, searchOptions); + const hello3 = term.searchHelper.findInLine('hello', 1, searchOptions); + expect(hello0).eql({col: 0, row: 0, term: 'Hello'}); + expect(hello1).eql(undefined); + expect(hello2).eql(undefined); + expect(hello3).eql(undefined); + }); + it('should respect whole-word + regex search options', function(): void { + search.apply(MockTerminal); + const term = new MockTerminal({cols: 20, rows: 5}); + term.core.write('Hello World Hello\r\nHelloWorldHello'); + term.pushWriteData(); + const searchOptions = { + regex: true, + wholeWord: true, + caseSensitive: false + }; + const hello0 = term.searchHelper.findInLine('Hello', 0, searchOptions); + const hello1 = term.searchHelper.findInLine('Hello$', 0, searchOptions); + const hello2 = term.searchHelper.findInLine('Hello', 1, searchOptions); + const hello3 = term.searchHelper.findInLine('Hello$', 1, searchOptions); + expect(hello0).eql({col: 0, row: 0, term: 'hello'}); + expect(hello1).eql({col: 12, row: 0, term: 'hello'}); + expect(hello2).eql(undefined); + expect(hello3).eql(undefined); + }); + it('should respect all search options', function(): void { + search.apply(MockTerminal); + const term = new MockTerminal({cols: 20, rows: 5}); + term.core.write('Hello World Hello\r\nHelloWorldHello'); + term.pushWriteData(); + const searchOptions = { + regex: true, + wholeWord: true, + caseSensitive: true + }; + const hello0 = term.searchHelper.findInLine('Hello', 0, searchOptions); + const hello1 = term.searchHelper.findInLine('Hello$', 0, searchOptions); + const hello2 = term.searchHelper.findInLine('hello', 0, searchOptions); + const hello3 = term.searchHelper.findInLine('hello$', 0, searchOptions); + const hello4 = term.searchHelper.findInLine('hello', 1, searchOptions); + const hello5 = term.searchHelper.findInLine('hello$', 1, searchOptions); + expect(hello0).eql({col: 0, row: 0, term: 'Hello'}); + expect(hello1).eql({col: 12, row: 0, term: 'Hello'}); + expect(hello2).eql(undefined); + expect(hello3).eql(undefined); + expect(hello4).eql(undefined); + expect(hello5).eql(undefined); + }); }); });