33 * @license MIT
44 */
55
6- import { ISearchHelper , ISearchAddonTerminal , ISearchOptions , ISearchResult } from './Interfaces' ;
6+ import { ISearchHelper , ISearchAddonTerminal , ISearchOptions , ISearchResult , ISearchIndex } from './Interfaces' ;
77const nonWordCharacters = ' ~!@#$%^&*()+`-=[]{}|\;:"\',./<>?' ;
88
99/**
@@ -29,27 +29,30 @@ export class SearchHelper implements ISearchHelper {
2929 }
3030
3131 let result : ISearchResult ;
32-
3332 let startRow = this . _terminal . _core . buffer . ydisp ;
33+ let startCol : number = 0 ;
3434 if ( this . _terminal . _core . selectionManager . selectionEnd ) {
3535 // Start from the selection end if there is a selection
3636 if ( this . _terminal . getSelection ( ) . length !== 0 ) {
3737 startRow = this . _terminal . _core . selectionManager . selectionEnd [ 1 ] ;
38+ startCol = this . _terminal . _core . selectionManager . selectionEnd [ 0 ] ;
3839 }
3940 }
4041
4142 // Search from ydisp + 1 to end
42- for ( let y = startRow + 1 ; y < this . _terminal . _core . buffer . ybase + this . _terminal . rows ; y ++ ) {
43- result = this . _findInLine ( term , y , searchOptions ) ;
43+ for ( let y = startRow ; y < this . _terminal . _core . buffer . ybase + this . _terminal . rows ; y ++ ) {
44+ result = this . _findInLine ( term , { row : y , col : startCol } , searchOptions ) ;
4445 if ( result ) {
4546 break ;
4647 }
48+ startCol = 0 ;
4749 }
4850
4951 // Search from the top to the current ydisp
5052 if ( ! result ) {
5153 for ( let y = 0 ; y < startRow ; y ++ ) {
52- result = this . _findInLine ( term , y , searchOptions ) ;
54+ startCol = 0 ;
55+ result = this . _findInLine ( term , { row : y , col : startCol } , searchOptions ) ;
5356 if ( result ) {
5457 break ;
5558 }
@@ -72,28 +75,35 @@ export class SearchHelper implements ISearchHelper {
7275 return false ;
7376 }
7477
75- let result : ISearchResult ;
78+ searchOptions . reverseSearch = true ;
7679
80+ let result : ISearchResult ;
7781 let startRow = this . _terminal . _core . buffer . ydisp ;
82+ let startCol : number = this . _terminal . _core . buffer . lines . get ( startRow ) . length ;
83+
7884 if ( this . _terminal . _core . selectionManager . selectionStart ) {
7985 // Start from the selection end if there is a selection
8086 if ( this . _terminal . getSelection ( ) . length !== 0 ) {
8187 startRow = this . _terminal . _core . selectionManager . selectionStart [ 1 ] ;
88+ startCol = this . _terminal . _core . selectionManager . selectionStart [ 0 ] ;
8289 }
8390 }
8491
8592 // Search from ydisp + 1 to end
86- for ( let y = startRow - 1 ; y >= 0 ; y -- ) {
87- result = this . _findInLine ( term , y , searchOptions ) ;
93+ for ( let y = startRow ; y >= 0 ; y -- ) {
94+ result = this . _findInLine ( term , { row : y , col : startCol } , searchOptions ) ;
8895 if ( result ) {
8996 break ;
9097 }
98+ startCol = y > 0 ? this . _terminal . _core . buffer . lines . get ( y - 1 ) . length : 0 ;
9199 }
92100
93101 // Search from the top to the current ydisp
94102 if ( ! result ) {
95- for ( let y = this . _terminal . _core . buffer . ybase + this . _terminal . rows - 1 ; y > startRow ; y -- ) {
96- result = this . _findInLine ( term , y , searchOptions ) ;
103+ const searchFrom = this . _terminal . _core . buffer . ybase + this . _terminal . rows - 1 ;
104+ for ( let y = searchFrom ; y > startRow ; y -- ) {
105+ startCol = this . _terminal . _core . buffer . lines . get ( y ) . length ;
106+ result = this . _findInLine ( term , { row : y , col : startCol } , searchOptions ) ;
97107 if ( result ) {
98108 break ;
99109 }
@@ -125,61 +135,73 @@ export class SearchHelper implements ISearchHelper {
125135 * @param searchOptions Search options.
126136 * @return The search result if it was found.
127137 */
128- protected _findInLine ( term : string , y : number , searchOptions : ISearchOptions = { } ) : ISearchResult {
129- if ( this . _terminal . _core . buffer . lines . get ( y ) . isWrapped ) {
138+ protected _findInLine ( term : string , searchIndex : ISearchIndex , searchOptions : ISearchOptions = { } ) : ISearchResult {
139+ if ( this . _terminal . _core . buffer . lines . get ( searchIndex . row ) . isWrapped ) {
130140 return ;
131141 }
132142
133- const stringLine = this . translateBufferLineToStringWithWrap ( y , true ) ;
134- const searchStringLine = searchOptions . caseSensitive ? stringLine : stringLine . toLowerCase ( ) ;
143+ const stringLine = this . translateBufferLineToStringWithWrap ( searchIndex . row , true ) ;
135144 const searchTerm = searchOptions . caseSensitive ? term : term . toLowerCase ( ) ;
136- let searchIndex = - 1 ;
145+ const searchStringLine = searchOptions . caseSensitive ? stringLine : stringLine . toLowerCase ( ) ;
137146
147+ let resultIndex = - 1 ;
138148 if ( searchOptions . regex ) {
139149 const searchRegex = RegExp ( searchTerm , 'g' ) ;
140- const foundTerm = searchRegex . exec ( searchStringLine ) ;
141- if ( foundTerm && foundTerm [ 0 ] . length > 0 ) {
142- searchIndex = searchRegex . lastIndex - foundTerm [ 0 ] . length ;
143- term = foundTerm [ 0 ] ;
150+ let foundTerm : RegExpExecArray ;
151+ if ( searchOptions . reverseSearch ) {
152+ while ( foundTerm = searchRegex . exec ( searchStringLine . slice ( 0 , searchIndex . col ) ) ) {
153+ resultIndex = searchRegex . lastIndex - foundTerm [ 0 ] . length ;
154+ term = foundTerm [ 0 ] ;
155+ searchRegex . lastIndex -= ( term . length - 1 ) ;
156+ }
157+ } else {
158+ foundTerm = searchRegex . exec ( searchStringLine . slice ( searchIndex . col ) ) ;
159+ if ( foundTerm && foundTerm [ 0 ] . length > 0 ) {
160+ resultIndex = searchIndex . col + ( searchRegex . lastIndex - foundTerm [ 0 ] . length ) ;
161+ term = foundTerm [ 0 ] ;
162+ }
144163 }
145164 } else {
146- searchIndex = searchStringLine . indexOf ( searchTerm ) ;
165+ if ( searchOptions . reverseSearch ) {
166+ resultIndex = searchStringLine . lastIndexOf ( searchTerm , searchIndex . col - searchTerm . length ) ;
167+ } else {
168+ resultIndex = searchStringLine . indexOf ( searchTerm , searchIndex . col ) ;
169+ }
147170 }
148171
149- if ( searchIndex >= 0 ) {
172+ if ( resultIndex >= 0 ) {
150173 // Adjust the row number and search index if needed since a "line" of text can span multiple rows
151- if ( searchIndex >= this . _terminal . cols ) {
152- y += Math . floor ( searchIndex / this . _terminal . cols ) ;
153- searchIndex = searchIndex % this . _terminal . cols ;
174+ if ( resultIndex >= this . _terminal . cols ) {
175+ searchIndex . row += Math . floor ( resultIndex / this . _terminal . cols ) ;
176+ resultIndex = resultIndex % this . _terminal . cols ;
154177 }
155- if ( searchOptions . wholeWord && ! this . _isWholeWord ( searchIndex , searchStringLine , term ) ) {
178+ if ( searchOptions . wholeWord && ! this . _isWholeWord ( resultIndex , searchStringLine , term ) ) {
156179 return ;
157180 }
158181
159- const line = this . _terminal . _core . buffer . lines . get ( y ) ;
182+ const line = this . _terminal . _core . buffer . lines . get ( searchIndex . row ) ;
160183
161- for ( let i = 0 ; i < searchIndex ; i ++ ) {
184+ for ( let i = 0 ; i < resultIndex ; i ++ ) {
162185 const charData = line . get ( i ) ;
163186 // Adjust the searchIndex to normalize emoji into single chars
164187 const char = charData [ 1 /*CHAR_DATA_CHAR_INDEX*/ ] ;
165188 if ( char . length > 1 ) {
166- searchIndex -= char . length - 1 ;
189+ resultIndex -= char . length - 1 ;
167190 }
168191 // Adjust the searchIndex for empty characters following wide unicode
169192 // chars (eg. CJK)
170193 const charWidth = charData [ 2 /*CHAR_DATA_WIDTH_INDEX*/ ] ;
171194 if ( charWidth === 0 ) {
172- searchIndex ++ ;
195+ resultIndex ++ ;
173196 }
174197 }
175198 return {
176199 term,
177- col : searchIndex ,
178- row : y
200+ col : resultIndex ,
201+ row : searchIndex . row
179202 } ;
180203 }
181204 }
182-
183205 /**
184206 * Translates a buffer line to a string, including subsequent lines if they are wraps.
185207 * Wide characters will count as two columns in the resulting string. This
0 commit comments