<commit_before>﻿// 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.

#nullable enable

using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Classification;
using Microsoft.CodeAnalysis.Editor;
using Microsoft.CodeAnalysis.FindSymbols;
using Microsoft.CodeAnalysis.FindSymbols.Finders;
using Microsoft.CodeAnalysis.FindUsages;
using Microsoft.VisualStudio.LanguageServer.Protocol;
using Microsoft.VisualStudio.Text.Adornments;
using Roslyn.Utilities;
using LSP = Microsoft.VisualStudio.LanguageServer.Protocol;

namespace Microsoft.CodeAnalysis.LanguageServer.CustomProtocol
{
    internal class FindUsagesLSPContext : FindUsagesContext
    {

        private const int MaxResultsChunkSize = 32;

        private int _id = 0;

        private static readonly SemaphoreSlim s_semaphore = new SemaphoreSlim(1);

        private readonly Dictionary<DefinitionItem, int> _definitionToId =
            new Dictionary<DefinitionItem, int>();

        private readonly List<VSReferenceItem> _resultsChunk =
            new List<VSReferenceItem>();

        private readonly IProgress<object[]> _progress;
        private readonly Document _document;
        private readonly int _position;
        private readonly IMetadataAsSourceFileService _metadataAsSourceFileService;

        public override CancellationToken CancellationToken { get; }

        public FindUsagesLSPContext(
            IProgress<object[]> progress,
            Document document,
            int position,
            IMetadataAsSourceFileService metadataAsSourceFileService,
            CancellationToken cancellationToken)
        {
            _progress = progress;
            _document = document;
            _position = position;
            _metadataAsSourceFileService = metadataAsSourceFileService;

            CancellationToken = cancellationToken;
        }

        public async override Task OnCompletedAsync()
        {
            await s_semaphore.WaitAsync(CancellationToken).ConfigureAwait(false);
            if (_resultsChunk.IsEmpty())
            {
                return;
            }

            var results = _resultsChunk.ToArray();
            _resultsChunk.Clear();

            s_semaphore.Release();
            _progress.Report(results);
        }

        public async override Task OnDefinitionFoundAsync(DefinitionItem definition)
        {
            await s_semaphore.WaitAsync(CancellationToken).ConfigureAwait(false);

            if (!_definitionToId.ContainsKey(definition))
            {
                // Assinging a new id to the definition
                _definitionToId.Add(definition, _id);

                // Creating a new VSReferenceItem for the definition
                var definitionItem = await GenerateVSReferenceItemAsync(
                    _id, definitionId: _id, _document, _position, definition.SourceSpans.FirstOrDefault(),
                    definition.DisplayableProperties, _metadataAsSourceFileService, definition.GetClassifiedText(),
                    symbolUsageInfo: null, CancellationToken).ConfigureAwait(false);

                if (definitionItem.Location != null)
                {
                    AddAndReportResultsIfAtMax(definitionItem);
                    _id++;
                }
            }

            s_semaphore.Release();
        }

        public async override Task OnReferenceFoundAsync(SourceReferenceItem reference)
        {
            await s_semaphore.WaitAsync(CancellationToken).ConfigureAwait(false);

            if (_definitionToId.TryGetValue(reference.Definition, out var definitionId))
            {
                // Creating a new VSReferenceItem for the reference
                var referenceItem = await GenerateVSReferenceItemAsync(
                    _id, definitionId, _document, _position, reference.SourceSpan,
                    reference.AdditionalProperties, _metadataAsSourceFileService, definitionText: null,
                    reference.SymbolUsageInfo, CancellationToken).ConfigureAwait(false);

                if (referenceItem.Location != null)
                {
                    AddAndReportResultsIfAtMax(referenceItem);
                    _id++;
                }
            }

            s_semaphore.Release();
        }

        private void AddAndReportResultsIfAtMax(VSReferenceItem item)
        {
            _resultsChunk.Add(item);
            if (_resultsChunk.Count < MaxResultsChunkSize)
            {
                return;
            }

            _progress.Report(_resultsChunk.ToArray());
            _resultsChunk.Clear();
        }

        private static async Task<LSP.VSReferenceItem> GenerateVSReferenceItemAsync(
            int id,
            int? definitionId,
            Document document,
            int position,
            DocumentSpan documentSpan,
            ImmutableDictionary<string, string> properties,
            IMetadataAsSourceFileService metadataAsSourceFileService,
            ClassifiedTextElement? definitionText,
            SymbolUsageInfo? symbolUsageInfo,
            CancellationToken cancellationToken)
        {
            LSP.Location? location = null;

            // If we have no source span, our location may be in metadata.
            if (documentSpan == default)
            {
                var symbol = await SymbolFinder.FindSymbolAtPositionAsync(document, position, cancellationToken).ConfigureAwait(false);
                if (symbol != null && symbol.Locations != null && !symbol.Locations.IsEmpty && symbol.Locations.First().IsInMetadata)
                {
                    var declarationFile = await metadataAsSourceFileService.GetGeneratedFileAsync(
                        document.Project, symbol, allowDecompilation: false, cancellationToken).ConfigureAwait(false);

                    var linePosSpan = declarationFile.IdentifierLocation.GetLineSpan().Span;
                    location = new LSP.Location
                    {
                        Uri = new Uri(declarationFile.FilePath),
                        Range = ProtocolConversions.LinePositionToRange(linePosSpan),
                    };
                }
            }
            else
            {
                location = await ProtocolConversions.DocumentSpanToLocationAsync(documentSpan, cancellationToken).ConfigureAwait(false);
            }

            // TO-DO: The Origin property should be added once Rich-Nav is completed.
            // https://github.com/dotnet/roslyn/issues/42847
            var result = new LSP.VSReferenceItem
            {
                ContainingMember = properties.TryGetValue(
                    AbstractReferenceFinder.ContainingMemberInfoPropertyName, out var referenceContainingMember) ? referenceContainingMember : null,
                ContainingType = properties.TryGetValue(
                    AbstractReferenceFinder.ContainingTypeInfoPropertyName, out var referenceContainingType) ? referenceContainingType : null,
                DefinitionId = definitionId,
                DefinitionText = definitionText,    // Only definitions should have a non-null DefinitionText
                DisplayPath = location?.Uri.LocalPath,
                DocumentName = documentSpan == default ? null : documentSpan.Document.Name,
                Id = id,
                Kind = symbolUsageInfo.HasValue ? ProtocolConversions.SymbolUsageInfoToReferenceKinds(symbolUsageInfo.Value) : new ReferenceKind[] { },
                Location = location,
                ProjectName = documentSpan == default ? null : documentSpan.Document.Project.Name,
                ResolutionStatus = ResolutionStatusKind.ConfirmedAsReference,
            };

            // Properly assigning the text property.
            if (id == definitionId)
            {
                result.Text = definitionText;
            }
            else if (documentSpan != default)
            {
                var classifiedSpansAndHighlightSpan = await ClassifiedSpansAndHighlightSpanFactory.ClassifyAsync(
                    documentSpan, cancellationToken).ConfigureAwait(false);
                var classifiedSpans = classifiedSpansAndHighlightSpan.ClassifiedSpans;
                var docText = await documentSpan.Document.GetTextAsync(cancellationToken).ConfigureAwait(false);

                result.Text = new ClassifiedTextElement(
                    classifiedSpans.Select(cspan => new ClassifiedTextRun(cspan.ClassificationType, docText.ToString(cspan.TextSpan))));
            }

            return result;
        }
    }
}
<commit_msg>SemaphoreSlim fix<commit_after>﻿// 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.

#nullable enable

