@@ -49,6 +49,7 @@ define(function (require, exports, module) {
4949 Strings = require ( "strings" ) ,
5050 StringUtils = require ( "utils/StringUtils" ) ,
5151 ProjectManager = require ( "project/ProjectManager" ) ,
52+ DocumentModule = require ( "document/Document" ) ,
5253 DocumentManager = require ( "document/DocumentManager" ) ,
5354 EditorManager = require ( "editor/EditorManager" ) ,
5455 PanelManager = require ( "view/PanelManager" ) ,
@@ -69,7 +70,8 @@ define(function (require, exports, module) {
6970
7071 /** @cost Constants used to define the maximum results show per page and found in a single file */
7172 var RESULTS_PER_PAGE = 100 ,
72- FIND_IN_FILE_MAX = 300 ;
73+ FIND_IN_FILE_MAX = 300 ,
74+ UPDATE_TIMEOUT = 400 ;
7375
7476 /**
7577 * Map of all the last search results
@@ -86,12 +88,18 @@ define(function (require, exports, module) {
8688 /** @type {string } The current search query */
8789 var currentQuery = "" ;
8890
91+ /** @type {RegExp } The current search query regular expression */
92+ var currentQueryExpr = null ;
93+
8994 /** @type {Array.<FileEntry> } An array of the files where it should look or null/empty to search the entire project */
9095 var currentScope = null ;
9196
9297 /** @type {boolean } True if the matches in a file reached FIND_IN_FILE_MAX */
9398 var maxHitsFoundInFile = false ;
9499
100+ /** @type {string } The setTimeout id, used to clear it if required */
101+ var timeoutID = null ;
102+
95103 /** @type {$.Element } jQuery elements used in the search results */
96104 var $searchResults ,
97105 $searchSummary ,
@@ -102,7 +110,7 @@ define(function (require, exports, module) {
102110 /**
103111 * @private
104112 * Returns a regular expression from the given query and shows an error in the modal-bar if it was invalid
105- * @param {! string } query - The query from the modal-bar input
113+ * @param {string } query The query from the modal-bar input
106114 * @return {RegExp }
107115 */
108116 function _getQueryRegExp ( query ) {
@@ -184,8 +192,8 @@ define(function (require, exports, module) {
184192
185193 /**
186194 * Shows the search dialog
187- * @param {? string } initialString Default text to prepopulate the search field with
188- * @param {? Entry } scope Search scope, or null to search whole project
195+ * @param {string= } initialString Default text to prepopulate the search field with
196+ * @param {Entry= } scope Search scope, or null to search whole project
189197 * @returns {$.Promise } that is resolved with the string to search for
190198 */
191199 FindInFilesDialog . prototype . showDialog = function ( initialString , scope ) {
@@ -238,6 +246,7 @@ define(function (require, exports, module) {
238246 function _hideSearchResults ( ) {
239247 if ( searchResultsPanel . isVisible ( ) ) {
240248 searchResultsPanel . hide ( ) ;
249+ $ ( DocumentModule ) . off ( ".findInFiles" ) ;
241250 }
242251 }
243252
@@ -537,11 +546,126 @@ define(function (require, exports, module) {
537546 _hideSearchResults ( ) ;
538547 }
539548 }
540-
549+
550+
551+
552+ /**
553+ * @private
554+ * Shows the search results and tries to restore the previous scroll and selection
555+ */
556+ function _restoreSearchResults ( ) {
557+ if ( searchResultsPanel . isVisible ( ) ) {
558+ var scrollTop = $searchContent . scrollTop ( ) ,
559+ index = $selectedRow ? $selectedRow . index ( ) : null ,
560+ numMatches = _countFilesMatches ( ) . matches ;
561+
562+ if ( currentStart > numMatches ) {
563+ currentStart = _getLastCurrentStart ( numMatches ) ;
564+ }
565+ _showSearchResults ( ) ;
566+
567+ $searchContent . scrollTop ( scrollTop ) ;
568+ if ( index ) {
569+ $selectedRow = $searchContent . find ( "tr:eq(" + index + ")" ) ;
570+ $selectedRow . addClass ( "selected" ) ;
571+ }
572+ }
573+ }
574+
575+ /**
576+ * @private
577+ * Update the search results using the given list of changes fr the given document
578+ * @param {Document } doc The Document that changed, should be the current one
579+ * @param {{from: {line:number,ch:number}, to: {line:number,ch:number}, text: string, next: change} } change
580+ * A linked list as described in the Document constructor
581+ * @param {boolean } resultsChanged True when the search results changed from a file change
582+ */
583+ function _updateSearchResults ( doc , change , resultsChanged ) {
584+ var i , diff , matches ,
585+ fullPath = doc . file . fullPath ,
586+ lines = [ ] ,
587+ start = 0 ,
588+ howMany = 0 ;
589+
590+ // There is no from or to positions, so the entire file changed, we must search all over again
591+ if ( ! change . from || ! change . to ) {
592+ _addSearchMatches ( fullPath , doc . getText ( ) , currentQueryExpr ) ;
593+ resultsChanged = true ;
594+
595+ } else {
596+ // Get only the lines that changed
597+ for ( i = 0 ; i < change . text . length ; i ++ ) {
598+ lines . push ( doc . getLine ( change . from . line + i ) ) ;
599+ }
600+
601+ // We need to know how many lines changed to update the rest of the lines
602+ if ( change . from . line !== change . to . line ) {
603+ diff = change . from . line - change . to . line ;
604+ } else {
605+ diff = lines . length - 1 ;
606+ }
607+
608+ if ( searchResults [ fullPath ] ) {
609+ // Search the last match before a replacement, the amount of matches deleted and update
610+ // the lines values for all the matches after the change
611+ searchResults [ fullPath ] . matches . forEach ( function ( item ) {
612+ if ( item . end . line < change . from . line ) {
613+ start ++ ;
614+ } else if ( item . end . line <= change . to . line ) {
615+ howMany ++ ;
616+ } else {
617+ item . start . line += diff ;
618+ item . end . line += diff ;
619+ }
620+ } ) ;
621+
622+ // Delete the lines that where deleted or replaced
623+ if ( howMany > 0 ) {
624+ searchResults [ fullPath ] . matches . splice ( start , howMany ) ;
625+ }
626+ resultsChanged = true ;
627+ }
628+
629+ // Searches only over the lines that changed
630+ matches = _getSearchMatches ( lines . join ( "\r\n" ) , currentQueryExpr ) ;
631+ if ( matches && matches . length ) {
632+ // Updates the line numbers, since we only searched part of the file
633+ matches . forEach ( function ( value , key ) {
634+ matches [ key ] . start . line += change . from . line ;
635+ matches [ key ] . end . line += change . from . line ;
636+ } ) ;
637+
638+ // If the file index exists, add the new matches to the file at the start index found before
639+ if ( searchResults [ fullPath ] ) {
640+ Array . prototype . splice . apply ( searchResults [ fullPath ] . matches , [ start , 0 ] . concat ( matches ) ) ;
641+ // If not, add the matches to a new file index
642+ } else {
643+ searchResults [ fullPath ] = {
644+ matches : matches ,
645+ collapsed : false
646+ } ;
647+ }
648+ resultsChanged = true ;
649+ }
650+
651+ // All the matches where deleted, remove the file from the results
652+ if ( searchResults [ fullPath ] && ! searchResults [ fullPath ] . matches . length ) {
653+ delete searchResults [ fullPath ] ;
654+ resultsChanged = true ;
655+ }
656+
657+ // This is link to the next change object, so we need to keep searching
658+ if ( change . next ) {
659+ return _updateSearchResults ( doc , change . next , resultsChanged ) ;
660+ }
661+ }
662+ return resultsChanged ;
663+ }
664+
541665 /**
542666 * @private
543- * @param {!FileInfo } fileInfo File in question
544- * @param {?Entry } scope Search scope, or null if whole project
667+ * @param {!FileInfo } fileInfo File in question
668+ * @param {?Entry } scope Search scope, or null if whole project
545669 * @return {boolean }
546670 */
547671 function _inScope ( fileInfo , scope ) {
@@ -556,12 +680,39 @@ define(function (require, exports, module) {
556680 }
557681 return true ;
558682 }
559-
683+
684+ /**
685+ * @private
686+ * Tries to update the search result on document changes
687+ * @param {$.Event } event
688+ * @param {Document } document
689+ * @param {{from: {line:number,ch:number}, to: {line:number,ch:number}, text: string, next: change} } change
690+ * A linked list as described in the Document constructor
691+ */
692+ function _documentChangeHandler ( event , document , change ) {
693+ if ( searchResultsPanel . isVisible ( ) && _inScope ( document . file , currentScope ) ) {
694+ var updateResults = _updateSearchResults ( document , change , false ) ;
695+
696+ if ( timeoutID ) {
697+ window . clearTimeout ( timeoutID ) ;
698+ updateResults = true ;
699+ }
700+ if ( updateResults ) {
701+ timeoutID = window . setTimeout ( function ( ) {
702+ _restoreSearchResults ( ) ;
703+ timeoutID = null ;
704+ } , UPDATE_TIMEOUT ) ;
705+ }
706+ }
707+ }
708+
709+
710+
560711 /**
561712 * @private
562713 * Displays a non-modal embedded dialog above the code mirror editor that allows the user to do
563714 * a find operation across all files in the project.
564- * @param {?Entry } scope Project file/subfolder to search within; else searches whole project.
715+ * @param {?Entry } scope Project file/subfolder to search within; else searches whole project.
565716 */
566717 function _doFindInFiles ( scope ) {
567718 if ( scope instanceof NativeFileSystem . InaccessibleFileEntry ) {
@@ -579,15 +730,17 @@ define(function (require, exports, module) {
579730 searchResults = { } ;
580731 currentStart = 0 ;
581732 currentQuery = "" ;
733+ currentQueryExpr = null ;
582734 currentScope = scope ;
583735 maxHitsFoundInFile = false ;
584736
585737 dialog . showDialog ( initialString , scope )
586738 . done ( function ( query ) {
587739 if ( query ) {
588- currentQuery = query ;
589- var queryExpr = _getQueryRegExp ( query ) ;
590- if ( ! queryExpr ) {
740+ currentQuery = query ;
741+ currentQueryExpr = _getQueryRegExp ( query ) ;
742+
743+ if ( ! currentQueryExpr ) {
591744 return ;
592745 }
593746 StatusBar . showBusyIndicator ( true ) ;
@@ -602,7 +755,7 @@ define(function (require, exports, module) {
602755 // Search one file
603756 DocumentManager . getDocumentForPath ( fileInfo . fullPath )
604757 . done ( function ( doc ) {
605- _addSearchMatches ( fileInfo . fullPath , doc . getText ( ) , queryExpr ) ;
758+ _addSearchMatches ( fileInfo . fullPath , doc . getText ( ) , currentQueryExpr ) ;
606759 result . resolve ( ) ;
607760 } )
608761 . fail ( function ( error ) {
@@ -617,6 +770,7 @@ define(function (require, exports, module) {
617770 // Done searching all files: show results
618771 _showSearchResults ( ) ;
619772 StatusBar . hideBusyIndicator ( ) ;
773+ $ ( DocumentModule ) . on ( "documentChange.findInFiles" , _documentChangeHandler ) ;
620774 } )
621775 . fail ( function ( ) {
622776 console . log ( "find in files failed." ) ;
@@ -637,23 +791,6 @@ define(function (require, exports, module) {
637791 }
638792
639793
640- /**
641- * @private
642- * Shows the search results and tries to restore the previous scroll and selection
643- */
644- function _restoreSearchResults ( ) {
645- var scrollTop = $searchContent . scrollTop ( ) ,
646- index = $selectedRow ? $selectedRow . index ( ) : null ;
647-
648- _showSearchResults ( ) ;
649-
650- $searchContent . scrollTop ( scrollTop ) ;
651- if ( $selectedRow ) {
652- $selectedRow = $searchContent . find ( "tr:eq(" + index + ")" ) ;
653- $selectedRow . addClass ( "selected" ) ;
654- }
655- }
656-
657794 /**
658795 * @private
659796 * Moves the search results from the previous path to the new one and updates the results list, if required
@@ -688,7 +825,7 @@ define(function (require, exports, module) {
688825 * @param {string } path
689826 */
690827 function _pathDeletedHandler ( event , path ) {
691- var resultsChanged = false , numMatches ;
828+ var resultsChanged = false ;
692829
693830 if ( searchResultsPanel . isVisible ( ) ) {
694831 // Update the search results
@@ -701,16 +838,13 @@ define(function (require, exports, module) {
701838
702839 // Restore the results if needed
703840 if ( resultsChanged ) {
704- numMatches = _countFilesMatches ( ) . matches ;
705- if ( currentStart > numMatches ) {
706- currentStart = _getLastCurrentStart ( numMatches ) ;
707- }
708841 _restoreSearchResults ( ) ;
709842 }
710843 }
711844 }
712845
713846
847+
714848 // Initialize items dependent on HTML DOM
715849 AppInit . htmlReady ( function ( ) {
716850 var panelHtml = Mustache . render ( searchPanelTemplate , Strings ) ;
@@ -722,8 +856,8 @@ define(function (require, exports, module) {
722856 } ) ;
723857
724858 // Initialize: register listeners
725- $ ( DocumentManager ) . on ( "fileNameChange" , _fileNameChangeHandler ) ;
726- $ ( DocumentManager ) . on ( "pathDeleted" , _pathDeletedHandler ) ;
859+ $ ( DocumentManager ) . on ( "fileNameChange" , _fileNameChangeHandler ) ;
860+ $ ( DocumentManager ) . on ( "pathDeleted" , _pathDeletedHandler ) ;
727861 $ ( ProjectManager ) . on ( "beforeProjectClose" , _hideSearchResults ) ;
728862
729863 // Initialize: command handlers
0 commit comments