Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,17 @@ public Task FileBasedApps_Project_01(TestHost testHost)
PPKeyword("project"),
String("../path/to/lib.csproj"));

[Theory, CombinatorialData]
public Task FileBasedApps_Ref_01(TestHost testHost)
=> TestAsync("""
#:ref ../utils/Helper.cs
""",
testHost,
PPKeyword("#"),
PPKeyword(":"),
PPKeyword("ref"),
String("../utils/Helper.cs"));

[Theory, CombinatorialData]
public Task FileBasedApps_Include_01(TestHost testHost)
=> TestAsync("""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,115 @@ public async Task PathRecommendation_03()
// Therefore we do not have "negative tests" here for file names.
}

public sealed class RefAppDirectiveCompletionProviderTests : AbstractAppDirectiveCompletionProviderTests
{
protected override string DirectiveKind => "ref";

internal override Type GetCompletionProviderType()
=> typeof(RefAppDirectiveCompletionProvider);

[Fact]
public async Task PathRecommendation_01()
{
using var tempRoot = new TempRoot();
var tempDirectory = tempRoot.CreateDirectory();
var nestedDirectory = tempDirectory.CreateDirectory("SubDirectory");
var scriptFilePath = Path.Combine(tempDirectory.Path, "App.cs");
var code = """
#:ref $$
""";
var markup = $"""
<Workspace>
<Project Language="C#" CommonReferences="true" AssemblyName="Test1" Features="FileBasedProgram=true">
<Document FilePath="{scriptFilePath}"><![CDATA[{code}]]></Document>
</Project>
</Workspace>
""";

await VerifyItemExistsAsync(markup, expectedItem: "SubDirectory");
await VerifyItemIsAbsentAsync(markup, expectedItem: "*.cs");
await VerifyItemIsAbsentAsync(markup, expectedItem: "**/*.cs");
}

[Fact]
public async Task PathRecommendation_02()
{
using var tempRoot = new TempRoot();
var tempDirectory = tempRoot.CreateDirectory();
var nestedDirectory = tempDirectory.CreateDirectory("SubDirectory");
var utilFile = nestedDirectory.CreateFile("Util.cs");
utilFile.WriteAllText("""
public class Util { }
""");

var scriptFilePath = Path.Combine(tempDirectory.Path, "App.cs");
var code = """
#:ref SubDirectory/$$
""";
var markup = $"""
<Workspace>
<Project Language="C#" CommonReferences="true" AssemblyName="Test1" Features="FileBasedProgram=true">
<Document FilePath="{scriptFilePath}"><![CDATA[{code}]]></Document>
</Project>
</Workspace>
""";
await VerifyItemExistsAsync(markup, expectedItem: "Util.cs");
await VerifyItemIsAbsentAsync(markup, expectedItem: "*.cs");
await VerifyItemIsAbsentAsync(markup, expectedItem: "**/*.cs");
}

[Fact]
public async Task PathRecommendation_OnlyShowsCsFiles()
{
using var tempRoot = new TempRoot();
var tempDirectory = tempRoot.CreateDirectory();
var csFile = tempDirectory.CreateFile("Library.cs");
csFile.WriteAllText("public class Library { }");
var txtFile = tempDirectory.CreateFile("Notes.txt");
txtFile.WriteAllText("some notes");

var scriptFilePath = Path.Combine(tempDirectory.Path, "App.cs");
var code = """
#:ref $$
""";
var markup = $"""
<Workspace>
<Project Language="C#" CommonReferences="true" AssemblyName="Test1" Features="FileBasedProgram=true">
<Document FilePath="{scriptFilePath}"><![CDATA[{code}]]></Document>
</Project>
</Workspace>
""";
await VerifyItemExistsAsync(markup, expectedItem: "Library.cs");
await VerifyItemIsAbsentAsync(markup, expectedItem: "Notes.txt");
}

[Fact]
public async Task PathRecommendation_Virtual()
{
// Test a virtual file scenario (e.g. ctrl+N in VS Code or other cases where there is not an actual file on disk.)
var code = """
#:ref $$
""";

var markup = $"""
<Workspace>
<Project Language="C#" CommonReferences="true" AssemblyName="Test1" Features="FileBasedProgram=true">
<Document FilePath="Untitled-1" ResolveFilePath="false"><![CDATA[{code}]]></Document>
</Project>
</Workspace>
""";

// In this case, only stuff like drive roots would be recommended.
var expectedRoot = PlatformInformation.IsWindows ? "C:" : "/";
await VerifyItemExistsAsync(markup, expectedRoot);
await VerifyItemIsAbsentAsync(markup, expectedItem: "*.cs");
await VerifyItemIsAbsentAsync(markup, expectedItem: "**/*.cs");
}

// Note: The editor uses a shared mechanism to filter out completion items which don't match the prefix of what the user is typing.
// Therefore we do not have "negative tests" here for file names.
}

public sealed class IncludeAppDirectiveCompletionProviderTests : AbstractAppDirectiveCompletionProviderTests
{
protected override string DirectiveKind => "include";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ public void TestCompletionProviderOrder()
typeof(PackageAppDirectiveCompletionProvider),
typeof(ProjectAppDirectiveCompletionProvider),
typeof(IncludeAppDirectiveCompletionProvider),
typeof(RefAppDirectiveCompletionProvider),

// Marker for end of built-in completion providers
typeof(LastBuiltInCompletionProvider),
Expand Down
7 changes: 7 additions & 0 deletions src/Features/CSharp/Portable/CSharpFeaturesResources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -629,6 +629,13 @@
<data name="Adds_a_file_reference" xml:space="preserve">
<value>Adds a file reference.</value>
</data>
<data name="Ref_directive_file_path" xml:space="preserve">
<value>path</value>
<comment>'path' is a placeholder for a file path in a directive like '#:ref path'.</comment>
</data>
Comment on lines +632 to +635
<data name="Adds_a_file_based_app_reference" xml:space="preserve">
<value>References another file-based app as a library.</value>
</data>
<data name="Add_using_0" xml:space="preserve">
<value>Add 'using {0};'</value>
<comment>{Locked="using"} "using" is a C# keyword and should not be localized.</comment>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.Collections.Immutable;
using System.Composition;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Completion;
using Microsoft.CodeAnalysis.CSharp.Completion.Providers;
using Microsoft.CodeAnalysis.Host.Mef;
using Roslyn.Utilities;

namespace Microsoft.CodeAnalysis.CSharp.Completion.Providers;

[ExportCompletionProvider(nameof(RefAppDirectiveCompletionProvider), LanguageNames.CSharp), Shared]
[ExtensionOrder(After = nameof(IncludeAppDirectiveCompletionProvider))]
[method: ImportingConstructor]
[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
internal sealed class RefAppDirectiveCompletionProvider() : AbstractAppDirectiveCompletionProvider
{
protected override string DirectiveKind => "ref";

protected sealed override void AddDirectiveKindCompletion(CompletionContext context)
{
context.AddItem(CommonCompletionItem.Create(DirectiveKind, displayTextSuffix: "", CompletionItemRules.Default, glyph: Glyph.Keyword,
description: [
new(SymbolDisplayPartKind.Keyword, symbol: null, "#:ref"),
new(SymbolDisplayPartKind.Space, symbol: null, " "),
new(SymbolDisplayPartKind.StringLiteral, symbol: null, CSharpFeaturesResources.Ref_directive_file_path),
new(SymbolDisplayPartKind.LineBreak, symbol: null, ""),
new(SymbolDisplayPartKind.Text, symbol: null, CSharpFeaturesResources.Adds_a_file_based_app_reference),
]));
}

protected override async Task AddDirectiveContentCompletionsAsync(CompletionContext context, ReadOnlyMemory<char> contentPrefix)
{
// Suppose we have a directive '#:ref path/to/fi$$'
// In this case, 'contentPrefix' is 'path/to/fi'.

var documentDirectory = PathUtilities.GetDirectoryName(context.Document.FilePath);
var baseDirectory = PathUtilities.IsAbsolute(documentDirectory) ? documentDirectory : null;
var fileSystemHelper = new FileSystemCompletionHelper(
Glyph.OpenFolder,
Glyph.CSharpFile,
searchPaths: [],
baseDirectory,
allowableExtensions: [".cs"],
CompletionItemRules.Default);

var contentDirectory = PathUtilities.GetDirectoryName(contentPrefix.ToString());
var items = await fileSystemHelper.GetItemsAsync(contentDirectory, context.CancellationToken).ConfigureAwait(false);
context.AddItems(items);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ namespace Microsoft.CodeAnalysis.CSharp.Completion.Providers;
/// provider does not provide any completions.
/// </summary>
[ExportCompletionProvider(nameof(LastBuiltInCompletionProvider), LanguageNames.CSharp)]
[ExtensionOrder(After = nameof(IncludeAppDirectiveCompletionProvider))]
[ExtensionOrder(After = nameof(RefAppDirectiveCompletionProvider))]
[Shared]
internal sealed class LastBuiltInCompletionProvider : CompletionProvider
{
Expand Down
10 changes: 10 additions & 0 deletions src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.cs.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.de.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.es.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.fr.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.it.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.ja.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.ko.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading