@@ -29,12 +29,17 @@ define(function (require, exports, module) {
2929 "use strict" ;
3030
3131 // Load dependent modules
32- var HTMLUtils = brackets . getModule ( "language/HTMLUtils" ) ,
33- HTMLTags = require ( "text!HtmlTags.json" ) ,
34- HTMLAttributes = require ( "text!HtmlAttributes.json" ) ,
35- CodeHintManager = brackets . getModule ( "editor/CodeHintManager" ) ,
36- tags = JSON . parse ( HTMLTags ) ,
37- attributes = JSON . parse ( HTMLAttributes ) ;
32+ var CodeHintManager = brackets . getModule ( "editor/CodeHintManager" ) ,
33+ DocumentManager = brackets . getModule ( "document/DocumentManager" ) ,
34+ EditorManager = brackets . getModule ( "editor/EditorManager" ) ,
35+ HTMLUtils = brackets . getModule ( "language/HTMLUtils" ) ,
36+ NativeFileSystem = brackets . getModule ( "file/NativeFileSystem" ) . NativeFileSystem ,
37+ ProjectManager = brackets . getModule ( "project/ProjectManager" ) ,
38+ StringUtils = brackets . getModule ( "utils/StringUtils" ) ,
39+ HTMLTags = require ( "text!HtmlTags.json" ) ,
40+ HTMLAttributes = require ( "text!HtmlAttributes.json" ) ,
41+ tags = JSON . parse ( HTMLTags ) ,
42+ attributes = JSON . parse ( HTMLAttributes ) ;
3843
3944 /**
4045 * @constructor
@@ -88,8 +93,9 @@ define(function (require, exports, module) {
8893 * @param {string } completion - text to insert into current code editor
8994 * @param {Editor } editor
9095 * @param {Cursor } current cursor location
96+ * @param {boolean } closeHints - true to close hints, or false to continue hinting
9197 */
92- TagHints . prototype . handleSelect = function ( completion , editor , cursor ) {
98+ TagHints . prototype . handleSelect = function ( completion , editor , cursor , closeHints ) {
9399 var start = { line : - 1 , ch : - 1 } ,
94100 end = { line : - 1 , ch : - 1 } ,
95101 tagInfo = HTMLUtils . getTagInfo ( editor , cursor ) ,
@@ -126,6 +132,7 @@ define(function (require, exports, module) {
126132 */
127133 function AttrHints ( ) {
128134 this . globalAttributes = this . readGlobalAttrHints ( ) ;
135+ this . cachedHints = null ;
129136 }
130137
131138 /**
@@ -146,8 +153,9 @@ define(function (require, exports, module) {
146153 * @param {string } completion - text to insert into current code editor
147154 * @param {Editor } editor
148155 * @param {Cursor } current cursor location
156+ * @param {boolean } closeHints - true to close hints, or false to continue hinting
149157 */
150- AttrHints . prototype . handleSelect = function ( completion , editor , cursor ) {
158+ AttrHints . prototype . handleSelect = function ( completion , editor , cursor , closeHints ) {
151159 var start = { line : - 1 , ch : - 1 } ,
152160 end = { line : - 1 , ch : - 1 } ,
153161 tagInfo = HTMLUtils . getTagInfo ( editor , cursor ) ,
@@ -195,15 +203,17 @@ define(function (require, exports, module) {
195203 }
196204 }
197205
198- if ( insertedName ) {
199- editor . setCursorPos ( start . line , start . ch + completion . length - 1 ) ;
200-
201- // Since we're now inside the double-quotes we just inserted,
202- // mmediately pop up the attribute value hint.
203- CodeHintManager . showHint ( editor ) ;
204- } else if ( tokenType === HTMLUtils . ATTR_VALUE && tagInfo . attr . hasEndQuote ) {
205- // Move the cursor to the right of the existing end quote after value insertion.
206- editor . setCursorPos ( start . line , start . ch + completion . length + 1 ) ;
206+ if ( closeHints ) {
207+ if ( insertedName ) {
208+ editor . setCursorPos ( start . line , start . ch + completion . length - 1 ) ;
209+
210+ // Since we're now inside the double-quotes we just inserted,
211+ // immediately pop up the attribute value hint.
212+ CodeHintManager . showHint ( editor ) ;
213+ } else if ( tokenType === HTMLUtils . ATTR_VALUE && tagInfo . attr . hasEndQuote ) {
214+ // Move the cursor to the right of the existing end quote after value insertion.
215+ editor . setCursorPos ( start . line , start . ch + completion . length + 1 ) ;
216+ }
207217 }
208218 } ;
209219
@@ -246,6 +256,141 @@ define(function (require, exports, module) {
246256 return query ;
247257 } ;
248258
259+ /**
260+ * Helper function for search(). Create a list of urls to existing files based on the query.
261+ * @param {Object.<queryStr: string, ... } query -- a query object with a required property queryStr
262+ * that will be used to filter out code hints
263+ * @return {Array.<string> }
264+ */
265+ AttrHints . prototype . _getUrlList = function ( query ) {
266+ var doc ,
267+ result = [ ] ;
268+
269+ // site-root relative links are not yet supported, so filter them out
270+ if ( query . queryStr . length > 0 && query . queryStr [ 0 ] === "/" ) {
271+ return result ;
272+ }
273+
274+ // get path to current document
275+ doc = DocumentManager . getCurrentDocument ( ) ;
276+ if ( ! doc || ! doc . file ) {
277+ return result ;
278+ }
279+
280+ var docUrl = window . PathUtils . parseUrl ( doc . file . fullPath ) ;
281+ if ( ! docUrl ) {
282+ return result ;
283+ }
284+
285+ var docDir = docUrl . domain + docUrl . directory ;
286+
287+ // get relative path from query string
288+ // TODO: handle site-root relative
289+ var queryDir = "" ;
290+ var queryUrl = window . PathUtils . parseUrl ( query . queryStr ) ;
291+ if ( queryUrl ) {
292+ queryDir = queryUrl . directory ;
293+ }
294+
295+ // build target folder path
296+ var targetDir = docDir + decodeURI ( queryDir ) ;
297+
298+ // get list of files from target folder
299+ var unfiltered = [ ] ;
300+
301+ // Getting the file/folder info is an asynch operation, so it works like this:
302+ //
303+ // The initial pass initiates the asynchronous retrieval of data and returns an
304+ // empty list, so no code hints are displayed. In the async callback, the code
305+ // hints and the original query are stored in a cache, and then the process to
306+ // show code hints is re-initiated.
307+ //
308+ // During the next pass, there should now be code hints cached from the initial
309+ // pass, but user may have typed while file/folder info was being retrieved from
310+ // disk, so we need to make sure code hints still apply to current query. If so,
311+ // display them, otherwise, clear cache and start over.
312+ //
313+ // As user types within a folder, the same unfiltered file/folder list is still
314+ // valid and re-used from cache. Filtering based on user input is done outside
315+ // of this method. When user moves to a new folder, then the cache is deleted,
316+ // and file/folder info for new folder is then retrieved.
317+
318+ if ( this . cachedHints ) {
319+ // url hints have been cached, so determine if they're stale
320+ if ( ! this . cachedHints . query ||
321+ this . cachedHints . query . tag !== query . tag ||
322+ this . cachedHints . query . attrName !== query . attrName ||
323+ this . cachedHints . queryDir !== queryDir ) {
324+
325+ // delete stale cache
326+ this . cachedHints = null ;
327+ }
328+ }
329+
330+ if ( this . cachedHints ) {
331+ // use cached hints
332+ unfiltered = this . cachedHints . unfiltered ;
333+
334+ } else {
335+ var self = this ,
336+ origEditor = EditorManager . getFocusedEditor ( ) ;
337+
338+ // create empty object so we can detect "waiting" state
339+ self . cachedHints = { } ;
340+ self . cachedHints . unfiltered = [ ] ;
341+
342+ NativeFileSystem . requestNativeFileSystem ( targetDir , function ( dirEntry ) {
343+ dirEntry . createReader ( ) . readEntries ( function ( entries ) {
344+
345+ entries . forEach ( function ( entry ) {
346+ if ( ProjectManager . shouldShow ( entry ) ) {
347+ // convert to doc relative path
348+ var entryStr = entry . fullPath . replace ( docDir , "" ) ;
349+
350+ // code hints show the same strings that are inserted into text,
351+ // so strings in list will be encoded. wysiwyg, baby!
352+ unfiltered . push ( encodeURI ( entryStr ) ) ;
353+ }
354+ } ) ;
355+
356+ self . cachedHints . unfiltered = unfiltered ;
357+ self . cachedHints . query = query ;
358+ self . cachedHints . queryDir = queryDir ;
359+
360+ // If the editor has not changed, then re-initiate code hints. Cached data
361+ // is still valid for folder even if we're not going to show it now.
362+ if ( origEditor === EditorManager . getFocusedEditor ( ) ) {
363+ CodeHintManager . showHint ( origEditor ) ;
364+ }
365+ } ) ;
366+ } ) ;
367+
368+ return result ;
369+ }
370+
371+ // build list
372+
373+ // without these entries, typing "../" will not display entries for containing folder
374+ if ( queryUrl . filename === "." ) {
375+ result . push ( queryDir + "." ) ;
376+ } else if ( queryUrl . filename === ".." ) {
377+ result . push ( queryDir + ".." ) ;
378+ }
379+
380+ // add file/folder entries
381+ unfiltered . forEach ( function ( item ) {
382+ result . push ( item ) ;
383+ } ) ;
384+
385+ // TODO: filter by desired file type based on tag, type attr, etc.
386+
387+ // TODO: add list item to top of list to popup modal File Finder dialog
388+ // New string: "Browse..." or "Choose a File..."
389+ // Command: Commands.FILE_OPEN
390+
391+ return result ;
392+ } ;
393+
249394 /**
250395 * Create a complete list of attributes for the tag in the query. Then filter
251396 * the list by attrName in the query and return the result.
@@ -260,7 +405,8 @@ define(function (require, exports, module) {
260405 var tagName = query . tag ,
261406 attrName = query . attrName ,
262407 filter = query . queryStr ,
263- unfiltered = [ ] ;
408+ unfiltered = [ ] ,
409+ sortFunc = null ;
264410
265411 if ( attrName ) {
266412 // We look up attribute values with tagName plus a slash and attrName first.
@@ -274,6 +420,9 @@ define(function (require, exports, module) {
274420 if ( attrInfo ) {
275421 if ( attrInfo . type === "boolean" ) {
276422 unfiltered = [ "false" , "true" ] ;
423+ } else if ( attrInfo . type === "url" ) {
424+ unfiltered = this . _getUrlList ( query ) ;
425+ sortFunc = StringUtils . urlSort ;
277426 } else if ( attrInfo . attribOption ) {
278427 unfiltered = attrInfo . attribOption ;
279428 }
@@ -285,11 +434,12 @@ define(function (require, exports, module) {
285434 }
286435
287436 if ( unfiltered . length ) {
437+ console . assert ( ! result . length ) ;
288438 result = $ . map ( unfiltered , function ( item ) {
289439 if ( item . indexOf ( filter ) === 0 ) {
290440 return item ;
291441 }
292- } ) . sort ( ) ;
442+ } ) . sort ( sortFunc ) ;
293443 }
294444 }
295445
0 commit comments