Skip to content

Commit 7b5affc

Browse files
Merge branch 'main' into state
2 parents 2064471 + f7b231a commit 7b5affc

31 files changed

+847
-25
lines changed

LibraryManager.sln

+30
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Web.LibraryManage
6262
EndProject
6363
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Web.LibraryManager.Build.Test", "test\LibraryManager.Build.Test\Microsoft.Web.LibraryManager.Build.Test.csproj", "{C8979E80-F4D3-3F21-D966-1B317CA64C23}"
6464
EndProject
65+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "libman.IntegrationTest", "test\libman.IntegrationTest\libman.IntegrationTest.csproj", "{BE84C694-91F1-C170-7366-7B86EBC9B033}"
66+
EndProject
67+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Web.LibraryManager.Build.IntegrationTest", "test\LibraryManager.Build.IntegrationTest\Microsoft.Web.LibraryManager.Build.IntegrationTest.csproj", "{EC152F12-CB03-4405-D8AE-EE7FC6E32666}"
68+
EndProject
6569
Global
6670
GlobalSection(SolutionConfigurationPlatforms) = preSolution
6771
Debug|Any CPU = Debug|Any CPU
@@ -216,6 +220,30 @@ Global
216220
{C8979E80-F4D3-3F21-D966-1B317CA64C23}.Release|x64.Build.0 = Release|Any CPU
217221
{C8979E80-F4D3-3F21-D966-1B317CA64C23}.Release|x86.ActiveCfg = Release|Any CPU
218222
{C8979E80-F4D3-3F21-D966-1B317CA64C23}.Release|x86.Build.0 = Release|Any CPU
223+
{BE84C694-91F1-C170-7366-7B86EBC9B033}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
224+
{BE84C694-91F1-C170-7366-7B86EBC9B033}.Debug|Any CPU.Build.0 = Debug|Any CPU
225+
{BE84C694-91F1-C170-7366-7B86EBC9B033}.Debug|x64.ActiveCfg = Debug|Any CPU
226+
{BE84C694-91F1-C170-7366-7B86EBC9B033}.Debug|x64.Build.0 = Debug|Any CPU
227+
{BE84C694-91F1-C170-7366-7B86EBC9B033}.Debug|x86.ActiveCfg = Debug|Any CPU
228+
{BE84C694-91F1-C170-7366-7B86EBC9B033}.Debug|x86.Build.0 = Debug|Any CPU
229+
{BE84C694-91F1-C170-7366-7B86EBC9B033}.Release|Any CPU.ActiveCfg = Release|Any CPU
230+
{BE84C694-91F1-C170-7366-7B86EBC9B033}.Release|Any CPU.Build.0 = Release|Any CPU
231+
{BE84C694-91F1-C170-7366-7B86EBC9B033}.Release|x64.ActiveCfg = Release|Any CPU
232+
{BE84C694-91F1-C170-7366-7B86EBC9B033}.Release|x64.Build.0 = Release|Any CPU
233+
{BE84C694-91F1-C170-7366-7B86EBC9B033}.Release|x86.ActiveCfg = Release|Any CPU
234+
{BE84C694-91F1-C170-7366-7B86EBC9B033}.Release|x86.Build.0 = Release|Any CPU
235+
{EC152F12-CB03-4405-D8AE-EE7FC6E32666}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
236+
{EC152F12-CB03-4405-D8AE-EE7FC6E32666}.Debug|Any CPU.Build.0 = Debug|Any CPU
237+
{EC152F12-CB03-4405-D8AE-EE7FC6E32666}.Debug|x64.ActiveCfg = Debug|Any CPU
238+
{EC152F12-CB03-4405-D8AE-EE7FC6E32666}.Debug|x64.Build.0 = Debug|Any CPU
239+
{EC152F12-CB03-4405-D8AE-EE7FC6E32666}.Debug|x86.ActiveCfg = Debug|Any CPU
240+
{EC152F12-CB03-4405-D8AE-EE7FC6E32666}.Debug|x86.Build.0 = Debug|Any CPU
241+
{EC152F12-CB03-4405-D8AE-EE7FC6E32666}.Release|Any CPU.ActiveCfg = Release|Any CPU
242+
{EC152F12-CB03-4405-D8AE-EE7FC6E32666}.Release|Any CPU.Build.0 = Release|Any CPU
243+
{EC152F12-CB03-4405-D8AE-EE7FC6E32666}.Release|x64.ActiveCfg = Release|Any CPU
244+
{EC152F12-CB03-4405-D8AE-EE7FC6E32666}.Release|x64.Build.0 = Release|Any CPU
245+
{EC152F12-CB03-4405-D8AE-EE7FC6E32666}.Release|x86.ActiveCfg = Release|Any CPU
246+
{EC152F12-CB03-4405-D8AE-EE7FC6E32666}.Release|x86.Build.0 = Release|Any CPU
219247
EndGlobalSection
220248
GlobalSection(SolutionProperties) = preSolution
221249
HideSolutionNode = FALSE
@@ -234,6 +262,8 @@ Global
234262
{F01D971D-AA93-60A2-2D84-05AF9AB1566F} = {FFCD12F4-5CE2-4CC2-A2C4-EACC8F387D7A}
235263
{E199195A-7EE8-9273-37B1-3726BCEAB87A} = {FFCD12F4-5CE2-4CC2-A2C4-EACC8F387D7A}
236264
{C8979E80-F4D3-3F21-D966-1B317CA64C23} = {FFCD12F4-5CE2-4CC2-A2C4-EACC8F387D7A}
265+
{BE84C694-91F1-C170-7366-7B86EBC9B033} = {FFCD12F4-5CE2-4CC2-A2C4-EACC8F387D7A}
266+
{EC152F12-CB03-4405-D8AE-EE7FC6E32666} = {FFCD12F4-5CE2-4CC2-A2C4-EACC8F387D7A}
237267
EndGlobalSection
238268
GlobalSection(ExtensibilityGlobals) = postSolution
239269
SolutionGuid = {720C6A7F-67F7-4D00-881F-D3CDEA7ABE69}

