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' statement Obrá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' statement for-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' statement Invertir 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' statement Inverser 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' statement Inverti 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' statement Odwróć 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' statement Reverter 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' 陳述式