File: EmbeddedLanguages\DateAndTime\EmbeddedCompletionContext.cs
Web Access
Project: src\src\Features\Core\Portable\Microsoft.CodeAnalysis.Features.csproj (Microsoft.CodeAnalysis.Features)
// 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.Globalization;
using Microsoft.CodeAnalysis.Completion;
using Microsoft.CodeAnalysis.EmbeddedLanguages.VirtualChars;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Text;
 
namespace Microsoft.CodeAnalysis.Features.EmbeddedLanguages.DateAndTime;
 
internal sealed partial class DateAndTimeEmbeddedCompletionProvider
{
    private readonly struct EmbeddedCompletionContext
    {
        private static readonly DateTime s_exampleDateTime = DateTime.Parse("2009-06-15T13:45:30.1234567");
        private static readonly CultureInfo s_enUsCulture = CultureInfo.GetCultureInfo("en-US");
 
        private readonly ArrayBuilder<DateAndTimeItem> _items;
        private readonly TextSpan _replacementSpan;
 
        /// <summary>
        /// The portion of the user string token prior to the section we're replacing.  Used for building the
        /// example format to present.
        /// </summary>
        private readonly string _userFormatPrefix;
 
        /// <summary>
        /// The portion of the user string token after to the section we're replacing.  Used for building the
        /// example format to present.
        /// </summary>
        private readonly string _userFormatSuffix;
 
        public EmbeddedCompletionContext(
            SourceText text,
            CompletionContext context,
            VirtualCharSequence virtualChars,
            ArrayBuilder<DateAndTimeItem> items)
        {
            _items = items;
 
            var startPosition = context.Position;
            while (char.IsLetter(text[startPosition - 1]))
            {
                startPosition--;
            }
 
            _replacementSpan = TextSpan.FromBounds(startPosition, context.Position);
 
            (_userFormatPrefix, _userFormatSuffix) = GetUserFormatParts(virtualChars, startPosition, context.Position);
        }
 
        private static (string prefix, string suffix) GetUserFormatParts(
            VirtualCharSequence virtualChars, int startPosition, int endPosition)
        {
            virtualChars = virtualChars.IsDefault ? VirtualCharSequence.Empty : virtualChars;
 
            using var _1 = PooledStringBuilder.GetInstance(out var prefix);
            using var _2 = PooledStringBuilder.GetInstance(out var suffix);
            foreach (var ch in virtualChars)
            {
                if (ch.Span.End <= startPosition)
                    ch.AppendTo(prefix);
                else if (ch.Span.Start >= endPosition)
                    ch.AppendTo(suffix);
            }
 
            return (prefix.ToString(), suffix.ToString());
        }
 
        private void AddExamples(ArrayBuilder<string> examples, bool standard, string displayText)
        {
            var userFormat = _userFormatPrefix + displayText + _userFormatSuffix;
 
            var primaryCulture = CultureInfo.CurrentCulture;
            var secondaryCulture = s_enUsCulture;
            var hideCulture = primaryCulture.Equals(secondaryCulture);
 
            AddExample(examples, standard, userFormat, primaryCulture, hideCulture);
            AddExample(examples, standard, userFormat, secondaryCulture, hideCulture);
            AddExample(examples, standard, displayText, primaryCulture, hideCulture);
            AddExample(examples, standard, displayText, secondaryCulture, hideCulture);
        }
 
        private static void AddExample(
            ArrayBuilder<string> examples, bool standard, string displayText, CultureInfo culture, bool hideCulture)
        {
            // Single letter custom strings need a %, or else they're interpreted as a format
            // standard format string (and will throw a format exception).
            var formatString = !standard && displayText.Length == 1
                ? "%" + displayText
                : displayText;
 
            if (formatString == "")
                return;
 
            // Format string may be invalid.  Just don't show anything in that case.
            string formattedDate;
            try
            {
                formattedDate = s_exampleDateTime.ToString(formatString, culture);
            }
            catch (FormatException)
            {
                return;
            }
            catch (ArgumentOutOfRangeException)
            {
                return;
            }
 
            var example = hideCulture
                ? $"   {displayText}{formattedDate}"
                : $"   {displayText} ({culture}) → {formattedDate}";
            if (!examples.Contains(example))
                examples.Add(example);
        }
 
        public void AddStandard(string displayText, string suffix, string description, bool isDefault = false)
            => Add(displayText, suffix, description, standard: true, isDefault);
 
        public void AddCustom(string displayText, string suffix, string description)
            => Add(displayText, suffix, description, standard: false, isDefault: false);
 
        private void Add(string displayText, string suffix, string description, bool standard, bool isDefault)
        {
            using var _1 = PooledStringBuilder.GetInstance(out var descriptionBuilder);
            using var _2 = ArrayBuilder<string>.GetInstance(out var examples);
 
            AddExamples(examples, standard, displayText);
 
            descriptionBuilder.AppendLine(
                examples.Count == 1 ? FeaturesResources.Example : FeaturesResources.Examples);
            foreach (var example in examples)
                descriptionBuilder.AppendLine(example);
 
            descriptionBuilder.AppendLine();
            descriptionBuilder.Append(description);
 
            _items.Add(new DateAndTimeItem(
                displayText, suffix, descriptionBuilder.ToString(),
                CompletionChange.Create(
                    new TextChange(_replacementSpan, displayText)),
                isDefault));
        }
    }
}