Extensions.cs 7.5 KB
Newer Older
J
Jonathon Marolf 已提交
1 2 3
// 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.
4

5 6
#nullable enable

7
using System;
8
using System.Collections.Immutable;
9 10 11 12
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.FindUsages;
D
David Barbet 已提交
13
using Microsoft.CodeAnalysis.Host;
14
using Microsoft.CodeAnalysis.Shared.Extensions;
15 16 17 18 19 20 21 22
using Microsoft.CodeAnalysis.Text;
using Microsoft.VisualStudio.LanguageServer.Protocol;
using Microsoft.VisualStudio.Text.Adornments;

namespace Microsoft.CodeAnalysis.LanguageServer
{
    internal static class Extensions
    {
23
        public static Uri GetURI(this TextDocument document)
24
        {
25
            return ProtocolConversions.GetUriFromFilePath(document.FilePath);
26 27
        }

28
        public static ImmutableArray<Document> GetDocuments(this Solution solution, Uri documentUri)
29
        {
M
Marco Goertz 已提交
30
            return GetDocuments<Document>(solution, documentUri, (s, i) => s.GetRequiredDocument(i));
31 32
        }

M
Marco Goertz 已提交
33
        public static ImmutableArray<TextDocument> GetTextDocuments(this Solution solution, Uri documentUri)
34
        {
M
Marco Goertz 已提交
35
            return GetDocuments<TextDocument>(solution, documentUri, (s, i) => s.GetRequiredTextDocument(i));
36 37
        }

M
Marco Goertz 已提交
38
        private static ImmutableArray<T> GetDocuments<T>(this Solution solution, Uri documentUri, Func<Solution, DocumentId, T> getDocument) where T : TextDocument
39 40 41 42 43 44 45 46 47 48 49
        {
            // TODO: we need to normalize this. but for now, we check both absolute and local path
            //       right now, based on who calls this, solution might has "/" or "\\" as directory
            //       separator
            var documentIds = solution.GetDocumentIdsWithFilePath(documentUri.AbsolutePath);

            if (!documentIds.Any())
            {
                documentIds = solution.GetDocumentIdsWithFilePath(documentUri.LocalPath);
            }

M
Marco Goertz 已提交
50 51 52 53 54 55
            return documentIds.SelectAsArray(id => getDocument(solution, id));
        }

        public static ImmutableArray<Document> GetDocuments(this ILspSolutionProvider solutionProvider, Uri uri, string? clientName)
        {
            return GetDocuments<Document>(solutionProvider, uri, (s, u, c) => s.GetDocuments(u), clientName);
56 57 58 59
        }

        public static ImmutableArray<TextDocument> GetTextDocuments(this ILspSolutionProvider solutionProvider, Uri uri, string? clientName)
        {
M
Marco Goertz 已提交
60 61 62 63 64 65
            return GetDocuments<TextDocument>(solutionProvider, uri, (s, u, c) => s.GetTextDocuments(u), clientName);
        }

        private static ImmutableArray<T> GetDocuments<T>(this ILspSolutionProvider solutionProvider, Uri uri, Func<ILspSolutionProvider, Uri, string?, ImmutableArray<T>> getDocuments, string? clientName) where T : TextDocument
        {
            var documents = getDocuments(solutionProvider, uri, clientName);
66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85

            // If we don't have a client name, then we're done filtering
            if (clientName == null)
            {
                return documents;
            }

            // We have a client name, so we need to filter to only documents that match that name
            return documents.WhereAsArray(document =>
            {
                var documentPropertiesService = document.Services.GetService<DocumentPropertiesService>();

                // When a client name is specified, only return documents that have a matching client name.
                // Allows the razor lsp server to return results only for razor documents.
                // This workaround should be removed when https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1106064/
                // is fixed (so that the razor language server is only asked about razor buffers).
                return Equals(documentPropertiesService?.DiagnosticsLspClientName, clientName);
            });
        }

M
Marco Goertz 已提交
86 87 88 89 90
        public static Document? GetDocument(this ILspSolutionProvider solutionProvider, TextDocumentIdentifier documentIdentifier, string? clientName = null)
        {
            return GetDocument<Document>(solutionProvider, documentIdentifier, (s, d, c) => s.GetDocuments(d, c), clientName);
        }

91 92
        public static TextDocument? GetTextDocument(this ILspSolutionProvider solutionProvider, TextDocumentIdentifier documentIdentifier, string? clientName = null)
        {
M
Marco Goertz 已提交
93 94 95 96 97 98
            return GetDocument<TextDocument>(solutionProvider, documentIdentifier, (s, d, c) => s.GetTextDocuments(d, c), clientName);
        }

        private static T? GetDocument<T>(this ILspSolutionProvider solutionProvider, TextDocumentIdentifier documentIdentifier, Func<ILspSolutionProvider, Uri, string?, ImmutableArray<T>> getDocuments, string? clientName = null) where T : TextDocument
        {
            var documents = getDocuments(solutionProvider, documentIdentifier.Uri, clientName);
99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129

            if (documents.Length == 0)
            {
                return null;
            }

            if (documents.Length > 1)
            {
                // We have more than one document; try to find the one that matches the right context
                if (documentIdentifier is VSTextDocumentIdentifier vsDocumentIdentifier)
                {
                    if (vsDocumentIdentifier.ProjectContext != null)
                    {
                        var projectId = ProtocolConversions.ProjectContextToProjectId(vsDocumentIdentifier.ProjectContext);
                        var matchingDocument = documents.FirstOrDefault(d => d.Project.Id == projectId);

                        if (matchingDocument != null)
                        {
                            return matchingDocument;
                        }
                    }
                }
            }

            // We either have only one document or have multiple, but none of them  matched our context. In the
            // latter case, we'll just return the first one arbitrarily since this might just be some temporary mis-sync
            // of client and server state.
            return documents[0];
        }

        public static async Task<int> GetPositionFromLinePositionAsync(this TextDocument document, LinePosition linePosition, CancellationToken cancellationToken)
130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162
        {
            var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false);
            return text.Lines.GetPosition(linePosition);
        }

        public static bool HasVisualStudioLspCapability(this ClientCapabilities clientCapabilities)
        {
            if (clientCapabilities is VSClientCapabilities vsClientCapabilities)
            {
                return vsClientCapabilities.SupportsVisualStudioExtensions;
            }

            return false;
        }

        public static string GetMarkdownLanguageName(this Document document)
        {
            switch (document.Project.Language)
            {
                case LanguageNames.CSharp:
                    return "csharp";
                case LanguageNames.VisualBasic:
                    return "vb";
                case LanguageNames.FSharp:
                    return "fsharp";
                case "TypeScript":
                    return "typescript";
                default:
                    throw new ArgumentException(string.Format("Document project language {0} is not valid", document.Project.Language));
            }
        }

        public static ClassifiedTextElement GetClassifiedText(this DefinitionItem definition)
163
            => new ClassifiedTextElement(definition.DisplayParts.Select(part => new ClassifiedTextRun(part.Tag.ToClassificationTypeName(), part.Text)));
164 165
    }
}