| 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 51c42d29c85938cbfd68b7131c3ee0b8235bf761..fb1c3b4f5976cff18bd9f68d078ed152c813840c 100644
 | 
| --- a/pkg/analysis_server/lib/src/context_manager.dart
 | 
| +++ b/pkg/analysis_server/lib/src/context_manager.dart
 | 
| @@ -6,6 +6,7 @@ library context.directory.manager;
 | 
|  
 | 
|  import 'dart:async';
 | 
|  import 'dart:collection';
 | 
| +import 'dart:convert';
 | 
|  import 'dart:core' hide Resource;
 | 
|  
 | 
|  import 'package:analysis_server/src/analysis_server.dart';
 | 
| @@ -20,6 +21,9 @@ import 'package:analyzer/src/generated/engine.dart';
 | 
|  import 'package:analyzer/src/generated/java_io.dart';
 | 
|  import 'package:analyzer/src/generated/source.dart';
 | 
|  import 'package:analyzer/src/generated/source_io.dart';
 | 
| +import 'package:package_config/packages.dart';
 | 
| +import 'package:package_config/packages_file.dart' as pkgfile show parse;
 | 
| +import 'package:package_config/src/packages_impl.dart' show MapPackages;
 | 
|  import 'package:path/path.dart' as pathos;
 | 
|  import 'package:watcher/watcher.dart';
 | 
|  import 'package:yaml/yaml.dart';
 | 
| @@ -29,6 +33,12 @@ import 'package:yaml/yaml.dart';
 | 
|   * folders that should correspond to analysis contexts.
 | 
|   */
 | 
