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);
+ });
});
});