44 */
55
66import { ISearchHelper , ISearchAddonTerminal , ISearchOptions , ISearchResult } from './Interfaces' ;
7- const nonWordCharacters = ' ~!@#$%^&*()+`-=[]{}|\;:"\',./<>?' ;
7+
8+ const NON_WORD_CHARACTERS = ' ~!@#$%^&*()+`-=[]{}|\;:"\',./<>?' ;
9+ const LINES_CACHE_TIME_TO_LIVE = 15 * 1000 ; // 15 secs
810
911/**
1012 * A class that knows how to search the terminal and how to display the results.
1113 */
1214export class SearchHelper implements ISearchHelper {
15+ /**
16+ * translateBufferLineToStringWithWrap is a fairly expensive call.
17+ * We memoize the calls into an array that has a time based ttl.
18+ * _linesCache is also invalidated when the terminal cursor moves.
19+ */
20+ private _linesCache : string [ ] = null ;
21+ private _linesCacheTimeoutId = 0 ;
22+
1323 constructor ( private _terminal : ISearchAddonTerminal ) {
14- // TODO: Search for multiple instances on 1 line
15- // TODO: Don't use the actual selection, instead use a "find selection" so multiple instances can be highlighted
16- // TODO: Highlight other instances in the viewport
24+ this . _destroyLinesCache = this . _destroyLinesCache . bind ( this ) ;
1725 }
1826
1927 /**
@@ -24,29 +32,36 @@ export class SearchHelper implements ISearchHelper {
2432 * @return Whether a result was found.
2533 */
2634 public findNext ( term : string , searchOptions ?: ISearchOptions ) : boolean {
35+ const selectionManager = this . _terminal . _core . selectionManager ;
36+ const { incremental} = searchOptions ;
37+ let result : ISearchResult ;
38+
2739 if ( ! term || term . length === 0 ) {
40+ selectionManager . clearSelection ( ) ;
2841 return false ;
2942 }
3043
31- let result : ISearchResult ;
32-
3344 let startRow = this . _terminal . _core . buffer . ydisp ;
34- if ( this . _terminal . _core . selectionManager . selectionEnd ) {
45+
46+ if ( selectionManager . selectionEnd ) {
3547 // Start from the selection end if there is a selection
48+ // For incremental search, use existing row
3649 if ( this . _terminal . getSelection ( ) . length !== 0 ) {
37- startRow = this . _terminal . _core . selectionManager . selectionEnd [ 1 ] ;
50+ startRow = incremental ? selectionManager . selectionStart [ 1 ] : selectionManager . selectionEnd [ 1 ] ;
3851 }
3952 }
4053
41- // Search from ydisp + 1 to end
42- for ( let y = startRow + 1 ; y < this . _terminal . _core . buffer . ybase + this . _terminal . rows ; y ++ ) {
54+ this . _initLinesCache ( ) ;
55+
56+ // Search from startRow to end
57+ for ( let y = incremental ? startRow : startRow + 1 ; y < this . _terminal . _core . buffer . ybase + this . _terminal . rows ; y ++ ) {
4358 result = this . _findInLine ( term , y , searchOptions ) ;
4459 if ( result ) {
4560 break ;
4661 }
4762 }
4863
49- // Search from the top to the current ydisp
64+ // Search from the top to the startRow
5065 if ( ! result ) {
5166 for ( let y = 0 ; y < startRow ; y ++ ) {
5267 result = this . _findInLine ( term , y , searchOptions ) ;
@@ -68,29 +83,35 @@ export class SearchHelper implements ISearchHelper {
6883 * @return Whether a result was found.
6984 */
7085 public findPrevious ( term : string , searchOptions ?: ISearchOptions ) : boolean {
86+ const selectionManager = this . _terminal . _core . selectionManager ;
87+ const { incremental} = searchOptions ;
88+ let result : ISearchResult ;
89+
7190 if ( ! term || term . length === 0 ) {
91+ selectionManager . clearSelection ( ) ;
7292 return false ;
7393 }
7494
75- let result : ISearchResult ;
76-
7795 let startRow = this . _terminal . _core . buffer . ydisp ;
78- if ( this . _terminal . _core . selectionManager . selectionStart ) {
79- // Start from the selection end if there is a selection
96+
97+ if ( selectionManager . selectionStart ) {
98+ // Start from the selection start if there is a selection
8099 if ( this . _terminal . getSelection ( ) . length !== 0 ) {
81- startRow = this . _terminal . _core . selectionManager . selectionStart [ 1 ] ;
100+ startRow = selectionManager . selectionStart [ 1 ] ;
82101 }
83102 }
84103
85- // Search from ydisp + 1 to end
86- for ( let y = startRow - 1 ; y >= 0 ; y -- ) {
104+ this . _initLinesCache ( ) ;
105+
106+ // Search from startRow to top
107+ for ( let y = incremental ? startRow : startRow - 1 ; y >= 0 ; y -- ) {
87108 result = this . _findInLine ( term , y , searchOptions ) ;
88109 if ( result ) {
89110 break ;
90111 }
91112 }
92113
93- // Search from the top to the current ydisp
114+ // Search from the bottom to startRow
94115 if ( ! result ) {
95116 for ( let y = this . _terminal . _core . buffer . ybase + this . _terminal . rows - 1 ; y > startRow ; y -- ) {
96117 result = this . _findInLine ( term , y , searchOptions ) ;
@@ -104,15 +125,37 @@ export class SearchHelper implements ISearchHelper {
104125 return this . _selectResult ( result ) ;
105126 }
106127
128+ /**
129+ * Sets up a line cache with a ttl
130+ */
131+ private _initLinesCache ( ) : void {
132+ if ( ! this . _linesCache ) {
133+ this . _linesCache = new Array ( this . _terminal . _core . buffer . length ) ;
134+ this . _terminal . on ( 'cursormove' , this . _destroyLinesCache ) ;
135+ }
136+
137+ window . clearTimeout ( this . _linesCacheTimeoutId ) ;
138+ this . _linesCacheTimeoutId = window . setTimeout ( ( ) => this . _destroyLinesCache ( ) , LINES_CACHE_TIME_TO_LIVE ) ;
139+ }
140+
141+ private _destroyLinesCache ( ) : void {
142+ this . _linesCache = null ;
143+ this . _terminal . off ( 'cursormove' , this . _destroyLinesCache ) ;
144+ if ( this . _linesCacheTimeoutId ) {
145+ window . clearTimeout ( this . _linesCacheTimeoutId ) ;
146+ this . _linesCacheTimeoutId = 0 ;
147+ }
148+ }
149+
107150 /**
108151 * A found substring is a whole word if it doesn't have an alphanumeric character directly adjacent to it.
109152 * @param searchIndex starting indext of the potential whole word substring
110153 * @param line entire string in which the potential whole word was found
111154 * @param term the substring that starts at searchIndex
112155 */
113156 private _isWholeWord ( searchIndex : number , line : string , term : string ) : boolean {
114- return ( ( ( searchIndex === 0 ) || ( nonWordCharacters . indexOf ( line [ searchIndex - 1 ] ) !== - 1 ) ) &&
115- ( ( ( searchIndex + term . length ) === line . length ) || ( nonWordCharacters . indexOf ( line [ searchIndex + term . length ] ) !== - 1 ) ) ) ;
157+ return ( ( ( searchIndex === 0 ) || ( NON_WORD_CHARACTERS . indexOf ( line [ searchIndex - 1 ] ) !== - 1 ) ) &&
158+ ( ( ( searchIndex + term . length ) === line . length ) || ( NON_WORD_CHARACTERS . indexOf ( line [ searchIndex + term . length ] ) !== - 1 ) ) ) ;
116159 }
117160
118161 /**
@@ -130,7 +173,14 @@ export class SearchHelper implements ISearchHelper {
130173 return ;
131174 }
132175
133- const stringLine = this . translateBufferLineToStringWithWrap ( y , true ) ;
176+ let stringLine = this . _linesCache ? this . _linesCache [ y ] : void 0 ;
177+ if ( stringLine === void 0 ) {
178+ stringLine = this . translateBufferLineToStringWithWrap ( y , true ) ;
179+ if ( this . _linesCache ) {
180+ this . _linesCache [ y ] = stringLine ;
181+ }
182+ }
183+
134184 const searchStringLine = searchOptions . caseSensitive ? stringLine : stringLine . toLowerCase ( ) ;
135185 const searchTerm = searchOptions . caseSensitive ? term : term . toLowerCase ( ) ;
136186 let searchIndex = - 1 ;
0 commit comments