using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Classification;
using Microsoft.CodeAnalysis.Editor;
using Microsoft.CodeAnalysis.Editor.Implementation.Classification;
using Microsoft.CodeAnalysis.FindSymbols;
using Microsoft.CodeAnalysis.FindSymbols.Finders;
using Microsoft.CodeAnalysis.FindUsages;
using Microsoft.VisualStudio.LanguageServer.Protocol;
using Microsoft.VisualStudio.Text.Adornments;
using Roslyn.Utilities;
using LSP = Microsoft.VisualStudio.LanguageServer.Protocol;

namespace Microsoft.CodeAnalysis.LanguageServer.CustomProtocol
{
    internal class FindUsagesLSPContext : FindUsagesContext
    {

        private const int MaxResultsChunkSize = 32;

        private int _id = 0;

        private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1);

        private readonly Dictionary<DefinitionItem, int> _definitionToId =
            new Dictionary<DefinitionItem, int>();

        private readonly List<VSReferenceItem> _resultsChunk =
            new List<VSReferenceItem>();

        private readonly IProgress<object[]> _progress;
        private readonly Document _document;
        private readonly int _position;
        private readonly IMetadataAsSourceFileService _metadataAsSourceFileService;

        public override CancellationToken CancellationToken { get; }

        public FindUsagesLSPContext(
            IProgress<object[]> progress,
            Document document,
            int position,
            IMetadataAsSourceFileService metadataAsSourceFileService,
            CancellationToken cancellationToken)
        {
            _progress = progress;
            _document = document;
            _position = position;
            _metadataAsSourceFileService = metadataAsSourceFileService;

            CancellationToken = cancellationToken;
        }

        public async override Task OnCompletedAsync()
        {
            var results = Array.Empty<VSReferenceItem>();
            using (await _semaphore.DisposableWaitAsync(CancellationToken).ConfigureAwait(false))
            {
                if (_resultsChunk.IsEmpty())
                {
                    return;
                }

                results = _resultsChunk.ToArray();
                _resultsChunk.Clear();
            }

            _progress.Report(results);
        }

        public async override Task OnDefinitionFoundAsync(DefinitionItem definition)
        {
            using (await _semaphore.DisposableWaitAsync(CancellationToken).ConfigureAwait(false))
            {
                if (!_definitionToId.ContainsKey(definition))
                {
                    // Assinging a new id to the definition
                    _definitionToId.Add(definition, _id);

                    // Creating a new VSReferenceItem for the definition
                    var definitionItem = await GenerateVSReferenceItemAsync(
                        _id, definitionId: _id, _document, _position, definition.SourceSpans.FirstOrDefault(),
                        definition.DisplayableProperties, _metadataAsSourceFileService, definition.GetClassifiedText(),
                        symbolUsageInfo: null, CancellationToken).ConfigureAwait(false);

                    if (definitionItem.Location != null)
                    {
                        AddAndReportResultsIfAtMax(definitionItem);
                        _id++;
                    }
                }
            }
        }

        public async override Task OnReferenceFoundAsync(SourceReferenceItem reference)
        {
            using (await _semaphore.DisposableWaitAsync(CancellationToken).ConfigureAwait(false))
            {
                if (_definitionToId.TryGetValue(reference.Definition, out var definitionId))
                {
                    // Creating a new VSReferenceItem for the reference
                    var referenceItem = await GenerateVSReferenceItemAsync(
                        _id, definitionId, _document, _position, reference.SourceSpan,
                        reference.AdditionalProperties, _metadataAsSourceFileService, definitionText: null,
                        reference.SymbolUsageInfo, CancellationToken).ConfigureAwait(false);

                    if (referenceItem.Location != null)
                    {
                        AddAndReportResultsIfAtMax(referenceItem);
                        _id++;
                    }
                }
            }
        }

        private void AddAndReportResultsIfAtMax(VSReferenceItem item)
        {
            _resultsChunk.Add(item);
            if (_resultsChunk.Count < MaxResultsChunkSize)
            {
                return;
            }

            _progress.Report(_resultsChunk.ToArray());
            _resultsChunk.Clear();
        }