src/LibraryManager.Contracts/CompletionSet.cs

+5
Original file line numberDiff line numberDiff line change
@@ -140,8 +140,13 @@ public bool Equals(CompletionItem other)
140140
/// </summary>
141141
public override int GetHashCode()
142142
{
143+
#if NET8_0_OR_GREATER
144+
return HashCode.Combine(InsertionText, Description);
145+
#else
143146
// Much of the time, InsertionText == DisplayText, so XORing those would just cancel out.
147+
// So don't include DisplayText in the hash code.
144148
return InsertionText.GetHashCode() ^ Description.GetHashCode();
149+
#endif
145150
}
146151

147152
/// <summary>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System.Collections.Generic;
5+
using System.ComponentModel.Composition;
6+
using System.Diagnostics.CodeAnalysis;
7+
using System.Linq;
8+
using System.Threading;
9+
using System.Threading.Tasks;
10+
using Microsoft.VisualStudio.Imaging;
11+
using Microsoft.VisualStudio.Utilities;
12+
using Microsoft.Web.LibraryManager.Contracts;
13+
using Microsoft.Web.LibraryManager.Vsix.Contracts;
14+
using Microsoft.WebTools.Languages.Json.Editor.Completion;
15+
using Microsoft.WebTools.Languages.Json.Parser.Nodes;
16+
17+
namespace Microsoft.Web.LibraryManager.Vsix.Json.Completion
18+
{
19+
[Export(typeof(IJsonCompletionListProvider))]
20+
[Name(nameof(FileMappingRootCompletionProvider))]
21+
internal class FileMappingRootCompletionProvider : BaseCompletionProvider
22+
{
23+
private readonly IDependenciesFactory _dependenciesFactory;
24+
25+
[ImportingConstructor]
26+
internal FileMappingRootCompletionProvider(IDependenciesFactory dependenciesFactory)
27+
{
28+
_dependenciesFactory = dependenciesFactory;
29+
}
30+
31+
public override JsonCompletionContextType ContextType => JsonCompletionContextType.PropertyValue;
32+
33+
[SuppressMessage("Usage", "VSTHRD002:Avoid problematic synchronous waits", Justification = "Checked completion first")]
34+
protected override IEnumerable<JsonCompletionEntry> GetEntries(JsonCompletionContext context)
35+
{
36+
MemberNode member = context.ContextNode.FindType<MemberNode>();
37+
38+
// This provides completions for libraries/[n]/fileMappings/[m]/root
39+
if (member == null || member.UnquotedNameText != ManifestConstants.Root)
40+
yield break;
41+
42+
MemberNode possibleFileMappingsNode = member.Parent.FindType<MemberNode>();
43+
bool isInFileMapping = possibleFileMappingsNode?.UnquotedNameText == ManifestConstants.FileMappings;
44+
if (!isInFileMapping)
45+
yield break;
46+
47+
ObjectNode parent = possibleFileMappingsNode.Parent as ObjectNode;
48+
49+
if (!JsonHelpers.TryGetInstallationState(parent, out ILibraryInstallationState state))
50+
yield break;
51+
52+
if (string.IsNullOrEmpty(state.Name))
53+
yield break;
54+
55+
IDependencies dependencies = _dependenciesFactory.FromConfigFile(ConfigFilePath);
56+
IProvider provider = dependencies.GetProvider(state.ProviderId);
57+
ILibraryCatalog catalog = provider?.GetCatalog();
58+
59+
if (catalog is null)
60+
{
61+
yield break;
62+
}
63+
64+
Task<ILibrary> task = catalog.GetLibraryAsync(state.Name, state.Version, CancellationToken.None);
65+
66+
if (task.IsCompleted)
67+
{
68+
if (task.Result is ILibrary library)
69+
{
70+
foreach (JsonCompletionEntry item in GetRootCompletions(context, library))
71+
{
72+
yield return item;
73+
}
74+
}
75+
}
76+
else
77+
{
78+
yield return new SimpleCompletionEntry(Resources.Text.Loading, string.Empty, KnownMonikers.Loading, context.Session);
79+
_ = task.ContinueWith(async (t) =>
80+
{
81+
await VisualStudio.Shell.ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();
82+
83+
if (!(t.Result is ILibrary library))
84+
return;
85+
86+
if (!context.Session.IsDismissed)
87+
{
88+
IEnumerable<JsonCompletionEntry> completions = GetRootCompletions(context, library);
89+
90+
UpdateListEntriesSync(context, completions);
91+
}
92+
}, TaskScheduler.Default);
93+
}
94+
}
95+
96+
private IEnumerable<JsonCompletionEntry> GetRootCompletions(JsonCompletionContext context, ILibrary library)
97+
{
98+
HashSet<string> libraryFolders = [];
99+
foreach (string file in library.Files.Keys)
100+
{
101+
int sepIndex = file.LastIndexOf('/');
102+
if (sepIndex >= 0)
103+
{
104+
libraryFolders.Add(file.Substring(0, file.LastIndexOf('/')));
105+
}
106+
}
107+
108+
return libraryFolders.Select(folder => new SimpleCompletionEntry(folder, KnownMonikers.FolderClosed, context.Session));
109+
}
110+
}
111+
}

src/LibraryManager.Vsix/Json/Completion/FilesCompletionProvider.cs

+79-20
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using System;
45
using System.Collections.Generic;
56
using System.ComponentModel.Composition;
67
using System.Diagnostics.CodeAnalysis;
@@ -9,7 +10,6 @@
910
using System.Threading.Tasks;
1011
using System.Windows;
1112
using System.Windows.Data;
12-
using System.Windows.Media;
1313
using Microsoft.VisualStudio.Imaging;
1414
using Microsoft.VisualStudio.Imaging.Interop;
1515
using Microsoft.VisualStudio.PlatformUI;
@@ -19,6 +19,7 @@
1919
using Microsoft.Web.LibraryManager.Vsix.Shared;
2020
using Microsoft.WebTools.Languages.Json.Editor.Completion;
2121
using Microsoft.WebTools.Languages.Json.Parser.Nodes;
22+
using Microsoft.WebTools.Languages.Shared.Parser;
2223
using Microsoft.WebTools.Languages.Shared.Parser.Nodes;
2324

2425
namespace Microsoft.Web.LibraryManager.Vsix.Json.Completion
@@ -45,10 +46,20 @@ protected override IEnumerable<JsonCompletionEntry> GetEntries(JsonCompletionCon
4546
{
4647
MemberNode member = context.ContextNode.FindType<MemberNode>();
4748

49+
// We can show completions for "files". This could be libraries/[n]/files or
50+
// libraries/[n]/fileMappings/[m]/files.
4851
if (member == null || member.UnquotedNameText != "files")
4952
yield break;
5053

51-
var parent = member.Parent as ObjectNode;
54+
// If the current member is "files", then it is either:
55+
// - a library "files" property
56+
// - a fileMapping "files" property
57+
MemberNode possibleFileMappingsNode = member.Parent.FindType<MemberNode>();
58+
bool isFileMapping = possibleFileMappingsNode?.UnquotedNameText == "fileMappings";
59+
60+
ObjectNode parent = isFileMapping
61+
? possibleFileMappingsNode.Parent as ObjectNode
62+
: member.Parent as ObjectNode;
5263

5364
if (!JsonHelpers.TryGetInstallationState(parent, out ILibraryInstallationState state))
5465
yield break;
@@ -67,18 +78,23 @@ protected override IEnumerable<JsonCompletionEntry> GetEntries(JsonCompletionCon
6778
FrameworkElement presenter = GetPresenter(context);
6879
IEnumerable<string> usedFiles = GetUsedFiles(context);
6980

81+
string rootPathPrefix = isFileMapping ? GetRootValue(member) : string.Empty;
82+
static string GetRootValue(MemberNode fileMappingNode)
83+
{
84+
FindFileMappingRootVisitor visitor = new FindFileMappingRootVisitor();
85+
fileMappingNode.Parent?.Accept(visitor);
86+
return visitor.FoundNode?.UnquotedValueText ?? string.Empty;
87+
}
88+
7089
if (task.IsCompleted)
7190
{
7291
if (!(task.Result is ILibrary library))
7392
yield break;
7493

75-
foreach (string file in library.Files.Keys)
94+
IEnumerable<JsonCompletionEntry> completions = GetFileCompletions(context, usedFiles, library, rootPathPrefix);
95+
foreach (JsonCompletionEntry item in completions)
7696
{
77-
if (!usedFiles.Contains(file))
78-
{
79-
ImageMoniker glyph = WpfUtil.GetImageMonikerForFile(file);
80-
yield return new SimpleCompletionEntry(file, glyph, context.Session);
81-
}
97+
yield return item;
8298
}
8399
}
84100
else
@@ -94,23 +110,40 @@ protected override IEnumerable<JsonCompletionEntry> GetEntries(JsonCompletionCon
94110

95111
if (!context.Session.IsDismissed)
96112
{
97-
var results = new List<JsonCompletionEntry>();
98-
99-
foreach (string file in library.Files.Keys)
100-
{
101-
if (!usedFiles.Contains(file))
102-
{
103-
ImageMoniker glyph = WpfUtil.GetImageMonikerForFile(file);
104-
results.Add(new SimpleCompletionEntry(file, glyph, context.Session));
105-
}
106-
}
107-
108-
UpdateListEntriesSync(context, results);
113+
IEnumerable<JsonCompletionEntry> completions = GetFileCompletions(context, usedFiles, library, rootPathPrefix);
114+
115+
UpdateListEntriesSync(context, completions);
109116
}
110117
}, TaskScheduler.Default);
111118
}
112119
}
113120

