Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 library context.directory.manager; | 5 library context.directory.manager; |
| 6 | 6 |
| 7 import 'dart:async'; | 7 import 'dart:async'; |
| 8 import 'dart:collection'; | 8 import 'dart:collection'; |
| 9 | 9 |
| 10 import 'package:analysis_server/src/package_map_provider.dart'; | 10 import 'package:analysis_server/src/package_map_provider.dart'; |
| (...skipping 25 matching lines...) Expand all Loading... | |
| 36 * The [ResourceProvider] using which paths are converted into [Resource]s. | 36 * The [ResourceProvider] using which paths are converted into [Resource]s. |
| 37 */ | 37 */ |
| 38 final ResourceProvider resourceProvider; | 38 final ResourceProvider resourceProvider; |
| 39 | 39 |
| 40 /** | 40 /** |
| 41 * The context used to work with file system paths. | 41 * The context used to work with file system paths. |
| 42 */ | 42 */ |
| 43 pathos.Context pathContext; | 43 pathos.Context pathContext; |
| 44 | 44 |
| 45 /** | 45 /** |
| 46 * A list of excluded paths - folders and files. | |
| 47 */ | |
| 48 List<String> excludedPaths = <String>[]; | |
| 49 | |
| 50 /** | |
| 46 * Provider which is used to determine the mapping from package name to | 51 * Provider which is used to determine the mapping from package name to |
| 47 * package folder. | 52 * package folder. |
| 48 */ | 53 */ |
| 49 final PackageMapProvider packageMapProvider; | 54 final PackageMapProvider packageMapProvider; |
| 50 | 55 |
| 51 ContextManager(this.resourceProvider, this.packageMapProvider) { | 56 ContextManager(this.resourceProvider, this.packageMapProvider) { |
| 52 pathContext = resourceProvider.pathContext; | 57 pathContext = resourceProvider.pathContext; |
| 53 } | 58 } |
| 54 | 59 |
| 55 /** | 60 /** |
| 56 * Called when a new context needs to be created. | 61 * Called when a new context needs to be created. |
| 57 */ | 62 */ |
| 58 void addContext(Folder folder, Map<String, List<Folder>> packageMap); | 63 void addContext(Folder folder, Map<String, List<Folder>> packageMap); |
| 59 | 64 |
| 60 /** | 65 /** |
| 61 * Called when the set of files associated with a context have changed (or | 66 * Called when the set of files associated with a context have changed (or |
| 62 * some of those files have been modified). [changeSet] is the set of | 67 * some of those files have been modified). [changeSet] is the set of |
| 63 * changes that need to be applied to the context. | 68 * changes that need to be applied to the context. |
| 64 */ | 69 */ |
| 65 void applyChangesToContext(Folder contextFolder, ChangeSet changeSet); | 70 void applyChangesToContext(Folder contextFolder, ChangeSet changeSet); |
| 66 | 71 |
| 67 /** | 72 /** |
| 68 * Returns `true` if the given absolute [path] is in one of the current | 73 * Returns `true` if the given absolute [path] is in one of the current |
| 69 * root folders and is not excluded. | 74 * root folders and is not excluded. |
| 70 */ | 75 */ |
| 71 bool isInAnalysisRoot(String path) { | 76 bool isInAnalysisRoot(String path) { |
| 72 // TODO(scheglov) check for excluded paths | 77 // check if excluded |
| 78 if (_isExcluded(path)) { | |
| 79 return false; | |
| 80 } | |
| 81 // check if in of the roots | |
| 73 for (Folder root in _contexts.keys) { | 82 for (Folder root in _contexts.keys) { |
| 74 if (root.contains(path)) { | 83 if (root.contains(path)) { |
| 75 return true; | 84 return true; |
| 76 } | 85 } |
| 77 } | 86 } |
| 87 // no | |
| 78 return false; | 88 return false; |
| 79 } | 89 } |
| 80 | 90 |
| 81 /** | 91 /** |
| 82 * Remove the context associated with the given [folder]. | 92 * Remove the context associated with the given [folder]. |
| 83 */ | 93 */ |
| 84 void removeContext(Folder folder); | 94 void removeContext(Folder folder); |
| 85 | 95 |
| 86 /** | 96 /** |
| 87 * Change the set of paths which should be used as starting points to | 97 * Change the set of paths which should be used as starting points to |
| 88 * determine the context directories. | 98 * determine the context directories. |
| 89 */ | 99 */ |
| 90 void setRoots(List<String> includedPaths, List<String> excludedPaths) { | 100 void setRoots(List<String> includedPaths, List<String> excludedPaths) { |
| 91 List<Folder> contextFolders = _contexts.keys.toList(); | 101 List<Folder> contextFolders = _contexts.keys.toList(); |
| 92 // included | 102 // included |
| 93 Set<Folder> includedFolders = new HashSet<Folder>(); | 103 Set<Folder> includedFolders = new HashSet<Folder>(); |
| 94 for (int i = 0; i < includedPaths.length; i++) { | 104 for (int i = 0; i < includedPaths.length; i++) { |
| 95 String path = includedPaths[i]; | 105 String path = includedPaths[i]; |
| 96 Resource resource = resourceProvider.getResource(path); | 106 Resource resource = resourceProvider.getResource(path); |
| 97 if (resource is Folder) { | 107 if (resource is Folder) { |
| 98 includedFolders.add(resource); | 108 includedFolders.add(resource); |
| 99 } else { | 109 } else { |
| 100 // TODO(scheglov) implemented separate files analysis | 110 // TODO(scheglov) implemented separate files analysis |
| 101 throw new UnimplementedError( | 111 throw new UnimplementedError( |
| 102 '$path is not a folder. ' | 112 '$path is not a folder. ' |
| 103 'Only support for folder analysis is implemented currently.'); | 113 'Only support for folder analysis is implemented currently.'); |
| 104 } | 114 } |
| 105 } | 115 } |
| 106 // excluded | 116 // excluded |
| 107 // TODO(scheglov) remove when implemented | 117 List<String> oldExcludedPaths = this.excludedPaths; |
| 108 if (excludedPaths.isNotEmpty) { | 118 this.excludedPaths = excludedPaths; |
| 109 throw new UnimplementedError('Excluded paths are not supported yet'); | |
| 110 } | |
| 111 Set<Folder> excludedFolders = new HashSet<Folder>(); | |
| 112 // destroy old contexts | 119 // destroy old contexts |
| 113 for (Folder contextFolder in contextFolders) { | 120 for (Folder contextFolder in contextFolders) { |
| 114 bool isIncluded = includedFolders.any((folder) { | 121 bool isIncluded = includedFolders.any((folder) { |
| 115 return folder.contains(contextFolder.path); | 122 return folder.isOrContains(contextFolder.path); |
| 116 }); | 123 }); |
| 117 if (!isIncluded) { | 124 if (!isIncluded) { |
| 118 _destroyContext(contextFolder); | 125 _destroyContext(contextFolder); |
| 119 } | 126 } |
| 120 } | 127 } |
| 121 // create new contexts | 128 // create new contexts |
| 122 for (Folder includedFolder in includedFolders) { | 129 for (Folder includedFolder in includedFolders) { |
| 123 bool wasIncluded = contextFolders.any((folder) { | 130 bool wasIncluded = contextFolders.any((folder) { |
| 124 return folder.contains(includedFolder.path); | 131 return folder.isOrContains(includedFolder.path); |
| 125 }); | 132 }); |
| 126 if (!wasIncluded) { | 133 if (!wasIncluded) { |
| 127 _createContexts(includedFolder, false); | 134 _createContexts(includedFolder, false); |
| 128 } | 135 } |
| 129 } | 136 } |
| 137 // remove newly excluded sources | |
| 138 _contexts.forEach((folder, info) { | |
| 139 // prepare excluded sources | |
| 140 Map<String, Source> excludedSources = new HashMap<String, Source>(); | |
| 141 info.sources.forEach((String path, Source source) { | |
| 142 if (_isExcludedBy(excludedPaths, path) && | |
| 143 !_isExcludedBy(oldExcludedPaths, path)) { | |
| 144 excludedSources[path] = source; | |
| 145 } | |
| 146 }); | |
| 147 // apply exclusion | |
| 148 ChangeSet changeSet = new ChangeSet(); | |
| 149 excludedSources.forEach((String path, Source source) { | |
| 150 info.sources.remove(path); | |
| 151 changeSet.removedSource(source); | |
| 152 }); | |
| 153 applyChangesToContext(folder, changeSet); | |
| 154 }); | |
| 155 // add previously excluded sources | |
| 156 _contexts.forEach((folder, info) { | |
| 157 ChangeSet changeSet = new ChangeSet(); | |
|
Brian Wilkerson
2014/08/08 19:19:35
Can we merge this into the loop above so that each
| |
| 158 _addPreviouslyExcludedSources(info, changeSet, folder, oldExcludedPaths); | |
| 159 applyChangesToContext(folder, changeSet); | |
| 160 }); | |
| 130 } | 161 } |
| 131 | 162 |
| 132 /** | 163 /** |
| 133 * Called when the package map for a context has changed. | 164 * Called when the package map for a context has changed. |
| 134 */ | 165 */ |
| 135 void updateContextPackageMap(Folder contextFolder, Map<String, | 166 void updateContextPackageMap(Folder contextFolder, Map<String, |
| 136 List<Folder>> packageMap); | 167 List<Folder>> packageMap); |
| 137 | 168 |
| 138 /** | 169 /** |
| 170 * Resursively adds all Dart and HTML files to the [changeSet]. | |
| 171 */ | |
| 172 void _addPreviouslyExcludedSources(_ContextInfo info, ChangeSet changeSet, | |
| 173 Folder folder, List<String> oldExcludedPaths) { | |
| 174 if (info.excludesResource(folder)) { | |
| 175 return; | |
| 176 } | |
| 177 List<Resource> children = folder.getChildren(); | |
| 178 for (Resource child in children) { | |
| 179 String path = child.path; | |
| 180 // ignore if wasn't previously excluded | |
| 181 bool wasExcluded = | |
| 182 _isExcludedBy(oldExcludedPaths, path) && | |
| 183 !_isExcludedBy(excludedPaths, path); | |
| 184 if (!wasExcluded) { | |
| 185 continue; | |
| 186 } | |
| 187 // add files, recurse into folders | |
| 188 if (child is File) { | |
| 189 if (_shouldFileBeAnalyzed(child)) { | |
| 190 Source source = child.createSource(); | |
| 191 changeSet.addedSource(source); | |
| 192 info.sources[path] = source; | |
| 193 } | |
| 194 } else if (child is Folder) { | |
| 195 if (child.shortName == 'packages') { | |
|
Brian Wilkerson
2014/08/08 19:19:35
The string 'packages' should be a constant.
This
| |
| 196 // TODO(paulberry): perhaps we should only skip packages dirs if | |
| 197 // there is a pubspec.yaml? | |
| 198 continue; | |
| 199 } | |
| 200 _addPreviouslyExcludedSources(info, changeSet, child, oldExcludedPaths); | |
| 201 } | |
| 202 } | |
| 203 } | |
| 204 | |
| 205 /** | |
| 206 * Resursively adds all Dart and HTML files to the [changeSet]. | |
| 207 */ | |
| 208 void _addSourceFiles(ChangeSet changeSet, Folder folder, _ContextInfo info) { | |
| 209 if (info.excludesResource(folder)) { | |
| 210 return; | |
| 211 } | |
| 212 List<Resource> children = folder.getChildren(); | |
| 213 for (Resource child in children) { | |
| 214 String path = child.path; | |
| 215 // ignore excluded files or folders | |
| 216 if (_isExcluded(path)) { | |
| 217 continue; | |
| 218 } | |
| 219 // add files, recurse into folders | |
| 220 if (child is File) { | |
| 221 if (_shouldFileBeAnalyzed(child)) { | |
| 222 Source source = child.createSource(); | |
| 223 changeSet.addedSource(source); | |
| 224 info.sources[path] = source; | |
| 225 } | |
| 226 } else if (child is Folder) { | |
| 227 if (child.shortName == 'packages') { | |
| 228 // TODO(paulberry): perhaps we should only skip packages dirs if | |
| 229 // there is a pubspec.yaml? | |
| 230 continue; | |
| 231 } | |
| 232 _addSourceFiles(changeSet, child, info); | |
| 233 } | |
| 234 } | |
| 235 } | |
| 236 | |
| 237 /** | |
| 139 * Create a new empty context associated with [folder]. | 238 * Create a new empty context associated with [folder]. |
| 140 */ | 239 */ |
| 141 _ContextInfo _createContext(Folder folder, List<_ContextInfo> children) { | 240 _ContextInfo _createContext(Folder folder, List<_ContextInfo> children) { |
| 142 _ContextInfo info = new _ContextInfo(folder, children); | 241 _ContextInfo info = new _ContextInfo(folder, children); |
| 143 _contexts[folder] = info; | 242 _contexts[folder] = info; |
| 144 info.changeSubscription = folder.changes.listen((WatchEvent event) { | 243 info.changeSubscription = folder.changes.listen((WatchEvent event) { |
| 145 _handleWatchEvent(folder, info, event); | 244 _handleWatchEvent(folder, info, event); |
| 146 }); | 245 }); |
| 147 PackageMapInfo packageMapInfo = | 246 PackageMapInfo packageMapInfo = |
| 148 packageMapProvider.computePackageMap(folder); | 247 packageMapProvider.computePackageMap(folder); |
| (...skipping 94 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 243 extractedSources.forEach((path, source) { | 342 extractedSources.forEach((path, source) { |
| 244 oldInfo.sources.remove(path); | 343 oldInfo.sources.remove(path); |
| 245 changeSet.removedSource(source); | 344 changeSet.removedSource(source); |
| 246 }); | 345 }); |
| 247 applyChangesToContext(oldInfo.folder, changeSet); | 346 applyChangesToContext(oldInfo.folder, changeSet); |
| 248 } | 347 } |
| 249 } | 348 } |
| 250 | 349 |
| 251 void _handleWatchEvent(Folder folder, _ContextInfo info, WatchEvent event) { | 350 void _handleWatchEvent(Folder folder, _ContextInfo info, WatchEvent event) { |
| 252 String path = event.path; | 351 String path = event.path; |
| 253 // maybe excluded, so other context will handle it | 352 // maybe excluded globally |
| 353 if (_isExcluded(path)) { | |
| 354 return; | |
| 355 } | |
| 356 // maybe excluded from the context, so other context will handle it | |
| 254 if (info.excludes(path)) { | 357 if (info.excludes(path)) { |
| 255 return; | 358 return; |
| 256 } | 359 } |
| 257 // handle the change | 360 // handle the change |
| 258 switch (event.type) { | 361 switch (event.type) { |
| 259 case ChangeType.ADD: | 362 case ChangeType.ADD: |
| 260 if (_isInPackagesDir(path, folder)) { | 363 if (_isInPackagesDir(path, folder)) { |
| 261 // TODO(paulberry): perhaps we should only skip packages dirs if | 364 // TODO(paulberry): perhaps we should only skip packages dirs if |
| 262 // there is a pubspec.yaml? | 365 // there is a pubspec.yaml? |
| 263 break; | 366 break; |
| (...skipping 48 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 312 // while we're rerunning "pub list", since any analysis we complete while | 415 // while we're rerunning "pub list", since any analysis we complete while |
| 313 // "pub list" is in progress is just going to get thrown away anyhow. | 416 // "pub list" is in progress is just going to get thrown away anyhow. |
| 314 PackageMapInfo packageMapInfo = | 417 PackageMapInfo packageMapInfo = |
| 315 packageMapProvider.computePackageMap(folder); | 418 packageMapProvider.computePackageMap(folder); |
| 316 info.packageMapDependencies = packageMapInfo.dependencies; | 419 info.packageMapDependencies = packageMapInfo.dependencies; |
| 317 updateContextPackageMap(folder, packageMapInfo.packageMap); | 420 updateContextPackageMap(folder, packageMapInfo.packageMap); |
| 318 } | 421 } |
| 319 } | 422 } |
| 320 | 423 |
| 321 /** | 424 /** |
| 425 * Returns `true` if the given [path] is excluded by [excludedPaths]. | |
| 426 */ | |
| 427 bool _isExcluded(String path) { | |
| 428 return _isExcludedBy(excludedPaths, path); | |
| 429 } | |
| 430 | |
| 431 /** | |
| 432 * Returns `true` if the given [path] is excluded by [excludedPaths]. | |
| 433 */ | |
| 434 bool _isExcludedBy(List<String> excludedPaths, String path) { | |
| 435 return excludedPaths.any((excludedPath) { | |
| 436 if (pathContext.isWithin(excludedPath, path)) { | |
| 437 return true; | |
| 438 } | |
| 439 return path == excludedPath; | |
| 440 }); | |
| 441 } | |
| 442 | |
| 443 /** | |
| 322 * Determine if the path from [folder] to [path] contains a 'packages' | 444 * Determine if the path from [folder] to [path] contains a 'packages' |
| 323 * directory. | 445 * directory. |
| 324 */ | 446 */ |
| 325 bool _isInPackagesDir(String path, Folder folder) { | 447 bool _isInPackagesDir(String path, Folder folder) { |
| 326 String relativePath = pathContext.relative(path, from: folder.path); | 448 String relativePath = pathContext.relative(path, from: folder.path); |
| 327 List<String> pathParts = pathContext.split(relativePath); | 449 List<String> pathParts = pathContext.split(relativePath); |
| 328 for (int i = 0; i < pathParts.length - 1; i++) { | 450 for (int i = 0; i < pathParts.length - 1; i++) { |
| 329 if (pathParts[i] == 'packages') { | 451 if (pathParts[i] == 'packages') { |
| 330 return true; | 452 return true; |
| 331 } | 453 } |
| (...skipping 20 matching lines...) Expand all Loading... | |
| 352 parentInfo.children.remove(info); | 474 parentInfo.children.remove(info); |
| 353 ChangeSet changeSet = new ChangeSet(); | 475 ChangeSet changeSet = new ChangeSet(); |
| 354 info.sources.forEach((path, source) { | 476 info.sources.forEach((path, source) { |
| 355 parentInfo.sources[path] = source; | 477 parentInfo.sources[path] = source; |
| 356 changeSet.addedSource(source); | 478 changeSet.addedSource(source); |
| 357 }); | 479 }); |
| 358 applyChangesToContext(parentInfo.folder, changeSet); | 480 applyChangesToContext(parentInfo.folder, changeSet); |
| 359 } | 481 } |
| 360 } | 482 } |
| 361 | 483 |
| 362 /** | |
| 363 * Resursively adds all Dart and HTML files to the [changeSet]. | |
| 364 */ | |
| 365 static void _addSourceFiles(ChangeSet changeSet, Folder folder, | |
| 366 _ContextInfo info) { | |
| 367 if (info.excludesResource(folder)) { | |
| 368 return; | |
| 369 } | |
| 370 List<Resource> children = folder.getChildren(); | |
| 371 for (Resource child in children) { | |
| 372 if (child is File) { | |
| 373 if (_shouldFileBeAnalyzed(child)) { | |
| 374 Source source = child.createSource(); | |
| 375 changeSet.addedSource(source); | |
| 376 info.sources[child.path] = source; | |
| 377 } | |
| 378 } else if (child is Folder) { | |
| 379 if (child.shortName == 'packages') { | |
| 380 // TODO(paulberry): perhaps we should only skip packages dirs if | |
| 381 // there is a pubspec.yaml? | |
| 382 continue; | |
| 383 } | |
| 384 _addSourceFiles(changeSet, child, info); | |
| 385 } | |
| 386 } | |
| 387 } | |
| 388 | |
| 389 static bool _shouldFileBeAnalyzed(File file) { | 484 static bool _shouldFileBeAnalyzed(File file) { |
| 390 if (!(AnalysisEngine.isDartFileName(file.path) || | 485 if (!(AnalysisEngine.isDartFileName(file.path) || |
| 391 AnalysisEngine.isHtmlFileName(file.path))) { | 486 AnalysisEngine.isHtmlFileName(file.path))) { |
| 392 return false; | 487 return false; |
| 393 } | 488 } |
| 394 // Emacs creates dummy links to track the fact that a file is open for | 489 // Emacs creates dummy links to track the fact that a file is open for |
| 395 // editing and has unsaved changes (e.g. having unsaved changes to | 490 // editing and has unsaved changes (e.g. having unsaved changes to |
| 396 // 'foo.dart' causes a link '.#foo.dart' to be created, which points to the | 491 // 'foo.dart' causes a link '.#foo.dart' to be created, which points to the |
| 397 // non-existent file 'username@hostname.pid'. To avoid these dummy links | 492 // non-existent file 'username@hostname.pid'. To avoid these dummy links |
| 398 // causing the analyzer to thrash, just ignore links to non-existent files. | 493 // causing the analyzer to thrash, just ignore links to non-existent files. |
| (...skipping 71 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 470 return excludes(resource.path); | 565 return excludes(resource.path); |
| 471 } | 566 } |
| 472 | 567 |
| 473 /** | 568 /** |
| 474 * Returns `true` if [path] is the pubspec file of this context. | 569 * Returns `true` if [path] is the pubspec file of this context. |
| 475 */ | 570 */ |
| 476 bool isPubspec(String path) { | 571 bool isPubspec(String path) { |
| 477 return path == pubspecPath; | 572 return path == pubspecPath; |
| 478 } | 573 } |
| 479 } | 574 } |
| OLD | NEW |