public RequestExecutionQueue(
            ILspLogger logger,
            LspWorkspaceRegistrationService lspWorkspaceRegistrationService,
            LspMiscellaneousFilesWorkspace?lspMiscellaneousFilesWorkspace,
            IGlobalOptionService globalOptions,
            ImmutableArray <string> supportedLanguages,
            string serverName,
            string serverTypeName)
        {
            _logger             = logger;
            _globalOptions      = globalOptions;
            _supportedLanguages = supportedLanguages;
            _serverName         = serverName;

            _queue        = new AsyncQueue <QueueItem>();
            _cancelSource = new CancellationTokenSource();

            // Pass the language client instance type name to the telemetry logger to ensure we can
            // differentiate between the different C# LSP servers that have the same client name.
            // We also don't use the language client's name property as it is a localized user facing string
            // which is difficult to write telemetry queries for.
            _requestTelemetryLogger = new RequestTelemetryLogger(serverTypeName);

            _lspWorkspaceManager = new LspWorkspaceManager(logger, lspMiscellaneousFilesWorkspace, lspWorkspaceRegistrationService, _requestTelemetryLogger);

            // Start the queue processing
            _ = ProcessQueueAsync();
        }
 public TestAccessor(LspWorkspaceManager manager)
 => _manager = manager;
Exemple #3
0
        public static RequestContext?Create(
            bool requiresLSPSolution,
            TextDocumentIdentifier?textDocument,
            string?clientName,
            ILspLogger logger,
            ClientCapabilities clientCapabilities,
            LspWorkspaceManager lspWorkspaceManager,
            IDocumentChangeTracker documentChangeTracker,
            ImmutableArray <string> supportedLanguages,
            IGlobalOptionService globalOptions)
        {
            // Retrieve the current LSP tracked text as of this request.
            // This is safe as all creation of request contexts cannot happen concurrently.
            var trackedDocuments = lspWorkspaceManager.GetTrackedLspText();

            // If the handler doesn't need an LSP solution we do two important things:
            // 1. We don't bother building the LSP solution for perf reasons
            // 2. We explicitly don't give the handler a solution or document, even if we could
            //    so they're not accidentally operating on stale solution state.
            if (!requiresLSPSolution)
            {
                return(new RequestContext(solution: null, logger, clientCapabilities, clientName, document: null, documentChangeTracker, trackedDocuments, supportedLanguages, globalOptions));
            }

            // Go through each registered workspace, find the solution that contains the document that
            // this request is for, and then updates it based on the state of the world as we know it, based on the
            // text content in the document change tracker.

            Document?document          = null;
            var      workspaceSolution = lspWorkspaceManager.TryGetHostLspSolution();

            if (textDocument is not null)
            {
                // we were given a request associated with a document.  Find the corresponding roslyn
                // document for this.  If we can't, we cannot proceed.
                document = lspWorkspaceManager.GetLspDocument(textDocument, clientName);
                if (document != null)
                {
                    workspaceSolution = document.Project.Solution;
                }
            }

            if (workspaceSolution == null)
            {
                logger.TraceError("Could not find appropriate solution for operation");
                return(null);
            }

            var context = new RequestContext(
                workspaceSolution,
                logger,
                clientCapabilities,
                clientName,
                document,
                documentChangeTracker,
                trackedDocuments,
                supportedLanguages,
                globalOptions);

            return(context);
        }
        public static async Task <RequestContext?> CreateAsync(
            bool requiresLSPSolution,
            TextDocumentIdentifier?textDocument,
            WellKnownLspServerKinds serverKind,
            ILspLogger logger,
            ClientCapabilities clientCapabilities,
            LspWorkspaceManager lspWorkspaceManager,
            ILanguageServerNotificationManager notificationManager,
            IDocumentChangeTracker documentChangeTracker,
            ImmutableArray <string> supportedLanguages,
            IGlobalOptionService globalOptions,
            CancellationToken queueCancellationToken,
            CancellationToken requestCancellationToken)
        {
            // Retrieve the current LSP tracked text as of this request.
            // This is safe as all creation of request contexts cannot happen concurrently.
            var trackedDocuments = lspWorkspaceManager.GetTrackedLspText();

            // If the handler doesn't need an LSP solution we do two important things:
            // 1. We don't bother building the LSP solution for perf reasons
            // 2. We explicitly don't give the handler a solution or document, even if we could
            //    so they're not accidentally operating on stale solution state.
            if (!requiresLSPSolution)
            {
                return(new RequestContext(
                           solution: null, logger: logger, clientCapabilities: clientCapabilities, serverKind: serverKind, document: null,
                           documentChangeTracker: documentChangeTracker, trackedDocuments: trackedDocuments, supportedLanguages: supportedLanguages, globalOptions: globalOptions,
                           notificationManager: notificationManager, queueCancellationToken: queueCancellationToken));
            }

            Solution?workspaceSolution;
            Document?document = null;

            if (textDocument is not null)
            {
                // we were given a request associated with a document.  Find the corresponding roslyn document for this.
                // There are certain cases where we may be asked for a document that does not exist (for example a document is removed)
                // For example, document pull diagnostics can ask us after removal to clear diagnostics for a document.
                document = await lspWorkspaceManager.GetLspDocumentAsync(textDocument, requestCancellationToken).ConfigureAwait(false);
            }

            workspaceSolution = document?.Project.Solution ?? await lspWorkspaceManager.TryGetHostLspSolutionAsync(requestCancellationToken).ConfigureAwait(false);

            if (workspaceSolution == null)
            {
                logger.TraceError("Could not find appropriate solution for operation");
                return(null);
            }

            var context = new RequestContext(
                workspaceSolution,
                logger,
                clientCapabilities,
                serverKind,
                document,
                documentChangeTracker,
                trackedDocuments,
                supportedLanguages,
                globalOptions,
                notificationManager,
                queueCancellationToken);

            return(context);
        }
