@@ -66,7 +66,8 @@ define(function (require, exports, module) {
6666 FileUtils = require ( "file/FileUtils" ) ,
6767 NativeFileError = require ( "file/NativeFileError" ) ,
6868 Urls = require ( "i18n!nls/urls" ) ,
69- KeyEvent = require ( "utils/KeyEvent" ) ;
69+ KeyEvent = require ( "utils/KeyEvent" ) ,
70+ Async = require ( "utils/Async" ) ;
7071
7172 /**
7273 * @private
@@ -136,11 +137,14 @@ define(function (require, exports, module) {
136137 * @private
137138 * Used to initialize jstree state
138139 */
139- var _projectInitialLoad = {
140- previous : [ ] , /* array of arrays containing full paths to open at each depth of the tree */
141- id : 0 , /* incrementing id */
142- fullPathToIdMap : { } /* mapping of fullPath to tree node id attr */
143- } ;
140+ var _projectInitialLoad = null ;
141+
142+ /**
143+ * @private
144+ * While initially rendering the tree, stores a list of promises for folders waiting to be read.
145+ * Is null when tree is not doing its initial rendering.
146+ */
147+ var _renderPromises = null ;
144148
145149 var suppressToggleOpen = false ;
146150
@@ -377,7 +381,9 @@ define(function (require, exports, module) {
377381
378382 // suppress selectionChanged event from firing by jstree select_node
379383 _suppressSelectionChange = true ;
380- _projectTree . jstree ( "deselect_node" , current ) ;
384+ if ( current ) {
385+ _projectTree . jstree ( "deselect_node" , current ) ;
386+ }
381387 _projectTree . jstree ( "select_node" , target , false ) ;
382388 _suppressSelectionChange = false ;
383389
@@ -395,6 +401,43 @@ define(function (require, exports, module) {
395401 function _isInRename ( element ) {
396402 return ( $ ( element ) . closest ( "li" ) . find ( "input" ) . length > 0 ) ;
397403 }
404+
405+ /**
406+ * Resolves the given deferred when all renderPromises have been resolved. This is necessary in order
407+ * to determine when iniital rendering is really finished; we don't actually get a callback from jstree
408+ * once it's handled all of its "reopen" callbacks, so we instead track all of our own async file requests
409+ * (which should be the only asynchronicity involved in rendering the tree). Since new requests might get
410+ * added in the course of recursing the tree, we can't just do a simple $.when.apply() here.
411+ *
412+ * @param {$.Deferred } the deferred to resolve when rendering is finished
413+ */
414+ function _whenRenderedResolve ( deferred ) {
415+ if ( _renderPromises ) {
416+ _renderPromises . forEach ( function ( promise ) {
417+ // Only listen to each promise once.
418+ if ( ! promise . _project_listening ) {
419+ promise . _project_listening = true ;
420+ promise . always ( function ( ) {
421+ // One of the promises is completed (either resolved or rejected;
422+ // in either case we want to move on).
423+ // Remove it from the list, then see if we have any more left.
424+ var index = _renderPromises . indexOf ( promise ) ;
425+ if ( index !== - 1 ) {
426+ _renderPromises . splice ( index , 1 ) ;
427+ }
428+ if ( _renderPromises . length === 0 ) {
429+ // All done.
430+ deferred . resolve ( ) ;
431+ _renderPromises = null ;
432+ } else {
433+ // Add listeners to any new promises that have shown up.
434+ _whenRenderedResolve ( deferred ) ;
435+ }
436+ } ) ;
437+ }
438+ } ) ;
439+ }
440+ }
398441
399442 /**
400443 * @private
@@ -406,6 +449,8 @@ define(function (require, exports, module) {
406449 function _renderTree ( treeDataProvider ) {
407450 var result = new $ . Deferred ( ) ;
408451
452+ _renderPromises = [ ] ;
453+
409454 // For #1542, make sure the tree is scrolled to the top before refreshing.
410455 // If we try to do this later (e.g. after the tree has been refreshed), it
411456 // doesn't seem to work properly.
@@ -504,8 +549,8 @@ define(function (require, exports, module) {
504549 _projectTree . jstree ( "reload_nodes" , false ) ;
505550 }
506551 if ( _projectInitialLoad . previous . length === 0 ) {
507- // resolve after all paths are opened
508- result . resolve ( ) ;
552+ // resolve after all paths are opened and fully rendered
553+ _whenRenderedResolve ( result ) ;
509554 }
510555 }
511556 ) . bind (
@@ -583,7 +628,7 @@ define(function (require, exports, module) {
583628 $projectTreeContainer . show ( ) ;
584629 } ) ;
585630
586- return result . promise ( ) ;
631+ return Async . withTimeout ( result . promise ( ) , 1000 ) ;
587632 }
588633
589634 /**
@@ -658,7 +703,7 @@ define(function (require, exports, module) {
658703 * @param {function(Array) } jsTreeCallback jsTree callback to provide children to
659704 */
660705 function _treeDataProvider ( treeNode , jsTreeCallback ) {
661- var dirEntry , isProjectRoot = false ;
706+ var dirEntry , isProjectRoot = false , deferred = new $ . Deferred ( ) ;
662707
663708 function processEntries ( entries ) {
664709 var subtreeJSON = _convertEntriesToJSON ( entries ) ,
@@ -691,6 +736,8 @@ define(function (require, exports, module) {
691736 treeNode . trigger ( "open_node.jstree" ) ;
692737 }
693738 }
739+
740+ deferred . resolve ( ) ;
694741 }
695742
696743 if ( treeNode === - 1 ) {
@@ -701,6 +748,10 @@ define(function (require, exports, module) {
701748 // All other nodes: the DirectoryEntry is saved as jQ data in the tree (by _convertEntriesToJSON())
702749 dirEntry = treeNode . data ( "entry" ) ;
703750 }
751+
752+ if ( _renderPromises ) {
753+ _renderPromises . push ( deferred . promise ( ) ) ;
754+ }
704755
705756 // Fetch dirEntry's contents
706757 dirEntry . createReader ( ) . readEntries (
@@ -720,6 +771,8 @@ define(function (require, exports, module) {
720771 error . name
721772 )
722773 ) ;
774+ // Reject the render promise so we can move on.
775+ deferred . reject ( ) ;
723776 }
724777 }
725778 ) ;
@@ -780,25 +833,34 @@ define(function (require, exports, module) {
780833 *
781834 * @param {string } rootPath Absolute path to the root folder of the project.
782835 * If rootPath is undefined or null, the last open project will be restored.
836+ * @param {bool } isUpdating Indicates if it's just an update attempt to the
837+ * tree or if another project is being loaded.
783838 * @return {$.Promise } A promise object that will be resolved when the
784839 * project is loaded and tree is rendered, or rejected if the project path
785840 * fails to load.
786841 */
787- function _loadProject ( rootPath ) {
788- if ( _projectRoot && _projectRoot . fullPath === rootPath + "/" ) {
789- return ( new $ . Deferred ( ) ) . resolve ( ) . promise ( ) ;
790- }
791- if ( _projectRoot ) {
792- // close current project
793- $ ( exports ) . triggerHandler ( "beforeProjectClose" , _projectRoot ) ;
794- }
795-
796- // close all the old files
797- DocumentManager . closeAll ( ) ;
798-
799- // reset tree node id's
800- _projectInitialLoad . id = 0 ;
801842
843+ function _loadProject ( rootPath , isUpdating ) {
844+ if ( ! isUpdating ) {
845+ if ( _projectRoot && _projectRoot . fullPath === rootPath + "/" ) {
846+ return ( new $ . Deferred ( ) ) . resolve ( ) . promise ( ) ;
847+ }
848+ if ( _projectRoot ) {
849+ // close current project
850+ $ ( exports ) . triggerHandler ( "beforeProjectClose" , _projectRoot ) ;
851+ }
852+
853+ // close all the old files
854+ DocumentManager . closeAll ( ) ;
855+ }
856+
857+ // Clear project path map
858+ _projectInitialLoad = {
859+ previous : [ ] , /* array of arrays containing full paths to open at each depth of the tree */
860+ id : 0 , /* incrementing id */
861+ fullPathToIdMap : { } /* mapping of fullPath to tree node id attr */
862+ } ;
863+
802864 var result = new $ . Deferred ( ) ,
803865 resultRenderTree ;
804866
@@ -838,7 +900,7 @@ define(function (require, exports, module) {
838900 // immediate children, and so on.
839901 resultRenderTree = _renderTree ( _treeDataProvider ) ;
840902
841- resultRenderTree . done ( function ( ) {
903+ resultRenderTree . always ( function ( ) {
842904 if ( projectRootChanged ) {
843905 // Allow asynchronous event handlers to finish before resolving result by collecting promises from them
844906 var promises = [ ] ;
@@ -884,7 +946,6 @@ define(function (require, exports, module) {
884946 return result . promise ( ) ;
885947 }
886948
887-
888949 /**
889950 * Finds the tree node corresponding to the given file/folder (rejected if the path lies
890951 * outside the project, or if it doesn't exist).
@@ -947,6 +1008,29 @@ define(function (require, exports, module) {
9471008 return result . promise ( ) ;
9481009 }
9491010
1011+ /**
1012+ * Reloads the project's file tree
1013+ * @return {$.Promise } A promise object that will be resolved when the
1014+ * project tree is reloaded, or rejected if the project path
1015+ * fails to reload.
1016+ */
1017+ function refreshFileTree ( ) {
1018+ var selectedEntry ;
1019+ if ( _lastSelected ) {
1020+ selectedEntry = _lastSelected . data ( "entry" ) ;
1021+ }
1022+ _lastSelected = null ;
1023+
1024+ return _loadProject ( getProjectRoot ( ) . fullPath , true )
1025+ . done ( function ( ) {
1026+ if ( selectedEntry ) {
1027+ _findTreeNode ( selectedEntry ) . done ( function ( node ) {
1028+ _forceSelection ( null , node ) ;
1029+ } ) ;
1030+ }
1031+ } ) ;
1032+ }
1033+
9501034 /**
9511035 * Expands tree nodes to show the given file or folder and selects it. Silently no-ops if the
9521036 * path lies outside the project, or if it doesn't exist.
@@ -985,7 +1069,7 @@ define(function (require, exports, module) {
9851069 . done ( function ( ) {
9861070 if ( path ) {
9871071 // use specified path
988- _loadProject ( path ) . pipe ( result . resolve , result . reject ) ;
1072+ _loadProject ( path , false ) . pipe ( result . resolve , result . reject ) ;
9891073 } else {
9901074 // Pop up a folder browse dialog
9911075 NativeFileSystem . showOpenDialog ( false , true , Strings . CHOOSE_FOLDER , _projectRoot . fullPath , null ,
@@ -1489,6 +1573,7 @@ define(function (require, exports, module) {
14891573 // Commands
14901574 CommandManager . register ( Strings . CMD_OPEN_FOLDER , Commands . FILE_OPEN_FOLDER , openProject ) ;
14911575 CommandManager . register ( Strings . CMD_PROJECT_SETTINGS , Commands . FILE_PROJECT_SETTINGS , _projectSettings ) ;
1576+ CommandManager . register ( Strings . CMD_FILE_REFRESH , Commands . FILE_REFRESH , refreshFileTree ) ;
14921577
14931578 // Define public API
14941579 exports . getProjectRoot = getProjectRoot ;
@@ -1507,4 +1592,5 @@ define(function (require, exports, module) {
15071592 exports . deleteItem = deleteItem ;
15081593 exports . forceFinishRename = forceFinishRename ;
15091594 exports . showInTree = showInTree ;
1595+ exports . refreshFileTree = refreshFileTree ;
15101596} ) ;
0 commit comments