|
// 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.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Text;
using Microsoft.CodeAnalysis.Host.Mef;
namespace Microsoft.CodeAnalysis.CSharp.Completion.Providers;
/// <summary>
/// Base type for completion of "app directives" used in file-based programs.
/// See also https://github.com/dotnet/sdk/blob/main/documentation/general/dotnet-run-file.md#directives-for-project-metadata
/// Examples:
/// - '#:property LangVersion=preview'
/// - '#:project path/to/OtherProject.csproj'
/// - '#:package MyNugetPackage@Version'
/// </summary>
internal abstract class AbstractAppDirectiveCompletionProvider : LSPCompletionProvider
{
/// <summary>The directive kind. For example, `package` in `#:package MyNugetPackage@Version`.</summary>
/// <remarks>Term defined in feature doc: https://github.com/dotnet/sdk/blob/main/documentation/general/dotnet-run-file.md#directives-for-project-metadata</remarks>
protected abstract string DirectiveKind { get; }
public sealed override bool IsInsertionTrigger(SourceText text, int characterPosition, CompletionOptions options)
{
return TriggerCharacters.Contains(text[characterPosition])
|| (options.TriggerOnTypingLetters && CompletionUtilities.IsStartingNewWord(text, characterPosition));
}
public override ImmutableHashSet<char> TriggerCharacters { get; } = [':'];
internal sealed override string Language => LanguageNames.CSharp;
public override async Task ProvideCompletionsAsync(CompletionContext context)
{
var tree = await context.Document.GetRequiredSyntaxTreeAsync(context.CancellationToken).ConfigureAwait(false);
if (!tree.Options.Features.ContainsKey("FileBasedProgram"))
return;
var token = tree.GetRoot(context.CancellationToken).FindTokenOnLeftOfPosition(context.Position, includeDirectives: true);
if (token.Parent is not IgnoredDirectiveTriviaSyntax ignoredDirective)
return;
// Note that in the `#: $$` case, the whitespace is trailing trivia on the colon-token.
if (token == ignoredDirective.ColonToken)
{
AddDirectiveKindCompletion(context);
}
else if (token == ignoredDirective.Content)
{
// Consider a test case like '#: pro$$ Name=Value', where we may want to offer 'property' as a completion item:
// We know that 'token.Text == "pro Name=Value"', and, the below expressions correspond to text positions as shown:
// #: pro Name=Value
// │ │
// │ └─context.Position
// └────token.SpanStart
var textLeftOfCaret = token.Text.AsSpan(start: 0, length: context.Position - token.SpanStart);
if (DirectiveKind.StartsWith(textLeftOfCaret))
{
AddDirectiveKindCompletion(context);
}
}
}
protected abstract void AddDirectiveKindCompletion(CompletionContext context);
}
|