|  abstract class AbstractContextManager implements ContextManager {
 | 
| +
 | 
| +  /**
 | 
| +   * Temporary flag to hide WIP .packages support (DEP 5).
 | 
| +   */
 | 
| +  static bool ENABLE_PACKAGESPEC_SUPPORT = false;
 | 
| +
 | 
|    /**
 | 
|     * The name of the `lib` directory.
 | 
|     */
 | 
| @@ -45,6 +55,11 @@ abstract class AbstractContextManager implements ContextManager {
 | 
|    static const String PUBSPEC_NAME = 'pubspec.yaml';
 | 
|  
 | 
|    /**
 | 
| +   * File name of package spec files.
 | 
| +   */
 | 
| +  static const String PACKAGE_SPEC_NAME = '.packages';
 | 
| +
 | 
| +  /**
 | 
|     * [_ContextInfo] object for each included directory in the most
 | 
|     * recent successful call to [setRoots].
 | 
|     */
 | 
| @@ -113,7 +128,8 @@ abstract class AbstractContextManager implements ContextManager {
 | 
|    /**
 | 
|     * Create and return a new analysis context.
 | 
|     */
 | 
| -  AnalysisContext addContext(Folder folder, UriResolver packageUriResolver);
 | 
| +  AnalysisContext addContext(
 | 
| +      Folder folder, UriResolver packageUriResolver, Packages packages);
 | 
|  
 | 
|    /**
 | 
|     * Called when the set of files associated with a context have changed (or
 | 
| @@ -170,14 +186,20 @@ abstract class AbstractContextManager implements ContextManager {
 | 
|      // Do nothing.
 | 
|    }
 | 
|  
 | 
| -  /// Sets the [ignorePatterns] for the context [folder].
 | 
| -  void setIgnorePatternsForContext(Folder folder, List<String> ignorePatterns) {
 | 
| -    _ContextInfo info = _contexts[folder];
 | 
| -    if (info == null) {
 | 
| -      return;
 | 
| +  @override
 | 
| +  bool isInAnalysisRoot(String path) {
 | 
| +    // check if excluded
 | 
| +    if (_isExcluded(path)) {
 | 
| +      return false;
 | 
|      }
 | 
| -    var pathFilter = info.pathFilter;
 | 
| -    pathFilter.setIgnorePatterns(ignorePatterns);
 | 
| +    // check if in of the roots
 | 
| +    for (Folder root in _contexts.keys) {
 | 
| +      if (root.contains(path)) {
 | 
| +        return true;
 | 
| +      }
 | 
| +    }
 | 
| +    // no
 | 
| +    return false;
 | 
|    }
 | 
|  
 | 
|    /// Process [options] for the context [folder].
 | 
| @@ -200,22 +222,6 @@ abstract class AbstractContextManager implements ContextManager {
 | 
|    }
 | 
|  
 | 
|    @override
 | 
| -  bool isInAnalysisRoot(String path) {
 | 
| -    // check if excluded
 | 
| -    if (_isExcluded(path)) {
 | 
| -      return false;
 | 
| -    }
 | 
| -    // check if in of the roots
 | 
| -    for (Folder root in _contexts.keys) {
 | 
| -      if (root.contains(path)) {
 | 
| -        return true;
 | 
| -      }
 | 
| -    }
 | 
| -    // no
 | 
| -    return false;
 | 
| -  }
 | 
| -
 | 
| -  @override
 | 
|    void refresh(List<Resource> roots) {
 | 
|      // Destroy old contexts
 | 
|      List<Folder> contextFolders = _contexts.keys.toList();
 | 
| @@ -240,6 +246,16 @@ abstract class AbstractContextManager implements ContextManager {
 | 
|     */
 | 
|    void removeContext(Folder folder);
 | 
|  
 | 
| +  /// Sets the [ignorePatterns] for the context [folder].
 | 
| +  void setIgnorePatternsForContext(Folder folder, List<String> ignorePatterns) {
 | 
| +    _ContextInfo info = _contexts[folder];
 | 
| +    if (info == null) {
 | 
| +      return;
 | 
| +    }
 | 
| +    var pathFilter = info.pathFilter;
 | 
| +    pathFilter.setIgnorePatterns(ignorePatterns);
 | 
| +  }
 | 
| +
 | 
|    @override
 | 
|    void setRoots(List<String> includedPaths, List<String> excludedPaths,
 | 
|        Map<String, String> packageRoots) {
 | 
| @@ -334,7 +350,7 @@ abstract class AbstractContextManager implements ContextManager {
 | 
|     * Called when the package map for a context has changed.
 | 
|     */
 | 
|    void updateContextPackageUriResolver(
 | 
| -      Folder contextFolder, UriResolver packageUriResolver);
 | 
| +      Folder contextFolder, UriResolver packageUriResolver, Packages packages);
 | 
|  
 | 
|    /**
 | 
|     * Resursively adds all Dart and HTML files to the [changeSet].
 | 
| @@ -431,6 +447,22 @@ abstract class AbstractContextManager implements ContextManager {
 | 
|      info.dependencySubscriptions.clear();
 | 
|    }
 | 
|  
 | 
| +  void _checkForPackagespecUpdate(
 | 
| +      String path, _ContextInfo info, Folder folder) {
 | 
| +    // Check to see if this is the .packages file for this context and if so,
 | 
| +    // update the context's source factory.
 | 
| +    if (pathContext.basename(path) == PACKAGE_SPEC_NAME &&
 | 
| +        info.isPathToPackageDescription(path)) {
 | 
| +      File packagespec = resourceProvider.getFile(path);
 | 
| +      if (packagespec.exists) {
 | 
| +        Packages packages = _readPackagespec(packagespec);
 | 
| +        if (packages != null) {
 | 
| +          updateContextPackageUriResolver(folder, null, packages);
 | 
| +        }
 | 
| +      }
 | 
| +    }
 | 
| +  }
 | 
| +
 | 
|    /**
 | 
|     * Compute the appropriate package URI resolver for [folder], and store
 | 
|     * dependency information in [info]. Return `null` if no package map can
 | 
| @@ -511,9 +543,9 @@ abstract class AbstractContextManager implements ContextManager {
 | 
|     * Create a new empty context associated with [folder].
 | 
|     */
 | 
|    _ContextInfo _createContext(
 | 
| -      Folder folder, File pubspecFile, List<_ContextInfo> children) {
 | 
| +      Folder folder, File packagespecFile, List<_ContextInfo> children) {
 | 
|      _ContextInfo info = new _ContextInfo(
 | 
| -        folder, pubspecFile, children, normalizedPackageRoots[folder.path]);
 | 
| +        folder, packagespecFile, children, normalizedPackageRoots[folder.path]);
 | 
|      _contexts[folder] = info;
 | 
|      var options = analysisOptionsProvider.getOptions(folder);
 | 
|      processOptionsForContext(folder, options);
 | 
| @@ -521,8 +553,22 @@ abstract class AbstractContextManager implements ContextManager {
 | 
|        _handleWatchEvent(folder, info, event);
 | 
|      });
 | 
|      try {
 | 
| -      UriResolver packageUriResolver = _computePackageUriResolver(folder, info);
 | 
| -      info.context = addContext(folder, packageUriResolver);
 | 
| +      Packages packages;
 | 
| +      UriResolver packageUriResolver;
 | 
| +
 | 
| +      if (ENABLE_PACKAGESPEC_SUPPORT) {
 | 
| +        // Try .packages first.
 | 
| +        if (pathos.basename(packagespecFile.path) == PACKAGE_SPEC_NAME) {
 | 
| +          packages = _readPackagespec(packagespecFile);
 | 
| +        }
 | 
| +      }
 | 
| +
 | 
| +      // Next resort to a package uri resolver.
 | 
| +      if (packages == null) {
 | 
| +        packageUriResolver = _computePackageUriResolver(folder, info);
 | 
| +      }
 | 
| +
 | 
| +      info.context = addContext(folder, packageUriResolver, packages);
 | 
|        info.context.name = folder.path;
 | 
|      } catch (_) {
 | 
|        info.changeSubscription.cancel();
 | 
| @@ -538,13 +584,13 @@ abstract class AbstractContextManager implements ContextManager {
 | 
|     * created for them and excluded from the context associated with the
 | 
|     * [folder].
 | 
|     *
 | 
| -   * If [withPubspecOnly] is `true`, a context will be created only if there
 | 
| -   * is a 'pubspec.yaml' file in the [folder].
 | 
| +   * If [withPackageSpecOnly] is `true`, a context will be created only if there
 | 
| +   * is a 'pubspec.yaml' or '.packages' file in the [folder].
 | 
|     *
 | 
| -   * Returns create pubspec-based contexts.
 | 
| +   * Returns created contexts.
 | 
|     */
 | 
| -  List<_ContextInfo> _createContexts(Folder folder, bool withPubspecOnly) {
 | 
| -    // try to find subfolders with pubspec files
 | 
| +  List<_ContextInfo> _createContexts(Folder folder, bool withPackageSpecOnly) {
 | 
| +    // Try to find subfolders with pubspecs or .packages files.
 | 
|      List<_ContextInfo> children = <_ContextInfo>[];
 | 
|      try {
 | 
|        for (Resource child in folder.getChildren()) {
 | 
| @@ -556,20 +602,31 @@ abstract class AbstractContextManager implements ContextManager {
 | 
|        // The directory either doesn't exist or cannot be read. Either way, there
 | 
|        // are no subfolders that need to be added.
 | 
|      }
 | 
| -    // check whether there is a pubspec in the folder
 | 
| -    File pubspecFile = folder.getChild(PUBSPEC_NAME);
 | 
| -    if (pubspecFile.exists) {
 | 
| +
 | 
| +    File packageSpec;
 | 
| +
 | 
| +    if (ENABLE_PACKAGESPEC_SUPPORT) {
 | 
| +      // Start by looking for .packages.
 | 
| +      packageSpec = folder.getChild(PACKAGE_SPEC_NAME);
 | 
| +    }
 | 
| +
 | 
| +    // Fall back to looking for a pubspec.
 | 
| +    if (packageSpec == null || !packageSpec.exists) {
 | 
| +      packageSpec = folder.getChild(PUBSPEC_NAME);
 | 
| +    }
 | 
| +
 | 
| +    if (packageSpec.exists) {
 | 
|        return <_ContextInfo>[
 | 
| -        _createContextWithSources(folder, pubspecFile, children)
 | 
| +        _createContextWithSources(folder, packageSpec, children)
 | 
|        ];
 | 
|      }
 | 
| -    // no pubspec, done
 | 
| -    if (withPubspecOnly) {
 | 
| +    // No packagespec? Done.
 | 
| +    if (withPackageSpecOnly) {
 | 
|        return children;
 | 
|      }
 | 
| -    // OK, create a context without a pubspec
 | 
| +    // OK, create a context without a packagespec.
 | 
|      return <_ContextInfo>[
 | 
| -      _createContextWithSources(folder, pubspecFile, children)
 | 
| +      _createContextWithSources(folder, packageSpec, children)
 | 
|      ];
 | 
|    }
 | 
|  
 | 
| @@ -599,11 +656,11 @@ abstract class AbstractContextManager implements ContextManager {
 | 
|    }
 | 
|  
 | 
|    /**
 | 
| -   * Extract a new [pubspecFile]-based context from [oldInfo].
 | 
| +   * Extract a new [packagespecFile]-based context from [oldInfo].
 | 
|     */
 | 
| -  void _extractContext(_ContextInfo oldInfo, File pubspecFile) {
 | 
| -    Folder newFolder = pubspecFile.parent;
 | 
| -    _ContextInfo newInfo = _createContext(newFolder, pubspecFile, []);
 | 
| +  void _extractContext(_ContextInfo oldInfo, File packagespecFile) {
 | 
| +    Folder newFolder = packagespecFile.parent;
 | 
| +    _ContextInfo newInfo = _createContext(newFolder, packagespecFile, []);
 | 
|      newInfo.parent = oldInfo;
 | 
|      // prepare sources to extract
 | 
|      Map<String, Source> extractedSources = new HashMap<String, Source>();
 | 
| @@ -657,12 +714,46 @@ abstract class AbstractContextManager implements ContextManager {
 | 
|          if (_isInPackagesDir(path, folder)) {
 | 
|            return;
 | 
|          }
 | 
| +
 | 
|          Resource resource = resourceProvider.getResource(path);
 | 
| -        // pubspec was added in a sub-folder, extract a new context
 | 
| -        if (_isPubspec(path) && info.isRoot && !info.isPubspec(path)) {
 | 
| -          _extractContext(info, resource);
 | 
| -          return;
 | 
| +
 | 
| +        if (ENABLE_PACKAGESPEC_SUPPORT) {
 | 
| +          String directoryPath = pathContext.dirname(path);
 | 
| +
 | 
| +          // Check to see if we need to create a new context.
 | 
| +          if (info.isRoot) {
 | 
| +
 | 
| +            // Only create a new context if this is not the same directory
 | 
| +            // described by our info object.
 | 
| +            if (info.folder.path != directoryPath) {
 | 
| +              if (_isPubspec(path)) {
 | 
| +                // Check for a sibling .packages file.
 | 
| +                if (!resourceProvider.getFile(
 | 
| +                    pathos.join(directoryPath, PACKAGE_SPEC_NAME)).exists) {
 | 
| +                  _extractContext(info, resource);
 | 
| +                  return;
 | 
| +                }
 | 
| +              }
 | 
| +              if (_isPackagespec(path)) {
 | 
| +                // Check for a sibling pubspec.yaml file.
 | 
| +                if (!resourceProvider
 | 
| +                    .getFile(pathos.join(directoryPath, PUBSPEC_NAME)).exists) {
 | 
| +                  _extractContext(info, resource);
 | 
| +                  return;
 | 
| +                }
 | 
| +              }
 | 
| +            }
 | 
| +          }
 | 
| +        } else {
 | 
| +          // pubspec was added in a sub-folder, extract a new context
 | 
| +          if (_isPubspec(path) &&
 | 
| +              info.isRoot &&
 | 
| +              !info.isPathToPackageDescription(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.
 | 
| @@ -678,11 +769,41 @@ abstract class AbstractContextManager implements ContextManager {
 | 
|          }
 | 
|          break;
 | 
|        case ChangeType.REMOVE:
 | 
| -        // pubspec was removed, merge the context into its parent
 | 
| -        if (info.isPubspec(path) && !info.isRoot) {
 | 
| -          _mergeContext(info);
 | 
| -          return;
 | 
| +
 | 
| +        // If package spec info is removed, check to see if we can merge contexts.
 | 
| +        // Note that it's important to verify that there is NEITHER a .packages nor a
 | 
| +        // lingering pubspec.yaml before merging.
 | 
| +        if (!info.isRoot) {
 | 
| +          if (ENABLE_PACKAGESPEC_SUPPORT) {
 | 
| +            String directoryPath = pathContext.dirname(path);
 | 
| +
 | 
| +            // Only merge if this is the same directory described by our info object.
 | 
| +            if (info.folder.path == directoryPath) {
 | 
| +              if (_isPubspec(path)) {
 | 
| +                // Check for a sibling .packages file.
 | 
| +                if (!resourceProvider.getFile(
 | 
| +                    pathos.join(directoryPath, PACKAGE_SPEC_NAME)).exists) {
 | 
| +                  _mergeContext(info);
 | 
| +                  return;
 | 
| +                }
 | 
| +              }
 | 
| +              if (_isPackagespec(path)) {
 | 
| +                // Check for a sibling pubspec.yaml file.
 | 
| +                if (!resourceProvider
 | 
| +                    .getFile(pathos.join(directoryPath, PUBSPEC_NAME)).exists) {
 | 
| +                  _mergeContext(info);
 | 
| +                  return;
 | 
| +                }
 | 
| +              }
 | 
| +            }
 | 
| +          } else {
 | 
| +            if (info.isPathToPackageDescription(path)) {
 | 
| +              _mergeContext(info);
 | 
| +              return;
 | 
| +            }
 | 
| +          }
 | 
|          }
 | 
| +
 | 
|          List<Source> sources = info.context.getSourcesWithFullName(path);
 | 
|          if (!sources.isEmpty) {
 | 
|            ChangeSet changeSet = new ChangeSet();
 | 
| @@ -705,6 +826,9 @@ abstract class AbstractContextManager implements ContextManager {
 | 
|          break;
 | 
|      }
 | 
|  
 | 
| +    //TODO(pquitslund): find the right place for this
 | 
| +    _checkForPackagespecUpdate(path, info, folder);
 | 
| +
 | 
|      if (info.packageMapInfo != null &&
 | 
|          info.packageMapInfo.isChangedDependency(path, resourceProvider)) {
 | 
|        _recomputePackageUriResolver(info);
 | 
| @@ -714,9 +838,7 @@ abstract class AbstractContextManager implements ContextManager {
 | 
|    /**
 | 
|     * Returns `true` if the given [path] is excluded by [excludedPaths].
 | 
|     */
 | 
| -  bool _isExcluded(String path) {
 | 
| -    return _isExcludedBy(excludedPaths, path);
 | 
| -  }
 | 
| +  bool _isExcluded(String path) => _isExcludedBy(excludedPaths, path);
 | 
|  
 | 
|    /**
 | 
|     * Returns `true` if the given [path] is excluded by [excludedPaths].
 | 
| @@ -740,12 +862,10 @@ abstract class AbstractContextManager implements ContextManager {
 | 
|      return pathParts.contains(PACKAGES_NAME);
 | 
|    }
 | 
|  
 | 
| -  /**
 | 
| -   * Returns `true` if the given absolute [path] is a pubspec file.
 | 
| -   */
 | 
| -  bool _isPubspec(String path) {
 | 
| -    return pathContext.basename(path) == PUBSPEC_NAME;
 | 
| -  }
 | 
| +  bool _isPackagespec(String path) =>
 | 
| +      pathContext.basename(path) == PACKAGE_SPEC_NAME;
 | 
| +
 | 
| +  bool _isPubspec(String path) => pathContext.basename(path) == PUBSPEC_NAME;
 | 
|  
 | 
|    /**
 | 
|     * Merges [info] context into its parent.
 | 
| @@ -766,6 +886,18 @@ abstract class AbstractContextManager implements ContextManager {
 | 
|      }
 | 
|    }
 | 
|  
 | 
| +  Packages _readPackagespec(File specFile) {
 | 
| +    try {
 | 
| +      String contents = specFile.readAsStringSync();
 | 
| +      Map<String, Uri> map =
 | 
| +          pkgfile.parse(UTF8.encode(contents), new Uri.file(specFile.path));
 | 
| +      return new MapPackages(map);
 | 
| +    } catch (_) {
 | 
| +      //TODO(pquitslund): consider creating an error for the spec file.
 | 
| +      return null;
 | 
| +    }
 | 
| +  }
 | 
| +
 | 
|    /**
 | 
|     * Recompute the package URI resolver for the context described by [info],
 | 
|     * and update the client appropriately.
 | 
| @@ -777,7 +909,7 @@ abstract class AbstractContextManager implements ContextManager {
 | 
|      // "pub list" is in progress is just going to get thrown away anyhow.
 | 
|      UriResolver packageUriResolver =
 | 
|          _computePackageUriResolver(info.folder, info);
 | 
| -    updateContextPackageUriResolver(info.folder, packageUriResolver);
 | 
| +    updateContextPackageUriResolver(info.folder, packageUriResolver, null);
 | 
|    }
 | 
|  
 | 
|    /**
 | 
| @@ -923,9 +1055,9 @@ class _ContextInfo {
 | 
|    _ContextInfo parent;
 | 
|  
 | 
|    /**
 | 
| -   * The `pubspec.yaml` file path for this context.
 | 
| +   * The package description file path for this context.
 | 
|     */
 | 
| -  String pubspecPath;
 | 
| +  String packageDescriptionPath;
 | 
|  
 | 
|    /**
 | 
|     * Stream subscription we are using to watch the context's directory for
 | 
| @@ -958,10 +1090,11 @@ class _ContextInfo {
 | 
|     */
 | 
|    OptimizingPubPackageMapInfo packageMapInfo;
 | 
|  
 | 
| -  _ContextInfo(Folder folder, File pubspecFile, this.children, this.packageRoot)
 | 
| +  _ContextInfo(
 | 
| +      Folder folder, File packagespecFile, this.children, this.packageRoot)
 | 
|        : folder = folder,
 | 
|          pathFilter = new PathFilter(folder.path, null) {
 | 
| -    pubspecPath = pubspecFile.path;
 | 
| +    packageDescriptionPath = packagespecFile.path;
 | 
|      for (_ContextInfo child in children) {
 | 
|        child.parent = this;
 | 
|      }
 | 
| @@ -981,20 +1114,18 @@ class _ContextInfo {
 | 
|      });
 | 
|    }
 | 
|  
 | 
| -  /// Returns `true` if  [path] should be ignored.
 | 
| -  bool ignored(String path) => pathFilter.ignored(path);
 | 
| -
 | 
|    /**
 | 
|     * Returns `true` if [resource] is excluded, as it is in one of the children.
 | 
|     */
 | 
| -  bool excludesResource(Resource resource) {
 | 
| -    return excludes(resource.path);
 | 
| -  }
 | 
| +  bool excludesResource(Resource resource) => excludes(resource.path);
 | 
| +
 | 
| +  /// Returns `true` if  [path] should be ignored.
 | 
| +  bool ignored(String path) => pathFilter.ignored(path);
 | 
|  
 | 
|    /**
 | 
| -   * Returns `true` if [path] is the pubspec file of this context.
 | 
| +   * Returns `true` if [path] is the package description file for this context 
 | 
| +   * (pubspec.yaml or .packages).
 | 
|     */
 | 
| -  bool isPubspec(String path) {
 | 
| -    return path == pubspecPath;
 | 
| -  }
 | 
| +  bool isPathToPackageDescription(String path) =>
 | 
| +      path == packageDescriptionPath;
 | 
|  }
 | 
| 
 |