public async Task Server_HandleEmptyNotification_Success() { var testCompletion = new TaskCompletionSource <object>(); LspConnection clientConnection = await CreateClientConnection(); LspConnection serverConnection = await CreateServerConnection(); var clientDispatcher = new LspDispatcher(); clientDispatcher.HandleEmptyNotification("test", () => { Log.LogInformation("Got notification."); testCompletion.SetResult(null); }); clientConnection.Connect(clientDispatcher); serverConnection.Connect(new LspDispatcher()); serverConnection.SendEmptyNotification("test"); await testCompletion.Task; serverConnection.Disconnect(flushOutgoing: true); clientConnection.Disconnect(); await Task.WhenAll(clientConnection.HasHasDisconnected, serverConnection.HasHasDisconnected); }
public async Task Server_HandleRequest_Success() { LspConnection clientConnection = await CreateClientConnection(); LspConnection serverConnection = await CreateServerConnection(); var clientDispatcher = new LspDispatcher(); clientDispatcher.HandleRequest <TestRequest, TestResponse>("test", (request, cancellationToken) => { Log.LogInformation("Got request: {@Request}", request); return(Task.FromResult(new TestResponse { Value = request.Value.ToString() })); }); clientConnection.Connect(clientDispatcher); serverConnection.Connect(new LspDispatcher()); TestResponse response = await serverConnection.SendRequest <TestResponse>("test", new TestRequest { Value = 1234 }); Assert.Equal("1234", response.Value); Log.LogInformation("Got response: {@Response}", response); serverConnection.Disconnect(flushOutgoing: true); clientConnection.Disconnect(); await Task.WhenAll(clientConnection.HasHasDisconnected, serverConnection.HasHasDisconnected); }
public async Task Client_HandleCommandRequest_Success() { LspConnection clientConnection = await CreateClientConnection(); LspConnection serverConnection = await CreateServerConnection(); var serverDispatcher = new LspDispatcher(); serverDispatcher.HandleRequest <TestRequest>("test", (request, cancellationToken) => { Log.LogInformation("Got request: {@Request}", request); Assert.Equal(1234, request.Value); return(Task.CompletedTask); }); serverConnection.Connect(serverDispatcher); clientConnection.Connect(new LspDispatcher()); await clientConnection.SendRequest("test", new TestRequest { Value = 1234 }); clientConnection.Disconnect(flushOutgoing: true); serverConnection.Disconnect(); await Task.WhenAll(clientConnection.HasHasDisconnected, serverConnection.HasHasDisconnected); }
/// <summary> /// Close the connection. /// </summary> /// <param name="flushOutgoing"> /// If <c>true</c>, stop receiving and block until all outgoing messages have been sent. /// </param> public void Disconnect(bool flushOutgoing = false) { if (flushOutgoing) { // Stop receiving. _incoming.CompleteAdding(); // Wait for the outgoing message queue to drain. int remainingMessageCount = 0; DateTime then = DateTime.Now; while (DateTime.Now - then < FlushTimeout) { remainingMessageCount = _outgoing.Count; if (remainingMessageCount == 0) { break; } Thread.Sleep( TimeSpan.FromMilliseconds(200) ); } if (remainingMessageCount > 0) { Log.LogWarning("Failed to flush outgoing messages ({RemainingMessageCount} messages remaining).", _outgoing.Count); } } // Cancel all outstanding requests. // This should not be necessary because request cancellation tokens should be linked to _cancellationSource, but better to be sure we won't leave a caller hanging. foreach (TaskCompletionSource <ServerMessage> responseCompletion in _responseCompletions.Values) { responseCompletion.TrySetException( new OperationCanceledException("The request was canceled because the underlying connection was closed.") ); } try { _cancellationSource?.Cancel(); } catch (AggregateException e) when(e.InnerException is ObjectDisposedException) { // Swallow object disposed exception } _sendLoop = null; _receiveLoop = null; _dispatchLoop = null; _dispatcher = null; }
/// <summary> /// Register a message handler. /// </summary> /// <param name="handler"> /// The message handler. /// </param> /// <returns> /// An <see cref="IDisposable"/> representing the registration. /// </returns> public IDisposable RegisterHandler(IHandler handler) { if (handler == null) { throw new ArgumentNullException(nameof(handler)); } LspDispatcher dispatcher = _dispatcher; if (dispatcher == null) { throw new InvalidOperationException("The connection has not been opened."); } return(dispatcher.RegisterHandler(handler)); }
/// <summary> /// Create a new <see cref="LanguageClient"/>. /// </summary> /// <param name="loggerFactory"> /// The logger to use. /// </param> LanguageClient(ILoggerFactory loggerFactory) { if (loggerFactory == null) { throw new ArgumentNullException(nameof(loggerFactory)); } LoggerFactory = loggerFactory; Log = LoggerFactory.CreateLogger <LanguageClient>(); Workspace = new WorkspaceClient(this); Window = new WindowClient(this); TextDocument = new TextDocumentClient(this); _dispatcher = new LspDispatcher(_serializer); _dispatcher.RegisterHandler(_dynamicRegistrationHandler); }
/// <summary> /// Open the connection. /// </summary> /// <param name="dispatcher"> /// The <see cref="LspDispatcher"/> used to dispatch messages to handlers. /// </param> public void Connect(LspDispatcher dispatcher) { if (dispatcher == null) { throw new ArgumentNullException(nameof(dispatcher)); } if (IsOpen) { throw new InvalidOperationException("Connection is already open."); } _cancellationSource = new CancellationTokenSource(); _cancellation = _cancellationSource.Token; _dispatcher = dispatcher; _sendLoop = SendLoop(); _receiveLoop = ReceiveLoop(); _dispatchLoop = DispatchLoop(); _hasDisconnectedTask = Task.WhenAll(_sendLoop, _receiveLoop, _dispatchLoop); }