        private static async Task<LSP.VSReferenceItem> GenerateVSReferenceItemAsync(
            int id,
            int? definitionId,
            Document document,
            int position,
            DocumentSpan documentSpan,
            ImmutableDictionary<string, string> properties,
            IMetadataAsSourceFileService metadataAsSourceFileService,
            ClassifiedTextElement? definitionText,
            SymbolUsageInfo? symbolUsageInfo,
            CancellationToken cancellationToken)
        {
            LSP.Location? location = null;

            // If we have no source span, our location may be in metadata.
            if (documentSpan == default)
            {
                var symbol = await SymbolFinder.FindSymbolAtPositionAsync(document, position, cancellationToken).ConfigureAwait(false);
                if (symbol != null && symbol.Locations != null && !symbol.Locations.IsEmpty && symbol.Locations.First().IsInMetadata)
                {
                    var declarationFile = await metadataAsSourceFileService.GetGeneratedFileAsync(
                        document.Project, symbol, allowDecompilation: false, cancellationToken).ConfigureAwait(false);

                    var linePosSpan = declarationFile.IdentifierLocation.GetLineSpan().Span;
                    location = new LSP.Location
                    {
                        Uri = new Uri(declarationFile.FilePath),
                        Range = ProtocolConversions.LinePositionToRange(linePosSpan),
                    };
                }
            }
            else
            {
                location = await ProtocolConversions.DocumentSpanToLocationAsync(documentSpan, cancellationToken).ConfigureAwait(false);
            }

            // TO-DO: The Origin property should be added once Rich-Nav is completed.
            // https://github.com/dotnet/roslyn/issues/42847
            var result = new LSP.VSReferenceItem
            {
                ContainingMember = properties.TryGetValue(
                    AbstractReferenceFinder.ContainingMemberInfoPropertyName, out var referenceContainingMember) ? referenceContainingMember : null,
                ContainingType = properties.TryGetValue(
                    AbstractReferenceFinder.ContainingTypeInfoPropertyName, out var referenceContainingType) ? referenceContainingType : null,
                DefinitionId = definitionId,
                DefinitionText = definitionText,    // Only definitions should have a non-null DefinitionText
                DisplayPath = location?.Uri.LocalPath,
                DocumentName = documentSpan == default ? null : documentSpan.Document.Name,
                Id = id,
                Kind = symbolUsageInfo.HasValue ? ProtocolConversions.SymbolUsageInfoToReferenceKinds(symbolUsageInfo.Value) : new ReferenceKind[] { },
                Location = location,
                ProjectName = documentSpan == default ? null : documentSpan.Document.Project.Name,
                ResolutionStatus = ResolutionStatusKind.ConfirmedAsReference,
            };

            // Properly assigning the text property.
            if (id == definitionId)
            {
                result.Text = definitionText;
            }
            else if (documentSpan != default)
            {
                var classifiedSpansAndHighlightSpan = await ClassifiedSpansAndHighlightSpanFactory.ClassifyAsync(
                    documentSpan, cancellationToken).ConfigureAwait(false);
                var classifiedSpans = classifiedSpansAndHighlightSpan.ClassifiedSpans;
                var docText = await documentSpan.Document.GetTextAsync(cancellationToken).ConfigureAwait(false);

                result.Text = new ClassifiedTextElement(
                    classifiedSpans.Select(cspan => new ClassifiedTextRun(cspan.ClassificationType, docText.ToString(cspan.TextSpan))));
            }

            return result;
        }
    }
}

{'fraction_non_alphanumeric': 0.06538976993522448, 'fraction_numerical': 0.0010051373687737324, 'mean_word_length': 2.0824440619621343, 'pattern_counts': {'":': 0, '<': 22, '<?xml version=': 0, '>': 22, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6891684', 'n_tokens_mistral': 4111, 'n_tokens_neox': 3958, 'n_words': 896}