Kaynağa Gözat

Merge pull request #77 from devmil/feature/better_terminal_isolate_error_message

adds check if the TerminalIsolate has already been started
xuty 4 yıl önce
ebeveyn
işleme
50af3e713e

+ 1 - 1
example/android/app/src/main/AndroidManifest.xml

@@ -6,7 +6,7 @@
          additional functionality it is fine to subclass or reimplement
          FlutterApplication and put your custom class here. -->
     <application
-        android:name="io.flutter.app.FlutterApplication"
+        android:name="${applicationName}"
         android:label="example"
         android:icon="@mipmap/ic_launcher">
         <activity

+ 41 - 12
example/lib/isolate.dart

@@ -3,6 +3,8 @@ import 'dart:async';
 import 'package:flutter/material.dart';
 import 'package:xterm/flutter.dart';
 import 'package:xterm/isolate.dart';
+import 'package:xterm/theme/terminal_theme.dart';
+import 'package:xterm/theme/terminal_themes.dart';
 import 'package:xterm/xterm.dart';
 
 void main() {
@@ -18,13 +20,23 @@ class MyApp extends StatelessWidget {
         primarySwatch: Colors.blue,
         visualDensity: VisualDensity.adaptivePlatformDensity,
       ),
-      home: MyHomePage(),
+      home: MyHomePage(
+        theme: TerminalThemes.defaultTheme,
+        terminalOpacity: 0.8,
+      ),
     );
   }
 }
 
 class MyHomePage extends StatefulWidget {
-  MyHomePage({Key key}) : super(key: key);
+  MyHomePage({
+    Key? key,
+    required this.theme,
+    required this.terminalOpacity,
+  }) : super(key: key);
+
+  final TerminalTheme theme;
+  final double terminalOpacity;
 
   @override
   _MyHomePageState createState() => _MyHomePageState();
@@ -86,15 +98,21 @@ class FakeTerminalBackend extends TerminalBackend {
 }
 
 class _MyHomePageState extends State<MyHomePage> {
-  final terminal = TerminalIsolate(
-    backend: FakeTerminalBackend(),
-    maxLines: 10000,
-  );
+  TerminalIsolate? terminal;
+
+  Future<TerminalIsolate> _ensureTerminalStarted() async {
+    if (terminal == null) {
+      terminal = TerminalIsolate(
+        backend: FakeTerminalBackend(),
+        maxLines: 10000,
+        theme: widget.theme,
+      );
+    }
 
-  @override
-  void initState() {
-    super.initState();
-    terminal.start();
+    if (!terminal!.isReady) {
+      await terminal!.start();
+    }
+    return terminal!;
   }
 
   void onInput(String input) {}
@@ -102,8 +120,19 @@ class _MyHomePageState extends State<MyHomePage> {
   @override
   Widget build(BuildContext context) {
     return Scaffold(
-      body: SafeArea(
-        child: TerminalView(terminal: terminal),
+      body: FutureBuilder(
+        future: _ensureTerminalStarted(),
+        builder: (context, snapshot) {
+          return SafeArea(
+            child: snapshot.hasData
+                ? TerminalView(terminal: snapshot.data as TerminalIsolate)
+                : Container(
+                    constraints: const BoxConstraints.expand(),
+                    color: Color(widget.theme.background)
+                        .withOpacity(widget.terminalOpacity),
+                  ),
+          );
+        },
       ),
     );
   }

+ 1 - 1
example/lib/main.dart

@@ -23,7 +23,7 @@ class MyApp extends StatelessWidget {
 }
 
 class MyHomePage extends StatefulWidget {
-  MyHomePage({Key key}) : super(key: key);
+  MyHomePage({Key? key}) : super(key: key);
 
   @override
   _MyHomePageState createState() => _MyHomePageState();

+ 11 - 10
example/lib/ssh.dart

@@ -1,7 +1,8 @@
 import 'dart:async';
 import 'dart:convert';
+import 'dart:typed_data';
 
-import 'package:dartssh/client.dart';
+import 'package:dartssh2/dartssh2.dart';
 import 'package:flutter/material.dart';
 import 'package:xterm/flutter.dart';
 import 'package:xterm/xterm.dart';
@@ -29,14 +30,14 @@ class MyApp extends StatelessWidget {
 }
 
 class MyHomePage extends StatefulWidget {
-  MyHomePage({Key key}) : super(key: key);
+  MyHomePage({Key? key}) : super(key: key);
 
   @override
   _MyHomePageState createState() => _MyHomePageState();
 }
 
 class SSHTerminalBackend extends TerminalBackend {
-  SSHClient client;
+  late SSHClient client;
 
   String _host;
   String _username;
@@ -63,13 +64,13 @@ class SSHTerminalBackend extends TerminalBackend {
     onWrite('connecting $_host...');
     client = SSHClient(
       hostport: Uri.parse(_host),
-      login: _username,
+      username: _username,
       print: print,
       termWidth: 80,
       termHeight: 25,
       termvar: 'xterm-256color',
-      getPassword: () => utf8.encode(_password),
-      response: (transport, data) {
+      onPasswordRequest: () => _password,
+      response: (data) {
         _sshOutput.add(data);
       },
       success: () {
@@ -92,12 +93,12 @@ class SSHTerminalBackend extends TerminalBackend {
 
   @override
   void write(String input) {
-    client?.sendChannelData(utf8.encode(input));
+    client.sendChannelData(Uint8List.fromList(utf8.encode(input)));
   }
 
   @override
   void terminate() {
-    client?.disconnect('terminate');
+    client.disconnect('terminate');
   }
 
   @override
@@ -107,8 +108,8 @@ class SSHTerminalBackend extends TerminalBackend {
 }
 
 class _MyHomePageState extends State<MyHomePage> {
-  Terminal terminal;
-  SSHTerminalBackend backend;
+  late Terminal terminal;
+  late SSHTerminalBackend backend;
 
   @override
   void initState() {

+ 53 - 27
example/pubspec.lock

@@ -1,13 +1,20 @@
 # Generated by pub
 # See https://dart.dev/tools/pub/glossary#lockfile
 packages:
+  args:
+    dependency: transitive
+    description:
+      name: args
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "2.3.0"
   asn1lib:
     dependency: transitive
     description:
       name: asn1lib
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "0.5.15"
+    version: "1.0.3"
   async:
     dependency: transitive
     description:
@@ -64,15 +71,22 @@ packages:
       url: "https://pub.dartlang.org"
     source: hosted
     version: "0.1.3"
-  dartssh:
+  dart_console:
+    dependency: transitive
+    description:
+      name: dart_console
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "1.0.0"
+  dartssh2:
     dependency: "direct main"
     description:
       path: "."
       ref: HEAD
-      resolved-ref: "1c2244db550a56e2d53754864b8ce3da525a4b7d"
-      url: "https://github.com/TerminalStudio/dartssh"
+      resolved-ref: "3316b252ace4948f64812b7e5eca11f466d3f62d"
+      url: "https://github.com/TerminalStudio/dartssh2"
     source: git
-    version: "1.0.4+4"
+    version: "1.2.0-pre"
   equatable:
     dependency: transitive
     description:
@@ -87,13 +101,13 @@ packages:
       url: "https://pub.dartlang.org"
     source: hosted
     version: "1.2.0"
-  fixnum:
+  ffi:
     dependency: transitive
     description:
-      name: fixnum
+      name: ffi
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "0.10.11"
+    version: "1.1.2"
   flutter:
     dependency: "direct main"
     description: flutter
@@ -110,14 +124,21 @@ packages:
       name: http
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "0.12.2"
+    version: "0.13.4"
   http_parser:
     dependency: transitive
     description:
       name: http_parser
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "3.1.4"
+    version: "4.0.0"
+  js:
+    dependency: transitive
+    description:
+      name: js
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "0.6.3"
   matcher:
     dependency: transitive
     description:
@@ -125,6 +146,13 @@ packages:
       url: "https://pub.dartlang.org"
     source: hosted
     version: "0.12.11"
+  material_color_utilities:
+    dependency: transitive
+    description:
+      name: material_color_utilities
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "0.1.2"
   meta:
     dependency: transitive
     description:
@@ -139,13 +167,13 @@ packages:
       url: "https://pub.dartlang.org"
     source: hosted
     version: "1.8.0"
-  pedantic:
+  pinenacl:
     dependency: transitive
     description:
-      name: pedantic
+      name: pinenacl
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "1.9.2"
+    version: "0.3.3"
   platform_info:
     dependency: transitive
     description:
@@ -159,7 +187,7 @@ packages:
       name: pointycastle
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "1.0.2"
+    version: "3.4.0"
   quiver:
     dependency: transitive
     description:
@@ -213,16 +241,7 @@ packages:
       name: test_api
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "0.4.3"
-  tweetnacl:
-    dependency: transitive
-    description:
-      path: "."
-      ref: HEAD
-      resolved-ref: "028b09a31805c648df1db695f7a58f9fcd4099d9"
-      url: "https://github.com/TerminalStudio/tweetnacl-dart"
-    source: git
-    version: "0.3.2"
+    version: "0.4.8"
   typed_data:
     dependency: transitive
     description:
@@ -236,14 +255,21 @@ packages:
       name: validators
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "2.0.0+1"
+    version: "3.0.0"
   vector_math:
     dependency: transitive
     description:
       name: vector_math
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "2.1.0"
+    version: "2.1.1"
+  win32:
+    dependency: transitive
+    description:
+      name: win32
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "2.3.3"
   xterm:
     dependency: "direct main"
     description:
@@ -252,5 +278,5 @@ packages:
     source: path
     version: "2.5.0-pre"
 sdks:
-  dart: ">=2.12.0 <3.0.0"
+  dart: ">=2.14.0 <3.0.0"
   flutter: ">=2.0.0"

+ 3 - 3
example/pubspec.yaml

@@ -18,15 +18,15 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
 version: 1.0.0+1
 
 environment:
-  sdk: ">=2.7.0 <3.0.0"
+  sdk: ">=2.12.0 <3.0.0"
 
 dependencies:
   xterm:
     path: ../
 
-  dartssh:
+  dartssh2:
     git:
-      url: https://github.com/TerminalStudio/dartssh
+      url: https://github.com/TerminalStudio/dartssh2
 
   flutter:
     sdk: flutter

+ 74 - 20
lib/terminal/terminal_isolate.dart

@@ -286,6 +286,7 @@ class TerminalIsolate with Observable implements TerminalUiInteraction {
   final _receivePort = ReceivePort();
   SendPort? _sendPort;
   late Isolate _isolate;
+  bool _isStarted = false;
 
   final TerminalBackend? backend;
   final BellHandler onBell;
@@ -319,28 +320,50 @@ class TerminalIsolate with Observable implements TerminalUiInteraction {
         _refreshEventDebouncer = EventDebouncer(minRefreshDelay);
 
   @override
-  int get scrollOffsetFromBottom => _lastState!.scrollOffsetFromBottom;
+  int get scrollOffsetFromBottom {
+    _assertStarted();
+    return _lastState!.scrollOffsetFromBottom;
+  }
 
   @override
-  int get scrollOffsetFromTop => _lastState!.scrollOffsetFromTop;
+  int get scrollOffsetFromTop {
+    _assertStarted();
+    return _lastState!.scrollOffsetFromTop;
+  }
 
   @override
-  int get bufferHeight => _lastState!.bufferHeight;
+  int get bufferHeight {
+    _assertStarted();
+    return _lastState!.bufferHeight;
+  }
 
   @override
-  int get terminalHeight => _lastState!.viewHeight;
+  int get terminalHeight {
+    _assertStarted();
+    return _lastState!.viewHeight;
+  }
 
   @override
-  int get terminalWidth => _lastState!.viewWidth;
+  int get terminalWidth {
+    _assertStarted();
+    return _lastState!.viewWidth;
+  }
 
   @override
-  int get invisibleHeight => _lastState!.invisibleHeight;
+  int get invisibleHeight {
+    _assertStarted();
+    return _lastState!.invisibleHeight;
+  }
 
   @override
-  Selection? get selection => _lastState?.selection;
+  Selection? get selection {
+    return _lastState?.selection;
+  }
 
   @override
-  bool get showCursor => _lastState?.showCursor ?? true;
+  bool get showCursor {
+    return _lastState?.showCursor ?? true;
+  }
 
   @override
   List<BufferLine> getVisibleLines() {
@@ -351,10 +374,14 @@ class TerminalIsolate with Observable implements TerminalUiInteraction {
   }
 
   @override
-  int get cursorY => _lastState?.cursorY ?? 0;
+  int get cursorY {
+    return _lastState?.cursorY ?? 0;
+  }
 
   @override
-  int get cursorX => _lastState?.cursorX ?? 0;
+  int get cursorX {
+    return _lastState?.cursorX ?? 0;
+  }
 
   @override
   BufferLine? get currentLine {
@@ -371,10 +398,14 @@ class TerminalIsolate with Observable implements TerminalUiInteraction {
   }
 
   @override
-  int get cursorColor => _lastState?.cursorColor ?? 0;
+  int get cursorColor {
+    return _lastState?.cursorColor ?? 0;
+  }
 
   @override
-  int get backgroundColor => _lastState?.backgroundColor ?? 0;
+  int get backgroundColor {
+    return _lastState?.backgroundColor ?? 0;
+  }
 
   @override
   bool get dirty {
@@ -392,12 +423,14 @@ class TerminalIsolate with Observable implements TerminalUiInteraction {
   PlatformBehavior get platform => _platform;
 
   @override
-  String? get selectedText => _lastState?.selectedText;
+  String? get selectedText {
+    return _lastState?.selectedText;
+  }
 
   @override
   bool get isReady => _lastState != null;
 
-  Future<void> start() async {
+  Future<void> start({bool testingDontWaitForBootup = false}) async {
     final initialRefreshCompleted = Completer<bool>();
     var firstReceivePort = ReceivePort();
     _isolate = await Isolate.spawn(terminalMain, firstReceivePort.sendPort);
@@ -438,10 +471,14 @@ class TerminalIsolate with Observable implements TerminalUiInteraction {
       _IsolateCommand.init,
       TerminalInitData(this.backend, this.platform, this.theme, this.maxLines)
     ]);
-    await initialRefreshCompleted.future;
+    if (!testingDontWaitForBootup) {
+      await initialRefreshCompleted.future;
+    }
+    _isStarted = true;
   }
 
   void stop() {
+    _assertStarted();
     terminateBackend();
     _isolate.kill();
   }
@@ -562,7 +599,9 @@ class TerminalIsolate with Observable implements TerminalUiInteraction {
   bool get isTerminated => _isTerminated;
 
   @override
-  String get composingString => _lastState?.composingString ?? '';
+  String get composingString {
+    return _lastState?.composingString ?? '';
+  }
 
   @override
   void updateComposingString(String value) {
@@ -574,10 +613,14 @@ class TerminalIsolate with Observable implements TerminalUiInteraction {
       _lastState?.searchResult ?? TerminalSearchResult.empty();
 
   @override
-  int get numberOfSearchHits => userSearchResult.allHits.length;
+  int get numberOfSearchHits {
+    return userSearchResult.allHits.length;
+  }
 
   @override
-  int? get currentSearchHit => userSearchResult.currentSearchHit;
+  int? get currentSearchHit {
+    return userSearchResult.currentSearchHit;
+  }
 
   @override
   void set currentSearchHit(int? currentSearchHit) {
@@ -594,7 +637,9 @@ class TerminalIsolate with Observable implements TerminalUiInteraction {
   }
 
   @override
-  String? get userSearchPattern => _lastState?.userSearchPattern;
+  String? get userSearchPattern {
+    return _lastState?.userSearchPattern;
+  }
 
   @override
   void set userSearchPattern(String? newValue) {
@@ -602,11 +647,20 @@ class TerminalIsolate with Observable implements TerminalUiInteraction {
   }
 
   @override
-  bool get isUserSearchActive => _lastState?.isUserSearchActive ?? false;
+  bool get isUserSearchActive {
+    return _lastState?.isUserSearchActive ?? false;
+  }
 
   @override
   void set isUserSearchActive(bool isUserSearchActive) {
     _sendPort
         ?.send([_IsolateCommand.updateIsUserSearchActive, isUserSearchActive]);
   }
+
+  void _assertStarted() {
+    if (!_isStarted) {
+      throw Exception(
+          'The Terminal Isolate has to be started before using it! (call await terminalIsolate.start()');
+    }
+  }
 }

+ 86 - 0
test/terminal/terminal_isolate_test.dart

@@ -0,0 +1,86 @@
+import 'dart:async';
+
+import 'package:flutter_test/flutter_test.dart';
+import 'package:xterm/terminal/terminal_backend.dart';
+import 'package:xterm/terminal/terminal_isolate.dart';
+
+void main() {
+  group('Start behavior tests', () {
+    test('Using TerminalIsolate when not started throws exception', () {
+      final fixture = _TestFixture();
+      expect(() => fixture.uut.terminalWidth, throwsA(isA<Exception>()));
+    });
+    test('Using TerminalIsolate after started doesn\'t throw exceptions',
+        () async {
+      final fixture = _TestFixture();
+
+      await fixture.uut.start(testingDontWaitForBootup: true);
+
+      //no throw
+      fixture.uut.showCursor;
+    });
+  });
+}
+
+class _TestFixture {
+  _TestFixture() {
+    fakeBackend = FakeBackend();
+    uut = TerminalIsolate(maxLines: 10000, backend: fakeBackend);
+  }
+
+  late final TerminalIsolate uut;
+  late final FakeBackend fakeBackend;
+}
+
+class FakeBackend implements TerminalBackend {
+  @override
+  void ackProcessed() {}
+
+  @override
+  // TODO: implement exitCode
+  Future<int> get exitCode => _exitCodeCompleter.future;
+
+  @override
+  void init() {
+    _exitCodeCompleter = Completer<int>();
+    _outStream = StreamController<String>();
+    _hasInitBeenCalled = true;
+  }
+
+  @override
+  Stream<String> get out => _outStream.stream;
+
+  @override
+  void resize(int width, int height, int pixelWidth, int pixelHeight) {
+    _width = width;
+    _height = height;
+    _pixelWidth = pixelWidth;
+    _pixelHeight = pixelHeight;
+  }
+
+  @override
+  void terminate() {
+    _isTerminated = true;
+  }
+
+  @override
+  void write(String _) {}
+
+  bool get hasInitBeenCalled => _hasInitBeenCalled;
+  bool get isTerminated => _isTerminated;
+
+  int? get width => _width;
+  int? get height => _height;
+  int? get pixelWidth => _pixelWidth;
+  int? get pixelHeight => _pixelHeight;
+
+  bool _hasInitBeenCalled = false;
+  bool _isTerminated = false;
+  int? _width;
+  int? _height;
+  int? _pixelWidth;
+  int? _pixelHeight;
+
+  late final _exitCodeCompleter;
+  late final _outStream;
+}

+ 0 - 2
test/terminal/terminal_search_test.dart

@@ -1,5 +1,3 @@
-import 'dart:developer';
-
 import 'package:flutter_test/flutter_test.dart';
 import 'package:mockito/annotations.dart';
 import 'package:mockito/mockito.dart';