121+
private static IEnumerable<JsonCompletionEntry> GetFileCompletions(JsonCompletionContext context, IEnumerable<string> usedFiles, ILibrary library, string root)
122+
{
123+
static bool alwaysInclude(string s) => true;
124+
bool includeIfUnderRoot(string s) => FileHelpers.IsUnderRootDirectory(s, root);
125+
126+
Func<string, bool> filter = string.IsNullOrEmpty(root)
127+
? alwaysInclude
128+
: includeIfUnderRoot;
129+
130+
bool rootHasTrailingSlash = string.IsNullOrEmpty(root) || root.EndsWith("/") || root.EndsWith("\\");
131+
int nameOffset = rootHasTrailingSlash ? root.Length : root.Length + 1;
132+
133+
foreach (string file in library.Files.Keys)
134+
{
135+
if (filter(file))
136+
{
137+
string fileSubPath = file.Substring(nameOffset);
138+
if (!usedFiles.Contains(fileSubPath))
139+
{
140+
ImageMoniker glyph = WpfUtil.GetImageMonikerForFile(file);
141+
yield return new SimpleCompletionEntry(fileSubPath, glyph, context.Session);
142+
}
143+
}
144+
}
145+
}
146+
114147
private static IEnumerable<string> GetUsedFiles(JsonCompletionContext context)
115148
{
116149
ArrayNode array = context.ContextNode.FindType<ArrayNode>();
@@ -139,5 +172,31 @@ private FrameworkElement GetPresenter(JsonCompletionContext context)
139172

140173
return presenter;
141174
}
175+
176+
private class FindFileMappingRootVisitor : INodeVisitor
177+
{
178+
public MemberNode FoundNode { get; private set; }
179+
180+
public VisitNodeResult Visit(Node node)
181+
{
182+
if (node is ObjectNode)
183+
{
184+
return VisitNodeResult.Continue;
185+
}
186+
// we only look at the object and it's members, this is not a recursive search
187+
if (node is not MemberNode mn)
188+
{
189+
return VisitNodeResult.SkipChildren;
190+
}
191+
192+
if (mn.UnquotedNameText == ManifestConstants.Root)
193+
{
194+
FoundNode = mn;
195+
return VisitNodeResult.Cancel;
196+
}
197+
198+
return VisitNodeResult.SkipChildren;
199+
}
200+
}
142201
}
143202
}

