| OLD | NEW |
| 1 // Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2016, 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 analysis_server.src.single_context_manager; | 5 library analysis_server.src.single_context_manager; |
| 6 | 6 |
| 7 import 'dart:async'; |
| 7 import 'dart:core' hide Resource; | 8 import 'dart:core' hide Resource; |
| 8 import 'dart:math' as math; | 9 import 'dart:math' as math; |
| 9 | 10 |
| 10 import 'package:analysis_server/src/context_manager.dart'; | 11 import 'package:analysis_server/src/context_manager.dart'; |
| 11 import 'package:analyzer/file_system/file_system.dart'; | 12 import 'package:analyzer/file_system/file_system.dart'; |
| 12 import 'package:analyzer/source/path_filter.dart'; | 13 import 'package:analyzer/source/path_filter.dart'; |
| 13 import 'package:analyzer/src/generated/engine.dart'; | 14 import 'package:analyzer/src/generated/engine.dart'; |
| 14 import 'package:analyzer/src/generated/sdk.dart'; | 15 import 'package:analyzer/src/generated/sdk.dart'; |
| 15 import 'package:analyzer/src/generated/source.dart'; | 16 import 'package:analyzer/src/generated/source.dart'; |
| 16 import 'package:analyzer/src/util/glob.dart'; | 17 import 'package:analyzer/src/util/glob.dart'; |
| 17 import 'package:path/path.dart' as path; | 18 import 'package:path/path.dart' as path; |
| 19 import 'package:watcher/watcher.dart'; |
| 18 | 20 |
| 19 /** | 21 /** |
| 20 * A function that will return a [UriResolver] that can be used to resolve | 22 * A function that will return a [UriResolver] that can be used to resolve |
| 21 * `package:` URIs in [SingleContextManager]. | 23 * `package:` URIs in [SingleContextManager]. |
| 22 */ | 24 */ |
| 23 typedef UriResolver PackageResolverProvider(); | 25 typedef UriResolver PackageResolverProvider(); |
| 24 | 26 |
| 25 /** | 27 /** |
| 26 * Implementation of [ContextManager] that supports only one [AnalysisContext]. | 28 * Implementation of [ContextManager] that supports only one [AnalysisContext]. |
| 27 * So, sources from all analysis roots are added to this single context. All | 29 * So, sources from all analysis roots are added to this single context. All |
| 28 * features that could otherwise cause creating additional contexts, such as | 30 * features that could otherwise cause creating additional contexts, such as |
| 29 * presence of `pubspec.yaml` or `.packages` files, or `.analysis_options` files | 31 * presence of `pubspec.yaml` or `.packages` files, or `.analysis_options` files |
| 30 * are ignored. | 32 * are ignored. |
| 31 */ | 33 */ |
| 32 class SingleContextManager implements ContextManager { | 34 class SingleContextManager implements ContextManager { |
| 33 /** | 35 /** |
| 34 * The [ResourceProvider] using which paths are converted into [Resource]s. | 36 * The [ResourceProvider] using which paths are converted into [Resource]s. |
| 35 */ | 37 */ |
| 36 final ResourceProvider resourceProvider; | 38 final ResourceProvider resourceProvider; |
| 37 | 39 |
| 38 /** | 40 /** |
| 41 * The context used to work with file system paths. |
| 42 */ |
| 43 path.Context pathContext; |
| 44 |
| 45 /** |
| 39 * The manager used to access the SDK that should be associated with a | 46 * The manager used to access the SDK that should be associated with a |
| 40 * particular context. | 47 * particular context. |
| 41 */ | 48 */ |
| 42 final DartSdkManager sdkManager; | 49 final DartSdkManager sdkManager; |
| 43 | 50 |
| 44 /** | 51 /** |
| 45 * A function that will return a [UriResolver] that can be used to resolve | 52 * A function that will return a [UriResolver] that can be used to resolve |
| 46 * `package:` URIs. | 53 * `package:` URIs. |
| 47 */ | 54 */ |
| 48 final PackageResolverProvider packageResolverProvider; | 55 final PackageResolverProvider packageResolverProvider; |
| (...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 82 * The context in which everything is being analyzed. | 89 * The context in which everything is being analyzed. |
| 83 */ | 90 */ |
| 84 AnalysisContext context; | 91 AnalysisContext context; |
| 85 | 92 |
| 86 /** | 93 /** |
| 87 * The folder associated with the context. | 94 * The folder associated with the context. |
| 88 */ | 95 */ |
| 89 Folder contextFolder; | 96 Folder contextFolder; |
| 90 | 97 |
| 91 /** | 98 /** |
| 99 * The current [contextFolder] watch subscription. |
| 100 */ |
| 101 StreamSubscription<WatchEvent> watchSubscription; |
| 102 |
| 103 /** |
| 92 * The [PathFilter] used to filter sources from being analyzed. | 104 * The [PathFilter] used to filter sources from being analyzed. |
| 93 */ | 105 */ |
| 94 PathFilter pathFilter; | 106 PathFilter pathFilter; |
| 95 | 107 |
| 96 /** | 108 /** |
| 97 * The [packageResolverProvider] must not be `null`. | 109 * The [packageResolverProvider] must not be `null`. |
| 98 */ | 110 */ |
| 99 SingleContextManager(this.resourceProvider, this.sdkManager, | 111 SingleContextManager(this.resourceProvider, this.sdkManager, |
| 100 this.packageResolverProvider, this.analyzedFilesGlobs); | 112 this.packageResolverProvider, this.analyzedFilesGlobs) { |
| 113 pathContext = resourceProvider.pathContext; |
| 114 } |
| 101 | 115 |
| 102 @override | 116 @override |
| 103 Iterable<AnalysisContext> get analysisContexts => | 117 Iterable<AnalysisContext> get analysisContexts => |
| 104 context == null ? <AnalysisContext>[] : <AnalysisContext>[context]; | 118 context == null ? <AnalysisContext>[] : <AnalysisContext>[context]; |
| 105 | 119 |
| 106 @override | 120 @override |
| 107 Map<Folder, AnalysisContext> get folderMap => {contextFolder: context}; | 121 Map<Folder, AnalysisContext> get folderMap => {contextFolder: context}; |
| 108 | 122 |
| 109 @override | 123 @override |
| 110 List<AnalysisContext> contextsInAnalysisRoot(Folder analysisRoot) { | 124 List<AnalysisContext> contextsInAnalysisRoot(Folder analysisRoot) { |
| (...skipping 24 matching lines...) Expand all Loading... |
| 135 | 149 |
| 136 @override | 150 @override |
| 137 void refresh(List<Resource> roots) { | 151 void refresh(List<Resource> roots) { |
| 138 if (context != null) { | 152 if (context != null) { |
| 139 // TODO(brianwilkerson) Not sure whether this is right. | 153 // TODO(brianwilkerson) Not sure whether this is right. |
| 140 callbacks.removeContext(contextFolder, null); | 154 callbacks.removeContext(contextFolder, null); |
| 141 context.dispose(); | 155 context.dispose(); |
| 142 context = null; | 156 context = null; |
| 143 contextFolder = null; | 157 contextFolder = null; |
| 144 pathFilter = null; | 158 pathFilter = null; |
| 159 if (watchSubscription != null) { |
| 160 watchSubscription.cancel(); |
| 161 watchSubscription = null; |
| 162 } |
| 145 setRoots(includedPaths, excludedPaths, packageRoots); | 163 setRoots(includedPaths, excludedPaths, packageRoots); |
| 146 } | 164 } |
| 147 } | 165 } |
| 148 | 166 |
| 149 @override | 167 @override |
| 150 void setRoots(List<String> includedPaths, List<String> excludedPaths, | 168 void setRoots(List<String> includedPaths, List<String> excludedPaths, |
| 151 Map<String, String> packageRoots) { | 169 Map<String, String> packageRoots) { |
| 152 includedPaths = _nonOverlappingPaths(includedPaths); | 170 includedPaths = _nonOverlappingPaths(includedPaths); |
| 153 excludedPaths = _nonOverlappingPaths(excludedPaths); | 171 excludedPaths = _nonOverlappingPaths(excludedPaths); |
| 154 this.packageRoots = packageRoots; | 172 this.packageRoots = packageRoots; |
| 155 _updateNormalizedPackageRoots(); | 173 _updateNormalizedPackageRoots(); |
| 156 // Update context path. | 174 // Update context path. |
| 157 { | 175 { |
| 158 String contextPath = _commonPrefix(includedPaths); | 176 String contextPath = _commonPrefix(includedPaths); |
| 159 Folder contextFolder = resourceProvider.getFolder(contextPath); | 177 Folder contextFolder = resourceProvider.getFolder(contextPath); |
| 160 if (contextFolder != this.contextFolder) { | 178 if (contextFolder != this.contextFolder) { |
| 161 if (context != null) { | 179 if (context != null) { |
| 162 callbacks.moveContext(this.contextFolder, contextFolder); | 180 callbacks.moveContext(this.contextFolder, contextFolder); |
| 163 } | 181 } |
| 164 this.contextFolder = contextFolder; | 182 this.contextFolder = contextFolder; |
| 165 // TODO(scheglov) watch for changes in `contextFolder` | 183 // Start new watcher and cancel the old one. |
| 184 StreamSubscription<WatchEvent> watchSubscription = |
| 185 this.contextFolder.changes.listen(_handleWatchEvent); |
| 186 this.watchSubscription?.cancel(); |
| 187 this.watchSubscription = watchSubscription; |
| 166 } | 188 } |
| 167 } | 189 } |
| 168 if (context == null) { | 190 if (context == null) { |
| 169 UriResolver packageResolver = packageResolverProvider(); | 191 UriResolver packageResolver = packageResolverProvider(); |
| 170 context = callbacks.addContext(contextFolder, new AnalysisOptionsImpl(), | 192 context = callbacks.addContext(contextFolder, new AnalysisOptionsImpl(), |
| 171 new CustomPackageResolverDisposition(packageResolver)); | 193 new CustomPackageResolverDisposition(packageResolver)); |
| 172 ChangeSet changeSet = | 194 ChangeSet changeSet = |
| 173 _buildChangeSet(added: _includedFiles(includedPaths, excludedPaths)); | 195 _buildChangeSet(added: _includedFiles(includedPaths, excludedPaths)); |
| 174 callbacks.applyChangesToContext(contextFolder, changeSet); | 196 callbacks.applyChangesToContext(contextFolder, changeSet); |
| 175 } else { | 197 } else { |
| (...skipping 26 matching lines...) Expand all Loading... |
| 202 if (_matchesAnyAnalyzedFilesGlob(path) && resource.exists) { | 224 if (_matchesAnyAnalyzedFilesGlob(path) && resource.exists) { |
| 203 addedFiles.add(resource); | 225 addedFiles.add(resource); |
| 204 } | 226 } |
| 205 } else if (resource is Folder) { | 227 } else if (resource is Folder) { |
| 206 for (Resource child in _getChildrenSafe(resource)) { | 228 for (Resource child in _getChildrenSafe(resource)) { |
| 207 _addFilesInResource(addedFiles, child, excludedPaths); | 229 _addFilesInResource(addedFiles, child, excludedPaths); |
| 208 } | 230 } |
| 209 } | 231 } |
| 210 } | 232 } |
| 211 | 233 |
| 234 ChangeSet _buildChangeSet({List<File> added, List<File> removed}) { |
| 235 ChangeSet changeSet = new ChangeSet(); |
| 236 if (added != null) { |
| 237 for (File file in added) { |
| 238 Source source = createSourceInContext(context, file); |
| 239 changeSet.addedSource(source); |
| 240 } |
| 241 } |
| 242 if (removed != null) { |
| 243 for (File file in removed) { |
| 244 Source source = createSourceInContext(context, file); |
| 245 changeSet.removedSource(source); |
| 246 } |
| 247 } |
| 248 return changeSet; |
| 249 } |
| 250 |
| 251 String _commonPrefix(List<String> paths) { |
| 252 if (paths.isEmpty) { |
| 253 return ''; |
| 254 } |
| 255 List<String> left = pathContext.split(paths[0]); |
| 256 int count = left.length; |
| 257 for (int i = 1; i < paths.length; i++) { |
| 258 List<String> right = pathContext.split(paths[i]); |
| 259 count = _commonComponents(left, count, right); |
| 260 } |
| 261 return pathContext.joinAll(left.sublist(0, count)); |
| 262 } |
| 263 |
| 212 List<Resource> _existingResources(List<String> pathList) { | 264 List<Resource> _existingResources(List<String> pathList) { |
| 213 List<Resource> resources = <Resource>[]; | 265 List<Resource> resources = <Resource>[]; |
| 214 for (String path in pathList) { | 266 for (String path in pathList) { |
| 215 Resource resource = resourceProvider.getResource(path); | 267 Resource resource = resourceProvider.getResource(path); |
| 216 if (resource is Folder) { | 268 if (resource is Folder) { |
| 217 resources.add(resource); | 269 resources.add(resource); |
| 218 } else if (!resource.exists) { | 270 } else if (!resource.exists) { |
| 219 // Non-existent resources are ignored. TODO(paulberry): we should set | 271 // Non-existent resources are ignored. TODO(paulberry): we should set |
| 220 // up a watcher to ensure that if the resource appears later, we will | 272 // up a watcher to ensure that if the resource appears later, we will |
| 221 // begin analyzing it. | 273 // begin analyzing it. |
| 222 } else if (resource is File) { | 274 } else if (resource is File) { |
| 223 resources.add(resource); | 275 resources.add(resource); |
| 224 } else { | 276 } else { |
| 225 throw new UnimplementedError('$path is not a folder. ' | 277 throw new UnimplementedError('$path is not a folder. ' |
| 226 'Only support for file and folder analysis is implemented.'); | 278 'Only support for file and folder analysis is implemented.'); |
| 227 } | 279 } |
| 228 } | 280 } |
| 229 return resources; | 281 return resources; |
| 230 } | 282 } |
| 231 | 283 |
| 284 void _handleWatchEvent(WatchEvent event) { |
| 285 String path = event.path; |
| 286 // Ignore if excluded. |
| 287 if (_isExcludedPath(path)) { |
| 288 return; |
| 289 } |
| 290 // Ignore if not in a root. |
| 291 if (!_isContainedIn(includedPaths, path)) { |
| 292 return; |
| 293 } |
| 294 // Handle the change. |
| 295 switch (event.type) { |
| 296 case ChangeType.ADD: |
| 297 Resource resource = resourceProvider.getResource(path); |
| 298 if (resource is File) { |
| 299 if (_matchesAnyAnalyzedFilesGlob(path)) { |
| 300 callbacks.applyChangesToContext( |
| 301 contextFolder, _buildChangeSet(added: <File>[resource])); |
| 302 } |
| 303 } |
| 304 break; |
| 305 case ChangeType.REMOVE: |
| 306 List<Source> sources = context.getSourcesWithFullName(path); |
| 307 if (!sources.isEmpty) { |
| 308 ChangeSet changeSet = new ChangeSet(); |
| 309 sources.forEach(changeSet.removedSource); |
| 310 callbacks.applyChangesToContext(contextFolder, changeSet); |
| 311 } |
| 312 break; |
| 313 case ChangeType.MODIFY: |
| 314 List<Source> sources = context.getSourcesWithFullName(path); |
| 315 if (!sources.isEmpty) { |
| 316 ChangeSet changeSet = new ChangeSet(); |
| 317 sources.forEach(changeSet.changedSource); |
| 318 callbacks.applyChangesToContext(contextFolder, changeSet); |
| 319 } |
| 320 break; |
| 321 } |
| 322 } |
| 323 |
| 232 List<File> _includedFiles( | 324 List<File> _includedFiles( |
| 233 List<String> includedPaths, List<String> excludedPaths) { | 325 List<String> includedPaths, List<String> excludedPaths) { |
| 234 List<Resource> includedResources = _existingResources(includedPaths); | 326 List<Resource> includedResources = _existingResources(includedPaths); |
| 235 List<File> includedFiles = <File>[]; | 327 List<File> includedFiles = <File>[]; |
| 236 for (Resource resource in includedResources) { | 328 for (Resource resource in includedResources) { |
| 237 _addFilesInResource(includedFiles, resource, excludedPaths); | 329 _addFilesInResource(includedFiles, resource, excludedPaths); |
| 238 } | 330 } |
| 239 return includedFiles; | 331 return includedFiles; |
| 240 } | 332 } |
| 241 | 333 |
| 334 bool _isContainedIn(List<String> pathList, String path) { |
| 335 for (String pathInList in pathList) { |
| 336 if (_isEqualOrWithin(pathInList, path)) { |
| 337 return true; |
| 338 } |
| 339 } |
| 340 return false; |
| 341 } |
| 342 |
| 343 bool _isEqualOrWithin(String parent, String child) { |
| 344 return child == parent || pathContext.isWithin(parent, child); |
| 345 } |
| 346 |
| 347 bool _isEqualOrWithinAny(List<String> parents, String child) { |
| 348 for (String parent in parents) { |
| 349 if (_isEqualOrWithin(parent, child)) { |
| 350 return true; |
| 351 } |
| 352 } |
| 353 return false; |
| 354 } |
| 355 |
| 356 /** |
| 357 * Return `true` if the given [path] should be excluded, using explicit |
| 358 * or implicit rules. |
| 359 */ |
| 360 bool _isExcludedPath(String path) { |
| 361 List<String> parts = resourceProvider.pathContext.split(path); |
| 362 // Implicit rules. |
| 363 for (String part in parts) { |
| 364 if (part.startsWith('.')) { |
| 365 return true; |
| 366 } |
| 367 } |
| 368 // Explicitly excluded paths. |
| 369 if (_isEqualOrWithinAny(excludedPaths, path)) { |
| 370 return true; |
| 371 } |
| 372 // OK |
| 373 return false; |
| 374 } |
| 375 |
| 242 /** | 376 /** |
| 243 * Return `true` if the given [resource] and children should be excluded | 377 * Return `true` if the given [resource] and children should be excluded |
| 244 * because of some implicit exclusion rules, e.g. `.name`. | 378 * because of some implicit exclusion rules, e.g. `.name`. |
| 245 */ | 379 */ |
| 246 bool _isImplicitlyExcludedResource(Resource resource) { | 380 bool _isImplicitlyExcludedResource(Resource resource) { |
| 247 String shortName = resource.shortName; | 381 String shortName = resource.shortName; |
| 248 if (shortName.startsWith('.')) { | 382 if (shortName.startsWith('.')) { |
| 249 return true; | 383 return true; |
| 250 } | 384 } |
| 251 return false; | 385 return false; |
| 252 } | 386 } |
| 253 | 387 |
| 254 /** | 388 /** |
| 255 * Return `true` if the given [path] matches one of the [analyzedFilesGlobs]. | 389 * Return `true` if the given [path] matches one of the [analyzedFilesGlobs]. |
| 256 */ | 390 */ |
| 257 bool _matchesAnyAnalyzedFilesGlob(String path) { | 391 bool _matchesAnyAnalyzedFilesGlob(String path) { |
| 258 for (Glob glob in analyzedFilesGlobs) { | 392 for (Glob glob in analyzedFilesGlobs) { |
| 259 if (glob.matches(path)) { | 393 if (glob.matches(path)) { |
| 260 return true; | 394 return true; |
| 261 } | 395 } |
| 262 } | 396 } |
| 263 return false; | 397 return false; |
| 264 } | 398 } |
| 265 | 399 |
| 266 /** | 400 /** |
| 401 * Return a list consisting of the elements from [pathList] that describe the |
| 402 * minimal set of directories that include everything in the original list of |
| 403 * paths and nothing more. In particular: |
| 404 * |
| 405 * * if a path is in the input list multiple times it will appear at most |
| 406 * once in the output list, and |
| 407 * * if a directory D and a subdirectory of it are both in the input list |
| 408 * then only the directory D will be in the output list. |
| 409 * |
| 410 * The original list is not modified. |
| 411 */ |
| 412 List<String> _nonOverlappingPaths(List<String> pathList) { |
| 413 List<String> sortedPaths = new List<String>.from(pathList); |
| 414 sortedPaths.sort((a, b) => a.length - b.length); |
| 415 int pathCount = sortedPaths.length; |
| 416 for (int i = pathCount - 1; i > 0; i--) { |
| 417 String path = sortedPaths[i]; |
| 418 for (int j = 0; j < i; j++) { |
| 419 if (_isEqualOrWithin(path, sortedPaths[j])) { |
| 420 sortedPaths.removeAt(i); |
| 421 break; |
| 422 } |
| 423 } |
| 424 } |
| 425 return sortedPaths; |
| 426 } |
| 427 |
| 428 /** |
| 267 * Normalize all package root sources by mapping them to folders on the | 429 * Normalize all package root sources by mapping them to folders on the |
| 268 * filesystem. Ignore any package root sources that aren't folders. | 430 * filesystem. Ignore any package root sources that aren't folders. |
| 269 */ | 431 */ |
| 270 void _updateNormalizedPackageRoots() { | 432 void _updateNormalizedPackageRoots() { |
| 271 normalizedPackageRoots = <String, String>{}; | 433 normalizedPackageRoots = <String, String>{}; |
| 272 packageRoots.forEach((String sourcePath, String targetPath) { | 434 packageRoots.forEach((String sourcePath, String targetPath) { |
| 273 Resource resource = resourceProvider.getResource(sourcePath); | 435 Resource resource = resourceProvider.getResource(sourcePath); |
| 274 if (resource is Folder) { | 436 if (resource is Folder) { |
| 275 normalizedPackageRoots[resource.path] = targetPath; | 437 normalizedPackageRoots[resource.path] = targetPath; |
| 276 } | 438 } |
| 277 }); | 439 }); |
| 278 } | 440 } |
| 279 | 441 |
| 280 static ChangeSet _buildChangeSet({List<File> added, List<File> removed}) { | 442 /** |
| 281 ChangeSet changeSet = new ChangeSet(); | 443 * Create and return a source representing the given [file] within the given |
| 282 if (added != null) { | 444 * [context]. |
| 283 for (File file in added) { | 445 */ |
| 284 changeSet.addedSource(file.createSource()); | 446 static Source createSourceInContext(AnalysisContext context, File file) { |
| 285 } | 447 // TODO(brianwilkerson) Optimize this, by allowing support for source |
| 448 // factories to restore URI's from a file path rather than a source. |
| 449 Source source = file.createSource(); |
| 450 if (context == null) { |
| 451 return source; |
| 286 } | 452 } |
| 287 if (removed != null) { | 453 Uri uri = context.sourceFactory.restoreUri(source); |
| 288 for (File file in removed) { | 454 return file.createSource(uri); |
| 289 changeSet.removedSource(file.createSource()); | |
| 290 } | |
| 291 } | |
| 292 return changeSet; | |
| 293 } | 455 } |
| 294 | 456 |
| 295 static int _commonComponents( | 457 static int _commonComponents( |
| 296 List<String> left, int count, List<String> right) { | 458 List<String> left, int count, List<String> right) { |
| 297 int max = math.min(count, right.length); | 459 int max = math.min(count, right.length); |
| 298 for (int i = 0; i < max; i++) { | 460 for (int i = 0; i < max; i++) { |
| 299 if (left[i] != right[i]) { | 461 if (left[i] != right[i]) { |
| 300 return i; | 462 return i; |
| 301 } | 463 } |
| 302 } | 464 } |
| 303 return max; | 465 return max; |
| 304 } | 466 } |
| 305 | 467 |
| 306 static String _commonPrefix(List<String> paths) { | |
| 307 if (paths.isEmpty) { | |
| 308 return ''; | |
| 309 } | |
| 310 List<String> left = path.split(paths[0]); | |
| 311 int count = left.length; | |
| 312 for (int i = 1; i < paths.length; i++) { | |
| 313 List<String> right = path.split(paths[i]); | |
| 314 count = _commonComponents(left, count, right); | |
| 315 } | |
| 316 return path.joinAll(left.sublist(0, count)); | |
| 317 } | |
| 318 | |
| 319 /** | 468 /** |
| 320 * Return a list of all the files in the [left] that are not in the [right]. | 469 * Return a list of all the files in the [left] that are not in the [right]. |
| 321 */ | 470 */ |
| 322 static List<File> _diff(List<File> left, List<File> right) { | 471 static List<File> _diff(List<File> left, List<File> right) { |
| 323 List<File> diff = new List.from(left); | 472 List<File> diff = new List.from(left); |
| 324 for (File file in right) { | 473 for (File file in right) { |
| 325 diff.remove(file); | 474 diff.remove(file); |
| 326 } | 475 } |
| 327 return diff; | 476 return diff; |
| 328 } | 477 } |
| 329 | 478 |
| 330 static List<Resource> _getChildrenSafe(Folder folder) { | 479 static List<Resource> _getChildrenSafe(Folder folder) { |
| 331 try { | 480 try { |
| 332 return folder.getChildren(); | 481 return folder.getChildren(); |
| 333 } on FileSystemException { | 482 } on FileSystemException { |
| 334 // The folder either doesn't exist or cannot be read. | 483 // The folder either doesn't exist or cannot be read. |
| 335 // Either way, there are no children. | 484 // Either way, there are no children. |
| 336 return const <Resource>[]; | 485 return const <Resource>[]; |
| 337 } | 486 } |
| 338 } | 487 } |
| 339 | |
| 340 static bool _isContainedIn(List<String> pathList, String path) { | |
| 341 for (String pathInList in pathList) { | |
| 342 if (_isEqualOrWithin(path, pathInList)) { | |
| 343 return true; | |
| 344 } | |
| 345 } | |
| 346 return false; | |
| 347 } | |
| 348 | |
| 349 static bool _isEqualOrWithin(String parent, String child) { | |
| 350 return child == parent || path.isWithin(parent, child); | |
| 351 } | |
| 352 | |
| 353 static bool _isEqualOrWithinAny(List<String> parents, String child) { | |
| 354 for (String parent in parents) { | |
| 355 if (_isEqualOrWithin(parent, child)) { | |
| 356 return true; | |
| 357 } | |
| 358 } | |
| 359 return false; | |
| 360 } | |
| 361 | |
| 362 /** | |
| 363 * Return a list consisting of the elements from [pathList] that describe the | |
| 364 * minimal set of directories that include everything in the original list of | |
| 365 * paths and nothing more. In particular: | |
| 366 * | |
| 367 * * if a path is in the input list multiple times it will appear at most | |
| 368 * once in the output list, and | |
| 369 * * if a directory D and a subdirectory of it are both in the input list | |
| 370 * then only the directory D will be in the output list. | |
| 371 * | |
| 372 * The original list is not modified. | |
| 373 */ | |
| 374 static List<String> _nonOverlappingPaths(List<String> pathList) { | |
| 375 List<String> sortedPaths = new List<String>.from(pathList); | |
| 376 sortedPaths.sort((a, b) => a.length - b.length); | |
| 377 int pathCount = sortedPaths.length; | |
| 378 for (int i = pathCount - 1; i > 0; i--) { | |
| 379 String path = sortedPaths[i]; | |
| 380 for (int j = 0; j < i; j++) { | |
| 381 if (_isEqualOrWithin(path, sortedPaths[j])) { | |
| 382 sortedPaths.removeAt(i); | |
| 383 break; | |
| 384 } | |
| 385 } | |
| 386 } | |
| 387 return sortedPaths; | |
| 388 } | |
| 389 } | 488 } |
| OLD | NEW |