| // Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| import 'dart:async'; |
| import 'dart:convert'; |
| import 'dart:io'; |
| |
| /// Type of callbacks used to process notification. |
| typedef void NotificationProcessor(String event, Map<String, Object> params); |
| |
| /// Instances of the class [AnalysisServerClient] manage a connection to an |
| /// [AnalysisServer] process, and facilitate communication to and from the |
| /// client/user. |
| class AnalysisServerClient { |
| /// AnalysisServer process object, or null if the server has been shut down. |
| final Process _process; |
| |
| /// Commands that have been sent to the server but not yet acknowledged, |
| /// and the [Completer] objects which should be completed when |
| /// acknowledgement is received. |
| final Map<String, Completer> _pendingCommands = <String, Completer>{}; |
| |
| /// Number which should be used to compute the 'id' to send to the next |
| /// command sent to the server. |
| int _nextId = 0; |
| |
| AnalysisServerClient(this._process); |
| |
| /// Return a future that will complete when all commands that have been |
| /// sent to the server so far have been flushed to the OS buffer. |
| Future<Null> flushCommands() { |
| return _process.stdin.flush(); |
| } |
| |
| /// Force kill the server. Returns exit code future. |
| Future<int> kill() { |
| _process.kill(); |
| return _process.exitCode; |
| } |
| |
| void listenToOutput({NotificationProcessor notificationProcessor}) { |
| _process.stdout |
| .transform((new Utf8Codec()).decoder) |
| .transform(new LineSplitter()) |
| .listen((String line) { |
| String trimmedLine = line.trim(); |
| if (trimmedLine.startsWith('Observatory listening on ')) { |
| return; |
| } |
| final result = json.decoder.convert(trimmedLine) as Map; |
| if (result.containsKey('id')) { |
| final id = result['id'] as String; |
| final completer = _pendingCommands.remove(id); |
| |
| if (result.containsKey('error')) { |
| completer.completeError(new ServerErrorMessage(result['error'])); |
| } else { |
| completer.complete(result['result']); |
| } |
| } else if (notificationProcessor != null && result.containsKey('event')) { |
| // Message is a notification. It should have an event and possibly |
| // params. |
| notificationProcessor(result['event'], result['params']); |
| } |
| }); |
| } |
| |
| /// Sends a command to the server. An 'id' will be automatically assigned. |
| /// The returned [Future] will be completed when the server acknowledges |
| /// the command with a response. If the server acknowledges the command |
| /// with a normal (non-error) response, the future will be completed |
| /// with the 'result' field from the response. If the server acknowledges |
| /// the command with an error response, the future will be completed with an |
| /// error. |
| Future send(String method, Map<String, dynamic> params) { |
| String id = '${_nextId++}'; |
| Map<String, dynamic> command = <String, dynamic>{ |
| 'id': id, |
| 'method': method |
| }; |
| if (params != null) { |
| command['params'] = params; |
| } |
| Completer completer = new Completer(); |
| _pendingCommands[id] = completer; |
| String commandAsJson = json.encode(command); |
| _process.stdin.add(utf8.encoder.convert('$commandAsJson\n')); |
| return completer.future; |
| } |
| } |
| |
| class ServerErrorMessage { |
| final Map errorJson; |
| |
| ServerErrorMessage(this.errorJson); |
| |
| String get code => errorJson['code'].toString(); |
| String get message => errorJson['message']; |
| String get stackTrace => errorJson['stackTrace']; |
| } |