Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(77)

Unified Diff: pkg/analysis_server/lib/src/context_manager.dart

Issue 450103002: Support for pubspec based contexts in subfolders. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: tweak for tests Created 6 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « no previous file | pkg/analysis_server/test/context_manager_test.dart » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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 5e759893b94a5e10f4f7a97674103c5e426b811e..58f9ee38a27ee649b909dc5d2164ebfa28eb4365 100644
--- a/pkg/analysis_server/lib/src/context_manager.dart
+++ b/pkg/analysis_server/lib/src/context_manager.dart
@@ -11,24 +11,26 @@ import 'package:analysis_server/src/package_map_provider.dart';
import 'package:analyzer/file_system/file_system.dart';
import 'package:analyzer/src/generated/engine.dart';
import 'package:analyzer/src/generated/source.dart';
+import 'package:path/path.dart' as pathos;
import 'package:watcher/watcher.dart';
+
+/**
+ * File name of pubspec files.
+ */
+const String PUBSPEC_NAME = 'pubspec.yaml';
+
+
/**
* Class that maintains a mapping from included/excluded paths to a set of
* folders that should correspond to analysis contexts.
*/
abstract class ContextManager {
/**
- * File name of pubspec files.
- */
- static const String PUBSPEC_NAME = 'pubspec.yaml';
-
- /**
* [_ContextInfo] object for each included directory in the most
* recent successful call to [setRoots].
*/
- Map<Folder, _ContextInfo> _currentDirectoryInfo =
- new HashMap<Folder, _ContextInfo>();
+ Map<Folder, _ContextInfo> _contexts = new HashMap<Folder, _ContextInfo>();
/**
* The [ResourceProvider] using which paths are converted into [Resource]s.
@@ -36,12 +38,20 @@ abstract class ContextManager {
final ResourceProvider resourceProvider;
/**
+ * The [path.Context] for this manager.
Brian Wilkerson 2014/08/08 14:00:02 I assume "path" was suppose to be "pathos". As wo
scheglov 2014/08/08 15:54:33 Done.
+ */
+ pathos.Context pathContext;
+
+
+ /**
* Provider which is used to determine the mapping from package name to
* package folder.
*/
final PackageMapProvider packageMapProvider;
- ContextManager(this.resourceProvider, this.packageMapProvider);
+ ContextManager(this.resourceProvider, this.packageMapProvider) {
+ pathContext = resourceProvider.pathContext;
+ }
/**
* Called when a new context needs to be created.
@@ -61,7 +71,7 @@ abstract class ContextManager {
*/
bool isInAnalysisRoot(String path) {
// TODO(scheglov) check for excluded paths
- for (Folder root in _currentDirectoryInfo.keys) {
+ for (Folder root in _contexts.keys) {
if (root.contains(path)) {
return true;
}
@@ -100,16 +110,32 @@ abstract class ContextManager {
}
Set<Folder> excludedFolders = new HashSet<Folder>();
// diff
- Set<Folder> currentFolders = _currentDirectoryInfo.keys.toSet();
- Set<Folder> newFolders = includedFolders.difference(currentFolders);
- Set<Folder> oldFolders = currentFolders.difference(includedFolders);
+ Set<Folder> currentFolders = _contexts.keys.toSet();
Brian Wilkerson 2014/08/08 14:00:02 Are we making a copy to avoid modifying the keys w
scheglov 2014/08/08 15:54:33 We use it twice in the code below.
Paul Berry 2014/08/08 16:07:10 Agreed, but if we go with my suggestions below, th
scheglov 2014/08/08 16:34:23 Done.
+ Set<Folder> newFolders = new HashSet<Folder>();
+ Set<Folder> oldFolders = new HashSet<Folder>();
+ for (Folder currentFolder in currentFolders) {
+ bool isIncluded = includedFolders.any((folder) {
+ return folder.contains(currentFolder.path);
+ });
+ if (!isIncluded) {
+ oldFolders.add(currentFolder);
Paul Berry 2014/08/08 16:07:10 I think it would be clearer to just do: _destro
scheglov 2014/08/08 16:34:23 Done.
+ }
+ }
+ for (Folder includedFolder in includedFolders) {
+ bool wasIncluded = currentFolders.any((folder) {
+ return folder.contains(includedFolder.path);
+ });
+ if (!wasIncluded) {
+ newFolders.add(includedFolder);
Paul Berry 2014/08/08 16:07:10 Similarly, I think it would be clearer to just to:
scheglov 2014/08/08 16:34:23 Done.
+ }
+ }
// destroy old contexts
for (Folder folder in oldFolders) {
_destroyContext(folder);
}
// create new contexts
for (Folder folder in newFolders) {
- _createContext(folder);
+ _createContexts(folder, false);
}
}
@@ -120,44 +146,135 @@ abstract class ContextManager {
List<Folder>> packageMap);
/**
- * Create a new context associated with the given folder.
+ * Create a new empty context associated with [folder].
*/
- void _createContext(Folder folder) {
- _ContextInfo info = new _ContextInfo();
- _currentDirectoryInfo[folder] = info;
+ _ContextInfo _createContext(Folder folder, List<_ContextInfo> children) {
+ _ContextInfo info = new _ContextInfo(folder, children);
+ _contexts[folder] = info;
info.changeSubscription = folder.changes.listen((WatchEvent event) {
_handleWatchEvent(folder, info, event);
});
- File pubspecFile = folder.getChild(PUBSPEC_NAME);
PackageMapInfo packageMapInfo =
packageMapProvider.computePackageMap(folder);
info.packageMapDependencies = packageMapInfo.dependencies;
// TODO(paulberry): if any of the dependencies is outside of [folder],
// we'll need to watch their parent folders as well.
addContext(folder, packageMapInfo.packageMap);
+ return info;
+ }
+
+ /**
+ * Create a new context associated with [folder] and fills its with sources.
+ */
+ _ContextInfo _createContextWithSources(Folder folder,
+ List<_ContextInfo> children) {
+ _ContextInfo info = _createContext(folder, children);
ChangeSet changeSet = new ChangeSet();
_addSourceFiles(changeSet, folder, info);
applyChangesToContext(folder, changeSet);
+ return info;
+ }
+
+ /**
+ * Creates a new context associated with [folder].
+ *
+ * If there are subfolders with 'pubspec.yaml' files, separate contexts
+ * are created for them, and excluded from the context associated with
+ * [folder].
+ *
+ * If [folder] itself contains a 'pubspec.yaml' file, subfolders are ignored.
+ *
+ * Returns create pubspec-based contexts.
+ */
Paul Berry 2014/08/08 16:07:10 Can you document the meaning of "withPubspecOnly"
scheglov 2014/08/08 16:34:23 Done.
+ List<_ContextInfo> _createContexts(Folder folder, bool withPubspecOnly) {
+ // check if there is a pubspec in the folder
+ {
+ File pubspecFile = folder.getChild(PUBSPEC_NAME);
+ if (pubspecFile.exists) {
+ _ContextInfo info = _createContextWithSources(folder, <_ContextInfo>[]);
+ return [info];
+ }
+ }
+ // try to find subfolders with pubspec files
+ List<_ContextInfo> children = <_ContextInfo>[];
+ for (Resource child in folder.getChildren()) {
+ if (child is Folder) {
+ List<_ContextInfo> childContexts = _createContexts(child, true);
+ children.addAll(childContexts);
+ }
+ }
+ // no pubspec, done
+ if (withPubspecOnly) {
+ return children;
+ }
+ // OK, create a context without a pubspec
+ _createContextWithSources(folder, children);
+ return children;
}
/**
* Clean up and destroy the context associated with the given folder.
*/
void _destroyContext(Folder folder) {
- _currentDirectoryInfo[folder].changeSubscription.cancel();
- _currentDirectoryInfo.remove(folder);
+ _contexts[folder].changeSubscription.cancel();
+ _contexts.remove(folder);
removeContext(folder);
}
+ /**
+ * Extract a new [pubspecFile]-based context from [oldInfo].
+ */
+ void _extractContext(_ContextInfo oldInfo, File pubspecFile) {
+ Folder newFolder = pubspecFile.parent;
+ _ContextInfo newInfo = _createContext(newFolder, []);
+ newInfo.parent = oldInfo;
+ // prepare sources to extract
+ Map<String, Source> extractSources = new HashMap<String, Source>();
Paul Berry 2014/08/08 16:07:10 Rename to "extractedSources"
scheglov 2014/08/08 16:34:23 Done.
+ oldInfo.sources.forEach((path, source) {
+ if (newFolder.contains(path)) {
+ extractSources[path] = source;
+ }
+ });
+ // update new context
+ {
+ ChangeSet changeSet = new ChangeSet();
+ extractSources.forEach((path, source) {
+ newInfo.sources[path] = source;
+ changeSet.addedSource(source);
+ });
+ applyChangesToContext(newFolder, changeSet);
+ }
+ // update old context
+ {
+ ChangeSet changeSet = new ChangeSet();
+ extractSources.forEach((path, source) {
+ oldInfo.sources.remove(path);
+ changeSet.removedSource(source);
+ });
+ applyChangesToContext(oldInfo.folder, changeSet);
+ }
+ }
+
void _handleWatchEvent(Folder folder, _ContextInfo info, WatchEvent event) {
+ String path = event.path;
+ // maybe excluded, so other context will handle it
+ if (info.excludes(path)) {
+ return;
+ }
+ // handle the change
switch (event.type) {
case ChangeType.ADD:
- if (_isInPackagesDir(event.path, folder)) {
+ if (_isInPackagesDir(path, folder)) {
// TODO(paulberry): perhaps we should only skip packages dirs if
// there is a pubspec.yaml?
break;
}
- Resource resource = resourceProvider.getResource(event.path);
+ Resource resource = resourceProvider.getResource(path);
+ // pubspec was added, extract a new context
+ if (_isPubspec(path)) {
+ _extractContext(info, resource);
+ return;
+ }
// If the file went away and was replaced by a folder before we
// had a chance to process the event, resource might be a Folder. In
// that case don't add it.
@@ -168,21 +285,26 @@ abstract class ContextManager {
Source source = file.createSource();
changeSet.addedSource(source);
applyChangesToContext(folder, changeSet);
- info.sources[event.path] = source;
+ info.sources[path] = source;
}
}
break;
case ChangeType.REMOVE:
- Source source = info.sources[event.path];
+ // pubspec was removed, merge the context into its parent
+ if (info.isPubspec(path)) {
+ _mergeContext(info);
+ return;
+ }
+ Source source = info.sources[path];
if (source != null) {
ChangeSet changeSet = new ChangeSet();
changeSet.removedSource(source);
applyChangesToContext(folder, changeSet);
- info.sources.remove(event.path);
+ info.sources.remove(path);
}
break;
case ChangeType.MODIFY:
- Source source = info.sources[event.path];
+ Source source = info.sources[path];
if (source != null) {
ChangeSet changeSet = new ChangeSet();
changeSet.changedSource(source);
@@ -191,7 +313,7 @@ abstract class ContextManager {
break;
}
- if (info.packageMapDependencies.contains(event.path)) {
+ if (info.packageMapDependencies.contains(path)) {
// TODO(paulberry): when computePackageMap is changed into an
// asynchronous API call, we'll want to suspend analysis for this context
// while we're rerunning "pub list", since any analysis we complete while
@@ -208,9 +330,8 @@ abstract class ContextManager {
* directory.
*/
bool _isInPackagesDir(String path, Folder folder) {
- String relativePath =
- resourceProvider.pathContext.relative(path, from: folder.path);
- List<String> pathParts = resourceProvider.pathContext.split(relativePath);
+ String relativePath = pathContext.relative(path, from: folder.path);
+ List<String> pathParts = pathContext.split(relativePath);
for (int i = 0; i < pathParts.length - 1; i++) {
if (pathParts[i] == 'packages') {
return true;
@@ -220,10 +341,36 @@ abstract class ContextManager {
}
/**
+ * Returns `true` if the given absolute [path] is a pubspec file.
+ */
+ bool _isPubspec(String path) {
+ return pathContext.basename(path) == PUBSPEC_NAME;
+ }
+
+ /**
+ * Merges [info] context into its parent.
+ */
+ void _mergeContext(_ContextInfo info) {
+ // destroy the context
+ _destroyContext(info.folder);
+ // add files to the parent context
+ _ContextInfo parentInfo = info.parent;
+ if (parentInfo != null) {
+ parentInfo.children.remove(info);
+ ChangeSet changeSet = new ChangeSet();
+ _addSourceFiles(changeSet, info.folder, parentInfo);
Paul Berry 2014/08/08 16:07:10 It looks like this will re-read the directory cont
scheglov 2014/08/08 16:34:23 Done.
+ applyChangesToContext(parentInfo.folder, changeSet);
+ }
+ }
+
+ /**
* Resursively adds all Dart and HTML files to the [changeSet].
*/
static void _addSourceFiles(ChangeSet changeSet, Folder folder,
_ContextInfo info) {
+ if (info.excludesResource(folder)) {
+ return;
+ }
List<Resource> children = folder.getChildren();
for (Resource child in children) {
if (child is File) {
@@ -262,6 +409,26 @@ abstract class ContextManager {
*/
class _ContextInfo {
/**
+ * The [Folder] for which this information object is created.
+ */
+ final Folder folder;
+
+ /**
+ * The enclosed pubspec-based contexts.
+ */
+ final List<_ContextInfo> children;
+
+ /**
+ * The [_ContextInfo] that encloses this one.
+ */
+ _ContextInfo parent;
+
+ /**
+ * The `pubspec.yaml` file path for this context.
+ */
+ String pubspecPath;
+
+ /**
* Stream subscription we are using to watch the context's directory for
* changes.
*/
@@ -278,4 +445,34 @@ class _ContextInfo {
* If any of these files changes, the package map needs to be recomputed.
*/
Set<String> packageMapDependencies;
+
+ _ContextInfo(this.folder, this.children) {
+ pubspecPath = folder.getChild(PUBSPEC_NAME).path;
+ for (_ContextInfo child in children) {
+ child.parent = this;
+ }
+ }
+
+ /**
+ * Returns `true` if [path] is excluded, as it is in one of the children.
+ */
+ bool excludes(String path) {
+ return children.any((child) {
+ return child.folder.contains(path);
+ });
+ }
+
+ /**
+ * Returns `true` if [resource] is excldued, as it is in one of the children.
+ */
+ bool excludesResource(Resource resource) {
+ return excludes(resource.path);
+ }
+
+ /**
+ * Returns `true` if [path] is the pubspec file of this context.
+ */
+ bool isPubspec(String path) {
+ return path == pubspecPath;
+ }
}
« no previous file with comments | « no previous file | pkg/analysis_server/test/context_manager_test.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698