|
16 | 16 | import java.util.Collections; |
17 | 17 | import java.util.EnumMap; |
18 | 18 | import java.util.HashMap; |
| 19 | +import java.util.LinkedHashSet; |
19 | 20 | import java.util.LinkedList; |
20 | 21 | import java.util.List; |
21 | 22 | import java.util.Map; |
@@ -371,7 +372,7 @@ private static List<PackageNode> getPackageRootChildren(PackageParams query, IPr |
371 | 372 | throw new CoreException( |
372 | 373 | new Status(IStatus.ERROR, JdtlsExtActivator.PLUGIN_ID, String.format("No package root found for %s", query.getPath()))); |
373 | 374 | } |
374 | | - List<Object> result = getPackageFragmentRootContent(packageRoot, query.isHierarchicalView(), pm); |
| 375 | + List<Object> result = getPackageFragmentRootContent(packageRoot, query.isHierarchicalView(), query.getSyncPaths(), pm); |
375 | 376 | ResourceSet resourceSet = new ResourceSet(result, query.isHierarchicalView()); |
376 | 377 | ResourceVisitor visitor = new JavaResourceVisitor(packageRoot.getJavaProject()); |
377 | 378 | resourceSet.accept(visitor); |
@@ -508,8 +509,62 @@ private static List<PackageNode> getFolderChildren(PackageParams query, IProgres |
508 | 509 | * @param pm the progress monitor |
509 | 510 | */ |
510 | 511 | public static List<Object> getPackageFragmentRootContent(IPackageFragmentRoot root, boolean isHierarchicalView, IProgressMonitor pm) throws CoreException { |
| 512 | + return getPackageFragmentRootContent(root, isHierarchicalView, null, pm); |
| 513 | + } |
| 514 | + |
| 515 | + public static List<Object> getPackageFragmentRootContent(IPackageFragmentRoot root, boolean isHierarchicalView, List<String> syncPaths, IProgressMonitor pm) throws CoreException { |
511 | 516 | ArrayList<Object> result = new ArrayList<>(); |
512 | | - refreshLocal(root.getResource(), pm); |
| 517 | + IResource rootResource = root.getResource(); |
| 518 | + if (rootResource instanceof IContainer && rootResource.exists() |
| 519 | + && root.getKind() == IPackageFragmentRoot.K_SOURCE) { |
| 520 | + // Packages created out-of-band (e.g. by code generators or |
| 521 | + // refactor-moves that write straight to disk) are otherwise never |
| 522 | + // surfaced. A shallow DEPTH_ONE refresh only syncs the source root's |
| 523 | + // immediate children and never discovers brand-new nested package |
| 524 | + // folders. Even a DEPTH_INFINITE resource refresh is not enough on |
| 525 | + // its own: the Java Model keeps a cached list of package fragments |
| 526 | + // for the root, so closing it forces getChildren() below to rebuild |
| 527 | + // that list from the freshly refreshed resource tree. |
| 528 | + // |
| 529 | + // On auto-refresh the client passes the changed resource URIs in |
| 530 | + // syncPaths so we only deep-refresh those subtrees instead of the |
| 531 | + // whole source tree. If any path cannot be resolved to an existing |
| 532 | + // resource inside this root we conservatively fall back to a full |
| 533 | + // DEPTH_INFINITE refresh so no package is ever missed. |
| 534 | + // See https://github.com/microsoft/vscode-java-dependency/issues/914 |
| 535 | + boolean refreshedTargets = false; |
| 536 | + if (syncPaths != null && !syncPaths.isEmpty()) { |
| 537 | + Set<IResource> targets = new LinkedHashSet<>(); |
| 538 | + boolean allResolved = true; |
| 539 | + for (String syncPath : syncPaths) { |
| 540 | + IResource target = findNearestExistingResource(syncPath, (IContainer) rootResource); |
| 541 | + if (target == null) { |
| 542 | + allResolved = false; |
| 543 | + break; |
| 544 | + } |
| 545 | + // Multiple changed paths can resolve to the same existing |
| 546 | + // ancestor (e.g. several new files in one new package); the |
| 547 | + // set keeps each distinct subtree so it is refreshed once. |
| 548 | + targets.add(target); |
| 549 | + } |
| 550 | + if (allResolved && !targets.isEmpty()) { |
| 551 | + for (IResource target : targets) { |
| 552 | + refreshLocal(target, IResource.DEPTH_INFINITE, pm); |
| 553 | + } |
| 554 | + refreshedTargets = true; |
| 555 | + } |
| 556 | + } |
| 557 | + if (!refreshedTargets) { |
| 558 | + refreshLocal(rootResource, IResource.DEPTH_INFINITE, pm); |
| 559 | + } |
| 560 | + try { |
| 561 | + root.close(); |
| 562 | + } catch (JavaModelException e) { |
| 563 | + JdtlsExtActivator.log(e); |
| 564 | + } |
| 565 | + } else { |
| 566 | + refreshLocal(rootResource, IResource.DEPTH_ONE, pm); |
| 567 | + } |
513 | 568 | if (isHierarchicalView) { |
514 | 569 | Map<String, IJavaElement> map = new HashMap<>(); |
515 | 570 | for (IJavaElement child : root.getChildren()) { |
@@ -599,13 +654,56 @@ public static IJavaProject getJavaProject(String projectUri) { |
599 | 654 | } |
600 | 655 |
|
601 | 656 | private static void refreshLocal(IResource resource, IProgressMonitor monitor) { |
| 657 | + refreshLocal(resource, IResource.DEPTH_ONE, monitor); |
| 658 | + } |
| 659 | + |
| 660 | + private static void refreshLocal(IResource resource, int depth, IProgressMonitor monitor) { |
602 | 661 | if (resource == null || !resource.exists()) { |
603 | 662 | return; |
604 | 663 | } |
605 | 664 | try { |
606 | | - resource.refreshLocal(IResource.DEPTH_ONE, monitor); |
| 665 | + resource.refreshLocal(depth, monitor); |
607 | 666 | } catch (CoreException e) { |
608 | 667 | JdtlsExtActivator.log(e); |
609 | 668 | } |
610 | 669 | } |
| 670 | + |
| 671 | + /** |
| 672 | + * Resolve a changed resource URI to the nearest ancestor that already exists |
| 673 | + * in the workspace resource tree and lives inside the given source root. |
| 674 | + * Used to scope an auto-refresh to only the affected subtree. Returns null |
| 675 | + * when the URI cannot be mapped to a resource within the root, in which case |
| 676 | + * the caller falls back to a full refresh so no package is missed. |
| 677 | + */ |
| 678 | + private static IResource findNearestExistingResource(String uriStr, IContainer root) { |
| 679 | + if (StringUtils.isBlank(uriStr) || root == null) { |
| 680 | + return null; |
| 681 | + } |
| 682 | + try { |
| 683 | + URI uri = JDTUtils.toURI(uriStr); |
| 684 | + if (uri == null) { |
| 685 | + return null; |
| 686 | + } |
| 687 | + IWorkspaceRoot wsRoot = ResourcesPlugin.getWorkspace().getRoot(); |
| 688 | + IResource resource = null; |
| 689 | + IFile[] files = wsRoot.findFilesForLocationURI(uri); |
| 690 | + if (files.length > 0) { |
| 691 | + resource = files[0]; |
| 692 | + } else { |
| 693 | + IContainer[] containers = wsRoot.findContainersForLocationURI(uri); |
| 694 | + if (containers.length > 0) { |
| 695 | + resource = containers[0]; |
| 696 | + } |
| 697 | + } |
| 698 | + while (resource != null && !resource.exists()) { |
| 699 | + resource = resource.getParent(); |
| 700 | + } |
| 701 | + if (resource != null && root.getFullPath().isPrefixOf(resource.getFullPath())) { |
| 702 | + return resource; |
| 703 | + } |
| 704 | + } catch (Exception e) { |
| 705 | + JdtlsExtActivator.logException("Failed to resolve sync path " + uriStr, e); |
| 706 | + } |
| 707 | + return null; |
| 708 | + } |
611 | 709 | } |
0 commit comments