|
5 | 5 | using Microsoft.CodeAnalysis;
|
6 | 6 | using Microsoft.CodeAnalysis.Host.Mef;
|
7 | 7 | using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem;
|
| 8 | +using Microsoft.VisualStudio.LanguageServices.Implementation.Venus; |
8 | 9 | using Microsoft.VisualStudio.Shell;
|
9 | 10 |
|
10 | 11 | namespace Microsoft.VisualStudio.LanguageServices.Implementation.SolutionExplorer
|
@@ -43,56 +44,70 @@ public bool TryGetProjectId(IVsHierarchyItem hierarchyItem, string targetFramewo
|
43 | 44 | return false;
|
44 | 45 | }
|
45 | 46 |
|
46 |
| - var project = _workspace.DeferredState.ProjectTracker.ImmutableProjects |
47 |
| - .Where(p => |
| 47 | + // First filter the projects by matching up properties on the input hierarchy against properties on each |
| 48 | + // project's hierarchy. |
| 49 | + var candidateProjects = _workspace.DeferredState.ProjectTracker.ImmutableProjects |
| 50 | + .Where(p => |
| 51 | + { |
| 52 | + // We're about to access various properties of the IVsHierarchy associated with the project. |
| 53 | + // The properties supported and the interpretation of their values varies from one project system |
| 54 | + // to another. This code is designed with C# and VB in mind, so we need to filter out everything |
| 55 | + // else. |
| 56 | + if (p.Language != LanguageNames.CSharp |
| 57 | + && p.Language != LanguageNames.VisualBasic) |
48 | 58 | {
|
49 |
| - // We're about to access various properties of the IVsHierarchy associated with the project. |
50 |
| - // The properties supported and the interpretation of their values varies from one project system |
51 |
| - // to another. This code is designed with C# and VB in mind, so we need to filter out everything |
52 |
| - // else. |
53 |
| - if (p.Language != LanguageNames.CSharp |
54 |
| - && p.Language != LanguageNames.VisualBasic) |
55 |
| - { |
56 |
| - return false; |
57 |
| - } |
| 59 | + return false; |
| 60 | + } |
58 | 61 |
|
59 |
| - // Here we try to match the hierarchy from Solution Explorer to a hierarchy from the Roslyn project. |
60 |
| - // The canonical name of a hierarchy item must be unique _within_ an hierarchy, but since we're |
61 |
| - // examining multiple hierarchies the canonical name could be the same. Indeed this happens when two |
62 |
| - // project files are in the same folder--they both use the full path to the _folder_ as the canonical |
63 |
| - // name. To distinguish them we also examine the "regular" name, which will necessarily be different |
64 |
| - // if the two projects are in the same folder. |
65 |
| - // Note that if a project has been loaded with Lightweight Solution Load it won't even have a |
66 |
| - // hierarchy, so we need to check for null first. |
67 |
| - if (p.Hierarchy != null |
68 |
| - && p.Hierarchy.TryGetCanonicalName((uint)VSConstants.VSITEMID.Root, out string projectCanonicalName) |
69 |
| - && p.Hierarchy.TryGetItemName((uint)VSConstants.VSITEMID.Root, out string projectName) |
70 |
| - && projectCanonicalName.Equals(nestedCanonicalName, System.StringComparison.OrdinalIgnoreCase) |
71 |
| - && projectName.Equals(nestedName)) |
| 62 | + // Here we try to match the hierarchy from Solution Explorer to a hierarchy from the Roslyn project. |
| 63 | + // The canonical name of a hierarchy item must be unique _within_ an hierarchy, but since we're |
| 64 | + // examining multiple hierarchies the canonical name could be the same. Indeed this happens when two |
| 65 | + // project files are in the same folder--they both use the full path to the _folder_ as the canonical |
| 66 | + // name. To distinguish them we also examine the "regular" name, which will necessarily be different |
| 67 | + // if the two projects are in the same folder. |
| 68 | + // Note that if a project has been loaded with Lightweight Solution Load it won't even have a |
| 69 | + // hierarchy, so we need to check for null first. |
| 70 | + if (p.Hierarchy != null |
| 71 | + && p.Hierarchy.TryGetCanonicalName((uint)VSConstants.VSITEMID.Root, out string projectCanonicalName) |
| 72 | + && p.Hierarchy.TryGetItemName((uint)VSConstants.VSITEMID.Root, out string projectName) |
| 73 | + && projectCanonicalName.Equals(nestedCanonicalName, System.StringComparison.OrdinalIgnoreCase) |
| 74 | + && projectName.Equals(nestedName)) |
| 75 | + { |
| 76 | + if (targetFrameworkMoniker == null) |
72 | 77 | {
|
73 |
| - if (targetFrameworkMoniker == null) |
74 |
| - { |
75 |
| - return true; |
76 |
| - } |
77 |
| - |
78 |
| - return p.Hierarchy.TryGetTargetFrameworkMoniker((uint)VSConstants.VSITEMID.Root, out string projectTargetFrameworkMoniker) |
79 |
| - && projectTargetFrameworkMoniker.Equals(targetFrameworkMoniker); |
| 78 | + return true; |
80 | 79 | }
|
81 | 80 |
|
82 |
| - return false; |
83 |
| - }) |
84 |
| - .SingleOrDefault(); |
| 81 | + return p.Hierarchy.TryGetTargetFrameworkMoniker((uint)VSConstants.VSITEMID.Root, out string projectTargetFrameworkMoniker) |
| 82 | + && projectTargetFrameworkMoniker.Equals(targetFrameworkMoniker); |
| 83 | + } |
| 84 | + |
| 85 | + return false; |
| 86 | + }) |
| 87 | + .ToArray(); |
85 | 88 |
|
86 |
| - if (project == null) |
| 89 | + // If we only have one candidate then no further checks are required. |
| 90 | + if (candidateProjects.Length == 1) |
87 | 91 | {
|
88 |
| - projectId = default(ProjectId); |
89 |
| - return false; |
| 92 | + projectId = candidateProjects[0].Id; |
| 93 | + return true; |
90 | 94 | }
|
91 |
| - else |
| 95 | + |
| 96 | + // If we have multiple candidates then we might be dealing with Web Application Projects. In this case |
| 97 | + // there will be one main project plus one project for each open aspx/cshtml/vbhtml file, all with |
| 98 | + // identical properties on their hierarchies. We can find the main project by taking the first project |
| 99 | + // without a ContainedDocument. |
| 100 | + foreach (var candidateProject in candidateProjects) |
92 | 101 | {
|
93 |
| - projectId = project.Id; |
94 |
| - return true; |
| 102 | + if (!candidateProject.GetCurrentDocuments().Any(doc => doc is ContainedDocument)) |
| 103 | + { |
| 104 | + projectId = candidateProject.Id; |
| 105 | + return true; |
| 106 | + } |
95 | 107 | }
|
| 108 | + |
| 109 | + projectId = default(ProjectId); |
| 110 | + return false; |
96 | 111 | }
|
97 | 112 | }
|
98 | 113 | }
|
0 commit comments