VisualStudioSymbolSearchService.cs 7.7 KB
Newer Older
1 2 3 4
// Copyright (c) Microsoft.  All Rights Reserved.  Licensed under the Apache License, Version 2.0.  See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
5
using System.Collections.Immutable;
6 7 8 9 10 11 12 13 14 15
using System.Composition;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.Packaging;
using Microsoft.CodeAnalysis.SymbolSearch;
using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem;
using Microsoft.VisualStudio.Settings;
16
using Microsoft.VisualStudio.Shell.Interop;
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
using Microsoft.VisualStudio.Shell.Settings;
using Roslyn.Utilities;
using VSShell = Microsoft.VisualStudio.Shell;

namespace Microsoft.VisualStudio.LanguageServices.SymbolSearch
{
    /// <summary>
    /// A service which enables searching for packages matching certain criteria.
    /// It works against an <see cref="Microsoft.CodeAnalysis.Elfie"/> database to find results.
    /// 
    /// This implementation also spawns a task which will attempt to keep that database up to
    /// date by downloading patches on a daily basis.
    /// </summary>
    [ExportWorkspaceService(typeof(ISymbolSearchService), ServiceLayer.Host), Shared]
    internal partial class VisualStudioSymbolSearchService : AbstractDelayStartedService, ISymbolSearchService
    {
33 34
        private readonly SemaphoreSlim _gate = new SemaphoreSlim(initialCount: 1);
        private ISymbolSearchUpdateEngine _updateEngine;
35

36
        private readonly VisualStudioWorkspaceImpl _workspace;
37 38
        private readonly IPackageInstallerService _installerService;
        private readonly string _localSettingsDirectory;
C
CyrusNajmabadi 已提交
39
        private readonly LogService _logService;
40 41

        private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
42 43 44 45 46 47 48 49 50

        [ImportingConstructor]
        public VisualStudioSymbolSearchService(
            VisualStudioWorkspaceImpl workspace,
            VSShell.SVsServiceProvider serviceProvider)
            : base(workspace, SymbolSearchOptions.Enabled,
                              SymbolSearchOptions.SuggestForTypesInReferenceAssemblies,
                              SymbolSearchOptions.SuggestForTypesInNuGetPackages)
        {
51
            _workspace = workspace;
52
            _installerService = workspace.Services.GetService<IPackageInstallerService>();
53
            _localSettingsDirectory = new ShellSettingsManager(serviceProvider).GetApplicationDataFolder(ApplicationDataFolder.LocalSettings);
54

C
CyrusNajmabadi 已提交
55
            _logService = new LogService((IVsActivityLog)serviceProvider.GetService(typeof(SVsActivityLog)));
56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
        }

        protected override void EnableService()
        {
            // When our service is enabled hook up to package source changes.
            // We need to know when the list of sources have changed so we can
            // kick off the work to process them.
            _installerService.PackageSourcesChanged += OnPackageSourcesChanged;
        }

        private void OnPackageSourcesChanged(object sender, EventArgs e)
        {
            StartWorking();
        }

        protected override void StartWorking()
        {
73
            // Kick off a database update.
74 75 76 77 78 79
            var sources = _installerService.PackageSources;

            // Always pull down the nuget.org index.  It contains the MS reference assembly index
            // inside of it.
            var allSources = sources.Concat(new PackageSource(
                SymbolSearchUpdateEngine.NugetOrgSource, source: null));
80

81 82 83 84 85 86
            foreach (var source in allSources)
            {
                Task.Run(() => UpdateSourceInBackgroundAsync(source.Name));
            }
        }

87 88 89 90 91 92
        private async Task<ISymbolSearchUpdateEngine> GetEngine(CancellationToken cancellationToken)
        {
            using (await _gate.DisposableWaitAsync(cancellationToken).ConfigureAwait(false))
            {
                if (_updateEngine == null)
                {
93
                    _updateEngine = await SymbolSearchUpdateEngineFactory.CreateEngineAsync(
94 95 96 97 98 99 100
                        _workspace, _logService, cancellationToken).ConfigureAwait(false);
                }

                return _updateEngine;
            }
        }

101
        private async Task UpdateSourceInBackgroundAsync(string sourceName)
102
        {
103
            var engine = await GetEngine(_cancellationTokenSource.Token).ConfigureAwait(false);
104
            await engine.UpdateContinuouslyAsync(sourceName, _localSettingsDirectory).ConfigureAwait(false);
105 106
        }

107
        public async Task<ImmutableArray<PackageWithTypeResult>> FindPackagesWithTypeAsync(
108 109
            string source, string name, int arity, CancellationToken cancellationToken)
        {
110
            var engine = await GetEngine(cancellationToken).ConfigureAwait(false);
111
            var allPackagesWithType = await engine.FindPackagesWithTypeAsync(
112
                source, name, arity).ConfigureAwait(false);
113

114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131
            return FilterAndOrderPackages(allPackagesWithType);
        }

        public async Task<ImmutableArray<PackageWithAssemblyResult>> FindPackagesWithAssemblyAsync(
            string source, string assemblyName, CancellationToken cancellationToken)
        {
            var engine = await GetEngine(cancellationToken).ConfigureAwait(false);
            var allPackagesWithAssembly = await engine.FindPackagesWithAssemblyAsync(
                source, assemblyName).ConfigureAwait(false);

            return FilterAndOrderPackages(allPackagesWithAssembly);
        }

        private ImmutableArray<TPackageResult> FilterAndOrderPackages<TPackageResult>(
            ImmutableArray<TPackageResult> allPackages) where TPackageResult : PackageResult
        {
            var packagesUsedInOtherProjects = new List<TPackageResult>();
            var packagesNotUsedInOtherProjects = new List<TPackageResult>();
132

133
            foreach (var package in allPackages)
134
            {
135 136 137
                var resultList = _installerService.GetInstalledVersions(package.PackageName).Any()
                    ? packagesUsedInOtherProjects
                    : packagesNotUsedInOtherProjects;
138

139
                resultList.Add(package);
140 141
            }

142
            var result = ArrayBuilder<TPackageResult>.GetInstance();
143

144
            // We always returm types from packages that we've use elsewhere in the project.
145
            result.AddRange(packagesUsedInOtherProjects);
146 147 148 149 150 151

            // For all other hits include as long as the popularity is high enough.  
            // Popularity ranks are in powers of two.  So if two packages differ by 
            // one rank, then one is at least twice as popular as the next.  Two 
            // ranks would be four times as popular.  Three ranks = 8 times,  etc. 
            // etc.  We keep packages that within 1 rank of the best package we find.
152
            int? bestRank = packagesUsedInOtherProjects.LastOrDefault()?.Rank;
153
            foreach (var packageWithType in packagesNotUsedInOtherProjects)
154 155 156 157 158 159
            {
                var rank = packageWithType.Rank;
                bestRank = bestRank == null ? rank : Math.Max(bestRank.Value, rank);

                if (Math.Abs(bestRank.Value - rank) > 1)
                {
160
                    break;
161 162
                }

163
                result.Add(packageWithType);
164
            }
165 166

            return result.ToImmutableAndFree();
167 168
        }

169
        public async Task<ImmutableArray<ReferenceAssemblyWithTypeResult>> FindReferenceAssembliesWithTypeAsync(
170 171
            string name, int arity, CancellationToken cancellationToken)
        {
172
            var engine = await GetEngine(cancellationToken).ConfigureAwait(false);
173
            return await engine.FindReferenceAssembliesWithTypeAsync(
174
                name, arity).ConfigureAwait(false);
175 176
        }
    }
177
}