diff --git a/src/EditorFeatures/CSharpTest/Classification/SyntacticClassifierTests_FileBasedApps.cs b/src/EditorFeatures/CSharpTest/Classification/SyntacticClassifierTests_FileBasedApps.cs
index e9e6c149c8119..fe12e5fff0821 100644
--- a/src/EditorFeatures/CSharpTest/Classification/SyntacticClassifierTests_FileBasedApps.cs
+++ b/src/EditorFeatures/CSharpTest/Classification/SyntacticClassifierTests_FileBasedApps.cs
@@ -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("""
diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/AppDirectiveCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/AppDirectiveCompletionProviderTests.cs
index 6f4a98fa8b0b0..85ed155b10b24 100644
--- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/AppDirectiveCompletionProviderTests.cs
+++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/AppDirectiveCompletionProviderTests.cs
@@ -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 = $"""
+
+
+
+
+
+ """;
+
+ 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 = $"""
+
+
+
+
+
+ """;
+ 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 = $"""
+
+
+
+
+
+ """;
+ 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 = $"""
+
+
+
+
+
+ """;
+
+ // 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";
diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/CompletionProviderOrderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/CompletionProviderOrderTests.cs
index 9e6f341bf2db2..fcc1294a68ed3 100644
--- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/CompletionProviderOrderTests.cs
+++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/CompletionProviderOrderTests.cs
@@ -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),
diff --git a/src/Features/CSharp/Portable/CSharpFeaturesResources.resx b/src/Features/CSharp/Portable/CSharpFeaturesResources.resx
index 3982e4e4fa8e1..c98ef024db631 100644
--- a/src/Features/CSharp/Portable/CSharpFeaturesResources.resx
+++ b/src/Features/CSharp/Portable/CSharpFeaturesResources.resx
@@ -629,6 +629,13 @@
Adds a file reference.
+
+ path
+ 'path' is a placeholder for a file path in a directive like '#:ref path'.
+
+
+ References another file-based app as a library.
+
Add 'using {0};'{Locked="using"} "using" is a C# keyword and should not be localized.
diff --git a/src/Features/CSharp/Portable/Completion/CompletionProviders/FileBasedPrograms/RefAppDirectiveCompletionProvider.cs b/src/Features/CSharp/Portable/Completion/CompletionProviders/FileBasedPrograms/RefAppDirectiveCompletionProvider.cs
new file mode 100644
index 0000000000000..ca9586a5d5393
--- /dev/null
+++ b/src/Features/CSharp/Portable/Completion/CompletionProviders/FileBasedPrograms/RefAppDirectiveCompletionProvider.cs
@@ -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 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);
+ }
+}
diff --git a/src/Features/CSharp/Portable/Completion/CompletionProviders/LastBuiltInCompletionProvider.cs b/src/Features/CSharp/Portable/Completion/CompletionProviders/LastBuiltInCompletionProvider.cs
index ff25235f4f95d..810c8bfdc2d30 100644
--- a/src/Features/CSharp/Portable/Completion/CompletionProviders/LastBuiltInCompletionProvider.cs
+++ b/src/Features/CSharp/Portable/Completion/CompletionProviders/LastBuiltInCompletionProvider.cs
@@ -15,7 +15,7 @@ namespace Microsoft.CodeAnalysis.CSharp.Completion.Providers;
/// provider does not provide any completions.
///
[ExportCompletionProvider(nameof(LastBuiltInCompletionProvider), LanguageNames.CSharp)]
-[ExtensionOrder(After = nameof(IncludeAppDirectiveCompletionProvider))]
+[ExtensionOrder(After = nameof(RefAppDirectiveCompletionProvider))]
[Shared]
internal sealed class LastBuiltInCompletionProvider : CompletionProvider
{
diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.cs.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.cs.xlf
index 6e8023d0eb8d7..e2433b6fed1b2 100644
--- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.cs.xlf
+++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.cs.xlf
@@ -32,6 +32,11 @@
Přidá odkaz na balíček NuGet.
+
+ References another file-based app as a library.
+ References another file-based app as a library.
+
+ Adds a file reference.Přidá odkaz na soubor.
@@ -282,6 +287,11 @@
cesta'path' is a placeholder for a project file or directory path in a directive like '#:project path'.
+
+ path
+ path
+ 'path' is a placeholder for a file path in a directive like '#:ref path'.
+ Reverse 'for' statementObrátit příkaz for
diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.de.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.de.xlf
index f5b0f9078b2e5..58781b4bcb316 100644
--- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.de.xlf
+++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.de.xlf
@@ -32,6 +32,11 @@
Fügt einen NuGet-Paketverweis hinzu.
+
+ References another file-based app as a library.
+ References another file-based app as a library.
+
+ Adds a file reference.Fügt einen Dateiverweis hinzu.
@@ -282,6 +287,11 @@
Pfad'path' is a placeholder for a project file or directory path in a directive like '#:project path'.
+
+ path
+ path
+ 'path' is a placeholder for a file path in a directive like '#:ref path'.
+ Reverse 'for' statementfor-Anweisung umkehren
diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.es.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.es.xlf
index ab35e6a3554cd..e0c291ae8cd8c 100644
--- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.es.xlf
+++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.es.xlf
@@ -32,6 +32,11 @@
Agrega una referencia a un paquete NuGet.
+
+ References another file-based app as a library.
+ References another file-based app as a library.
+
+ Adds a file reference.Añade una referencia de archivo.
@@ -282,6 +287,11 @@
ruta de acceso'path' is a placeholder for a project file or directory path in a directive like '#:project path'.
+
+ path
+ path
+ 'path' is a placeholder for a file path in a directive like '#:ref path'.
+ Reverse 'for' statementInvertir la instrucción "for"
diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.fr.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.fr.xlf
index be8d40f158123..790ed709aed51 100644
--- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.fr.xlf
+++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.fr.xlf
@@ -32,6 +32,11 @@
Ajouter une référence de package NuGet.
+
+ References another file-based app as a library.
+ References another file-based app as a library.
+
+ Adds a file reference.Ajoute une référence de fichier.
@@ -282,6 +287,11 @@
chemin'path' is a placeholder for a project file or directory path in a directive like '#:project path'.
+
+ path
+ path
+ 'path' is a placeholder for a file path in a directive like '#:ref path'.
+ Reverse 'for' statementInverser l'instruction 'for'
diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.it.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.it.xlf
index 10fa892c39f78..fbb432fe7a31d 100644
--- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.it.xlf
+++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.it.xlf
@@ -32,6 +32,11 @@
Aggiunge un riferimento al pacchetto NuGet.
+
+ References another file-based app as a library.
+ References another file-based app as a library.
+
+ Adds a file reference.Aggiunge un riferimento a un file.
@@ -282,6 +287,11 @@
percorso'path' is a placeholder for a project file or directory path in a directive like '#:project path'.
+
+ path
+ path
+ 'path' is a placeholder for a file path in a directive like '#:ref path'.
+ Reverse 'for' statementInverti l'istruzione 'for'
diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.ja.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.ja.xlf
index 1e4230f8914aa..76ce661647f63 100644
--- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.ja.xlf
+++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.ja.xlf
@@ -32,6 +32,11 @@
NuGet パッケージ参照を追加します。
+
+ References another file-based app as a library.
+ References another file-based app as a library.
+
+ Adds a file reference.ファイル参照を追加します。
@@ -282,6 +287,11 @@
path'path' is a placeholder for a project file or directory path in a directive like '#:project path'.
+
+ path
+ path
+ 'path' is a placeholder for a file path in a directive like '#:ref path'.
+ Reverse 'for' statement'for' ステートメントの反転
diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.ko.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.ko.xlf
index bdb64b1215c54..78d7c2331e668 100644
--- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.ko.xlf
+++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.ko.xlf
@@ -32,6 +32,11 @@
NuGet 패키지 참조를 추가합니다.
+
+ References another file-based app as a library.
+ References another file-based app as a library.
+
+ Adds a file reference.파일 참조를 추가합니다.
@@ -282,6 +287,11 @@
경로'path' is a placeholder for a project file or directory path in a directive like '#:project path'.
+
+ path
+ path
+ 'path' is a placeholder for a file path in a directive like '#:ref path'.
+ Reverse 'for' statement역 'for' 문
diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.pl.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.pl.xlf
index e717092df2629..08edeab202b28 100644
--- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.pl.xlf
+++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.pl.xlf
@@ -32,6 +32,11 @@
Dodaje odwołanie do pakietu NuGet.
+
+ References another file-based app as a library.
+ References another file-based app as a library.
+
+ Adds a file reference.Dodaje odwołanie do pliku.
@@ -282,6 +287,11 @@
ścieżka'path' is a placeholder for a project file or directory path in a directive like '#:project path'.
+
+ path
+ path
+ 'path' is a placeholder for a file path in a directive like '#:ref path'.
+ Reverse 'for' statementOdwróć instrukcję „for”
diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.pt-BR.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.pt-BR.xlf
index 5452bb462c720..ef1e44f5528ab 100644
--- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.pt-BR.xlf
+++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.pt-BR.xlf
@@ -32,6 +32,11 @@
Adiciona uma referência de pacote NuGet.
+
+ References another file-based app as a library.
+ References another file-based app as a library.
+
+ Adds a file reference.Adiciona uma referência de arquivo.
@@ -282,6 +287,11 @@
caminho'path' is a placeholder for a project file or directory path in a directive like '#:project path'.
+
+ path
+ path
+ 'path' is a placeholder for a file path in a directive like '#:ref path'.
+ Reverse 'for' statementReverter instrução 'for'
diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.ru.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.ru.xlf
index 568e4ecd54764..a3e0de269e9ae 100644
--- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.ru.xlf
+++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.ru.xlf
@@ -32,6 +32,11 @@
Добавляет ссылку на пакет NuGet.
+
+ References another file-based app as a library.
+ References another file-based app as a library.
+
+ Adds a file reference.Добавляет ссылку на файл.
@@ -282,6 +287,11 @@
путь'path' is a placeholder for a project file or directory path in a directive like '#:project path'.
+
+ path
+ path
+ 'path' is a placeholder for a file path in a directive like '#:ref path'.
+ Reverse 'for' statementОбратить оператор "for"
diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.tr.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.tr.xlf
index 25cf782bbf0ee..63cdcbf2d4e66 100644
--- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.tr.xlf
+++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.tr.xlf
@@ -32,6 +32,11 @@
NNuGet paket başvurusu ekler.
+
+ References another file-based app as a library.
+ References another file-based app as a library.
+
+ Adds a file reference.Dosya başvurusu ekler.
@@ -282,6 +287,11 @@
yol'path' is a placeholder for a project file or directory path in a directive like '#:project path'.
+
+ path
+ path
+ 'path' is a placeholder for a file path in a directive like '#:ref path'.
+ Reverse 'for' statement'for' ifadesini tersine çevir
diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.zh-Hans.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.zh-Hans.xlf
index d08f578ddf866..c141b2154e816 100644
--- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.zh-Hans.xlf
+++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.zh-Hans.xlf
@@ -32,6 +32,11 @@
添加 NuGet 包引用。
+
+ References another file-based app as a library.
+ References another file-based app as a library.
+
+ Adds a file reference.添加文件引用。
@@ -282,6 +287,11 @@
路径'path' is a placeholder for a project file or directory path in a directive like '#:project path'.
+
+ path
+ path
+ 'path' is a placeholder for a file path in a directive like '#:ref path'.
+ Reverse 'for' statement反向“for”语句
diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.zh-Hant.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.zh-Hant.xlf
index 3608406388468..ee818731e54bb 100644
--- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.zh-Hant.xlf
+++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.zh-Hant.xlf
@@ -32,6 +32,11 @@
新增 NuGet 套件參考。
+
+ References another file-based app as a library.
+ References another file-based app as a library.
+
+ Adds a file reference.新增檔案參考。
@@ -282,6 +287,11 @@
路徑'path' is a placeholder for a project file or directory path in a directive like '#:project path'.
+
+ path
+ path
+ 'path' is a placeholder for a file path in a directive like '#:ref path'.
+ Reverse 'for' statement反向 'for' 陳述式