src/LibraryManager.Vsix/Microsoft.Web.LibraryManager.Vsix.csproj

+2-1
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
<Compile Include="Commands\InstallLibraryCommand.cs" />
4949
<Compile Include="Contracts\DependenciesFactory.cs" />
5050
<Compile Include="Contracts\IDependenciesFactory.cs" />
51+
<Compile Include="Json\Completion\FileMappingRootCompletionProvider.cs" />
5152
<Compile Include="Search\ISearchService.cs" />
5253
<Compile Include="Search\LocationSearchService.cs" />
5354
<Compile Include="Search\ProviderCatalogSearchService.cs" />
@@ -417,4 +418,4 @@
417418
<VSIXSourceItem Remove="@(_VsixSourceItemsFromNuGet)" />
418419
</ItemGroup>
419420
</Target>
420-
</Project>
421+
</Project>

src/LibraryManager/Cache/CacheFileMetadata.cs

+4
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,11 @@ public override bool Equals(object obj)
4646
/// <inheritdoc />
4747
public override int GetHashCode()
4848
{
49+
#if NET8_0_OR_GREATER
50+
return DestinationPath.GetHashCode(StringComparison.Ordinal); // this should be a unique identifier
51+
#else
4952
return DestinationPath.GetHashCode(); // this should be a unique identifier
53+
#endif
5054
}
5155
}
5256
}

src/LibraryManager/FileIdentifier.cs

+4-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using System;
5-
using System.Collections.Generic;
65

76
namespace Microsoft.Web.LibraryManager
87
{
@@ -35,10 +34,14 @@ public override bool Equals(object obj)
3534

3635
public override int GetHashCode()
3736
{
37+
#if NET8_0_OR_GREATER
38+
return HashCode.Combine(Path, Version);
39+
#else
3840
int hashPath = Path == null ? 0 : Path.GetHashCode();
3941
int hashVersion = Version == null ? 0 : Version.GetHashCode();
4042

4143
return hashPath ^ hashVersion;
44+
#endif
4245
}
4346
}
4447
}

0 commit comments

Comments
 (0)