Exemple #5
0
        internal LanguageServerTarget(
            AbstractRequestDispatcherFactory requestDispatcherFactory,
            JsonRpc jsonRpc,
            ICapabilitiesProvider capabilitiesProvider,
            LspWorkspaceRegistrationService workspaceRegistrationService,
            LspMiscellaneousFilesWorkspace?lspMiscellaneousFilesWorkspace,
            IGlobalOptionService globalOptions,
            IAsynchronousOperationListenerProvider listenerProvider,
            ILspLogger logger,
            ImmutableArray <string> supportedLanguages,
            WellKnownLspServerKinds serverKind)
        {
            _requestDispatcher = requestDispatcherFactory.CreateRequestDispatcher(serverKind);

            _capabilitiesProvider = capabilitiesProvider;
            _logger = logger;

            _jsonRpc = jsonRpc;
            _jsonRpc.AddLocalRpcTarget(this);
            _jsonRpc.Disconnected += JsonRpc_Disconnected;

            _notificationManager = new LanguageServerNotificationManager(_jsonRpc);
            _listener            = listenerProvider.GetListener(FeatureAttribute.LanguageServer);

            // Pass the language client instance type name to the telemetry logger to ensure we can
            // differentiate between the different C# LSP servers that have the same client name.
            // We also don't use the language client's name property as it is a localized user facing string
            // which is difficult to write telemetry queries for.
            _requestTelemetryLogger = new RequestTelemetryLogger(serverKind.ToTelemetryString());
            _lspWorkspaceManager    = new LspWorkspaceManager(logger, lspMiscellaneousFilesWorkspace, workspaceRegistrationService, _requestTelemetryLogger);

            _queue = new RequestExecutionQueue(
                logger,
                globalOptions,
                supportedLanguages,
                serverKind,
                _requestTelemetryLogger,
                _lspWorkspaceManager,
                _notificationManager);
            _queue.RequestServerShutdown += RequestExecutionQueue_Errored;

            var entryPointMethod = typeof(DelegatingEntryPoint).GetMethod(nameof(DelegatingEntryPoint.EntryPointAsync));

            Contract.ThrowIfNull(entryPointMethod, $"{typeof(DelegatingEntryPoint).FullName} is missing method {nameof(DelegatingEntryPoint.EntryPointAsync)}");

            foreach (var metadata in _requestDispatcher.GetRegisteredMethods())
            {
                // Instead of concretely defining methods for each LSP method, we instead dynamically construct the
                // generic method info from the exported handler types.  This allows us to define multiple handlers for
                // the same method but different type parameters.  This is a key functionality to support TS external
                // access as we do not want to couple our LSP protocol version dll to theirs.
                //
                // We also do not use the StreamJsonRpc support for JToken as the rpc method parameters because we want
                // StreamJsonRpc to do the deserialization to handle streaming requests using IProgress<T>.
                var delegatingEntryPoint = new DelegatingEntryPoint(metadata.MethodName, this);

                var genericEntryPointMethod = entryPointMethod.MakeGenericMethod(metadata.RequestType, metadata.ResponseType);

                _jsonRpc.AddLocalRpcMethod(genericEntryPointMethod, delegatingEntryPoint, new JsonRpcMethodAttribute(metadata.MethodName)
                {
                    UseSingleObjectParameterDeserialization = true
                });
            }
        }