File: Protocol\Internal\Efficiency\OptimizedVSCompletionListJsonConverter.cs
Web Access
Project: src\src\LanguageServer\Protocol\Microsoft.CodeAnalysis.LanguageServer.Protocol.csproj (Microsoft.CodeAnalysis.LanguageServer.Protocol)
// 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.Concurrent;
using System.Collections.Generic;
using System.Text.Json;
using System.Text.Json.Serialization;
using Roslyn.Core.Imaging;
 
namespace Roslyn.LanguageServer.Protocol;
 
internal class OptimizedVSCompletionListJsonConverter : JsonConverter<OptimizedVSCompletionList>
{
    public static readonly OptimizedVSCompletionListJsonConverter Instance = new();
    private static readonly ConcurrentDictionary<ImageId, string> IconRawJson = new ConcurrentDictionary<ImageId, string>();
 
    public override OptimizedVSCompletionList Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => throw new NotImplementedException();
 
    public override void Write(Utf8JsonWriter writer, OptimizedVSCompletionList value, JsonSerializerOptions options)
    {
        if (value is null)
        {
            writer.WriteNullValue();
            return;
        }
 
        var completionList = (VSInternalCompletionList)value;
 
        writer.WriteStartObject();
 
        if (completionList.SuggestionMode)
        {
            writer.WriteBoolean(VSInternalCompletionList.SuggestionModeSerializedName, completionList.SuggestionMode);
        }
        else
        {
            // Default is "false" so no need to serialize
        }
 
        if (completionList.ContinueCharacters != null && completionList.ContinueCharacters.Length > 0)
        {
            writer.WritePropertyName(VSInternalCompletionList.ContinueCharactersSerializedName);
            JsonSerializer.Serialize(writer, completionList.ContinueCharacters, options);
        }
 
        if (completionList.Data != null)
        {
            writer.WritePropertyName(VSInternalCompletionList.DataSerializedName);
            JsonSerializer.Serialize(writer, completionList.Data, options);
        }
 
        if (completionList.CommitCharacters != null)
        {
            writer.WritePropertyName(VSInternalCompletionList.CommitCharactersSerializedName);
            JsonSerializer.Serialize(writer, completionList.CommitCharacters, options);
        }
 
        // this is a required property per the LSP spec
        writer.WriteBoolean("isIncomplete", completionList.IsIncomplete);
 
        writer.WritePropertyName("items");
        if (completionList.Items == null || completionList.Items.Length == 0)
        {
            writer.WriteRawValue("[]"[]");
        }
        else
        {
            writer.WriteStartArray();
 
            var itemRawJsonCache = new Dictionary<object, string>(capacity: 1);
 
            foreach (var completionItem in completionList.Items)
            {
                if (completionItem == null)
                {
                    continue;
                }
 
                WriteCompletionItem(writer, completionItem, options, itemRawJsonCache);
            }
 
            writer.WriteEndArray();
        }
 
        if (completionList.ItemDefaults != null)
        {
            writer.WritePropertyName("itemDefaults");
            JsonSerializer.Serialize(writer, completionList.ItemDefaults, options);
        }
 
        writer.WriteEndObject();
    }
 
    private static void WriteCompletionItem(Utf8JsonWriter writer, CompletionItem completionItem, JsonSerializerOptions options, Dictionary<object, string> itemRawJsonCache)
    {
        writer.WriteStartObject();
 
        if (completionItem is VSInternalCompletionItem vsCompletionItem)
        {
            if (vsCompletionItem.Icon != null)
            {
                if (!IconRawJson.TryGetValue(vsCompletionItem.Icon.ImageId, out var jsonString))
                {
                    jsonString = JsonSerializer.Serialize(vsCompletionItem.Icon, options);
                    IconRawJson.TryAdd(vsCompletionItem.Icon.ImageId, jsonString);
                }
                writer.WritePropertyName(VSInternalCompletionItem.IconSerializedName);
                writer.WriteRawValue(jsonString);
            }
 
            if (vsCompletionItem.Description != null)
            {
                writer.WritePropertyName(VSInternalCompletionItem.DescriptionSerializedName);
                JsonSerializer.Serialize(writer, vsCompletionItem.Description, options);
            }
 
            if (vsCompletionItem.VsCommitCharacters?.Value is string[] basicCommitCharacters
                && basicCommitCharacters.Length > 0)
            {
                if (!itemRawJsonCache.TryGetValue(basicCommitCharacters, out var jsonString))
                {
                    jsonString = JsonSerializer.Serialize(basicCommitCharacters, options);
                    itemRawJsonCache.Add(basicCommitCharacters, jsonString);
                }
 
                writer.WritePropertyName(VSInternalCompletionItem.VsCommitCharactersSerializedName);
                writer.WriteRawValue(jsonString);
            }
            else if (vsCompletionItem.VsCommitCharacters?.Value is VSInternalCommitCharacter[] augmentedCommitCharacters
                && augmentedCommitCharacters.Length > 0)
            {
                if (!itemRawJsonCache.TryGetValue(augmentedCommitCharacters, out var jsonString))
                {
                    jsonString = JsonSerializer.Serialize(augmentedCommitCharacters, options);
                    itemRawJsonCache.Add(augmentedCommitCharacters, jsonString);
                }
 
                writer.WritePropertyName(VSInternalCompletionItem.VsCommitCharactersSerializedName);
                writer.WriteRawValue(jsonString);
            }
 
            if (vsCompletionItem.VsResolveTextEditOnCommit)
            {
                writer.WriteBoolean(VSInternalCompletionItem.VsResolveTextEditOnCommitName, vsCompletionItem.VsResolveTextEditOnCommit);
            }
        }
 
        var label = completionItem.Label;
        writer.WriteString("label", label);
 
        if (completionItem.LabelDetails != null)
        {
            writer.WritePropertyName("labelDetails");
            JsonSerializer.Serialize(writer, completionItem.LabelDetails, options);
        }
 
        writer.WriteNumber("kind", (int)completionItem.Kind);
 
        if (completionItem.Detail != null)
        {
            writer.WriteString("detail", completionItem.Detail);
        }
 
        if (completionItem.Documentation != null)
        {
            writer.WritePropertyName("documentation");
            JsonSerializer.Serialize(writer, completionItem.Documentation, options);
        }
 
        // Only render preselect if it's "true"
        if (completionItem.Preselect)
        {
            writer.WriteBoolean("preselect", completionItem.Preselect);
        }
 
        if (completionItem.SortText != null && !string.Equals(completionItem.SortText, label, StringComparison.Ordinal))
        {
            writer.WriteString("sortText", completionItem.SortText);
        }
 
        if (completionItem.FilterText != null && !string.Equals(completionItem.FilterText, label, StringComparison.Ordinal))
        {
            writer.WriteString("filterText", completionItem.FilterText);
        }
 
        if (completionItem.InsertText != null && !string.Equals(completionItem.InsertText, label, StringComparison.Ordinal))
        {
            writer.WriteString("insertText", completionItem.InsertText);
        }
 
        if (completionItem.InsertTextFormat != default && completionItem.InsertTextFormat != InsertTextFormat.Plaintext)
        {
            writer.WriteNumber("insertTextFormat", (int)completionItem.InsertTextFormat);
        }
 
        if (completionItem.TextEdit != null)
        {
            writer.WritePropertyName("textEdit");
            JsonSerializer.Serialize(writer, completionItem.TextEdit, options);
        }
 
        if (completionItem.TextEditText != null)
        {
            writer.WritePropertyName("textEditText");
            JsonSerializer.Serialize(writer, completionItem.TextEditText, options);
        }
 
        if (completionItem.AdditionalTextEdits != null && completionItem.AdditionalTextEdits.Length > 0)
        {
            writer.WritePropertyName("additionalTextEdits");
            JsonSerializer.Serialize(writer, completionItem.AdditionalTextEdits, options);
        }
 
        if (completionItem.CommitCharacters != null && completionItem.CommitCharacters.Length > 0)
        {
            if (!itemRawJsonCache.TryGetValue(completionItem.CommitCharacters, out var jsonString))
            {
                jsonString = JsonSerializer.Serialize(completionItem.CommitCharacters, options);
                itemRawJsonCache.Add(completionItem.CommitCharacters, jsonString);
            }
 
            writer.WritePropertyName("commitCharacters");
            writer.WriteRawValue(jsonString);
        }
 
        if (completionItem.Command != null)
        {
            writer.WritePropertyName("command");
            JsonSerializer.Serialize(writer, completionItem.Command, options);
        }
 
        if (completionItem.Data != null)
        {
            writer.WritePropertyName("data");
            JsonSerializer.Serialize(writer, completionItem.Data, options);
        }
 
        writer.WriteEndObject();
    }
}