2222 */
2323
2424/*jslint vars: true, plusplus: true, devel: true, nomen: true, regexp: true, indent: 4, maxerr: 50 */
25- /*global define, $, PathUtils, window */
25+ /*global define, $, PathUtils, Mustache, window */
2626
2727/*
2828 * Adds a "find in files" command to allow the user to find all occurances of a string in all files in
4242define ( function ( require , exports , module ) {
4343 "use strict" ;
4444
45- var Async = require ( "utils/Async" ) ,
46- CommandManager = require ( "command/CommandManager" ) ,
47- Commands = require ( "command/Commands" ) ,
48- Strings = require ( "strings" ) ,
49- StringUtils = require ( "utils/StringUtils" ) ,
50- ProjectManager = require ( "project/ProjectManager" ) ,
51- DocumentManager = require ( "document/DocumentManager" ) ,
52- EditorManager = require ( "editor/EditorManager" ) ,
53- FileIndexManager = require ( "project/FileIndexManager" ) ,
54- KeyEvent = require ( "utils/KeyEvent" ) ,
55- AppInit = require ( "utils/AppInit" ) ,
56- StatusBar = require ( "widgets/StatusBar" ) ,
57- ModalBar = require ( "widgets/ModalBar" ) . ModalBar ;
58-
45+ var Async = require ( "utils/Async" ) ,
46+ CommandManager = require ( "command/CommandManager" ) ,
47+ Commands = require ( "command/Commands" ) ,
48+ Strings = require ( "strings" ) ,
49+ StringUtils = require ( "utils/StringUtils" ) ,
50+ ProjectManager = require ( "project/ProjectManager" ) ,
51+ DocumentManager = require ( "document/DocumentManager" ) ,
52+ EditorManager = require ( "editor/EditorManager" ) ,
53+ FileIndexManager = require ( "project/FileIndexManager" ) ,
54+ KeyEvent = require ( "utils/KeyEvent" ) ,
55+ AppInit = require ( "utils/AppInit" ) ,
56+ StatusBar = require ( "widgets/StatusBar" ) ,
57+ ModalBar = require ( "widgets/ModalBar" ) . ModalBar ,
58+ SearchDialogTemplate = require ( "text!htmlContent/search-dialog.html" ) ,
59+ SearchResultsTemplate = require ( "text!htmlContent/search-results.html" ) ;
60+
61+
62+ /** @type {$.Element } jQuery elements used in the search results */
63+ var $searchResults ,
64+ $searchSummary ,
65+ $searchContent ,
66+ $selectedRow ;
67+
68+
5969 var searchResults = [ ] ;
6070
6171 var FIND_IN_FILES_MAX = 100 ,
@@ -113,18 +123,17 @@ define(function (require, exports, module) {
113123 // class that everyone can use.
114124
115125 /**
116- * FindInFilesDialog class
117- * @constructor
118- *
119- */
126+ * FindInFilesDialog class
127+ * @constructor
128+ */
120129 function FindInFilesDialog ( ) {
121130 this . closed = false ;
122131 this . result = null ; // $.Deferred
123132 }
124133
125134 /**
126- * Closes the search dialog and resolves the promise that showDialog returned
127- */
135+ * Closes the search dialog and resolves the promise that showDialog returned
136+ */
128137 FindInFilesDialog . prototype . _close = function ( value ) {
129138 if ( this . closed ) {
130139 return ;
@@ -137,26 +146,25 @@ define(function (require, exports, module) {
137146 } ;
138147
139148 /**
140- * Shows the search dialog
141- * @param {?string } initialString Default text to prepopulate the search field with
142- * @param {?Entry } scope Search scope, or null to search whole proj
143- * @returns {$.Promise } that is resolved with the string to search for
144- */
149+ * Shows the search dialog
150+ * @param {?string } initialString Default text to prepopulate the search field with
151+ * @param {?Entry } scope Search scope, or null to search whole proj
152+ * @returns {$.Promise } that is resolved with the string to search for
153+ */
145154 FindInFilesDialog . prototype . showDialog = function ( initialString , scope ) {
146155 // Note the prefix label is a simple "Find:" - the "in ..." part comes after the text field
147- var dialogHTML = Strings . CMD_FIND +
148- ": <input type='text' id='findInFilesInput' style='width: 10em'> <span id='findInFilesScope'></span> " +
149- "<div class='message'><span style='color: #888'>(" + Strings . SEARCH_REGEXP_INFO + ")</span></div><div class='error'></div>" ;
156+ var templateVars = {
157+ value : initialString || "" ,
158+ label : _labelForScope ( scope )
159+ } ;
160+ var dialogHTML = Mustache . render ( SearchDialogTemplate , $ . extend ( templateVars , Strings ) ) ;
161+
150162 this . result = new $ . Deferred ( ) ;
151163 this . modalBar = new ModalBar ( dialogHTML , false ) ;
152- var $searchField = $ ( "input#findInFilesInput " ) ;
164+ var $searchField = $ ( "input#searchInput " ) ;
153165 var that = this ;
154166
155- $searchField . attr ( "value" , initialString || "" ) ;
156167 $searchField . get ( 0 ) . select ( ) ;
157-
158- $ ( "#findInFilesScope" ) . html ( _labelForScope ( scope ) ) ;
159-
160168 $searchField . bind ( "keydown" , function ( event ) {
161169 if ( event . keyCode === KeyEvent . DOM_VK_RETURN || event . keyCode === KeyEvent . DOM_VK_ESCAPE ) { // Enter/Return key or Esc key
162170 event . stopPropagation ( ) ;
@@ -184,7 +192,13 @@ define(function (require, exports, module) {
184192 return this . result . promise ( ) ;
185193 } ;
186194
187-
195+ /**
196+ * @private
197+ * Searches throught the contents an returns an array of matches
198+ * @param {string } contents
199+ * @param {RegExp } queryExpr
200+ * @return {Array.<{start: {line:number,ch:number}, end: {line:number,ch:number}, line: string}> }
201+ */
188202 function _getSearchMatches ( contents , queryExpr ) {
189203 // Quick exit if not found
190204 if ( contents . search ( queryExpr ) === - 1 ) {
@@ -196,22 +210,21 @@ define(function (require, exports, module) {
196210 var matchStart ;
197211 var matches = [ ] ;
198212
199-
200213 var match ;
201214 var lines = StringUtils . getLines ( contents ) ;
202215 while ( ( match = queryExpr . exec ( contents ) ) !== null ) {
203- var lineNum = StringUtils . offsetToLineNum ( lines , match . index ) ;
204- var line = lines [ lineNum ] ;
205- var ch = match . index - contents . lastIndexOf ( "\n" , match . index ) - 1 ; // 0-based index
216+ var lineNum = StringUtils . offsetToLineNum ( lines , match . index ) ;
217+ var line = lines [ lineNum ] ;
218+ var ch = match . index - contents . lastIndexOf ( "\n" , match . index ) - 1 ; // 0-based index
206219 var matchLength = match [ 0 ] . length ;
207220
208221 // Don't store more than 200 chars per line
209222 line = line . substr ( 0 , Math . min ( 200 , line . length ) ) ;
210223
211224 matches . push ( {
212225 start : { line : lineNum , ch : ch } ,
213- end : { line : lineNum , ch : ch + matchLength } ,
214- line : line
226+ end : { line : lineNum , ch : ch + matchLength } ,
227+ line : line
215228 } ) ;
216229
217230 // We have the max hits in just this 1 file. Stop searching this file.
@@ -227,11 +240,7 @@ define(function (require, exports, module) {
227240 }
228241
229242 function _showSearchResults ( searchResults , query , scope ) {
230- var $searchResultsDiv = $ ( "#search-results" ) ;
231-
232243 if ( searchResults && searchResults . length ) {
233- var $resultTable = $ ( "<table class='zebra-striped condensed-table' />" )
234- . append ( "<tbody>" ) ;
235244
236245 // Count the total number of matches
237246 var numMatches = 0 ;
@@ -257,78 +266,103 @@ define(function (require, exports, module) {
257266 scope ? _labelForScope ( scope ) : ""
258267 ) ;
259268
260- $ ( "#search-result-summary" )
269+ // Insert the search summary
270+ $searchSummary
261271 . html ( summary +
262272 ( numMatches > FIND_IN_FILES_MAX ? StringUtils . format ( Strings . FIND_IN_FILES_MAX , FIND_IN_FILES_MAX ) : "" ) )
263273 . prepend ( " " ) ; // putting a normal space before the "-" is not enough
264274
265- var resultsDisplayed = 0 ;
275+ // Create the results template search list
276+ var searchList = [ ] ;
277+ var resultsDisplayed = 0 , i ;
278+ var searchItems , match ;
266279
267280 searchResults . forEach ( function ( item ) {
268281 if ( item && resultsDisplayed < FIND_IN_FILES_MAX ) {
269- var makeCell = function ( content ) {
270- return $ ( "<td/>" ) . html ( content ) ;
271- } ;
272-
273- // shorthand function name
274- var esc = StringUtils . htmlEscape ;
275-
276- var highlightMatch = function ( line , start , end ) {
277- return esc ( line . substr ( 0 , start ) ) + "<span class='highlight'>" + esc ( line . substring ( start , end ) ) + "</span>" + esc ( line . substr ( end ) ) ;
278- } ;
282+ i = 0 ;
279283
280- // Add row for file name
281- var displayFileName = StringUtils . format ( Strings . FIND_IN_FILES_FILE_PATH ,
282- StringUtils . breakableUrl ( esc ( item . fullPath ) ) ) ;
283- $ ( "<tr class='file-section' />" )
284- . append ( "<td colspan='3'><span class='disclosure-triangle expanded'></span>" + displayFileName + "</td>" )
285- . click ( function ( ) {
286- // Clicking file section header collapses/expands result rows for that file
287- var $fileHeader = $ ( this ) ;
288- $fileHeader . nextUntil ( ".file-section" ) . toggle ( ) ;
289-
290- var $triangle = $ ( ".disclosure-triangle" , $fileHeader ) ;
291- $triangle . toggleClass ( "expanded" ) . toggleClass ( "collapsed" ) ;
292- } )
293- . appendTo ( $resultTable ) ;
284+ // Add a row for each match in the file
285+ searchItems = [ ] ;
286+ while ( i < item . matches . length && resultsDisplayed < FIND_IN_FILES_MAX ) {
287+ match = item . matches [ i ] ;
288+ searchItems . push ( {
289+ file : searchList . length ,
290+ item : i ,
291+ line : StringUtils . format ( Strings . FIND_IN_FILES_LINE , ( match . start . line + 1 ) ) ,
292+ pre : match . line . substr ( 0 , match . start . ch ) ,
293+ highlight : match . line . substring ( match . start . ch , match . end . ch ) ,
294+ post : match . line . substr ( match . end . ch ) ,
295+ start : match . start ,
296+ end : match . end
297+ } ) ;
298+ resultsDisplayed ++ ;
299+ i ++ ;
300+ }
294301
295- // Add row for each match in file
296- item . matches . forEach ( function ( match ) {
297- if ( resultsDisplayed < FIND_IN_FILES_MAX ) {
298- var $row = $ ( "<tr/>" )
299- . append ( makeCell ( " " ) ) // Indent
300- . append ( makeCell ( StringUtils . format ( Strings . FIND_IN_FILES_LINE , ( match . start . line + 1 ) ) ) )
301- . append ( makeCell ( highlightMatch ( match . line , match . start . ch , match . end . ch ) ) )
302- . appendTo ( $resultTable ) ;
303-
304- $row . click ( function ( ) {
305- CommandManager . execute ( Commands . FILE_OPEN , { fullPath : item . fullPath } )
306- . done ( function ( doc ) {
307- // Opened document is now the current main editor
308- EditorManager . getCurrentFullEditor ( ) . setSelection ( match . start , match . end , true ) ;
309- } ) ;
310- } ) ;
311- resultsDisplayed ++ ;
312- }
302+ // Add a row for each file
303+ searchList . push ( {
304+ file : searchList . length ,
305+ filename : StringUtils . breakableUrl ( StringUtils . htmlEscape ( item . fullPath ) ) ,
306+ fullPath : item . fullPath ,
307+ items : searchItems
313308 } ) ;
314309
315310 }
316311 } ) ;
317312
318- $ ( "#search-results .table-container" )
313+ // Insert the search results
314+ $searchContent
319315 . empty ( )
320- . append ( $resultTable )
316+ . append ( Mustache . render ( SearchResultsTemplate , { searchList : searchList } ) )
321317 . scrollTop ( 0 ) ; // otherwise scroll pos from previous contents is remembered
322318
323- $ ( "#search-results .close")
319+ $searchResults . find ( " .close")
324320 . one ( "click" , function ( ) {
325- $searchResultsDiv . hide ( ) ;
321+ $searchResults . hide ( ) ;
326322 EditorManager . resizeEditor ( ) ;
327323 } ) ;
328324
329- $searchResultsDiv . show ( ) ;
325+ // Add the click event listener directly on the table parent
326+ $searchContent
327+ . off ( ".searchList" ) // Remove the old events
328+ . on ( "click.searchList" , function ( e ) {
329+ var $row = $ ( e . target ) . closest ( "tr" ) ;
330+
331+ if ( $row . length ) {
332+ if ( $selectedRow ) {
333+ $selectedRow . removeClass ( "selected" ) ;
334+ }
335+ $row . addClass ( "selected" ) ;
336+ $selectedRow = $row ;
337+
338+ var searchItem = searchList [ $row . data ( "file" ) ] ;
339+ var fullPath = searchItem . fullPath ;
340+
341+ // This is a file title row, expand/collapse on click
342+ if ( $row . hasClass ( "file-section" ) ) {
343+ // Clicking the file section header collapses/expands result rows for that file
344+ $row . nextUntil ( ".file-section" ) . toggle ( ) ;
345+
346+ var $triangle = $ ( ".disclosure-triangle" , $row ) ;
347+ $triangle . toggleClass ( "expanded" ) . toggleClass ( "collapsed" ) ;
348+
349+ // This is a file row, show the result on click
350+ } else {
351+ // Grab the required item data
352+ var item = searchItem . items [ $row . data ( "item" ) ] ;
353+
354+ CommandManager . execute ( Commands . FILE_OPEN , { fullPath : fullPath } )
355+ . done ( function ( doc ) {
356+ // Opened document is now the current main editor
357+ EditorManager . getCurrentFullEditor ( ) . setSelection ( item . start , item . end , true ) ;
358+ } ) ;
359+ }
360+ }
361+ } ) ;
362+
363+ $searchResults . show ( ) ;
330364 } else {
331- $searchResultsDiv . hide ( ) ;
365+ $searchResults . hide ( ) ;
332366 }
333367
334368 EditorManager . resizeEditor ( ) ;
@@ -435,12 +469,6 @@ define(function (require, exports, module) {
435469 }
436470
437471
438- // Initialize items dependent on HTML DOM
439- AppInit . htmlReady ( function ( ) {
440- var $searchResults = $ ( "#search-results" ) ,
441- $searchContent = $ ( "#search-results .table-container" ) ;
442- } ) ;
443-
444472 function _fileNameChangeHandler ( event , oldName , newName ) {
445473 if ( $ ( "#search-results" ) . is ( ":visible" ) ) {
446474 // Update the search results
@@ -451,8 +479,18 @@ define(function (require, exports, module) {
451479 }
452480 }
453481
482+
483+ // Initialize items dependent on HTML DOM
484+ AppInit . htmlReady ( function ( ) {
485+ $searchResults = $ ( "#search-results" ) ;
486+ $searchSummary = $ ( "#search-result-summary" ) ;
487+ $searchContent = $ ( "#search-results .table-container" ) ;
488+ } ) ;
489+
490+ // Initialize: register listeners
454491 $ ( DocumentManager ) . on ( "fileNameChange" , _fileNameChangeHandler ) ;
455492
493+ // Initialize: command handlers
456494 CommandManager . register ( Strings . CMD_FIND_IN_FILES , Commands . EDIT_FIND_IN_FILES , doFindInFiles ) ;
457495 CommandManager . register ( Strings . CMD_FIND_IN_SUBTREE , Commands . EDIT_FIND_IN_SUBTREE , doFindInSubtree ) ;
458496} ) ;
0 commit comments