Index: pkg/analysis_server/lib/src/context_manager.dart |
diff --git a/pkg/analysis_server/lib/src/context_manager.dart b/pkg/analysis_server/lib/src/context_manager.dart |
index ed2e3a01193b239245bdbcd6b2b364186ddd0b83..68f85ca6154c444d1f117ce31476df823ddda3d2 100644 |
--- a/pkg/analysis_server/lib/src/context_manager.dart |
+++ b/pkg/analysis_server/lib/src/context_manager.dart |
@@ -110,6 +110,16 @@ class ContextInfo { |
pathFilter = null; |
/** |
+ * Iterate through all [children] and their children, recursively. |
+ */ |
+ Iterable<ContextInfo> get descendants sync* { |
+ for (ContextInfo child in children) { |
+ yield child; |
+ yield* child.descendants; |
+ } |
+ } |
+ |
+ /** |
* Returns `true` if this is a "top level" context, meaning that the folder |
* associated with it is not contained within any other folders that have an |
* associated context. |
@@ -130,6 +140,19 @@ class ContextInfo { |
*/ |
bool excludesResource(Resource resource) => excludes(resource.path); |
+ /** |
+ * Return the first [ContextInfo] in [children] whose associated folder is or |
+ * contains [path]. If there is no such [ContextInfo], return `null`. |
+ */ |
+ ContextInfo findChildInfoFor(String path) { |
+ for (ContextInfo info in children) { |
+ if (info.folder.isOrContains(path)) { |
+ return info; |
+ } |
+ } |
+ return null; |
+ } |
+ |
/// Returns `true` if [path] should be ignored. |
bool ignored(String path) => pathFilter.ignored(path); |
@@ -181,8 +204,9 @@ abstract class ContextManager { |
List<String> get includedPaths; |
/** |
- * Return a list containing all of the contexts contained in the given |
- * [analysisRoot]. |
+ * Return a list of all of the contexts reachable from the given |
+ * [analysisRoot] (the context associated with [analysisRoot] and all of its |
+ * descendants). |
*/ |
List<AnalysisContext> contextsInAnalysisRoot(Folder analysisRoot); |
@@ -297,12 +321,6 @@ class ContextManagerImpl implements ContextManager { |
static const String PACKAGE_SPEC_NAME = '.packages'; |
/** |
- * [ContextInfo] object for each included directory in the most |
- * recent successful call to [setRoots]. |
- */ |
- Map<Folder, ContextInfo> _contexts = new HashMap<Folder, ContextInfo>(); |
- |
- /** |
* The [ResourceProvider] using which paths are converted into [Resource]s. |
*/ |
final ResourceProvider resourceProvider; |
@@ -374,18 +392,36 @@ class ContextManagerImpl implements ContextManager { |
@override |
List<AnalysisContext> contextsInAnalysisRoot(Folder analysisRoot) { |
List<AnalysisContext> contexts = <AnalysisContext>[]; |
- _contexts.forEach((Folder contextFolder, ContextInfo info) { |
- if (analysisRoot.isOrContains(contextFolder.path)) { |
- contexts.add(info.context); |
+ ContextInfo innermostContainingInfo = |
+ _getInnermostContextInfoFor(analysisRoot.path); |
+ void addContextAndDescendants(ContextInfo info) { |
+ contexts.add(info.context); |
+ info.children.forEach(addContextAndDescendants); |
+ } |
+ if (innermostContainingInfo != null) { |
+ if (analysisRoot == innermostContainingInfo.folder) { |
+ addContextAndDescendants(innermostContainingInfo); |
+ } else { |
+ for (ContextInfo info in innermostContainingInfo.children) { |
+ if (analysisRoot.isOrContains(info.folder.path)) { |
+ addContextAndDescendants(info); |
+ } |
+ } |
} |
- }); |
+ } |
return contexts; |
} |
/** |
* For testing: get the [ContextInfo] object for the given [folder], if any. |
*/ |
- ContextInfo getContextInfoFor(Folder folder) => _contexts[folder]; |
+ ContextInfo getContextInfoFor(Folder folder) { |
+ ContextInfo info = _getInnermostContextInfoFor(folder.path); |
+ if (folder == info.folder) { |
+ return info; |
+ } |
+ return null; |
+ } |
@override |
bool isInAnalysisRoot(String path) { |
@@ -393,9 +429,9 @@ class ContextManagerImpl implements ContextManager { |
if (_isExcluded(path)) { |
return false; |
} |
- // check if in of the roots |
- for (Folder root in _contexts.keys) { |
- if (root.contains(path)) { |
+ // check if in one of the roots |
+ for (ContextInfo info in _rootInfo.children) { |
+ if (info.folder.contains(path)) { |
return true; |
} |
} |
@@ -424,14 +460,15 @@ class ContextManagerImpl implements ContextManager { |
@override |
void refresh(List<Resource> roots) { |
// Destroy old contexts |
- List<Folder> contextFolders = _contexts.keys.toList(); |
+ List<ContextInfo> contextInfos = _rootInfo.descendants.toList(); |
if (roots == null) { |
- contextFolders.forEach(_destroyContext); |
+ contextInfos.forEach(_destroyContext); |
} else { |
roots.forEach((Resource resource) { |
- contextFolders.forEach((Folder contextFolder) { |
- if (resource is Folder && resource.isOrContains(contextFolder.path)) { |
- _destroyContext(contextFolder); |
+ contextInfos.forEach((ContextInfo contextInfo) { |
+ if (resource is Folder && |
+ resource.isOrContains(contextInfo.folder.path)) { |
+ _destroyContext(contextInfo); |
} |
}); |
}); |
@@ -464,7 +501,7 @@ class ContextManagerImpl implements ContextManager { |
} |
}); |
- List<Folder> contextFolders = _contexts.keys.toList(); |
+ List<ContextInfo> contextInfos = _rootInfo.descendants.toList(); |
// included |
Set<Folder> includedFolders = new HashSet<Folder>(); |
for (int i = 0; i < includedPaths.length; i++) { |
@@ -483,33 +520,33 @@ class ContextManagerImpl implements ContextManager { |
List<String> oldExcludedPaths = this.excludedPaths; |
this.excludedPaths = excludedPaths; |
// destroy old contexts |
- for (Folder contextFolder in contextFolders) { |
+ for (ContextInfo contextInfo in contextInfos) { |
bool isIncluded = includedFolders.any((folder) { |
- return folder.isOrContains(contextFolder.path); |
+ return folder.isOrContains(contextInfo.folder.path); |
}); |
if (!isIncluded) { |
- _destroyContext(contextFolder); |
+ _destroyContext(contextInfo); |
} |
} |
// Update package roots for existing contexts |
- _contexts.forEach((Folder folder, ContextInfo info) { |
- String newPackageRoot = normalizedPackageRoots[folder.path]; |
+ for (ContextInfo info in _rootInfo.descendants) { |
+ String newPackageRoot = normalizedPackageRoots[info.folder.path]; |
if (info.packageRoot != newPackageRoot) { |
info.packageRoot = newPackageRoot; |
_recomputePackageUriResolver(info); |
} |
- }); |
+ } |
// create new contexts |
for (Folder includedFolder in includedFolders) { |
- bool wasIncluded = contextFolders.any((folder) { |
- return folder.isOrContains(includedFolder.path); |
+ bool wasIncluded = contextInfos.any((info) { |
+ return info.folder.isOrContains(includedFolder.path); |
}); |
if (!wasIncluded) { |
_createContexts(_rootInfo, includedFolder, false); |
} |
} |
// remove newly excluded sources |
- _contexts.forEach((folder, info) { |
+ for (ContextInfo info in _rootInfo.descendants) { |
// prepare excluded sources |
Map<String, Source> excludedSources = new HashMap<String, Source>(); |
info.sources.forEach((String path, Source source) { |
@@ -524,14 +561,15 @@ class ContextManagerImpl implements ContextManager { |
info.sources.remove(path); |
changeSet.removedSource(source); |
}); |
- callbacks.applyChangesToContext(folder, changeSet); |
- }); |
+ callbacks.applyChangesToContext(info.folder, changeSet); |
+ } |
// add previously excluded sources |
- _contexts.forEach((folder, info) { |
+ for (ContextInfo info in _rootInfo.descendants) { |
ChangeSet changeSet = new ChangeSet(); |
- _addPreviouslyExcludedSources(info, changeSet, folder, oldExcludedPaths); |
- callbacks.applyChangesToContext(folder, changeSet); |
- }); |
+ _addPreviouslyExcludedSources( |
+ info, changeSet, info.folder, oldExcludedPaths); |
+ callbacks.applyChangesToContext(info.folder, changeSet); |
+ } |
} |
/** |
@@ -651,13 +689,13 @@ class ContextManagerImpl implements ContextManager { |
* orphaned by this context being removed (no other context includes this |
* file.) |
*/ |
- List<String> _computeFlushedFiles(Folder folder) { |
- AnalysisContext context = _contexts[folder].context; |
+ List<String> _computeFlushedFiles(ContextInfo info) { |
+ AnalysisContext context = info.context; |
HashSet<String> flushedFiles = new HashSet<String>(); |
for (Source source in context.sources) { |
flushedFiles.add(source.fullName); |
} |
- for (ContextInfo contextInfo in _contexts.values) { |
+ for (ContextInfo contextInfo in _rootInfo.descendants) { |
AnalysisContext contextN = contextInfo.context; |
if (context != contextN) { |
for (Source source in contextN.sources) { |
@@ -752,7 +790,6 @@ class ContextManagerImpl implements ContextManager { |
ContextInfo parent, Folder folder, File packagespecFile) { |
ContextInfo info = new ContextInfo( |
parent, folder, packagespecFile, normalizedPackageRoots[folder.path]); |
- _contexts[folder] = info; |
Map<String, YamlNode> options = analysisOptionsProvider.getOptions(folder); |
processOptionsForContext(info, options); |
info.changeSubscription = folder.changes.listen((WatchEvent event) { |
@@ -841,14 +878,12 @@ class ContextManagerImpl implements ContextManager { |
/** |
* Clean up and destroy the context associated with the given folder. |
*/ |
- void _destroyContext(Folder folder) { |
- ContextInfo info = _contexts[folder]; |
+ void _destroyContext(ContextInfo info) { |
info.changeSubscription.cancel(); |
_cancelDependencySubscriptions(info); |
- callbacks.removeContext(folder, _computeFlushedFiles(folder)); |
+ callbacks.removeContext(info.folder, _computeFlushedFiles(info)); |
bool wasRemoved = info.parent.children.remove(info); |
assert(wasRemoved); |
- _contexts.remove(folder); |
} |
/** |
@@ -887,6 +922,29 @@ class ContextManagerImpl implements ContextManager { |
// adopted by newInfo now. |
} |
+ /** |
+ * Return the [ContextInfo] for the "innermost" context whose associated |
+ * folder is or contains the given path. ("innermost" refers to the nesting |
+ * of contexts, so if there is a context for path /foo and a context for |
+ * path /foo/bar, then the innermost context containing /foo/bar/baz.dart is |
+ * the context for /foo/bar.) |
+ * |
+ * If no context contains the given path, `null` is returned. |
+ */ |
+ ContextInfo _getInnermostContextInfoFor(String path) { |
+ ContextInfo info = _rootInfo.findChildInfoFor(path); |
+ if (info == null) { |
+ return null; |
+ } |
+ while (true) { |
+ ContextInfo childInfo = info.findChildInfoFor(path); |
+ if (childInfo == null) { |
+ return info; |
+ } |
+ info = childInfo; |
+ } |
+ } |
+ |
void _handleWatchEvent(Folder folder, ContextInfo info, WatchEvent event) { |
// TODO(brianwilkerson) If a file is explicitly included in one context |
// but implicitly referenced in another context, we will only send a |
@@ -1070,7 +1128,7 @@ class ContextManagerImpl implements ContextManager { |
*/ |
void _mergeContext(ContextInfo info) { |
// destroy the context |
- _destroyContext(info.folder); |
+ _destroyContext(info); |
// add files to the parent context |
ContextInfo parentInfo = info.parent; |
if (parentInfo != null) { |