| // 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']; | |
| } |