Bläddra i källkod

Merge 'v3' to 'master'

xuty 3 år sedan
förälder
incheckning
b0ad0c00b8
100 ändrade filer med 4859 tillägg och 3827 borttagningar
  1. 3 0
      .gitignore
  2. 29 0
      CHANGELOG.md
  3. 29 0
      analysis_options.yaml
  4. 57 0
      bin/xterm_bench.dart
  5. 116 0
      bin/xterm_dump.dart
  6. 38 3
      example/.metadata
  7. 23 25
      example/analysis_options.yaml
  8. 2 0
      example/android/.gitignore
  9. 12 7
      example/android/app/build.gradle
  10. 3 16
      example/android/app/src/main/AndroidManifest.xml
  11. 1 1
      example/android/app/src/main/res/values-night/styles.xml
  12. 5 5
      example/android/app/src/main/res/values/styles.xml
  13. 4 4
      example/android/build.gradle
  14. 0 1
      example/android/gradle.properties
  15. 1 1
      example/android/gradle/wrapper/gradle-wrapper.properties
  16. 1 1
      example/ios/Flutter/AppFrameworkInfo.plist
  17. 1 0
      example/ios/Flutter/Debug.xcconfig
  18. 1 0
      example/ios/Flutter/Release.xcconfig
  19. 41 0
      example/ios/Podfile
  20. 22 9
      example/ios/Runner.xcodeproj/project.pbxproj
  21. 1 1
      example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
  22. 1 1
      example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
  23. 0 150
      example/lib/isolate.dart
  24. 0 100
      example/lib/local.dart
  25. 75 62
      example/lib/main.dart
  26. 107 0
      example/lib/mock.dart
  27. 53 83
      example/lib/ssh.dart
  28. 4 0
      example/linux/flutter/generated_plugin_registrant.cc
  29. 2 0
      example/linux/flutter/generated_plugins.cmake
  30. 1 0
      example/macos/.gitignore
  31. 1 0
      example/macos/Flutter/Flutter-Debug.xcconfig
  32. 1 0
      example/macos/Flutter/Flutter-Release.xcconfig
  33. 2 0
      example/macos/Flutter/GeneratedPluginRegistrant.swift
  34. 40 0
      example/macos/Podfile
  35. 28 0
      example/macos/Podfile.lock
  36. 65 17
      example/macos/Runner.xcodeproj/project.pbxproj
  37. 4 18
      example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
  38. 3 0
      example/macos/Runner.xcworkspace/contents.xcworkspacedata
  39. 4 0
      example/macos/Runner/Base.lproj/MainMenu.xib
  40. 1 1
      example/macos/Runner/Configs/AppInfo.xcconfig
  41. 0 16
      example/macos/Runner/DebugProfile.entitlements
  42. 10 6
      example/macos/Runner/MainFlutterWindow.swift
  43. 1 18
      example/macos/Runner/Release.entitlements
  44. 146 31
      example/pubspec.lock
  45. 14 7
      example/pubspec.yaml
  46. 0 30
      example/test/widget_test.dart
  47. 3 0
      example/windows/flutter/generated_plugin_registrant.cc
  48. 2 0
      example/windows/flutter/generated_plugins.cmake
  49. 0 0
      fixture/htop_80x25_3s.txt
  50. 0 555
      lib/buffer/buffer.dart
  51. 0 266
      lib/buffer/line/line.dart
  52. 0 293
      lib/buffer/line/line_bytedata_data.dart
  53. 0 267
      lib/buffer/line/line_list_data.dart
  54. 0 11
      lib/buffer/reflow_strategy.dart
  55. 0 91
      lib/buffer/reflow_strategy_narrower.dart
  56. 0 76
      lib/buffer/reflow_strategy_wider.dart
  57. 2 0
      lib/core.dart
  58. 534 0
      lib/core/buffer/buffer.dart
  59. 0 0
      lib/core/buffer/cell_flags.dart
  60. 295 0
      lib/core/buffer/line.dart
  61. 55 0
      lib/core/buffer/position.dart
  62. 71 0
      lib/core/buffer/range.dart
  63. 51 0
      lib/core/buffer/segment.dart
  64. 66 0
      lib/core/cell.dart
  65. 0 0
      lib/core/charset.dart
  66. 19 0
      lib/core/color.dart
  67. 137 0
      lib/core/cursor.dart
  68. 29 0
      lib/core/escape/emitter.dart
  69. 211 0
      lib/core/escape/handler.dart
  70. 1095 0
      lib/core/escape/parser.dart
  71. 131 0
      lib/core/input/handler.dart
  72. 0 0
      lib/core/input/keys.dart
  73. 110 0
      lib/core/input/keytab/keytab.dart
  74. 2 2
      lib/core/input/keytab/keytab_default.dart
  75. 1 1
      lib/core/input/keytab/keytab_escape.dart
  76. 5 5
      lib/core/input/keytab/keytab_parse.dart
  77. 34 26
      lib/core/input/keytab/keytab_record.dart
  78. 0 0
      lib/core/input/keytab/keytab_token.dart
  79. 1 1
      lib/core/input/keytab/qt_keyname.dart
  80. 23 0
      lib/core/mouse.dart
  81. 157 0
      lib/core/reflow.dart
  82. 3 0
      lib/core/snapshot.dart
  83. 40 0
      lib/core/state.dart
  84. 46 0
      lib/core/tabs.dart
  85. 783 0
      lib/core/terminal.dart
  86. 0 1
      lib/flutter.dart
  87. 0 30
      lib/frontend/char_size.dart
  88. 0 23
      lib/frontend/helpers.dart
  89. 0 17
      lib/frontend/input_behavior.dart
  90. 0 84
      lib/frontend/input_behavior_default.dart
  91. 0 5
      lib/frontend/input_behavior_desktop.dart
  92. 0 43
      lib/frontend/input_behavior_mobile.dart
  93. 0 18
      lib/frontend/input_behaviors.dart
  94. 0 268
      lib/frontend/input_listener.dart
  95. 0 28
      lib/frontend/mouse_listener.dart
  96. 0 78
      lib/frontend/oscillator.dart
  97. 0 423
      lib/frontend/terminal_painters.dart
  98. 0 563
      lib/frontend/terminal_view.dart
  99. 0 37
      lib/input/keytab/keytab.dart
  100. 0 1
      lib/input/shortcut.dart

+ 3 - 0
.gitignore

@@ -73,3 +73,6 @@ build/
 !**/ios/**/default.pbxuser
 !**/ios/**/default.pbxuser
 !**/ios/**/default.perspectivev3
 !**/ios/**/default.perspectivev3
 !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
 !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
+
+.vscode/
+example/lib/debug.dart

+ 29 - 0
CHANGELOG.md

@@ -1,3 +1,32 @@
+## [3.0.6-alpha] - 2022-4-4
+* Export `TerminalViewState`
+* Added `onTap` callback to `TerminalView`
+
+## [3.0.5-alpha] - 2022-4-4
+* Avoid resize when `RenderBox.size` is zero.
+* Added `charInput` and `textInput`method.
+* Added `requestKeyboard`, `closeKeyboard` and `hasInputConnection`method.
+* Export `KeyboardVisibilty`
+
+## [3.0.4-alpha] - 2022-4-1
+* Improved text editing
+* Added composing state painting
+* Adapt to `MediaQuery.padding`
+
+## [3.0.3-alpha] - 2022-3-28
+* Improved scroll handing
+* Improved resize handing
+* Fix focus repaint
+* Fix OSC title update
+
+## [3.0.2-alpha] - 2022-3-28
+* Re-design `KeyboardVisibilty`
+
+## [3.0.1-alpha] - 2022-3-27
+* Add `KeyboardVisibilty`
+
+## [3.0.0-alpha] - 2022-3-26
+* Initial release of v3.
 
 
 ## [2.6.0] - 2021-12-28
 ## [2.6.0] - 2021-12-28
 * Add scrollBehavior field to the TerminalView class [#55].
 * Add scrollBehavior field to the TerminalView class [#55].

+ 29 - 0
analysis_options.yaml

@@ -0,0 +1,29 @@
+include: package:lints/recommended.yaml
+
+linter:
+  rules:
+    prefer_function_declarations_over_variables: false
+    prefer_conditional_assignment: false
+
+analyzer:
+  plugins:
+    - dart_code_metrics
+
+dart_code_metrics:
+  anti-patterns:
+  # - long-method
+  # - long-parameter-list
+  metrics:
+    cyclomatic-complexity: 20
+    maximum-nesting-level: 5
+    number-of-parameters: 4
+    source-lines-of-code: 50
+  metrics-exclude:
+    - test/**
+  rules:
+    # - no-boolean-literal-compare
+    # - no-empty-block
+    - prefer-trailing-comma
+    - no-equal-then-else
+  rules-exclude:
+    - prefer-conditional-expressions

+ 57 - 0
bin/xterm_bench.dart

@@ -0,0 +1,57 @@
+import 'package:flutter/material.dart';
+import 'package:xterm/core/terminal.dart';
+
+class Test extends StatelessWidget {
+  const Test({Key? key}) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return TextSelectionToolbar(
+      anchorAbove: Offset(50, 50),
+      anchorBelow: Offset(50, 50),
+      children: [
+        TextSelectionToolbarTextButton(
+          child: Text('Copy'),
+          onPressed: () {},
+          padding: TextSelectionToolbarTextButton.getPadding(0, 1),
+        ),
+        TextSelectionToolbarTextButton(
+          child: Text('Paste'),
+          onPressed: () {},
+          padding: TextSelectionToolbarTextButton.getPadding(1, 1),
+        ),
+      ],
+    );
+  }
+}
+
+void main(List<String> args) async {
+  final lines = 1000;
+
+  final terminal = Terminal(maxLines: lines);
+
+  bench('write $lines lines', () {
+    for (var i = 0; i < lines; i++) {
+      terminal.write('https://github.com/TerminalStudio/dartssh2\r\n');
+    }
+  });
+
+  final regexp = RegExp(
+    r'[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)',
+  );
+
+  bench('search $lines line', () {
+    var count = 0;
+    for (var line in terminal.lines.toList()) {
+      final matches = regexp.allMatches(line.toString());
+      count += matches.length;
+    }
+    print('count: $count');
+  });
+}
+
+void bench(String description, void Function() f) {
+  final sw = Stopwatch()..start();
+  f();
+  print('$description took ${sw.elapsedMilliseconds}ms');
+}

+ 116 - 0
bin/xterm_dump.dart

@@ -0,0 +1,116 @@
+// import 'dart:convert';
+// import 'dart:io';
+// import 'dart:typed_data';
+
+// import 'package:xterm/core/escape/handler.dart';
+// import 'package:xterm/core/escape/parser.dart';
+
+// final handler = DebugTerminalHandler();
+// final protocol = EscapeParser(handler);
+// final input = BytesBuilder(copy: true);
+
+// void main(List<String> args) async {
+//   final inputStream = args.isNotEmpty ? File(args.first).openRead() : stdin;
+
+//   await for (var chunk in inputStream.transform(Utf8Decoder())) {
+//     input.add(chunk);
+//     protocol.write(chunk);
+//   }
+
+//   handler.flush();
+// }
+
+// extension StringEscape on String {
+//   String escapeInvisible() {
+//     return this.replaceAllMapped(RegExp('[\x00-\x1F]'), (match) {
+//       return '\\x${match.group(0)!.codeUnitAt(0).toRadixString(16).padLeft(2, '0')}';
+//     });
+//   }
+// }
+
+// class DebugTerminalHandler implements EscapeHandler {
+//   final stringBuffer = StringBuffer();
+
+//   void flush() {
+//     if (stringBuffer.isEmpty) return;
+//     print(Color.green('TXT') + "'$stringBuffer'");
+//     stringBuffer.clear();
+//   }
+
+//   void recordCommand(String description) {
+//     flush();
+//     final raw = input.toBytes().sublist(protocol.tokenBegin, protocol.tokenEnd);
+//     final token = utf8.decode(raw).replaceAll('\x1b', 'ESC').escapeInvisible();
+//     print(Color.magenta('CMD ') + token.padRight(40) + '$description');
+//   }
+
+//   @override
+//   void writeChar(int char) {
+//     stringBuffer.writeCharCode(char);
+//   }
+
+//   @override
+//   void setCursor(int x, int y) {
+//     recordCommand('setCursor $x, $y');
+//   }
+
+//   @override
+//   void designateCharset(int charset) {
+//     recordCommand('designateCharset $charset');
+//   }
+
+//   @override
+//   void unkownEscape(int char) {
+//     recordCommand('unkownEscape ${String.fromCharCode(char)}');
+//   }
+
+//   @override
+//   void backspaceReturn() {
+//     recordCommand('backspaceReturn');
+//   }
+
+//   @override
+//   void carriageReturn() {
+//     recordCommand('carriageReturn');
+//   }
+
+//   @override
+//   void setCursorX(int x) {
+//     recordCommand('setCursorX $x');
+//   }
+
+//   @override
+//   void setCursorY(int y) {
+//     recordCommand('setCursorY $y');
+//   }
+
+//   @override
+//   void unkownCSI(int finalByte) {
+//     recordCommand('unkownCSI ${String.fromCharCode(finalByte)}');
+//   }
+
+//   @override
+//   void unkownSBC(int char) {
+//     recordCommand('unkownSBC ${String.fromCharCode(char)}');
+//   }
+
+//   @override
+//   noSuchMethod(Invocation invocation) {
+//     final name = invocation.memberName;
+//     final args = invocation.positionalArguments;
+//     recordCommand('noSuchMethod: $name $args');
+//   }
+// }
+
+// abstract class Color {
+//   static String red(String s) => '\u001b[31m$s\u001b[0m';
+//   static String green(String s) => '\u001b[32m$s\u001b[0m';
+//   static String yellow(String s) => '\u001b[33m$s\u001b[0m';
+//   static String blue(String s) => '\u001b[34m$s\u001b[0m';
+//   static String magenta(String s) => '\u001b[35m$s\u001b[0m';
+//   static String cyan(String s) => '\u001b[36m$s\u001b[0m';
+// }
+
+// abstract class Labels {
+//   static final txt = Color.green('TXT');
+// }

+ 38 - 3
example/.metadata

@@ -1,10 +1,45 @@
 # This file tracks properties of this Flutter project.
 # This file tracks properties of this Flutter project.
 # Used by Flutter tool to assess capabilities and perform upgrades etc.
 # Used by Flutter tool to assess capabilities and perform upgrades etc.
 #
 #
-# This file should be version controlled and should not be manually edited.
+# This file should be version controlled.
 
 
 version:
 version:
-  revision: a5fa083906fcaf88b039a717c6e78b9814f3a77c
-  channel: master
+  revision: f1875d570e39de09040c8f79aa13cc56baab8db1
+  channel: stable
 
 
 project_type: app
 project_type: app
+
+# Tracks metadata for the flutter migrate command
+migration:
+  platforms:
+    - platform: root
+      create_revision: f1875d570e39de09040c8f79aa13cc56baab8db1
+      base_revision: f1875d570e39de09040c8f79aa13cc56baab8db1
+    - platform: android
+      create_revision: f1875d570e39de09040c8f79aa13cc56baab8db1
+      base_revision: f1875d570e39de09040c8f79aa13cc56baab8db1
+    - platform: ios
+      create_revision: f1875d570e39de09040c8f79aa13cc56baab8db1
+      base_revision: f1875d570e39de09040c8f79aa13cc56baab8db1
+    - platform: linux
+      create_revision: f1875d570e39de09040c8f79aa13cc56baab8db1
+      base_revision: f1875d570e39de09040c8f79aa13cc56baab8db1
+    - platform: macos
+      create_revision: f1875d570e39de09040c8f79aa13cc56baab8db1
+      base_revision: f1875d570e39de09040c8f79aa13cc56baab8db1
+    - platform: web
+      create_revision: f1875d570e39de09040c8f79aa13cc56baab8db1
+      base_revision: f1875d570e39de09040c8f79aa13cc56baab8db1
+    - platform: windows
+      create_revision: f1875d570e39de09040c8f79aa13cc56baab8db1
+      base_revision: f1875d570e39de09040c8f79aa13cc56baab8db1
+
+  # User provided section
+
+  # List of Local paths (relative to this file) that should be
+  # ignored by the migrate tool.
+  #
+  # Files that are not part of the templates will be ignored by default.
+  unmanaged_files:
+    - 'lib/main.dart'
+    - 'ios/Runner.xcodeproj/project.pbxproj'

+ 23 - 25
example/analysis_options.yaml

@@ -1,29 +1,27 @@
-# This file configures the analyzer, which statically analyzes Dart code to
-# check for errors, warnings, and lints.
-#
-# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
-# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
-# invoked from the command line by running `flutter analyze`.
-
-# The following line activates a set of recommended lints for Flutter apps,
-# packages, and plugins designed to encourage good coding practices.
-include: package:flutter_lints/flutter.yaml
+include: package:lints/recommended.yaml
 
 
 linter:
 linter:
-  # The lint rules applied to this project can be customized in the
-  # section below to disable rules from the `package:flutter_lints/flutter.yaml`
-  # included above or to enable additional rules. A list of all available lints
-  # and their documentation is published at
-  # https://dart-lang.github.io/linter/lints/index.html.
-  #
-  # Instead of disabling a lint rule for the entire project in the
-  # section below, it can also be suppressed for a single line of code
-  # or a specific dart file by using the `// ignore: name_of_lint` and
-  # `// ignore_for_file: name_of_lint` syntax on the line or in the file
-  # producing the lint.
   rules:
   rules:
-    # avoid_print: false  # Uncomment to disable the `avoid_print` rule
-    # prefer_single_quotes: true  # Uncomment to enable the `prefer_single_quotes` rule
+    prefer_function_declarations_over_variables: false
+
+analyzer:
+  plugins:
+    - dart_code_metrics
 
 
-# Additional information about this file can be found at
-# https://dart.dev/guides/language/analysis-options
+dart_code_metrics:
+  anti-patterns:
+  # - long-method
+  # - long-parameter-list
+  metrics:
+    cyclomatic-complexity: 20
+    maximum-nesting-level: 5
+    number-of-parameters: 4
+    source-lines-of-code: 50
+  metrics-exclude:
+    - test/**
+  rules:
+    # - no-boolean-literal-compare
+    # - no-empty-block
+    - prefer-trailing-comma
+    - prefer-conditional-expressions
+    - no-equal-then-else

+ 2 - 0
example/android/.gitignore

@@ -9,3 +9,5 @@ GeneratedPluginRegistrant.java
 # Remember to never publicly share your keystore.
 # Remember to never publicly share your keystore.
 # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
 # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
 key.properties
 key.properties
+**/*.keystore
+**/*.jks

+ 12 - 7
example/android/app/build.gradle

@@ -26,21 +26,26 @@ apply plugin: 'kotlin-android'
 apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
 apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
 
 
 android {
 android {
-    compileSdkVersion 28
+    compileSdkVersion flutter.compileSdkVersion
 
 
-    sourceSets {
-        main.java.srcDirs += 'src/main/kotlin'
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_8
+        targetCompatibility JavaVersion.VERSION_1_8
+    }
+
+    kotlinOptions {
+        jvmTarget = '1.8'
     }
     }
 
 
-    lintOptions {
-        disable 'InvalidPackage'
+    sourceSets {
+        main.java.srcDirs += 'src/main/kotlin'
     }
     }
 
 
     defaultConfig {
     defaultConfig {
         // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
         // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
         applicationId "com.example.example"
         applicationId "com.example.example"
-        minSdkVersion 16
-        targetSdkVersion 28
+        minSdkVersion flutter.minSdkVersion
+        targetSdkVersion flutter.targetSdkVersion
         versionCode flutterVersionCode.toInteger()
         versionCode flutterVersionCode.toInteger()
         versionName flutterVersionName
         versionName flutterVersionName
     }
     }

+ 3 - 16
example/android/app/src/main/AndroidManifest.xml

@@ -1,16 +1,12 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="com.example.example">
     package="com.example.example">
-    <!-- io.flutter.app.FlutterApplication is an android.app.Application that
-         calls FlutterMain.startInitialization(this); in its onCreate method.
-         In most cases you can leave this as-is, but you if you want to provide
-         additional functionality it is fine to subclass or reimplement
-         FlutterApplication and put your custom class here. -->
-    <application
-        android:name="${applicationName}"
+   <application
         android:label="example"
         android:label="example"
+        android:name="${applicationName}"
         android:icon="@mipmap/ic_launcher">
         android:icon="@mipmap/ic_launcher">
         <activity
         <activity
             android:name=".MainActivity"
             android:name=".MainActivity"
+            android:exported="true"
             android:launchMode="singleTop"
             android:launchMode="singleTop"
             android:theme="@style/LaunchTheme"
             android:theme="@style/LaunchTheme"
             android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
             android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
@@ -24,15 +20,6 @@
               android:name="io.flutter.embedding.android.NormalTheme"
               android:name="io.flutter.embedding.android.NormalTheme"
               android:resource="@style/NormalTheme"
               android:resource="@style/NormalTheme"
               />
               />
-            <!-- Displays an Android View that continues showing the launch screen
-                 Drawable until Flutter paints its first frame, then this splash
-                 screen fades out. A splash screen is useful to avoid any visual
-                 gap between the end of Android's launch screen and the painting of
-                 Flutter's first frame. -->
-            <meta-data
-              android:name="io.flutter.embedding.android.SplashScreenDrawable"
-              android:resource="@drawable/launch_background"
-              />
             <intent-filter>
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
                 <action android:name="android.intent.action.MAIN"/>
                 <category android:name="android.intent.category.LAUNCHER"/>
                 <category android:name="android.intent.category.LAUNCHER"/>

+ 1 - 1
example/android/app/src/main/res/values-night/styles.xml

@@ -10,7 +10,7 @@
          This theme determines the color of the Android Window while your
          This theme determines the color of the Android Window while your
          Flutter UI initializes, as well as behind your Flutter UI while its
          Flutter UI initializes, as well as behind your Flutter UI while its
          running.
          running.
-         
+
          This Theme is only used starting with V2 of Flutter's Android embedding. -->
          This Theme is only used starting with V2 of Flutter's Android embedding. -->
     <style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
     <style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
         <item name="android:windowBackground">?android:colorBackground</item>
         <item name="android:windowBackground">?android:colorBackground</item>

+ 5 - 5
example/android/app/src/main/res/values/styles.xml

@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 <?xml version="1.0" encoding="utf-8"?>
 <resources>
 <resources>
-    <!-- Theme applied to the Android Window while the process is starting -->
-    <style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
+    <!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
+    <style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
         <!-- Show a splash screen on the activity. Automatically removed when
         <!-- Show a splash screen on the activity. Automatically removed when
              Flutter draws its first frame -->
              Flutter draws its first frame -->
         <item name="android:windowBackground">@drawable/launch_background</item>
         <item name="android:windowBackground">@drawable/launch_background</item>
@@ -10,9 +10,9 @@
          This theme determines the color of the Android Window while your
          This theme determines the color of the Android Window while your
          Flutter UI initializes, as well as behind your Flutter UI while its
          Flutter UI initializes, as well as behind your Flutter UI while its
          running.
          running.
-         
+
          This Theme is only used starting with V2 of Flutter's Android embedding. -->
          This Theme is only used starting with V2 of Flutter's Android embedding. -->
-    <style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
-        <item name="android:windowBackground">@android:color/white</item>
+    <style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
+        <item name="android:windowBackground">?android:colorBackground</item>
     </style>
     </style>
 </resources>
 </resources>

+ 4 - 4
example/android/build.gradle

@@ -1,12 +1,12 @@
 buildscript {
 buildscript {
-    ext.kotlin_version = '1.3.50'
+    ext.kotlin_version = '1.6.10'
     repositories {
     repositories {
         google()
         google()
-        jcenter()
+        mavenCentral()
     }
     }
 
 
     dependencies {
     dependencies {
-        classpath 'com.android.tools.build:gradle:3.5.0'
+        classpath 'com.android.tools.build:gradle:4.1.0'
         classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
         classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
     }
     }
 }
 }
@@ -14,7 +14,7 @@ buildscript {
 allprojects {
 allprojects {
     repositories {
     repositories {
         google()
         google()
-        jcenter()
+        mavenCentral()
     }
     }
 }
 }
 
 

+ 0 - 1
example/android/gradle.properties

@@ -1,4 +1,3 @@
 org.gradle.jvmargs=-Xmx1536M
 org.gradle.jvmargs=-Xmx1536M
-android.enableR8=true
 android.useAndroidX=true
 android.useAndroidX=true
 android.enableJetifier=true
 android.enableJetifier=true

+ 1 - 1
example/android/gradle/wrapper/gradle-wrapper.properties

@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
 distributionPath=wrapper/dists
 zipStoreBase=GRADLE_USER_HOME
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
 zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip

+ 1 - 1
example/ios/Flutter/AppFrameworkInfo.plist

@@ -21,6 +21,6 @@
   <key>CFBundleVersion</key>
   <key>CFBundleVersion</key>
   <string>1.0</string>
   <string>1.0</string>
   <key>MinimumOSVersion</key>
   <key>MinimumOSVersion</key>
-  <string>8.0</string>
+  <string>9.0</string>
 </dict>
 </dict>
 </plist>
 </plist>

+ 1 - 0
example/ios/Flutter/Debug.xcconfig

@@ -1 +1,2 @@
+#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
 #include "Generated.xcconfig"
 #include "Generated.xcconfig"

+ 1 - 0
example/ios/Flutter/Release.xcconfig

@@ -1 +1,2 @@
+#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
 #include "Generated.xcconfig"
 #include "Generated.xcconfig"

+ 41 - 0
example/ios/Podfile

@@ -0,0 +1,41 @@
+# Uncomment this line to define a global platform for your project
+# platform :ios, '9.0'
+
+# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
+ENV['COCOAPODS_DISABLE_STATS'] = 'true'
+
+project 'Runner', {
+  'Debug' => :debug,
+  'Profile' => :release,
+  'Release' => :release,
+}
+
+def flutter_root
+  generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
+  unless File.exist?(generated_xcode_build_settings_path)
+    raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
+  end
+
+  File.foreach(generated_xcode_build_settings_path) do |line|
+    matches = line.match(/FLUTTER_ROOT\=(.*)/)
+    return matches[1].strip if matches
+  end
+  raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
+end
+
+require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
+
+flutter_ios_podfile_setup
+
+target 'Runner' do
+  use_frameworks!
+  use_modular_headers!
+
+  flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
+end
+
+post_install do |installer|
+  installer.pods_project.targets.each do |target|
+    flutter_additional_ios_build_settings(target)
+  end
+end

+ 22 - 9
example/ios/Runner.xcodeproj/project.pbxproj

@@ -3,7 +3,7 @@
 	archiveVersion = 1;
 	archiveVersion = 1;
 	classes = {
 	classes = {
 	};
 	};
-	objectVersion = 46;
+	objectVersion = 50;
 	objects = {
 	objects = {
 
 
 /* Begin PBXBuildFile section */
 /* Begin PBXBuildFile section */
@@ -127,7 +127,7 @@
 		97C146E61CF9000F007C117D /* Project object */ = {
 		97C146E61CF9000F007C117D /* Project object */ = {
 			isa = PBXProject;
 			isa = PBXProject;
 			attributes = {
 			attributes = {
-				LastUpgradeCheck = 1020;
+				LastUpgradeCheck = 1300;
 				ORGANIZATIONNAME = "";
 				ORGANIZATIONNAME = "";
 				TargetAttributes = {
 				TargetAttributes = {
 					97C146ED1CF9000F007C117D = {
 					97C146ED1CF9000F007C117D = {
@@ -272,7 +272,7 @@
 				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
 				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
 				GCC_WARN_UNUSED_FUNCTION = YES;
 				GCC_WARN_UNUSED_FUNCTION = YES;
 				GCC_WARN_UNUSED_VARIABLE = YES;
 				GCC_WARN_UNUSED_VARIABLE = YES;
-				IPHONEOS_DEPLOYMENT_TARGET = 8.0;
+				IPHONEOS_DEPLOYMENT_TARGET = 9.0;
 				MTL_ENABLE_DEBUG_INFO = NO;
 				MTL_ENABLE_DEBUG_INFO = NO;
 				SDKROOT = iphoneos;
 				SDKROOT = iphoneos;
 				SUPPORTED_PLATFORMS = iphoneos;
 				SUPPORTED_PLATFORMS = iphoneos;
@@ -288,13 +288,17 @@
 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
 				CLANG_ENABLE_MODULES = YES;
 				CLANG_ENABLE_MODULES = YES;
 				CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
 				CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
+				DEVELOPMENT_TEAM = BA88US33G6;
 				ENABLE_BITCODE = NO;
 				ENABLE_BITCODE = NO;
 				FRAMEWORK_SEARCH_PATHS = (
 				FRAMEWORK_SEARCH_PATHS = (
 					"$(inherited)",
 					"$(inherited)",
 					"$(PROJECT_DIR)/Flutter",
 					"$(PROJECT_DIR)/Flutter",
 				);
 				);
 				INFOPLIST_FILE = Runner/Info.plist;
 				INFOPLIST_FILE = Runner/Info.plist;
-				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
+				LD_RUNPATH_SEARCH_PATHS = (
+					"$(inherited)",
+					"@executable_path/Frameworks",
+				);
 				LIBRARY_SEARCH_PATHS = (
 				LIBRARY_SEARCH_PATHS = (
 					"$(inherited)",
 					"$(inherited)",
 					"$(PROJECT_DIR)/Flutter",
 					"$(PROJECT_DIR)/Flutter",
@@ -354,7 +358,7 @@
 				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
 				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
 				GCC_WARN_UNUSED_FUNCTION = YES;
 				GCC_WARN_UNUSED_FUNCTION = YES;
 				GCC_WARN_UNUSED_VARIABLE = YES;
 				GCC_WARN_UNUSED_VARIABLE = YES;
-				IPHONEOS_DEPLOYMENT_TARGET = 8.0;
+				IPHONEOS_DEPLOYMENT_TARGET = 9.0;
 				MTL_ENABLE_DEBUG_INFO = YES;
 				MTL_ENABLE_DEBUG_INFO = YES;
 				ONLY_ACTIVE_ARCH = YES;
 				ONLY_ACTIVE_ARCH = YES;
 				SDKROOT = iphoneos;
 				SDKROOT = iphoneos;
@@ -403,11 +407,12 @@
 				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
 				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
 				GCC_WARN_UNUSED_FUNCTION = YES;
 				GCC_WARN_UNUSED_FUNCTION = YES;
 				GCC_WARN_UNUSED_VARIABLE = YES;
 				GCC_WARN_UNUSED_VARIABLE = YES;
-				IPHONEOS_DEPLOYMENT_TARGET = 8.0;
+				IPHONEOS_DEPLOYMENT_TARGET = 9.0;
 				MTL_ENABLE_DEBUG_INFO = NO;
 				MTL_ENABLE_DEBUG_INFO = NO;
 				SDKROOT = iphoneos;
 				SDKROOT = iphoneos;
 				SUPPORTED_PLATFORMS = iphoneos;
 				SUPPORTED_PLATFORMS = iphoneos;
-				SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
+				SWIFT_COMPILATION_MODE = wholemodule;
+				SWIFT_OPTIMIZATION_LEVEL = "-O";
 				TARGETED_DEVICE_FAMILY = "1,2";
 				TARGETED_DEVICE_FAMILY = "1,2";
 				VALIDATE_PRODUCT = YES;
 				VALIDATE_PRODUCT = YES;
 			};
 			};
@@ -420,13 +425,17 @@
 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
 				CLANG_ENABLE_MODULES = YES;
 				CLANG_ENABLE_MODULES = YES;
 				CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
 				CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
+				DEVELOPMENT_TEAM = BA88US33G6;
 				ENABLE_BITCODE = NO;
 				ENABLE_BITCODE = NO;
 				FRAMEWORK_SEARCH_PATHS = (
 				FRAMEWORK_SEARCH_PATHS = (
 					"$(inherited)",
 					"$(inherited)",
 					"$(PROJECT_DIR)/Flutter",
 					"$(PROJECT_DIR)/Flutter",
 				);
 				);
 				INFOPLIST_FILE = Runner/Info.plist;
 				INFOPLIST_FILE = Runner/Info.plist;
-				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
+				LD_RUNPATH_SEARCH_PATHS = (
+					"$(inherited)",
+					"@executable_path/Frameworks",
+				);
 				LIBRARY_SEARCH_PATHS = (
 				LIBRARY_SEARCH_PATHS = (
 					"$(inherited)",
 					"$(inherited)",
 					"$(PROJECT_DIR)/Flutter",
 					"$(PROJECT_DIR)/Flutter",
@@ -447,13 +456,17 @@
 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
 				CLANG_ENABLE_MODULES = YES;
 				CLANG_ENABLE_MODULES = YES;
 				CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
 				CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
+				DEVELOPMENT_TEAM = BA88US33G6;
 				ENABLE_BITCODE = NO;
 				ENABLE_BITCODE = NO;
 				FRAMEWORK_SEARCH_PATHS = (
 				FRAMEWORK_SEARCH_PATHS = (
 					"$(inherited)",
 					"$(inherited)",
 					"$(PROJECT_DIR)/Flutter",
 					"$(PROJECT_DIR)/Flutter",
 				);
 				);
 				INFOPLIST_FILE = Runner/Info.plist;
 				INFOPLIST_FILE = Runner/Info.plist;
-				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
+				LD_RUNPATH_SEARCH_PATHS = (
+					"$(inherited)",
+					"@executable_path/Frameworks",
+				);
 				LIBRARY_SEARCH_PATHS = (
 				LIBRARY_SEARCH_PATHS = (
 					"$(inherited)",
 					"$(inherited)",
 					"$(PROJECT_DIR)/Flutter",
 					"$(PROJECT_DIR)/Flutter",

+ 1 - 1
example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata

@@ -2,6 +2,6 @@
 <Workspace
 <Workspace
    version = "1.0">
    version = "1.0">
    <FileRef
    <FileRef
-      location = "group:Runner.xcodeproj">
+      location = "self:">
    </FileRef>
    </FileRef>
 </Workspace>
 </Workspace>

+ 1 - 1
example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme

@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <?xml version="1.0" encoding="UTF-8"?>
 <Scheme
 <Scheme
-   LastUpgradeVersion = "1020"
+   LastUpgradeVersion = "1300"
    version = "1.3">
    version = "1.3">
    <BuildAction
    <BuildAction
       parallelizeBuildables = "YES"
       parallelizeBuildables = "YES"

+ 0 - 150
example/lib/isolate.dart

@@ -1,150 +0,0 @@
-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() {
-  runApp(MyApp());
-}
-
-class MyApp extends StatelessWidget {
-  @override
-  Widget build(BuildContext context) {
-    return MaterialApp(
-      title: 'xterm.dart demo',
-      theme: ThemeData(
-        primarySwatch: Colors.blue,
-        visualDensity: VisualDensity.adaptivePlatformDensity,
-      ),
-      home: MyHomePage(
-        theme: TerminalThemes.defaultTheme,
-        terminalOpacity: 0.8,
-      ),
-    );
-  }
-}
-
-class MyHomePage extends StatefulWidget {
-  MyHomePage({
-    Key? key,
-    required this.theme,
-    required this.terminalOpacity,
-  }) : super(key: key);
-
-  final TerminalTheme theme;
-  final double terminalOpacity;
-
-  @override
-  _MyHomePageState createState() => _MyHomePageState();
-}
-
-class FakeTerminalBackend extends TerminalBackend {
-  // we do a late initialization of those backend members as the backend gets
-  // transferred into the Isolate.
-  // It is not allowed to e.g. transfer closures which we can not guarantee
-  // to not exist in our member types.
-  // The Isolate will call init() once it starts (from its context) and that is
-  // the place where we initialize those members
-  late final _exitCodeCompleter;
-  // ignore: close_sinks
-  late final _outStream;
-
-  @override
-  bool get isReady => true;
-
-  @override
-  Future<int> get exitCode => _exitCodeCompleter.future;
-
-  @override
-  void init() {
-    _exitCodeCompleter = Completer<int>();
-    _outStream = StreamController<String>();
-    _outStream.sink.add('xterm.dart demo');
-    _outStream.sink.add('\r\n');
-    _outStream.sink.add('\$ ');
-  }
-
-  @override
-  Stream<String> get out => _outStream.stream;
-
-  @override
-  void resize(int width, int height, int pixelWidth, int pixelHeight) {
-    // NOOP
-  }
-
-  @override
-  void write(String input) {
-    if (input.length <= 0) {
-      return;
-    }
-    // in a "real" terminal emulation you would connect onInput to the backend
-    // (like a pty or ssh connection) that then handles the changes in the
-    // terminal.
-    // As we don't have a connected backend here we simulate the changes by
-    // directly writing to the terminal.
-    if (input == '\r') {
-      _outStream.sink.add('\r\n');
-      _outStream.sink.add('\$ ');
-    } else if (input.codeUnitAt(0) == 127) {
-      // Backspace handling
-      _outStream.sink.add('\b \b');
-    } else {
-      _outStream.sink.add(input);
-    }
-  }
-
-  @override
-  void terminate() {
-    //NOOP
-  }
-
-  @override
-  void ackProcessed() {
-    //NOOP
-  }
-}
-
-class _MyHomePageState extends State<MyHomePage> {
-  TerminalIsolate? terminal;
-
-  Future<TerminalIsolate> _ensureTerminalStarted() async {
-    if (terminal == null) {
-      terminal = TerminalIsolate(
-        backend: FakeTerminalBackend(),
-        maxLines: 10000,
-        theme: widget.theme,
-      );
-    }
-
-    if (!terminal!.isReady) {
-      await terminal!.start();
-    }
-    return terminal!;
-  }
-
-  void onInput(String input) {}
-
-  @override
-  Widget build(BuildContext context) {
-    return Scaffold(
-      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),
-                  ),
-          );
-        },
-      ),
-    );
-  }
-}

+ 0 - 100
example/lib/local.dart

@@ -1,100 +0,0 @@
-import 'dart:io';
-
-import 'package:flutter/material.dart';
-import 'package:pty/pty.dart';
-import 'package:xterm/flutter.dart';
-import 'package:xterm/xterm.dart';
-
-void main() {
-  runApp(MyApp());
-}
-
-class MyApp extends StatelessWidget {
-  @override
-  Widget build(BuildContext context) {
-    return MaterialApp(
-      title: 'xterm.dart demo',
-      theme: ThemeData(
-        primarySwatch: Colors.blue,
-        visualDensity: VisualDensity.adaptivePlatformDensity,
-      ),
-      home: LocalTerminal(),
-    );
-  }
-}
-
-class LocalTerminalBackend extends TerminalBackend {
-  LocalTerminalBackend();
-
-  final pty = PseudoTerminal.start(
-    '/system/bin/sh',
-    [],
-    blocking: false,
-    environment: Platform.environment,
-  );
-
-  @override
-  bool get isReady => true;
-
-  @override
-  Future<int> get exitCode => pty.exitCode;
-
-  @override
-  void init() {
-    pty.init();
-  }
-
-  @override
-  Stream<String> get out => pty.out;
-
-  @override
-  void resize(int width, int height, int pixelWidth, int pixelHeight) {
-    pty.resize(width, height);
-  }
-
-  @override
-  void write(String input) {
-    pty.write(input);
-  }
-
-  @override
-  void terminate() {
-    // client.disconnect('terminate');
-  }
-
-  @override
-  void ackProcessed() {
-    // NOOP
-  }
-}
-
-class LocalTerminal extends StatefulWidget {
-  const LocalTerminal({Key? key}) : super(key: key);
-
-  @override
-  _LocalTerminalState createState() => _LocalTerminalState();
-}
-
-class _LocalTerminalState extends State<LocalTerminal> {
-  final terminal = Terminal(maxLines: 10000, backend: LocalTerminalBackend());
-
-  @override
-  void initState() {
-    super.initState();
-  }
-
-  void onInput(String input) {
-    print('input: $input');
-  }
-
-  @override
-  Widget build(BuildContext context) {
-    return Scaffold(
-      body: SafeArea(
-        child: TerminalView(
-          terminal: terminal,
-        ),
-      ),
-    );
-  }
-}

+ 75 - 62
example/lib/main.dart

@@ -1,22 +1,44 @@
-import 'dart:async';
+import 'dart:convert';
+import 'dart:io';
 
 
+import 'package:flutter/foundation.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
-import 'package:xterm/flutter.dart';
+import 'package:flutter_acrylic/flutter_acrylic.dart';
+import 'package:flutter_pty/flutter_pty.dart';
 import 'package:xterm/xterm.dart';
 import 'package:xterm/xterm.dart';
 
 
 void main() {
 void main() {
+  WidgetsFlutterBinding.ensureInitialized();
+
+  if (isDesktop) {
+    setupAcrylic();
+  }
+
   runApp(MyApp());
   runApp(MyApp());
 }
 }
 
 
+bool get isDesktop {
+  if (kIsWeb) return false;
+  return [
+    TargetPlatform.windows,
+    TargetPlatform.linux,
+    TargetPlatform.macOS,
+  ].contains(defaultTargetPlatform);
+}
+
+Future<void> setupAcrylic() async {
+  await Window.initialize();
+  await Window.makeTitlebarTransparent();
+  await Window.setEffect(effect: WindowEffect.aero);
+  await Window.setBlurViewState(MacOSBlurViewState.active);
+}
+
 class MyApp extends StatelessWidget {
 class MyApp extends StatelessWidget {
   @override
   @override
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
     return MaterialApp(
     return MaterialApp(
       title: 'xterm.dart demo',
       title: 'xterm.dart demo',
-      theme: ThemeData(
-        primarySwatch: Colors.blue,
-        visualDensity: VisualDensity.adaptivePlatformDensity,
-      ),
+      debugShowCheckedModeBanner: false,
       home: MyHomePage(),
       home: MyHomePage(),
     );
     );
   }
   }
@@ -26,84 +48,75 @@ class MyHomePage extends StatefulWidget {
   MyHomePage({Key? key}) : super(key: key);
   MyHomePage({Key? key}) : super(key: key);
 
 
   @override
   @override
+  // ignore: library_private_types_in_public_api
   _MyHomePageState createState() => _MyHomePageState();
   _MyHomePageState createState() => _MyHomePageState();
 }
 }
 
 
-class FakeTerminalBackend extends TerminalBackend {
-  final _exitCodeCompleter = Completer<int>();
-  // ignore: close_sinks
-  final _outStream = StreamController<String>();
+class _MyHomePageState extends State<MyHomePage> {
+  final terminal = Terminal(
+    maxLines: 10000,
+  );
 
 
-  @override
-  bool get isReady => true;
+  late final Pty pty;
 
 
   @override
   @override
-  Future<int> get exitCode => _exitCodeCompleter.future;
+  void initState() {
+    super.initState();
 
 
-  @override
-  void init() {
-    _outStream.sink.add('xterm.dart demo');
-    _outStream.sink.add('\r\n');
-    _outStream.sink.add('\$ ');
+    WidgetsBinding.instance.endOfFrame.then(
+      (_) {
+        if (mounted) _startPty();
+      },
+    );
   }
   }
 
 
-  @override
-  Stream<String> get out => _outStream.stream;
+  void _startPty() {
+    pty = Pty.start(
+      shell,
+      columns: terminal.viewWidth,
+      rows: terminal.viewHeight,
+    );
 
 
-  @override
-  void resize(int width, int height, int pixelWidth, int pixelHeight) {
-    // NOOP
-  }
+    pty.output
+        .cast<List<int>>()
+        .transform(Utf8Decoder())
+        .listen(terminal.write);
 
 
-  @override
-  void write(String input) {
-    if (input.length <= 0) {
-      return;
-    }
-    // in a "real" terminal emulation you would connect onInput to the backend
-    // (like a pty or ssh connection) that then handles the changes in the
-    // terminal.
-    // As we don't have a connected backend here we simulate the changes by
-    // directly writing to the terminal.
-    if (input == '\r') {
-      _outStream.sink.add('\r\n');
-      _outStream.sink.add('\$ ');
-    } else if (input.codeUnitAt(0) == 127) {
-      // Backspace handling
-      _outStream.sink.add('\b \b');
-    } else {
-      _outStream.sink.add(input);
-    }
-  }
+    pty.exitCode.then((code) {
+      terminal.write('the process exited with exit code $code');
+    });
 
 
-  @override
-  void terminate() {
-    //NOOP
-  }
+    terminal.onOutput = (data) {
+      pty.write(const Utf8Encoder().convert(data));
+    };
 
 
-  @override
-  void ackProcessed() {
-    //NOOP
+    terminal.onResize = (w, h, pw, ph) {
+      pty.resize(h, w);
+    };
   }
   }
-}
-
-class _MyHomePageState extends State<MyHomePage> {
-  final terminal = Terminal(
-    backend: FakeTerminalBackend(),
-    maxLines: 10000,
-  );
-
-  void onInput(String input) {}
 
 
   @override
   @override
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
     return Scaffold(
     return Scaffold(
+      backgroundColor: Colors.transparent,
       body: SafeArea(
       body: SafeArea(
         child: TerminalView(
         child: TerminalView(
-          terminal: terminal,
-          style: TerminalStyle(fontFamily: ['Cascadia Mono']),
+          terminal,
+          backgroundOpacity: 0.8,
         ),
         ),
       ),
       ),
     );
     );
   }
   }
 }
 }
+
+String get shell {
+  if (Platform.isMacOS || Platform.isLinux) {
+    return Platform.environment['SHELL'] ?? 'bash';
+  }
+
+  if (Platform.isWindows) {
+    return 'cmd.exe';
+  }
+
+  return 'sh';
+}

+ 107 - 0
example/lib/mock.dart

@@ -0,0 +1,107 @@
+import 'dart:async';
+
+import 'package:flutter/foundation.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_acrylic/flutter_acrylic.dart';
+import 'package:xterm/xterm.dart';
+
+void main() {
+  WidgetsFlutterBinding.ensureInitialized();
+
+  if (isDesktop) {}
+  setupAcrylic();
+
+  runApp(MyApp());
+}
+
+bool get isDesktop {
+  if (kIsWeb) return false;
+  return [
+    TargetPlatform.windows,
+    TargetPlatform.linux,
+    TargetPlatform.macOS,
+  ].contains(defaultTargetPlatform);
+}
+
+Future<void> setupAcrylic() async {
+  await Window.initialize();
+  await Window.makeTitlebarTransparent();
+  await Window.setEffect(effect: WindowEffect.aero);
+  await Window.setBlurViewState(MacOSBlurViewState.active);
+}
+
+class MyApp extends StatelessWidget {
+  @override
+  Widget build(BuildContext context) {
+    return MaterialApp(
+      title: 'xterm.dart demo',
+      debugShowCheckedModeBanner: false,
+      home: MyHomePage(),
+    );
+  }
+}
+
+class MyHomePage extends StatefulWidget {
+  MyHomePage({Key? key}) : super(key: key);
+
+  @override
+  // ignore: library_private_types_in_public_api
+  _MyHomePageState createState() => _MyHomePageState();
+}
+
+class _MyHomePageState extends State<MyHomePage> {
+  final terminal = Terminal(
+    maxLines: 1000,
+  );
+
+  late final MockRepl pty;
+
+  @override
+  void initState() {
+    super.initState();
+
+    pty = MockRepl(terminal.write);
+
+    terminal.onOutput = pty.write;
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return Scaffold(
+      backgroundColor: Colors.transparent,
+      body: SafeArea(
+        child: TerminalView(
+          terminal,
+          backgroundOpacity: 0.7,
+        ),
+      ),
+    );
+  }
+}
+
+class MockRepl {
+  MockRepl(this.onOutput) {
+    onOutput('Welcome to xterm.dart!\r\n');
+    onOutput('Type "help" for more information.\r\n');
+    onOutput('\n');
+    onOutput('\$ ');
+  }
+
+  final void Function(String data) onOutput;
+
+  void write(String input) {
+    for (var char in input.codeUnits) {
+      switch (char) {
+        case 13: // carriage return
+          onOutput.call('\r\n');
+          onOutput.call('\$ ');
+          break;
+        case 127: // backspace
+          onOutput.call('\b \b');
+          break;
+        default:
+          onOutput.call(String.fromCharCode(char));
+      }
+    }
+  }
+}

+ 53 - 83
example/lib/ssh.dart

@@ -3,11 +3,11 @@ import 'dart:convert';
 import 'dart:typed_data';
 import 'dart:typed_data';
 
 
 import 'package:dartssh2/dartssh2.dart';
 import 'package:dartssh2/dartssh2.dart';
-import 'package:flutter/material.dart';
-import 'package:xterm/flutter.dart';
+import 'package:flutter/cupertino.dart';
 import 'package:xterm/xterm.dart';
 import 'package:xterm/xterm.dart';
 
 
-const host = 'ssh://localhost:22';
+const host = 'localhost';
+const port = 22;
 const username = '<your username>';
 const username = '<your username>';
 const password = '<your password>';
 const password = '<your password>';
 
 
@@ -18,12 +18,8 @@ void main() {
 class MyApp extends StatelessWidget {
 class MyApp extends StatelessWidget {
   @override
   @override
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
-    return MaterialApp(
+    return CupertinoApp(
       title: 'xterm.dart demo',
       title: 'xterm.dart demo',
-      theme: ThemeData(
-        primarySwatch: Colors.blue,
-        visualDensity: VisualDensity.adaptivePlatformDensity,
-      ),
       home: MyHomePage(),
       home: MyHomePage(),
     );
     );
   }
   }
@@ -33,101 +29,75 @@ class MyHomePage extends StatefulWidget {
   MyHomePage({Key? key}) : super(key: key);
   MyHomePage({Key? key}) : super(key: key);
 
 
   @override
   @override
+  // ignore: library_private_types_in_public_api
   _MyHomePageState createState() => _MyHomePageState();
   _MyHomePageState createState() => _MyHomePageState();
 }
 }
 
 
-class SSHTerminalBackend extends TerminalBackend {
-  late SSHClient client;
-
-  String _host;
-  String _username;
-  String _password;
-
-  final _exitCodeCompleter = Completer<int>();
-  final _outStream = StreamController<String>();
-
-  SSHTerminalBackend(this._host, this._username, this._password);
+class _MyHomePageState extends State<MyHomePage> {
+  final terminal = Terminal();
 
 
-  void onWrite(String data) {
-    _outStream.sink.add(data);
-  }
+  var title = host;
 
 
   @override
   @override
-  bool get isReady => true;
+  void initState() {
+    super.initState();
+    initTerminal();
+  }
 
 
-  @override
-  Future<int> get exitCode => _exitCodeCompleter.future;
+  Future<void> initTerminal() async {
+    terminal.write('Connecting...\r\n');
 
 
-  @override
-  void init() {
-    // Use utf8.decoder to handle broken utf8 chunks
-    final _sshOutput = StreamController<List<int>>();
-    _sshOutput.stream.transform(utf8.decoder).listen(onWrite);
-
-    onWrite('connecting $_host...');
-    client = SSHClient(
-      hostport: Uri.parse(_host),
-      username: _username,
-      print: print,
-      termWidth: 80,
-      termHeight: 25,
-      termvar: 'xterm-256color',
-      onPasswordRequest: () => _password,
-      response: (data) {
-        _sshOutput.add(data);
-      },
-      success: () {
-        onWrite('connected.\n');
-      },
-      disconnected: () {
-        onWrite('disconnected.');
-        _outStream.close();
-      },
+    final client = SSHClient(
+      await SSHSocket.connect(host, port),
+      username: username,
+      onPasswordRequest: () => password,
     );
     );
-  }
 
 
-  @override
-  Stream<String> get out => _outStream.stream;
+    terminal.write('Connected\r\n');
 
 
-  @override
-  void resize(int width, int height, int pixelWidth, int pixelHeight) {
-    client.setTerminalWindowSize(width, height);
-  }
+    final session = await client.shell(
+      pty: SSHPtyConfig(
+        width: terminal.viewWidth,
+        height: terminal.viewHeight,
+      ),
+    );
 
 
-  @override
-  void write(String input) {
-    client.sendChannelData(Uint8List.fromList(utf8.encode(input)));
-  }
+    terminal.buffer.clear();
+    terminal.buffer.setCursor(0, 0);
 
 
-  @override
-  void terminate() {
-    client.disconnect('terminate');
-  }
+    terminal.onTitleChange = (title) {
+      setState(() => this.title = title);
+    };
 
 
-  @override
-  void ackProcessed() {
-    // NOOP
-  }
-}
+    terminal.onResize = (width, height, pixelWidth, pixelHeight) {
+      session.resizeTerminal(width, height, pixelWidth, pixelHeight);
+    };
 
 
-class _MyHomePageState extends State<MyHomePage> {
-  late Terminal terminal;
-  late SSHTerminalBackend backend;
+    terminal.onOutput = (data) {
+      session.write(utf8.encode(data) as Uint8List);
+    };
 
 
-  @override
-  void initState() {
-    super.initState();
-    backend = SSHTerminalBackend(host, username, password);
-    terminal = Terminal(backend: backend, maxLines: 10000);
+    session.stdout
+        .cast<List<int>>()
+        .transform(Utf8Decoder())
+        .listen(terminal.write);
+
+    session.stderr
+        .cast<List<int>>()
+        .transform(Utf8Decoder())
+        .listen(terminal.write);
   }
   }
 
 
   @override
   @override
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
-    return Scaffold(
-      body: SafeArea(
-        child: TerminalView(
-          terminal: terminal,
-        ),
+    return CupertinoPageScaffold(
+      navigationBar: CupertinoNavigationBar(
+        middle: Text(title),
+        backgroundColor:
+            CupertinoTheme.of(context).barBackgroundColor.withOpacity(0.5),
+      ),
+      child: TerminalView(
+        terminal,
       ),
       ),
     );
     );
   }
   }

+ 4 - 0
example/linux/flutter/generated_plugin_registrant.cc

@@ -6,6 +6,10 @@
 
 
 #include "generated_plugin_registrant.h"
 #include "generated_plugin_registrant.h"
 
 
+#include <flutter_acrylic/flutter_acrylic_plugin.h>
 
 
 void fl_register_plugins(FlPluginRegistry* registry) {
 void fl_register_plugins(FlPluginRegistry* registry) {
+  g_autoptr(FlPluginRegistrar) flutter_acrylic_registrar =
+      fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterAcrylicPlugin");
+  flutter_acrylic_plugin_register_with_registrar(flutter_acrylic_registrar);
 }
 }

+ 2 - 0
example/linux/flutter/generated_plugins.cmake

@@ -3,9 +3,11 @@
 #
 #
 
 
 list(APPEND FLUTTER_PLUGIN_LIST
 list(APPEND FLUTTER_PLUGIN_LIST
+  flutter_acrylic
 )
 )
 
 
 list(APPEND FLUTTER_FFI_PLUGIN_LIST
 list(APPEND FLUTTER_FFI_PLUGIN_LIST
+  flutter_pty
 )
 )
 
 
 set(PLUGIN_BUNDLED_LIBRARIES)
 set(PLUGIN_BUNDLED_LIBRARIES)

+ 1 - 0
example/macos/.gitignore

@@ -3,4 +3,5 @@
 **/Pods/
 **/Pods/
 
 
 # Xcode-related
 # Xcode-related
+**/dgph
 **/xcuserdata/
 **/xcuserdata/

+ 1 - 0
example/macos/Flutter/Flutter-Debug.xcconfig

@@ -1 +1,2 @@
+#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
 #include "ephemeral/Flutter-Generated.xcconfig"
 #include "ephemeral/Flutter-Generated.xcconfig"

+ 1 - 0
example/macos/Flutter/Flutter-Release.xcconfig

@@ -1 +1,2 @@
+#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
 #include "ephemeral/Flutter-Generated.xcconfig"
 #include "ephemeral/Flutter-Generated.xcconfig"

+ 2 - 0
example/macos/Flutter/GeneratedPluginRegistrant.swift

@@ -5,6 +5,8 @@
 import FlutterMacOS
 import FlutterMacOS
 import Foundation
 import Foundation
 
 
+import flutter_acrylic
 
 
 func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
 func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
+  FlutterAcrylicPlugin.register(with: registry.registrar(forPlugin: "FlutterAcrylicPlugin"))
 }
 }

+ 40 - 0
example/macos/Podfile

@@ -0,0 +1,40 @@
+platform :osx, '10.11'
+
+# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
+ENV['COCOAPODS_DISABLE_STATS'] = 'true'
+
+project 'Runner', {
+  'Debug' => :debug,
+  'Profile' => :release,
+  'Release' => :release,
+}
+
+def flutter_root
+  generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__)
+  unless File.exist?(generated_xcode_build_settings_path)
+    raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first"
+  end
+
+  File.foreach(generated_xcode_build_settings_path) do |line|
+    matches = line.match(/FLUTTER_ROOT\=(.*)/)
+    return matches[1].strip if matches
+  end
+  raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\""
+end
+
+require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
+
+flutter_macos_podfile_setup
+
+target 'Runner' do
+  use_frameworks!
+  use_modular_headers!
+
+  flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__))
+end
+
+post_install do |installer|
+  installer.pods_project.targets.each do |target|
+    flutter_additional_macos_build_settings(target)
+  end
+end

+ 28 - 0
example/macos/Podfile.lock

@@ -0,0 +1,28 @@
+PODS:
+  - flutter_acrylic (0.1.0):
+    - FlutterMacOS
+  - flutter_pty (0.0.1):
+    - FlutterMacOS
+  - FlutterMacOS (1.0.0)
+
+DEPENDENCIES:
+  - flutter_acrylic (from `Flutter/ephemeral/.symlinks/plugins/flutter_acrylic/macos`)
+  - flutter_pty (from `Flutter/ephemeral/.symlinks/plugins/flutter_pty/macos`)
+  - FlutterMacOS (from `Flutter/ephemeral`)
+
+EXTERNAL SOURCES:
+  flutter_acrylic:
+    :path: Flutter/ephemeral/.symlinks/plugins/flutter_acrylic/macos
+  flutter_pty:
+    :path: Flutter/ephemeral/.symlinks/plugins/flutter_pty/macos
+  FlutterMacOS:
+    :path: Flutter/ephemeral
+
+SPEC CHECKSUMS:
+  flutter_acrylic: c3df24ae52ab6597197837ce59ef2a8542640c17
+  flutter_pty: 41b6f848ade294be726a6b94cdd4a67c3bc52f59
+  FlutterMacOS: 57701585bf7de1b3fc2bb61f6378d73bbdea8424
+
+PODFILE CHECKSUM: 6eac6b3292e5142cfc23bdeb71848a40ec51c14c
+
+COCOAPODS: 1.11.3

+ 65 - 17
example/macos/Runner.xcodeproj/project.pbxproj

@@ -26,6 +26,7 @@
 		33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; };
 		33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; };
 		33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; };
 		33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; };
 		33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; };
 		33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; };
+		A6151BD419F68182C8FF85D2 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 413CC3D3B2FBCF7B69907E26 /* Pods_Runner.framework */; };
 /* End PBXBuildFile section */
 /* End PBXBuildFile section */
 
 
 /* Begin PBXContainerItemProxy section */
 /* Begin PBXContainerItemProxy section */
@@ -54,7 +55,7 @@
 /* Begin PBXFileReference section */
 /* Begin PBXFileReference section */
 		333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = "<group>"; };
 		333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = "<group>"; };
 		335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = "<group>"; };
 		335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = "<group>"; };
-		33CC10ED2044A3C60003C045 /* example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "example.app"; sourceTree = BUILT_PRODUCTS_DIR; };
+		33CC10ED2044A3C60003C045 /* example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = example.app; sourceTree = BUILT_PRODUCTS_DIR; };
 		33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
 		33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
 		33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = "<group>"; };
 		33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = "<group>"; };
 		33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = "<group>"; };
 		33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = "<group>"; };
@@ -66,8 +67,12 @@
 		33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = "<group>"; };
 		33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = "<group>"; };
 		33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = "<group>"; };
 		33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = "<group>"; };
 		33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = "<group>"; };
 		33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = "<group>"; };
+		413CC3D3B2FBCF7B69907E26 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+		49AA7380F80473893FD60C2E /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
 		7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = "<group>"; };
 		7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = "<group>"; };
 		9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = "<group>"; };
 		9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = "<group>"; };
+		E0725F2979814304119369B0 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
+		F2CA369AF483BC5EA72B3581 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
 /* End PBXFileReference section */
 /* End PBXFileReference section */
 
 
 /* Begin PBXFrameworksBuildPhase section */
 /* Begin PBXFrameworksBuildPhase section */
@@ -75,6 +80,7 @@
 			isa = PBXFrameworksBuildPhase;
 			isa = PBXFrameworksBuildPhase;
 			buildActionMask = 2147483647;
 			buildActionMask = 2147483647;
 			files = (
 			files = (
+				A6151BD419F68182C8FF85D2 /* Pods_Runner.framework in Frameworks */,
 			);
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 			runOnlyForDeploymentPostprocessing = 0;
 		};
 		};
@@ -99,6 +105,7 @@
 				33CEB47122A05771004F2AC0 /* Flutter */,
 				33CEB47122A05771004F2AC0 /* Flutter */,
 				33CC10EE2044A3C60003C045 /* Products */,
 				33CC10EE2044A3C60003C045 /* Products */,
 				D73912EC22F37F3D000D13A0 /* Frameworks */,
 				D73912EC22F37F3D000D13A0 /* Frameworks */,
+				F3BFAC646F7524479B4C81FB /* Pods */,
 			);
 			);
 			sourceTree = "<group>";
 			sourceTree = "<group>";
 		};
 		};
@@ -148,10 +155,22 @@
 		D73912EC22F37F3D000D13A0 /* Frameworks */ = {
 		D73912EC22F37F3D000D13A0 /* Frameworks */ = {
 			isa = PBXGroup;
 			isa = PBXGroup;
 			children = (
 			children = (
+				413CC3D3B2FBCF7B69907E26 /* Pods_Runner.framework */,
 			);
 			);
 			name = Frameworks;
 			name = Frameworks;
 			sourceTree = "<group>";
 			sourceTree = "<group>";
 		};
 		};
+		F3BFAC646F7524479B4C81FB /* Pods */ = {
+			isa = PBXGroup;
+			children = (
+				49AA7380F80473893FD60C2E /* Pods-Runner.debug.xcconfig */,
+				F2CA369AF483BC5EA72B3581 /* Pods-Runner.release.xcconfig */,
+				E0725F2979814304119369B0 /* Pods-Runner.profile.xcconfig */,
+			);
+			name = Pods;
+			path = Pods;
+			sourceTree = "<group>";
+		};
 /* End PBXGroup section */
 /* End PBXGroup section */
 
 
 /* Begin PBXNativeTarget section */
 /* Begin PBXNativeTarget section */
@@ -159,11 +178,13 @@
 			isa = PBXNativeTarget;
 			isa = PBXNativeTarget;
 			buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */;
 			buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */;
 			buildPhases = (
 			buildPhases = (
+				3B927F223EF0FB46C39660FE /* [CP] Check Pods Manifest.lock */,
 				33CC10E92044A3C60003C045 /* Sources */,
 				33CC10E92044A3C60003C045 /* Sources */,
 				33CC10EA2044A3C60003C045 /* Frameworks */,
 				33CC10EA2044A3C60003C045 /* Frameworks */,
 				33CC10EB2044A3C60003C045 /* Resources */,
 				33CC10EB2044A3C60003C045 /* Resources */,
 				33CC110E2044A8840003C045 /* Bundle Framework */,
 				33CC110E2044A8840003C045 /* Bundle Framework */,
 				3399D490228B24CF009A79C7 /* ShellScript */,
 				3399D490228B24CF009A79C7 /* ShellScript */,
+				9092D76C686A497A89A31B0A /* [CP] Embed Pods Frameworks */,
 			);
 			);
 			buildRules = (
 			buildRules = (
 			);
 			);
@@ -182,8 +203,8 @@
 			isa = PBXProject;
 			isa = PBXProject;
 			attributes = {
 			attributes = {
 				LastSwiftUpdateCheck = 0920;
 				LastSwiftUpdateCheck = 0920;
-				LastUpgradeCheck = 0930;
-				ORGANIZATIONNAME = "The Flutter Authors";
+				LastUpgradeCheck = 1300;
+				ORGANIZATIONNAME = "";
 				TargetAttributes = {
 				TargetAttributes = {
 					33CC10EC2044A3C60003C045 = {
 					33CC10EC2044A3C60003C045 = {
 						CreatedOnToolsVersion = 9.2;
 						CreatedOnToolsVersion = 9.2;
@@ -202,7 +223,7 @@
 				};
 				};
 			};
 			};
 			buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */;
 			buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */;
-			compatibilityVersion = "Xcode 8.0";
+			compatibilityVersion = "Xcode 9.3";
 			developmentRegion = en;
 			developmentRegion = en;
 			hasScannedForEncodings = 0;
 			hasScannedForEncodings = 0;
 			knownRegions = (
 			knownRegions = (
@@ -268,7 +289,46 @@
 			);
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 			runOnlyForDeploymentPostprocessing = 0;
 			shellPath = /bin/sh;
 			shellPath = /bin/sh;
-			shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh\ntouch Flutter/ephemeral/tripwire\n";
+			shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire";
+		};
+		3B927F223EF0FB46C39660FE /* [CP] Check Pods Manifest.lock */ = {
+			isa = PBXShellScriptBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			inputFileListPaths = (
+			);
+			inputPaths = (
+				"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
+				"${PODS_ROOT}/Manifest.lock",
+			);
+			name = "[CP] Check Pods Manifest.lock";
+			outputFileListPaths = (
+			);
+			outputPaths = (
+				"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+			shellPath = /bin/sh;
+			shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n    # print error to STDERR\n    echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n    exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
+			showEnvVarsInLog = 0;
+		};
+		9092D76C686A497A89A31B0A /* [CP] Embed Pods Frameworks */ = {
+			isa = PBXShellScriptBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			inputFileListPaths = (
+				"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
+			);
+			name = "[CP] Embed Pods Frameworks";
+			outputFileListPaths = (
+				"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+			shellPath = /bin/sh;
+			shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
+			showEnvVarsInLog = 0;
 		};
 		};
 /* End PBXShellScriptBuildPhase section */
 /* End PBXShellScriptBuildPhase section */
 
 
@@ -361,10 +421,6 @@
 				CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements;
 				CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements;
 				CODE_SIGN_STYLE = Automatic;
 				CODE_SIGN_STYLE = Automatic;
 				COMBINE_HIDPI_IMAGES = YES;
 				COMBINE_HIDPI_IMAGES = YES;
-				FRAMEWORK_SEARCH_PATHS = (
-					"$(inherited)",
-					"$(PROJECT_DIR)/Flutter/ephemeral",
-				);
 				INFOPLIST_FILE = Runner/Info.plist;
 				INFOPLIST_FILE = Runner/Info.plist;
 				LD_RUNPATH_SEARCH_PATHS = (
 				LD_RUNPATH_SEARCH_PATHS = (
 					"$(inherited)",
 					"$(inherited)",
@@ -491,10 +547,6 @@
 				CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements;
 				CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements;
 				CODE_SIGN_STYLE = Automatic;
 				CODE_SIGN_STYLE = Automatic;
 				COMBINE_HIDPI_IMAGES = YES;
 				COMBINE_HIDPI_IMAGES = YES;
-				FRAMEWORK_SEARCH_PATHS = (
-					"$(inherited)",
-					"$(PROJECT_DIR)/Flutter/ephemeral",
-				);
 				INFOPLIST_FILE = Runner/Info.plist;
 				INFOPLIST_FILE = Runner/Info.plist;
 				LD_RUNPATH_SEARCH_PATHS = (
 				LD_RUNPATH_SEARCH_PATHS = (
 					"$(inherited)",
 					"$(inherited)",
@@ -515,10 +567,6 @@
 				CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements;
 				CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements;
 				CODE_SIGN_STYLE = Automatic;
 				CODE_SIGN_STYLE = Automatic;
 				COMBINE_HIDPI_IMAGES = YES;
 				COMBINE_HIDPI_IMAGES = YES;
-				FRAMEWORK_SEARCH_PATHS = (
-					"$(inherited)",
-					"$(PROJECT_DIR)/Flutter/ephemeral",
-				);
 				INFOPLIST_FILE = Runner/Info.plist;
 				INFOPLIST_FILE = Runner/Info.plist;
 				LD_RUNPATH_SEARCH_PATHS = (
 				LD_RUNPATH_SEARCH_PATHS = (
 					"$(inherited)",
 					"$(inherited)",

+ 4 - 18
example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme

@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <?xml version="1.0" encoding="UTF-8"?>
 <Scheme
 <Scheme
-   LastUpgradeVersion = "1000"
+   LastUpgradeVersion = "1300"
    version = "1.3">
    version = "1.3">
    <BuildAction
    <BuildAction
       parallelizeBuildables = "YES"
       parallelizeBuildables = "YES"
@@ -27,18 +27,6 @@
       selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
       selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
       selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
       selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
       shouldUseLaunchSchemeArgsEnv = "YES">
       shouldUseLaunchSchemeArgsEnv = "YES">
-      <Testables>
-         <TestableReference
-            skipped = "NO">
-            <BuildableReference
-               BuildableIdentifier = "primary"
-               BlueprintIdentifier = "00380F9121DF178D00097171"
-               BuildableName = "RunnerUITests.xctest"
-               BlueprintName = "RunnerUITests"
-               ReferencedContainer = "container:Runner.xcodeproj">
-            </BuildableReference>
-         </TestableReference>
-      </Testables>
       <MacroExpansion>
       <MacroExpansion>
          <BuildableReference
          <BuildableReference
             BuildableIdentifier = "primary"
             BuildableIdentifier = "primary"
@@ -48,8 +36,8 @@
             ReferencedContainer = "container:Runner.xcodeproj">
             ReferencedContainer = "container:Runner.xcodeproj">
          </BuildableReference>
          </BuildableReference>
       </MacroExpansion>
       </MacroExpansion>
-      <AdditionalOptions>
-      </AdditionalOptions>
+      <Testables>
+      </Testables>
    </TestAction>
    </TestAction>
    <LaunchAction
    <LaunchAction
       buildConfiguration = "Debug"
       buildConfiguration = "Debug"
@@ -71,11 +59,9 @@
             ReferencedContainer = "container:Runner.xcodeproj">
             ReferencedContainer = "container:Runner.xcodeproj">
          </BuildableReference>
          </BuildableReference>
       </BuildableProductRunnable>
       </BuildableProductRunnable>
-      <AdditionalOptions>
-      </AdditionalOptions>
    </LaunchAction>
    </LaunchAction>
    <ProfileAction
    <ProfileAction
-      buildConfiguration = "Release"
+      buildConfiguration = "Profile"
       shouldUseLaunchSchemeArgsEnv = "YES"
       shouldUseLaunchSchemeArgsEnv = "YES"
       savedToolIdentifier = ""
       savedToolIdentifier = ""
       useCustomWorkingDirectory = "NO"
       useCustomWorkingDirectory = "NO"

+ 3 - 0
example/macos/Runner.xcworkspace/contents.xcworkspacedata

@@ -4,4 +4,7 @@
    <FileRef
    <FileRef
       location = "group:Runner.xcodeproj">
       location = "group:Runner.xcodeproj">
    </FileRef>
    </FileRef>
+   <FileRef
+      location = "group:Pods/Pods.xcodeproj">
+   </FileRef>
 </Workspace>
 </Workspace>

+ 4 - 0
example/macos/Runner/Base.lproj/MainMenu.xib

@@ -323,6 +323,10 @@
                         </items>
                         </items>
                     </menu>
                     </menu>
                 </menuItem>
                 </menuItem>
+                <menuItem title="Help" id="EPT-qC-fAb">
+                    <modifierMask key="keyEquivalentModifierMask"/>
+                    <menu key="submenu" title="Help" systemMenu="help" id="rJ0-wn-3NY"/>
+                </menuItem>
             </items>
             </items>
             <point key="canvasLocation" x="142" y="-258"/>
             <point key="canvasLocation" x="142" y="-258"/>
         </menu>
         </menu>

+ 1 - 1
example/macos/Runner/Configs/AppInfo.xcconfig

@@ -11,4 +11,4 @@ PRODUCT_NAME = example
 PRODUCT_BUNDLE_IDENTIFIER = com.example.example
 PRODUCT_BUNDLE_IDENTIFIER = com.example.example
 
 
 // The copyright displayed in application information
 // The copyright displayed in application information
-PRODUCT_COPYRIGHT = Copyright © 2020 com.example. All rights reserved.
+PRODUCT_COPYRIGHT = Copyright © 2022 com.example. All rights reserved.

+ 0 - 16
example/macos/Runner/DebugProfile.entitlements

@@ -2,23 +2,7 @@
 <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
 <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
 <plist version="1.0">
 <plist version="1.0">
 <dict>
 <dict>
-	<key>com.apple.security.app-sandbox</key>
-	<true/>
-	<key>com.apple.security.assets.movies.read-write</key>
-	<true/>
-	<key>com.apple.security.assets.music.read-write</key>
-	<true/>
-	<key>com.apple.security.assets.pictures.read-write</key>
-	<true/>
 	<key>com.apple.security.cs.allow-jit</key>
 	<key>com.apple.security.cs.allow-jit</key>
 	<true/>
 	<true/>
-	<key>com.apple.security.files.downloads.read-write</key>
-	<true/>
-	<key>com.apple.security.files.user-selected.read-write</key>
-	<true/>
-	<key>com.apple.security.network.client</key>
-	<true/>
-	<key>com.apple.security.network.server</key>
-	<true/>
 </dict>
 </dict>
 </plist>
 </plist>

+ 10 - 6
example/macos/Runner/MainFlutterWindow.swift

@@ -1,15 +1,19 @@
 import Cocoa
 import Cocoa
 import FlutterMacOS
 import FlutterMacOS
+import flutter_acrylic
 
 
 class MainFlutterWindow: NSWindow {
 class MainFlutterWindow: NSWindow {
   override func awakeFromNib() {
   override func awakeFromNib() {
-    let flutterViewController = FlutterViewController.init()
-    let windowFrame = self.frame
-    self.contentViewController = flutterViewController
-    self.setFrame(windowFrame, display: true)
+   let windowFrame = self.frame
+   let blurryContainerViewController = BlurryContainerViewController()
+   self.contentViewController = blurryContainerViewController
+   self.setFrame(windowFrame, display: true)
 
 
-    RegisterGeneratedPlugins(registry: flutterViewController)
+   /* Initialize the flutter_acrylic plugin */
+   MainFlutterWindowManipulator.start(mainFlutterWindow: self)
+
+   RegisterGeneratedPlugins(registry: blurryContainerViewController.flutterViewController)
 
 
     super.awakeFromNib()
     super.awakeFromNib()
   }
   }
-}
+}

+ 1 - 18
example/macos/Runner/Release.entitlements

@@ -1,22 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <?xml version="1.0" encoding="UTF-8"?>
 <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
 <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
 <plist version="1.0">
 <plist version="1.0">
-<dict>
-	<key>com.apple.security.app-sandbox</key>
-	<true/>
-	<key>com.apple.security.assets.movies.read-write</key>
-	<true/>
-	<key>com.apple.security.assets.music.read-write</key>
-	<true/>
-	<key>com.apple.security.assets.pictures.read-write</key>
-	<true/>
-	<key>com.apple.security.files.downloads.read-write</key>
-	<true/>
-	<key>com.apple.security.files.user-selected.read-write</key>
-	<true/>
-	<key>com.apple.security.network.client</key>
-	<true/>
-	<key>com.apple.security.network.server</key>
-	<true/>
-</dict>
+<dict/>
 </plist>
 </plist>

+ 146 - 31
example/pubspec.lock

@@ -1,6 +1,41 @@
 # Generated by pub
 # Generated by pub
 # See https://dart.dev/tools/pub/glossary#lockfile
 # See https://dart.dev/tools/pub/glossary#lockfile
 packages:
 packages:
+  _fe_analyzer_shared:
+    dependency: transitive
+    description:
+      name: _fe_analyzer_shared
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "46.0.0"
+  after_layout:
+    dependency: "direct main"
+    description:
+      name: after_layout
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "1.1.0"
+  analyzer:
+    dependency: transitive
+    description:
+      name: analyzer
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "4.6.0"
+  analyzer_plugin:
+    dependency: transitive
+    description:
+      name: analyzer_plugin
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "0.10.0"
+  ansicolor:
+    dependency: transitive
+    description:
+      name: ansicolor
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "2.0.1"
   args:
   args:
     dependency: transitive
     dependency: transitive
     description:
     description:
@@ -64,6 +99,20 @@ packages:
       url: "https://pub.dartlang.org"
       url: "https://pub.dartlang.org"
     source: hosted
     source: hosted
     version: "3.0.0"
     version: "3.0.0"
+  crypto:
+    dependency: transitive
+    description:
+      name: crypto
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "3.0.2"
+  csslib:
+    dependency: transitive
+    description:
+      name: csslib
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "0.17.2"
   cupertino_icons:
   cupertino_icons:
     dependency: "direct main"
     dependency: "direct main"
     description:
     description:
@@ -71,6 +120,13 @@ packages:
       url: "https://pub.dartlang.org"
       url: "https://pub.dartlang.org"
     source: hosted
     source: hosted
     version: "0.1.3"
     version: "0.1.3"
+  dart_code_metrics:
+    dependency: "direct dev"
+    description:
+      name: dart_code_metrics
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "4.17.1"
   dart_console:
   dart_console:
     dependency: transitive
     dependency: transitive
     description:
     description:
@@ -78,15 +134,20 @@ packages:
       url: "https://pub.dartlang.org"
       url: "https://pub.dartlang.org"
     source: hosted
     source: hosted
     version: "1.0.0"
     version: "1.0.0"
+  dart_style:
+    dependency: transitive
+    description:
+      name: dart_style
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "2.2.3"
   dartssh2:
   dartssh2:
     dependency: "direct main"
     dependency: "direct main"
     description:
     description:
-      path: "."
-      ref: HEAD
-      resolved-ref: "3316b252ace4948f64812b7e5eca11f466d3f62d"
-      url: "https://github.com/TerminalStudio/dartssh2"
-    source: git
-    version: "1.2.0-pre"
+      name: dartssh2
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "2.5.0"
   equatable:
   equatable:
     dependency: transitive
     dependency: transitive
     description:
     description:
@@ -107,31 +168,52 @@ packages:
       name: ffi
       name: ffi
       url: "https://pub.dartlang.org"
       url: "https://pub.dartlang.org"
     source: hosted
     source: hosted
-    version: "1.1.2"
+    version: "1.2.1"
+  file:
+    dependency: transitive
+    description:
+      name: file
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "6.1.4"
   flutter:
   flutter:
     dependency: "direct main"
     dependency: "direct main"
     description: flutter
     description: flutter
     source: sdk
     source: sdk
     version: "0.0.0"
     version: "0.0.0"
+  flutter_acrylic:
+    dependency: "direct main"
+    description:
+      name: flutter_acrylic
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "1.0.0+2"
+  flutter_pty:
+    dependency: "direct main"
+    description:
+      name: flutter_pty
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "0.1.1"
   flutter_test:
   flutter_test:
     dependency: "direct dev"
     dependency: "direct dev"
     description: flutter
     description: flutter
     source: sdk
     source: sdk
     version: "0.0.0"
     version: "0.0.0"
-  http:
+  glob:
     dependency: transitive
     dependency: transitive
     description:
     description:
-      name: http
+      name: glob
       url: "https://pub.dartlang.org"
       url: "https://pub.dartlang.org"
     source: hosted
     source: hosted
-    version: "0.13.4"
-  http_parser:
+    version: "2.1.0"
+  html:
     dependency: transitive
     dependency: transitive
     description:
     description:
-      name: http_parser
+      name: html
       url: "https://pub.dartlang.org"
       url: "https://pub.dartlang.org"
     source: hosted
     source: hosted
-    version: "4.0.0"
+    version: "0.15.0"
   js:
   js:
     dependency: transitive
     dependency: transitive
     description:
     description:
@@ -139,6 +221,13 @@ packages:
       url: "https://pub.dartlang.org"
       url: "https://pub.dartlang.org"
     source: hosted
     source: hosted
     version: "0.6.3"
     version: "0.6.3"
+  lints:
+    dependency: "direct dev"
+    description:
+      name: lints
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "2.0.0"
   matcher:
   matcher:
     dependency: transitive
     dependency: transitive
     description:
     description:
@@ -160,6 +249,13 @@ packages:
       url: "https://pub.dartlang.org"
       url: "https://pub.dartlang.org"
     source: hosted
     source: hosted
     version: "1.7.0"
     version: "1.7.0"
+  package_config:
+    dependency: transitive
+    description:
+      name: package_config
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "2.1.0"
   path:
   path:
     dependency: transitive
     dependency: transitive
     description:
     description:
@@ -167,6 +263,13 @@ packages:
       url: "https://pub.dartlang.org"
       url: "https://pub.dartlang.org"
     source: hosted
     source: hosted
     version: "1.8.1"
     version: "1.8.1"
+  petitparser:
+    dependency: transitive
+    description:
+      name: petitparser
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "5.0.0"
   pinenacl:
   pinenacl:
     dependency: transitive
     dependency: transitive
     description:
     description:
@@ -180,7 +283,7 @@ packages:
       name: platform_info
       name: platform_info
       url: "https://pub.dartlang.org"
       url: "https://pub.dartlang.org"
     source: hosted
     source: hosted
-    version: "3.2.0"
+    version: "3.1.0"
   pointycastle:
   pointycastle:
     dependency: transitive
     dependency: transitive
     description:
     description:
@@ -188,15 +291,13 @@ packages:
       url: "https://pub.dartlang.org"
       url: "https://pub.dartlang.org"
     source: hosted
     source: hosted
     version: "3.4.0"
     version: "3.4.0"
-  pty:
-    dependency: "direct main"
+  pub_semver:
+    dependency: transitive
     description:
     description:
-      path: "."
-      ref: HEAD
-      resolved-ref: "5c3798fef3f304316a8c8b8b66d611c944213dac"
-      url: "https://github.com/TerminalStudio/pty"
-    source: git
-    version: "0.2.2-pre"
+      name: pub_semver
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "2.1.1"
   quiver:
   quiver:
     dependency: transitive
     dependency: transitive
     description:
     description:
@@ -258,20 +359,20 @@ packages:
       url: "https://pub.dartlang.org"
       url: "https://pub.dartlang.org"
     source: hosted
     source: hosted
     version: "1.3.0"
     version: "1.3.0"
-  validators:
+  vector_math:
     dependency: transitive
     dependency: transitive
     description:
     description:
-      name: validators
+      name: vector_math
       url: "https://pub.dartlang.org"
       url: "https://pub.dartlang.org"
     source: hosted
     source: hosted
-    version: "3.0.0"
-  vector_math:
+    version: "2.1.2"
+  watcher:
     dependency: transitive
     dependency: transitive
     description:
     description:
-      name: vector_math
+      name: watcher
       url: "https://pub.dartlang.org"
       url: "https://pub.dartlang.org"
     source: hosted
     source: hosted
-    version: "2.1.2"
+    version: "1.0.1"
   win32:
   win32:
     dependency: transitive
     dependency: transitive
     description:
     description:
@@ -279,13 +380,27 @@ packages:
       url: "https://pub.dartlang.org"
       url: "https://pub.dartlang.org"
     source: hosted
     source: hosted
     version: "2.3.3"
     version: "2.3.3"
+  xml:
+    dependency: transitive
+    description:
+      name: xml
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "6.1.0"
   xterm:
   xterm:
     dependency: "direct main"
     dependency: "direct main"
     description:
     description:
       path: ".."
       path: ".."
       relative: true
       relative: true
     source: path
     source: path
-    version: "2.6.0"
+    version: "3.0.6-alpha"
+  yaml:
+    dependency: transitive
+    description:
+      name: yaml
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "3.1.1"
 sdks:
 sdks:
-  dart: ">=2.17.0-0 <3.0.0"
-  flutter: ">=2.0.0"
+  dart: ">=2.17.0 <3.0.0"
+  flutter: ">=3.0.0"

+ 14 - 7
example/pubspec.yaml

@@ -3,7 +3,7 @@ description: A new Flutter project.
 
 
 # The following line prevents the package from being accidentally published to
 # The following line prevents the package from being accidentally published to
 # pub.dev using `pub publish`. This is preferred for private packages.
 # pub.dev using `pub publish`. This is preferred for private packages.
-publish_to: 'none' # Remove this line if you wish to publish to pub.dev
+publish_to: "none" # Remove this line if you wish to publish to pub.dev
 
 
 # The following defines the version and build number for your application.
 # The following defines the version and build number for your application.
 # A version number is three numbers separated by dots, like 1.2.43
 # A version number is three numbers separated by dots, like 1.2.43
@@ -24,29 +24,36 @@ dependencies:
   xterm:
   xterm:
     path: ../
     path: ../
 
 
-  dartssh2:
-    git:
-      url: https://github.com/TerminalStudio/dartssh2
-  pty:
-    git: https://github.com/TerminalStudio/pty
+  dartssh2: ^2.5.0
+
+  flutter_pty: ^0.1.1
+
+  flutter_acrylic: ^1.0.0+2
+
   flutter:
   flutter:
     sdk: flutter
     sdk: flutter
 
 
+  # google_fonts: ^2.3.1
 
 
   # The following adds the Cupertino Icons font to your application.
   # The following adds the Cupertino Icons font to your application.
   # Use with the CupertinoIcons class for iOS style icons.
   # Use with the CupertinoIcons class for iOS style icons.
   cupertino_icons: ^0.1.3
   cupertino_icons: ^0.1.3
 
 
+  after_layout: ^1.1.0
+
 dev_dependencies:
 dev_dependencies:
   flutter_test:
   flutter_test:
     sdk: flutter
     sdk: flutter
 
 
+  lints: ^2.0.0
+
+  dart_code_metrics: ^4.16.0
+
 # For information on the generic Dart part of this file, see the
 # For information on the generic Dart part of this file, see the
 # following page: https://dart.dev/tools/pub/pubspec
 # following page: https://dart.dev/tools/pub/pubspec
 
 
 # The following section is specific to Flutter.
 # The following section is specific to Flutter.
 flutter:
 flutter:
-
   # The following line ensures that the Material Icons font is
   # The following line ensures that the Material Icons font is
   # included with your application, so that you can use the icons in
   # included with your application, so that you can use the icons in
   # the material Icons class.
   # the material Icons class.

+ 0 - 30
example/test/widget_test.dart

@@ -1,30 +0,0 @@
-// // This is a basic Flutter widget test.
-// //
-// // To perform an interaction with a widget in your test, use the WidgetTester
-// // utility that Flutter provides. For example, you can send tap and scroll
-// // gestures. You can also use WidgetTester to find child widgets in the widget
-// // tree, read text, and verify that the values of widget properties are correct.
-
-// import 'package:flutter/material.dart';
-// import 'package:flutter_test/flutter_test.dart';
-
-// import 'package:example/main.dart';
-
-// void main() {
-//   testWidgets('Counter increments smoke test', (WidgetTester tester) async {
-//     // Build our app and trigger a frame.
-//     await tester.pumpWidget(MyApp());
-
-//     // Verify that our counter starts at 0.
-//     expect(find.text('0'), findsOneWidget);
-//     expect(find.text('1'), findsNothing);
-
-//     // Tap the '+' icon and trigger a frame.
-//     await tester.tap(find.byIcon(Icons.add));
-//     await tester.pump();
-
-//     // Verify that our counter has incremented.
-//     expect(find.text('0'), findsNothing);
-//     expect(find.text('1'), findsOneWidget);
-//   });
-// }

+ 3 - 0
example/windows/flutter/generated_plugin_registrant.cc

@@ -6,6 +6,9 @@
 
 
 #include "generated_plugin_registrant.h"
 #include "generated_plugin_registrant.h"
 
 
+#include <flutter_acrylic/flutter_acrylic_plugin.h>
 
 
 void RegisterPlugins(flutter::PluginRegistry* registry) {
 void RegisterPlugins(flutter::PluginRegistry* registry) {
+  FlutterAcrylicPluginRegisterWithRegistrar(
+      registry->GetRegistrarForPlugin("FlutterAcrylicPlugin"));
 }
 }

+ 2 - 0
example/windows/flutter/generated_plugins.cmake

@@ -3,9 +3,11 @@
 #
 #
 
 
 list(APPEND FLUTTER_PLUGIN_LIST
 list(APPEND FLUTTER_PLUGIN_LIST
+  flutter_acrylic
 )
 )
 
 
 list(APPEND FLUTTER_FFI_PLUGIN_LIST
 list(APPEND FLUTTER_FFI_PLUGIN_LIST
+  flutter_pty
 )
 )
 
 
 set(PLUGIN_BUNDLED_LIBRARIES)
 set(PLUGIN_BUNDLED_LIBRARIES)

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 0 - 0
fixture/htop_80x25_3s.txt


+ 0 - 555
lib/buffer/buffer.dart

@@ -1,555 +0,0 @@
-import 'dart:math' show max, min;
-
-import 'package:xterm/buffer/line/line.dart';
-import 'package:xterm/buffer/reflow_strategy_narrower.dart';
-import 'package:xterm/buffer/reflow_strategy_wider.dart';
-import 'package:xterm/terminal/charset.dart';
-import 'package:xterm/terminal/terminal.dart';
-import 'package:xterm/util/circular_list.dart';
-import 'package:xterm/util/scroll_range.dart';
-import 'package:xterm/util/unicode_v11.dart';
-
-class Buffer {
-  Buffer({
-    required Terminal terminal,
-    required this.isAltBuffer,
-  }) : _terminal = terminal {
-    resetVerticalMargins();
-
-    lines = CircularList(
-      _terminal.maxLines,
-    );
-    for (int i = 0; i < _terminal.viewHeight; i++) {
-      lines.push(_newEmptyLine());
-    }
-  }
-
-  final Terminal _terminal;
-  final bool isAltBuffer;
-  final charset = Charset();
-
-  int get viewHeight => _terminal.viewHeight;
-  int get viewWidth => _terminal.viewWidth;
-
-  /// lines of the buffer. the length of [lines] should always be equal or
-  /// greater than [Terminal.viewHeight].
-  late final CircularList<BufferLine> lines;
-
-  int? _savedCursorX;
-  int? _savedCursorY;
-  int? _savedCellFgColor;
-  int? _savedCellBgColor;
-  int? _savedCellFlags;
-
-  // Indicates how far the bottom of the viewport is from the bottom of the
-  // entire buffer. 0 if the viewport overlaps the terminal screen.
-  int get scrollOffsetFromBottom => _scrollOffsetFromBottom;
-  int _scrollOffsetFromBottom = 0;
-
-  // Indicates how far the top of the viewport is from the top of the entire
-  // buffer. 0 if the viewport is scrolled to the top.
-  int get scrollOffsetFromTop {
-    return _terminal.invisibleHeight - scrollOffsetFromBottom;
-  }
-
-  /// Indicated whether the terminal should automatically scroll to bottom when
-  /// new lines are added. When user is scrolling, [isUserScrolling] is true and
-  /// the automatical scroll-to-bottom behavior is disabled.
-  bool get isUserScrolling {
-    return _scrollOffsetFromBottom != 0;
-  }
-
-  /// Horizontal position of the cursor relative to the top-left cornor of the
-  /// screen, starting from 0.
-  int get cursorX => _cursorX.clamp(0, _terminal.viewWidth - 1);
-  int _cursorX = 0;
-
-  /// Vertical position of the cursor relative to the top-left cornor of the
-  /// screen, starting from 0.
-  int get cursorY => _cursorY;
-  int _cursorY = 0;
-
-  int get marginTop => _marginTop;
-  late int _marginTop;
-
-  int get marginBottom => _marginBottom;
-  late int _marginBottom;
-
-  /// Writes data to the _terminal. Terminal sequences or special characters are
-  /// not interpreted and directly added to the buffer.
-  ///
-  /// See also: [Terminal.write]
-  void write(String text) {
-    for (var char in text.runes) {
-      writeChar(char);
-    }
-  }
-
-  /// Writes a single character to the _terminal. Special chatacters are not
-  /// interpreted and directly added to the buffer.
-  ///
-  /// See also: [Terminal.writeChar]
-  void writeChar(int codePoint) {
-    codePoint = charset.translate(codePoint);
-
-    final cellWidth = unicodeV11.wcwidth(codePoint);
-    if (_cursorX >= _terminal.viewWidth) {
-      newLine();
-      setCursorX(0);
-      if (_terminal.autoWrapMode) {
-        currentLine.isWrapped = true;
-      }
-    }
-
-    final line = currentLine;
-    line.ensure(_cursorX + 1);
-
-    line.cellInitialize(
-      _cursorX,
-      content: codePoint,
-      width: cellWidth,
-      cursor: _terminal.cursor,
-    );
-
-    if (_cursorX < _terminal.viewWidth) {
-      _cursorX++;
-    }
-
-    if (cellWidth == 2) {
-      writeChar(0);
-    }
-  }
-
-  /// get line in the viewport. [index] starts from 0, must be smaller than
-  /// [Terminal.viewHeight].
-  BufferLine getViewLine(int index) {
-    index = index.clamp(0, _terminal.viewHeight - 1);
-    return lines[convertViewLineToRawLine(index)];
-  }
-
-  BufferLine get currentLine {
-    return getViewLine(_cursorY);
-  }
-
-  int get height {
-    return lines.length;
-  }
-
-  int convertViewLineToRawLine(int viewLine) {
-    if (_terminal.viewHeight > height) {
-      return viewLine;
-    }
-
-    return viewLine + (height - _terminal.viewHeight);
-  }
-
-  int convertRawLineToViewLine(int rawLine) {
-    if (_terminal.viewHeight > height) {
-      return rawLine;
-    }
-
-    return rawLine - (height - _terminal.viewHeight);
-  }
-
-  void newLine() {
-    if (_terminal.newLineMode) {
-      setCursorX(0);
-    }
-
-    index();
-  }
-
-  void carriageReturn() {
-    setCursorX(0);
-  }
-
-  void backspace() {
-    if (_cursorX == 0 && currentLine.isWrapped) {
-      currentLine.isWrapped = false;
-      movePosition(_terminal.viewWidth - 1, -1);
-    } else if (_cursorX == _terminal.viewWidth) {
-      movePosition(-2, 0);
-    } else {
-      movePosition(-1, 0);
-    }
-  }
-
-  List<BufferLine> getVisibleLines() {
-    if (height < _terminal.viewHeight) {
-      return lines.toList();
-    }
-
-    final result = <BufferLine>[];
-
-    for (var i = height - _terminal.viewHeight; i < height; i++) {
-      final y = i - scrollOffsetFromBottom;
-      if (y >= 0 && y < height) {
-        result.add(lines[y]);
-      }
-    }
-
-    return result;
-  }
-
-  void eraseDisplayFromCursor() {
-    eraseLineFromCursor();
-
-    for (var i = _cursorY + 1; i < _terminal.viewHeight; i++) {
-      final line = getViewLine(i);
-      line.isWrapped = false;
-      line.erase(_terminal.cursor, 0, _terminal.viewWidth);
-    }
-  }
-
-  void eraseDisplayToCursor() {
-    eraseLineToCursor();
-
-    for (var i = 0; i < _cursorY; i++) {
-      final line = getViewLine(i);
-      line.isWrapped = false;
-      line.erase(_terminal.cursor, 0, _terminal.viewWidth);
-    }
-  }
-
-  void eraseDisplay() {
-    for (var i = 0; i < _terminal.viewHeight; i++) {
-      final line = getViewLine(i);
-      line.isWrapped = false;
-      line.erase(_terminal.cursor, 0, _terminal.viewWidth);
-    }
-  }
-
-  void eraseLineFromCursor() {
-    currentLine.isWrapped = false;
-    currentLine.erase(_terminal.cursor, _cursorX, _terminal.viewWidth);
-  }
-
-  void eraseLineToCursor() {
-    currentLine.isWrapped = false;
-    currentLine.erase(_terminal.cursor, 0, _cursorX);
-  }
-
-  void eraseLine() {
-    currentLine.isWrapped = false;
-    currentLine.erase(_terminal.cursor, 0, _terminal.viewWidth);
-  }
-
-  void eraseCharacters(int count) {
-    final start = _cursorX;
-    currentLine.erase(_terminal.cursor, start, start + count);
-  }
-
-  ScrollRange getAreaScrollRange() {
-    var top = convertViewLineToRawLine(_marginTop);
-    var bottom = convertViewLineToRawLine(_marginBottom) + 1;
-    if (bottom > lines.length) {
-      bottom = lines.length;
-    }
-    return ScrollRange(top, bottom);
-  }
-
-  void areaScrollDown(int lines) {
-    final scrollRange = getAreaScrollRange();
-
-    for (var i = scrollRange.bottom; i > scrollRange.top;) {
-      i--;
-      if (i >= scrollRange.top + lines) {
-        this.lines[i] = this.lines[i - lines];
-      } else {
-        this.lines[i] = _newEmptyLine();
-      }
-    }
-  }
-
-  void areaScrollUp(int lines) {
-    final scrollRange = getAreaScrollRange();
-
-    for (var i = scrollRange.top; i < scrollRange.bottom; i++) {
-      if (i + lines < scrollRange.bottom) {
-        this.lines[i] = this.lines[i + lines];
-      } else {
-        this.lines[i] = _newEmptyLine();
-      }
-    }
-  }
-
-  /// https://vt100.net/docs/vt100-ug/chapter3.html#IND IND – Index
-  ///
-  /// ESC D
-  ///
-  /// [index] causes the active position to move downward one line without
-  /// changing the column position. If the active position is at the bottom
-  /// margin, a scroll up is performed.
-  void index() {
-    if (isInScrollableRegion) {
-      if (_cursorY < _marginBottom) {
-        moveCursorY(1);
-      } else {
-        areaScrollUp(1);
-      }
-      return;
-    }
-
-    // the cursor is not in the scrollable region
-    if (_cursorY >= _terminal.viewHeight - 1) {
-      // we are at the bottom so a new line is created.
-      lines.push(_newEmptyLine());
-
-      // keep viewport from moving if user is scrolling.
-      if (isUserScrolling) {
-        _scrollOffsetFromBottom++;
-      }
-    } else {
-      // there're still lines so we simply move cursor down.
-      moveCursorY(1);
-    }
-  }
-
-  /// https://vt100.net/docs/vt100-ug/chapter3.html#RI
-  void reverseIndex() {
-    if (_cursorY == _marginTop) {
-      areaScrollDown(1);
-    } else if (_cursorY > 0) {
-      moveCursorY(-1);
-    }
-  }
-
-  void cursorGoForward() {
-    setCursorX(_cursorX + 1);
-  }
-
-  void setCursorX(int cursorX) {
-    _cursorX = cursorX.clamp(0, _terminal.viewWidth - 1);
-  }
-
-  void setCursorY(int cursorY) {
-    _cursorY = cursorY.clamp(0, _terminal.viewHeight - 1);
-  }
-
-  void moveCursorX(int offset) {
-    setCursorX(_cursorX + offset);
-  }
-
-  void moveCursorY(int offset) {
-    setCursorY(_cursorY + offset);
-  }
-
-  void setPosition(int cursorX, int cursorY) {
-    var maxLine = _terminal.viewHeight - 1;
-
-    if (_terminal.originMode) {
-      cursorY += _marginTop;
-      maxLine = _marginBottom;
-    }
-
-    _cursorX = cursorX.clamp(0, _terminal.viewWidth - 1);
-    _cursorY = cursorY.clamp(0, maxLine);
-  }
-
-  void movePosition(int offsetX, int offsetY) {
-    final cursorX = _cursorX + offsetX;
-    final cursorY = _cursorY + offsetY;
-    setPosition(cursorX, cursorY);
-  }
-
-  void setScrollOffsetFromBottom(int offsetFromBottom) {
-    if (height < _terminal.viewHeight) return;
-    final maxOffsetFromBottom = height - _terminal.viewHeight;
-    _scrollOffsetFromBottom = offsetFromBottom.clamp(0, maxOffsetFromBottom);
-  }
-
-  void setScrollOffsetFromTop(int offsetFromTop) {
-    final bottomOffset = _terminal.invisibleHeight - offsetFromTop;
-    setScrollOffsetFromBottom(bottomOffset);
-  }
-
-  void screenScrollUp(int lines) {
-    setScrollOffsetFromBottom(scrollOffsetFromBottom + lines);
-  }
-
-  void screenScrollDown(int lines) {
-    setScrollOffsetFromBottom(scrollOffsetFromBottom - lines);
-  }
-
-  void saveCursor() {
-    _savedCellFlags = _terminal.cursor.flags;
-    _savedCellFgColor = _terminal.cursor.fg;
-    _savedCellBgColor = _terminal.cursor.bg;
-    _savedCursorX = _cursorX;
-    _savedCursorY = _cursorY;
-    charset.save();
-  }
-
-  void restoreCursor() {
-    if (_savedCellFlags != null) {
-      _terminal.cursor.flags = _savedCellFlags!;
-    }
-
-    if (_savedCellFgColor != null) {
-      _terminal.cursor.fg = _savedCellFgColor!;
-    }
-
-    if (_savedCellBgColor != null) {
-      _terminal.cursor.bg = _savedCellBgColor!;
-    }
-
-    if (_savedCursorX != null) {
-      _cursorX = _savedCursorX!;
-    }
-
-    if (_savedCursorY != null) {
-      _cursorY = _savedCursorY!;
-    }
-
-    charset.restore();
-  }
-
-  void setVerticalMargins(int top, int bottom) {
-    _marginTop = top.clamp(0, _terminal.viewHeight - 1);
-    _marginBottom = bottom.clamp(0, _terminal.viewHeight - 1);
-
-    _marginTop = min(_marginTop, _marginBottom);
-    _marginBottom = max(_marginTop, _marginBottom);
-  }
-
-  bool get hasScrollableRegion {
-    return _marginTop > 0 || _marginBottom < (_terminal.viewHeight - 1);
-  }
-
-  bool get isInScrollableRegion {
-    return hasScrollableRegion &&
-        _cursorY >= _marginTop &&
-        _cursorY <= _marginBottom;
-  }
-
-  void resetVerticalMargins() {
-    setVerticalMargins(0, _terminal.viewHeight - 1);
-  }
-
-  void deleteChars(int count) {
-    final start = _cursorX.clamp(0, _terminal.viewWidth);
-    final end = min(_cursorX + count, _terminal.viewWidth);
-    currentLine.removeRange(start, end);
-  }
-
-  void clearScrollback() {
-    if (lines.length <= _terminal.viewHeight) {
-      return;
-    }
-
-    lines.trimStart(lines.length - _terminal.viewHeight);
-  }
-
-  void clear() {
-    lines.clear();
-    for (int i = 0; i < _terminal.viewHeight; i++) {
-      lines.push(_newEmptyLine());
-    }
-  }
-
-  void insertBlankCharacters(int count) {
-    for (var i = 0; i < count; i++) {
-      currentLine.insert(_cursorX + i);
-      currentLine.cellSetFlags(_cursorX + i, _terminal.cursor.flags);
-    }
-  }
-
-  void insertLines(int count) {
-    if (hasScrollableRegion && !isInScrollableRegion) {
-      return;
-    }
-
-    setCursorX(0);
-
-    for (var i = 0; i < count; i++) {
-      insertLine();
-    }
-  }
-
-  void insertLine() {
-    if (!isInScrollableRegion) {
-      final index = convertViewLineToRawLine(_cursorX);
-      final newLine = _newEmptyLine();
-      lines.insert(index, newLine);
-    } else {
-      final newLine = _newEmptyLine();
-      lines.insert(_cursorY, newLine);
-    }
-  }
-
-  void deleteLines(int count) {
-    if (hasScrollableRegion && !isInScrollableRegion) {
-      return;
-    }
-
-    setCursorX(0);
-
-    for (var i = 0; i < count; i++) {
-      deleteLine();
-    }
-  }
-
-  void deleteLine() {
-    final index = convertViewLineToRawLine(_cursorX);
-
-    if (index >= height) {
-      return;
-    }
-
-    lines.remove(index);
-  }
-
-  void resize(int oldWidth, int oldHeight, int newWidth, int newHeight) {
-    if (newWidth > oldWidth) {
-      lines.forEach((item) => item.ensure(newWidth));
-    }
-
-    if (newHeight > oldHeight) {
-      while (lines.length < newHeight) {
-        lines.push(_newEmptyLine());
-      }
-      // Grow larger
-      for (var i = 0; i < newHeight - oldHeight; i++) {
-        if (_cursorY < oldHeight - 1) {
-          lines.push(_newEmptyLine());
-        } else {
-          _cursorY++;
-        }
-      }
-    } else {
-      // Shrink smaller
-      for (var i = 0; i < oldHeight - newHeight; i++) {
-        if (_cursorY < oldHeight - 1) {
-          lines.pop();
-        } else {
-          _cursorY++;
-        }
-      }
-    }
-
-    // Ensure cursor is within the screen.
-    _cursorX = _cursorX.clamp(0, newWidth - 1);
-    _cursorY = _cursorY.clamp(0, newHeight - 1);
-
-    if (!isAltBuffer) {
-      final reflowStrategy = newWidth > oldWidth
-          ? ReflowStrategyWider(this)
-          : ReflowStrategyNarrower(this);
-      reflowStrategy.reflow(newWidth, newHeight, oldWidth, oldHeight);
-    }
-  }
-
-  BufferLine _newEmptyLine() {
-    final line = BufferLine(length: _terminal.viewWidth);
-    return line;
-  }
-
-  adjustSavedCursor(int dx, int dy) {
-    if (_savedCursorX != null) {
-      _savedCursorX = _savedCursorX! + dx;
-    }
-    if (_savedCursorY != null) {
-      _savedCursorY = _savedCursorY! + dy;
-    }
-  }
-}

+ 0 - 266
lib/buffer/line/line.dart

@@ -1,266 +0,0 @@
-import 'dart:math';
-
-import 'package:meta/meta.dart';
-import 'package:xterm/buffer/line/line_bytedata_data.dart';
-import 'package:xterm/buffer/line/line_list_data.dart';
-import 'package:xterm/terminal/cursor.dart';
-import 'package:xterm/util/constants.dart';
-
-@sealed
-class BufferLine {
-  BufferLine({int length = 64, bool isWrapped = false}) {
-    _data = BufferLineData(length: length, isWrapped: isWrapped);
-  }
-
-  BufferLine.withDataFrom(BufferLine other) {
-    _data = other.data;
-  }
-
-  late BufferLineData _data;
-  final _nonDirtyTags = Set<String>();
-
-  void markTagAsNonDirty(String tag) {
-    _nonDirtyTags.add(tag);
-  }
-
-  bool isTagDirty(String tag) {
-    return !_nonDirtyTags.contains(tag);
-  }
-
-  BufferLineData get data => _data;
-
-  bool get isWrapped => _data.isWrapped;
-
-  set isWrapped(bool value) => _data.isWrapped = value;
-
-  void ensure(int length) => _data.ensure(length);
-
-  void insert(int index) {
-    _invalidateCaches();
-    _data.insert(index);
-  }
-
-  void insertN(int index, int count) {
-    _invalidateCaches();
-    _data.insertN(index, count);
-  }
-
-  void removeN(int index, int count) {
-    _invalidateCaches();
-    _data.removeN(index, count);
-  }
-
-  void clear() {
-    _invalidateCaches();
-    _data.clear();
-  }
-
-  void erase(Cursor cursor, int start, int end, [bool resetIsWrapped = false]) {
-    _invalidateCaches();
-    _data.erase(cursor, start, end);
-  }
-
-  void cellClear(int index) {
-    _invalidateCaches();
-    _data.cellClear(index);
-  }
-
-  void cellInitialize(
-    int index, {
-    required int content,
-    required int width,
-    required Cursor cursor,
-  }) {
-    _invalidateCaches();
-    _data.cellInitialize(
-      index,
-      content: content,
-      width: width,
-      cursor: cursor,
-    );
-  }
-
-  bool cellHasContent(int index) => _data.cellHasContent(index);
-
-  int cellGetContent(int index) => _data.cellGetContent(index);
-
-  void cellSetContent(int index, int content) {
-    _invalidateCaches();
-    _data.cellSetContent(index, content);
-  }
-
-  int cellGetFgColor(int index) => _data.cellGetFgColor(index);
-
-  void cellSetFgColor(int index, int color) =>
-      _data.cellSetFgColor(index, color);
-
-  int cellGetBgColor(int index) => _data.cellGetBgColor(index);
-
-  void cellSetBgColor(int index, int color) =>
-      _data.cellSetBgColor(index, color);
-
-  int cellGetFlags(int index) => _data.cellGetFlags(index);
-
-  void cellSetFlags(int index, int flags) => _data.cellSetFlags(index, flags);
-
-  int cellGetWidth(int index) => _data.cellGetWidth(index);
-
-  void cellSetWidth(int index, int width) {
-    _invalidateCaches();
-    _data.cellSetWidth(index, width);
-  }
-
-  void cellClearFlags(int index) => _data.cellClearFlags(index);
-
-  bool cellHasFlag(int index, int flag) => _data.cellHasFlag(index, flag);
-
-  void cellSetFlag(int index, int flag) => _data.cellSetFlag(index, flag);
-
-  void cellErase(int index, Cursor cursor) {
-    _invalidateCaches();
-    _data.cellErase(index, cursor);
-  }
-
-  int getTrimmedLength([int? cols]) => _data.getTrimmedLength(cols);
-
-  void copyCellsFrom(
-      covariant BufferLine src, int srcCol, int dstCol, int len) {
-    _invalidateCaches();
-    _data.copyCellsFrom(src.data, srcCol, dstCol, len);
-  }
-
-  void removeRange(int start, int end) {
-    _invalidateCaches();
-    _data.removeRange(start, end);
-  }
-
-  void clearRange(int start, int end) {
-    _invalidateCaches();
-    _data.clearRange(start, end);
-  }
-
-  String toDebugString(int cols) => _data.toDebugString(cols);
-
-  void _invalidateCaches() {
-    _searchStringCache = null;
-    _nonDirtyTags.clear();
-  }
-
-  String? _searchStringCache;
-  bool get hasCachedSearchString => _searchStringCache != null;
-
-  String toSearchString(int cols) {
-    if (_searchStringCache != null) {
-      return _searchStringCache!;
-    }
-    final searchString = StringBuffer();
-    final length = getTrimmedLength();
-    for (int i = 0; i < max(cols, length); i++) {
-      var code = cellGetContent(i);
-      if (code != 0) {
-        final cellString = String.fromCharCode(code);
-        searchString.write(cellString);
-        final widthDiff = cellGetWidth(i) - cellString.length;
-        if (widthDiff > 0) {
-          searchString.write(''.padRight(widthDiff));
-        }
-      }
-    }
-    _searchStringCache = searchString.toString();
-    return _searchStringCache!;
-  }
-}
-
-abstract class BufferLineData {
-  factory BufferLineData({int length = 64, bool isWrapped = false}) {
-    if (kIsWeb) {
-      return ListBufferLineData(length, isWrapped);
-    }
-
-    return ByteDataBufferLineData(length, isWrapped);
-  }
-
-  bool get isWrapped;
-
-  set isWrapped(bool value);
-
-  void ensure(int length);
-
-  void insert(int index);
-
-  void insertN(int index, int count);
-
-  void removeN(int index, int count);
-
-  void clear();
-
-  void erase(Cursor cursor, int start, int end, [bool resetIsWrapped = false]);
-
-  void cellClear(int index);
-
-  void cellInitialize(
-    int index, {
-    required int content,
-    required int width,
-    required Cursor cursor,
-  });
-
-  bool cellHasContent(int index);
-
-  int cellGetContent(int index);
-
-  void cellSetContent(int index, int content);
-
-  int cellGetFgColor(int index);
-
-  void cellSetFgColor(int index, int color);
-
-  int cellGetBgColor(int index);
-
-  void cellSetBgColor(int index, int color);
-
-  int cellGetFlags(int index);
-
-  void cellSetFlags(int index, int flags);
-
-  int cellGetWidth(int index);
-
-  void cellSetWidth(int index, int width);
-
-  void cellClearFlags(int index);
-
-  bool cellHasFlag(int index, int flag);
-
-  void cellSetFlag(int index, int flag);
-
-  void cellErase(int index, Cursor cursor);
-
-  int getTrimmedLength([int? cols]);
-
-  void copyCellsFrom(
-      covariant BufferLineData src, int srcCol, int dstCol, int len);
-
-  // int cellGetHash(int index);
-
-  void removeRange(int start, int end);
-
-  void clearRange(int start, int end);
-
-  @nonVirtual
-  String toDebugString(int cols) {
-    final result = StringBuffer();
-    final length = getTrimmedLength();
-    for (int i = 0; i < max(cols, length); i++) {
-      var code = cellGetContent(i);
-      if (code == 0) {
-        if (cellGetWidth(i) == 0) {
-          code = '_'.runes.first;
-        } else {
-          code = cellGetWidth(i).toString().runes.first;
-        }
-      }
-      result.writeCharCode(code);
-    }
-    return result.toString();
-  }
-}

+ 0 - 293
lib/buffer/line/line_bytedata_data.dart

@@ -1,293 +0,0 @@
-import 'dart:math';
-import 'dart:typed_data';
-
-import 'package:xterm/buffer/line/line.dart';
-import 'package:xterm/terminal/cursor.dart';
-
-/// Line layout:
-///   |  cell  |  cell  |  cell  |  cell  | ...
-///   (16 bytes per cell)
-///
-/// Cell layout:
-///   | code point |  fg color  |  bg color  | attributes |
-///       4bytes       4bytes       4bytes       4bytes
-///
-/// Attributes layout:
-///   |  width  |  flags  | reserved | reserved |
-///      1byte     1byte     1byte      1byte
-
-const _cellSize = 16;
-const _cellSize64Bit = _cellSize >> 3;
-
-const _cellContent = 0;
-const _cellFgColor = 4;
-const _cellBgColor = 8;
-
-// const _cellAttributes = 12;
-const _cellWidth = 12;
-const _cellFlags = 13;
-
-int _nextLength(int lengthRequirement) {
-  var nextLength = 2;
-  while (nextLength < lengthRequirement) {
-    nextLength *= 2;
-  }
-  return nextLength;
-}
-
-/// [ByteData] based [BufferLineData], used in non-web platforms to minimize memory
-/// footprint,
-class ByteDataBufferLineData with BufferLineData {
-  ByteDataBufferLineData(int length, this.isWrapped) {
-    _maxCols = _nextLength(length);
-    _cells = ByteData(_maxCols * _cellSize);
-  }
-
-  late ByteData _cells;
-
-  bool isWrapped;
-
-  int _maxCols = 64;
-
-  void ensure(int length) {
-    if (length <= _maxCols) {
-      return;
-    }
-
-    final nextLength = _nextLength(length);
-    final newCells = ByteData(nextLength * _cellSize);
-    newCells.buffer.asInt64List().setAll(0, _cells.buffer.asInt64List());
-    _cells = newCells;
-    _maxCols = nextLength;
-  }
-
-  void insert(int index) {
-    insertN(index, 1);
-  }
-
-  void removeN(int index, int count) {
-    final moveStart = index * _cellSize64Bit;
-    final moveOffset = count * _cellSize64Bit;
-    final moveEnd = (_maxCols - count) * _cellSize64Bit;
-    final bufferEnd = _maxCols * _cellSize64Bit;
-
-    // move data backward
-    final cells = _cells.buffer.asInt64List();
-    for (var i = moveStart; i < moveEnd; i++) {
-      cells[i] = cells[i + moveOffset];
-    }
-
-    // set empty cells to 0
-    for (var i = moveEnd; i < bufferEnd; i++) {
-      cells[i] = 0x00;
-    }
-  }
-
-  void insertN(int index, int count) {
-    //                       start
-    // +--------------------------|-----------------------------------+
-    // |                          |                                   |
-    // +--------------------------\--\--------------------------------+ end
-    //                             \  \
-    //                              \  \
-    //                               v  v
-    // +--------------------------|--|--------------------------------+
-    // |                          |  |                                |
-    // +--------------------------|--|--------------------------------+ end
-    //                       start   start+offset
-
-    final moveStart = index * _cellSize64Bit;
-    final moveOffset = count * _cellSize64Bit;
-    final bufferEnd = _maxCols * _cellSize64Bit;
-
-    // move data forward
-    final cells = _cells.buffer.asInt64List();
-    for (var i = bufferEnd - moveOffset - 1; i >= moveStart; i--) {
-      cells[i + moveOffset] = cells[i];
-    }
-
-    // set inserted cells to 0
-    for (var i = moveStart; i < moveStart + moveOffset; i++) {
-      cells[i] = 0x00;
-    }
-  }
-
-  void clear() {
-    clearRange(0, _cells.lengthInBytes ~/ _cellSize);
-  }
-
-  void erase(Cursor cursor, int start, int end, [bool resetIsWrapped = false]) {
-    ensure(end);
-    for (var i = start; i < end; i++) {
-      cellErase(i, cursor);
-    }
-    if (resetIsWrapped) {
-      isWrapped = false;
-    }
-  }
-
-  void cellClear(int index) {
-    _cells.setUint64(index * _cellSize, 0x00);
-    _cells.setUint64(index * _cellSize + 8, 0x00);
-  }
-
-  void cellInitialize(
-    int index, {
-    required int content,
-    required int width,
-    required Cursor cursor,
-  }) {
-    final cell = index * _cellSize;
-    _cells.setUint32(cell + _cellContent, content);
-    _cells.setUint32(cell + _cellFgColor, cursor.fg);
-    _cells.setUint32(cell + _cellBgColor, cursor.bg);
-    _cells.setUint8(cell + _cellWidth, width);
-    _cells.setUint8(cell + _cellFlags, cursor.flags);
-  }
-
-  bool cellHasContent(int index) {
-    return cellGetContent(index) != 0;
-  }
-
-  int cellGetContent(int index) {
-    if (index > _maxCols) {
-      return 0;
-    }
-    return _cells.getUint32(index * _cellSize + _cellContent);
-  }
-
-  void cellSetContent(int index, int content) {
-    _cells.setInt32(index * _cellSize + _cellContent, content);
-  }
-
-  int cellGetFgColor(int index) {
-    if (index >= _maxCols) {
-      return 0;
-    }
-    return _cells.getUint32(index * _cellSize + _cellFgColor);
-  }
-
-  void cellSetFgColor(int index, int color) {
-    _cells.setUint32(index * _cellSize + _cellFgColor, color);
-  }
-
-  int cellGetBgColor(int index) {
-    if (index >= _maxCols) {
-      return 0;
-    }
-    return _cells.getUint32(index * _cellSize + _cellBgColor);
-  }
-
-  void cellSetBgColor(int index, int color) {
-    _cells.setUint32(index * _cellSize + _cellBgColor, color);
-  }
-
-  int cellGetFlags(int index) {
-    if (index >= _maxCols) {
-      return 0;
-    }
-    return _cells.getUint8(index * _cellSize + _cellFlags);
-  }
-
-  void cellSetFlags(int index, int flags) {
-    _cells.setUint8(index * _cellSize + _cellFlags, flags);
-  }
-
-  int cellGetWidth(int index) {
-    if (index >= _maxCols) {
-      return 1;
-    }
-    return _cells.getUint8(index * _cellSize + _cellWidth);
-  }
-
-  void cellSetWidth(int index, int width) {
-    _cells.setUint8(index * _cellSize + _cellWidth, width);
-  }
-
-  void cellClearFlags(int index) {
-    cellSetFlags(index, 0);
-  }
-
-  bool cellHasFlag(int index, int flag) {
-    if (index >= _maxCols) {
-      return false;
-    }
-    return cellGetFlags(index) & flag != 0;
-  }
-
-  void cellSetFlag(int index, int flag) {
-    cellSetFlags(index, cellGetFlags(index) | flag);
-  }
-
-  void cellErase(int index, Cursor cursor) {
-    cellSetContent(index, 0x00);
-    cellSetFgColor(index, cursor.fg);
-    cellSetBgColor(index, cursor.bg);
-    cellSetFlags(index, cursor.flags);
-    cellSetWidth(index, 0);
-  }
-
-  int getTrimmedLength([int? cols]) {
-    if (cols == null) {
-      cols = _maxCols;
-    }
-    for (var i = cols - 1; i >= 0; i--) {
-      if (cellGetContent(i) != 0) {
-        // we are at the last cell in this line that has content.
-        // the length of this line is the index of this cell + 1
-        // the only exception is that if that last cell is wider
-        // than 1 then we have to add the diff
-        final lastCellWidth = cellGetWidth(i);
-        return i + lastCellWidth;
-      }
-    }
-    return 0;
-  }
-
-  void copyCellsFrom(
-      ByteDataBufferLineData src, int srcCol, int dstCol, int len) {
-    ensure(dstCol + len);
-
-    final intsToCopy = len * _cellSize64Bit;
-    final srcStart = srcCol * _cellSize64Bit;
-    final dstStart = dstCol * _cellSize64Bit;
-
-    final cells = _cells.buffer.asInt64List();
-    final srcCells = src._cells.buffer.asInt64List();
-    for (var i = 0; i < intsToCopy; i++) {
-      cells[dstStart + i] = srcCells[srcStart + i];
-    }
-  }
-
-  // int cellGetHash(int index) {
-  //   final cell = index * _cellSize;
-  //   final a = _cells.getInt64(cell);
-  //   final b = _cells.getInt64(cell + 8);
-  //   return a ^ b;
-  // }
-
-  void removeRange(int start, int end) {
-    end = min(end, _maxCols);
-    this.removeN(start, end - start);
-  }
-
-  void clearRange(int start, int end) {
-    end = min(end, _maxCols);
-    for (var index = start; index < end; index++) {
-      cellClear(index);
-    }
-  }
-
-  @override
-  String toString() {
-    final result = StringBuffer();
-    for (int i = 0; i < _maxCols; i++) {
-      final code = cellGetContent(i);
-      if (code == 0) {
-        continue;
-      }
-      result.writeCharCode(code);
-    }
-    return result.toString();
-  }
-}

+ 0 - 267
lib/buffer/line/line_list_data.dart

@@ -1,267 +0,0 @@
-import 'dart:math';
-
-import 'package:xterm/buffer/line/line.dart';
-import 'package:xterm/terminal/cursor.dart';
-
-/// Line layout:
-///   |  cell  |  cell  |  cell  |  cell  | ...
-///   (4 ints per cell)
-///
-/// Cell layout:
-///   | code point |  fg color  |  bg color  | attributes |
-///       1 int        1 int        1 int        1 int
-///
-/// Attributes layout:
-///   |  width  |  flags  | reserved | reserved |
-///      1byte     1byte     1byte      1byte
-
-const _cellSize = 4;
-
-const _cellContent = 0;
-const _cellFgColor = 1;
-const _cellBgColor = 2;
-const _cellAttributes = 3;
-
-const _cellWidth = 0;
-const _cellFlags = 8;
-
-int _nextLength(int lengthRequirement) {
-  var nextLength = 2;
-  while (nextLength < lengthRequirement) {
-    nextLength *= 2;
-  }
-  return nextLength;
-}
-
-/// [List] based [BufferLineData], used in browser where ByteData is not avaliable.
-class ListBufferLineData with BufferLineData {
-  ListBufferLineData(int length, this.isWrapped) {
-    _maxCols = _nextLength(length);
-    _cells = List.filled(_maxCols * _cellSize, 0);
-  }
-
-  late List<int> _cells;
-
-  bool isWrapped;
-
-  int _maxCols = 64;
-
-  void ensure(int length) {
-    if (length <= _maxCols) {
-      return;
-    }
-
-    final nextLength = _nextLength(length);
-    final newCells = List.filled(nextLength * _cellSize, 0);
-    newCells.setAll(0, _cells);
-    _cells = newCells;
-    _maxCols = nextLength;
-  }
-
-  void insert(int index) {
-    insertN(index, 1);
-  }
-
-  void removeN(int index, int count) {
-    final moveStart = index * _cellSize;
-    final moveOffset = count * _cellSize;
-    final moveEnd = (_maxCols - count) * _cellSize;
-    final bufferEnd = _maxCols * _cellSize;
-
-    // move data backward
-    for (var i = moveStart; i < moveEnd; i++) {
-      _cells[i] = _cells[i + moveOffset];
-    }
-
-    // set empty cells to 0
-    for (var i = moveEnd; i < bufferEnd; i++) {
-      _cells[i] = 0x00;
-    }
-  }
-
-  void insertN(int index, int count) {
-    final moveStart = index * _cellSize;
-    final moveOffset = count * _cellSize;
-    final bufferEnd = _maxCols * _cellSize;
-
-    // move data forward
-    for (var i = bufferEnd - moveOffset - 1; i >= moveStart; i--) {
-      _cells[i + moveOffset] = _cells[i];
-    }
-
-    // set inserted cells to 0
-    for (var i = moveStart; i < moveStart + moveOffset; i++) {
-      _cells[i] = 0x00;
-    }
-  }
-
-  void clear() {
-    clearRange(0, _cells.length ~/ _cellSize);
-  }
-
-  void erase(Cursor cursor, int start, int end, [bool resetIsWrapped = false]) {
-    ensure(end);
-    for (var i = start; i < end; i++) {
-      cellErase(i, cursor);
-    }
-    if (resetIsWrapped) {
-      isWrapped = false;
-    }
-  }
-
-  void cellClear(int index) {
-    _cells.fillRange(index * _cellSize, index * _cellSize + _cellSize, 0);
-  }
-
-  void cellInitialize(
-    int index, {
-    required int content,
-    required int width,
-    required Cursor cursor,
-  }) {
-    final cell = index * _cellSize;
-    _cells[cell + _cellContent] = content;
-    _cells[cell + _cellFgColor] = cursor.fg;
-    _cells[cell + _cellBgColor] = cursor.bg;
-    _cells[cell + _cellAttributes] =
-        (width << _cellWidth) + (cursor.flags << _cellFlags);
-  }
-
-  bool cellHasContent(int index) {
-    return cellGetContent(index) != 0;
-  }
-
-  int cellGetContent(int index) {
-    if (index >= _maxCols) return 0;
-    return _cells[index * _cellSize + _cellContent];
-  }
-
-  void cellSetContent(int index, int content) {
-    _cells[index * _cellSize + _cellContent] = content;
-  }
-
-  int cellGetFgColor(int index) {
-    if (index >= _maxCols) return 0;
-    return _cells[index * _cellSize + _cellFgColor];
-  }
-
-  void cellSetFgColor(int index, int color) {
-    _cells[index * _cellSize + _cellFgColor] = color;
-  }
-
-  int cellGetBgColor(int index) {
-    if (index >= _maxCols) return 0;
-    return _cells[index * _cellSize + _cellBgColor];
-  }
-
-  void cellSetBgColor(int index, int color) {
-    _cells[index * _cellSize + _cellBgColor] = color;
-  }
-
-  int cellGetFlags(int index) {
-    if (index >= _maxCols) return 0;
-    final offset = index * _cellSize + _cellAttributes;
-    return (_cells[offset] >> _cellFlags) & 0xFF;
-  }
-
-  void cellSetFlags(int index, int flags) {
-    final offset = index * _cellSize + _cellAttributes;
-    var result = _cells[offset];
-    result |= 0xFF << _cellFlags;
-    result &= flags << _cellFlags;
-    _cells[offset] = result;
-  }
-
-  int cellGetWidth(int index) {
-    if (index >= _maxCols) return 0;
-    final offset = index * _cellSize + _cellAttributes;
-    return (_cells[offset] >> _cellWidth) & 0xFF;
-  }
-
-  void cellSetWidth(int index, int width) {
-    final offset = index * _cellSize + _cellAttributes;
-    var result = _cells[offset];
-    result |= 0xFF << _cellWidth;
-    result &= width << _cellWidth;
-    _cells[offset] = result;
-  }
-
-  void cellClearFlags(int index) {
-    cellSetFlags(index, 0);
-  }
-
-  bool cellHasFlag(int index, int flag) {
-    if (index >= _maxCols) {
-      return false;
-    }
-    return cellGetFlags(index) & flag != 0;
-  }
-
-  void cellSetFlag(int index, int flag) {
-    cellSetFlags(index, cellGetFlags(index) | flag);
-  }
-
-  void cellErase(int index, Cursor cursor) {
-    cellSetContent(index, 0x00);
-    cellSetFgColor(index, cursor.fg);
-    cellSetBgColor(index, cursor.bg);
-    cellSetFlags(index, cursor.flags);
-    cellSetWidth(index, 0);
-  }
-
-  int getTrimmedLength([int? cols]) {
-    if (cols == null) {
-      cols = _maxCols;
-    }
-    for (var i = cols - 1; i >= 0; i--) {
-      if (cellGetContent(i) != 0) {
-        // we are at the last cell in this line that has content.
-        // the length of this line is the index of this cell + 1
-        // the only exception is that if that last cell is wider
-        // than 1 then we have to add the diff
-        final lastCellWidth = cellGetWidth(i);
-        return i + lastCellWidth;
-      }
-    }
-    return 0;
-  }
-
-  void copyCellsFrom(ListBufferLineData src, int srcCol, int dstCol, int len) {
-    ensure(dstCol + len);
-
-    final intsToCopy = len * _cellSize;
-    final srcStart = srcCol * _cellSize;
-    final dstStart = dstCol * _cellSize;
-
-    final cells = _cells;
-    final srcCells = src._cells;
-    for (var i = 0; i < intsToCopy; i++) {
-      cells[dstStart + i] = srcCells[srcStart + i];
-    }
-  }
-
-  void removeRange(int start, int end) {
-    end = min(end, _maxCols);
-    this.removeN(start, end - start);
-  }
-
-  void clearRange(int start, int end) {
-    end = min(end, _maxCols);
-    for (var index = start; index < end; index++) {
-      cellClear(index);
-    }
-  }
-
-  @override
-  String toString() {
-    final result = StringBuffer();
-    for (int i = 0; i < _maxCols; i++) {
-      final code = cellGetContent(i);
-      if (code == 0) {
-        continue;
-      }
-      result.writeCharCode(code);
-    }
-    return result.toString();
-  }
-}

+ 0 - 11
lib/buffer/reflow_strategy.dart

@@ -1,11 +0,0 @@
-import 'package:xterm/buffer/buffer.dart';
-
-abstract class ReflowStrategy {
-  final Buffer _buffer;
-
-  ReflowStrategy(this._buffer);
-
-  Buffer get buffer => _buffer;
-
-  void reflow(int newCols, int newRows, int oldCols, int oldRows);
-}

+ 0 - 91
lib/buffer/reflow_strategy_narrower.dart

@@ -1,91 +0,0 @@
-import 'package:xterm/buffer/buffer.dart';
-import 'package:xterm/buffer/line/line.dart';
-import 'package:xterm/buffer/reflow_strategy.dart';
-
-class ReflowStrategyNarrower extends ReflowStrategy {
-  ReflowStrategyNarrower(Buffer buffer) : super(buffer);
-
-  @override
-  void reflow(int newCols, int newRows, int oldCols, int oldRows) {
-    final linesAfterReflow = <BufferLine>[];
-
-    //print('Reflow narrower $oldCols -> $newCols');
-    for (var i = 0; i < buffer.lines.length; i++) {
-      final line = buffer.lines[i];
-      final lineLength = line.getTrimmedLength();
-      linesAfterReflow.add(line);
-
-      if (lineLength > newCols) {
-        var moveIndexStart = newCols;
-        var cellsToCopy = lineLength - newCols;
-
-        // when we have a double width character and are about to move the "0" placeholder,
-        // then we have to move the double width character as well
-        if (line.cellGetContent(moveIndexStart) == 0 &&
-            line.cellGetWidth(moveIndexStart - 1) == 2) {
-          moveIndexStart -= 1;
-          cellsToCopy += 1;
-        }
-
-        var addZero = false;
-        //when the last cell to copy is a double width cell, then add a "0"
-        if (line.cellGetWidth(moveIndexStart + cellsToCopy - 1) == 2) {
-          addZero = true;
-        }
-
-        // var alreadyInserted = 0;
-
-        //when we have aggregated a whole new line then insert it now
-        while (cellsToCopy > newCols) {
-          final newLine = BufferLine(length: newCols, isWrapped: true);
-          newLine.copyCellsFrom(line, moveIndexStart, 0, newCols);
-          // line.clearRange(moveIndexStart, moveIndexStart + newCols);
-          line.removeN(moveIndexStart, newCols);
-
-          linesAfterReflow.add(newLine);
-
-          cellsToCopy -= newCols;
-          // alreadyInserted++;
-        }
-
-        // we need to move cut cells to the next line
-        // if the next line is wrapped anyway, we can push them onto the beginning of that line
-        // otherwise, we need add a new wrapped line
-        // final nextLineIndex = i + alreadyInserted + 1;
-        final nextLineIndex = i + 1;
-        if (nextLineIndex < buffer.lines.length) {
-          final nextLine = buffer.lines[nextLineIndex];
-          if (nextLine.isWrapped) {
-            final nextLineLength = nextLine.getTrimmedLength();
-            nextLine.ensure(nextLineLength + cellsToCopy + (addZero ? 1 : 0));
-            nextLine.insertN(0, cellsToCopy + (addZero ? 1 : 0));
-            nextLine.copyCellsFrom(line, moveIndexStart, 0, cellsToCopy);
-            // clean the cells that we moved
-            line.removeN(moveIndexStart, cellsToCopy);
-            // line.erase(buffer.terminal.cursor, moveIndexStart,
-            //     moveIndexStart + cellsToCopy);
-            //print('M: ${i < 10 ? '0' : ''}$i: ${line.toDebugString(oldCols)}');
-            //print(
-            //    'N: ${i + 1 < 10 ? '0' : ''}${i + 1}: ${nextLine.toDebugString(oldCols)}');
-            continue;
-          }
-        }
-
-        final newLine = BufferLine(length: newCols, isWrapped: true);
-        newLine.copyCellsFrom(line, moveIndexStart, 0, cellsToCopy);
-        // clean the cells that we moved
-        line.removeN(moveIndexStart, cellsToCopy);
-
-        linesAfterReflow.add(newLine);
-
-        //TODO: scrolling is a bit weird afterwards
-
-        //print('S: ${i < 10 ? '0' : ''}$i: ${line.toDebugString(oldCols)}');
-      } else {
-        //print('N: ${i < 10 ? '0' : ''}$i: ${line.toDebugString(oldCols)}');
-      }
-    }
-
-    buffer.lines.replaceWith(linesAfterReflow);
-  }
-}

+ 0 - 76
lib/buffer/reflow_strategy_wider.dart

@@ -1,76 +0,0 @@
-import 'dart:math';
-
-import 'package:xterm/buffer/buffer.dart';
-import 'package:xterm/buffer/line/line.dart';
-import 'package:xterm/buffer/reflow_strategy.dart';
-
-class ReflowStrategyWider extends ReflowStrategy {
-  ReflowStrategyWider(Buffer buffer) : super(buffer);
-
-  @override
-  void reflow(int newCols, int newRows, int oldCols, int oldRows) {
-    final linesAfterReflow = <BufferLine>[];
-
-    for (var i = 0; i < buffer.lines.length; i++) {
-      final line = buffer.lines[i];
-      line.ensure(newCols);
-      linesAfterReflow.add(line);
-
-      var linesToSkip = 0;
-      for (var offset = 1; i + offset < buffer.lines.length; offset++) {
-        final nextLine = buffer.lines[i + offset];
-        if (!nextLine.isWrapped) {
-          break;
-        }
-        // when we are reflowing wider we can be sure that this line and the next all have equal to or less than
-        // 'newCols' length => we can pass newCols as the upper limit
-        final lineLength = line.getTrimmedLength(newCols);
-
-        var copyDestIndex = lineLength;
-        if (copyDestIndex >= 1 &&
-            line.cellGetWidth(copyDestIndex - 1) == 2 &&
-            line.cellGetContent(copyDestIndex) == 0) {
-          //we would override a wide char placeholder => move index one to the right
-          copyDestIndex += 1;
-        }
-
-        final spaceOnLine = newCols - copyDestIndex;
-        if (spaceOnLine <= 0) {
-          // no more space to unwrap
-          break;
-        }
-        // when we are reflowing wider we can be sure that this line and the next all have equal to or less than
-        // 'newCols' length => we can pass newCols as the upper limit
-        final nextLineLength = nextLine.getTrimmedLength(newCols);
-        var moveCount = min(spaceOnLine, nextLineLength);
-        if (moveCount <= 0) {
-          break;
-        }
-
-        // when we are about to copy a double width character
-        // to the end of the line then we just ignore it as the target width
-        // would be too much
-        if (nextLine.cellGetWidth(moveCount - 1) == 2) {
-          moveCount -= 1;
-        }
-        line.copyCellsFrom(nextLine, 0, copyDestIndex, moveCount);
-        if (moveCount >= nextLineLength) {
-          // if we unwrapped all cells off the next line, skip it
-          linesToSkip++;
-        } else {
-          // otherwise just remove the characters we moved up a line
-          nextLine.removeN(0, moveCount);
-        }
-      }
-
-      // skip empty lines.
-      i += linesToSkip;
-    }
-    //buffer doesn't have enough lines
-    while (linesAfterReflow.length < buffer.viewHeight) {
-      linesAfterReflow.add(BufferLine(length: buffer.viewWidth));
-    }
-
-    buffer.lines.replaceWith(linesAfterReflow);
-  }
-}

+ 2 - 0
lib/core.dart

@@ -0,0 +1,2 @@
+export 'core/terminal.dart';
+export 'ui/terminal_view.dart';

+ 534 - 0
lib/core/buffer/buffer.dart

@@ -0,0 +1,534 @@
+import 'dart:math' show max, min;
+
+import 'package:xterm/core/buffer/position.dart';
+import 'package:xterm/core/buffer/range.dart';
+import 'package:xterm/core/cursor.dart';
+import 'package:xterm/core/buffer/line.dart';
+import 'package:xterm/core/reflow.dart';
+import 'package:xterm/core/state.dart';
+import 'package:xterm/core/charset.dart';
+import 'package:xterm/utils/circular_list.dart';
+import 'package:xterm/utils/unicode_v11.dart';
+
+class Buffer {
+  final TerminalState terminal;
+
+  final int maxLines;
+
+  final bool isAltBuffer;
+
+  Buffer(
+    this.terminal, {
+    required this.maxLines,
+    required this.isAltBuffer,
+  }) {
+    for (int i = 0; i < terminal.viewHeight; i++) {
+      lines.push(_newEmptyLine());
+    }
+
+    resetVerticalMargins();
+  }
+
+  int _cursorX = 0;
+
+  int _cursorY = 0;
+
+  late int _marginTop;
+
+  late int _marginBottom;
+
+  var _savedCursorX = 0;
+
+  var _savedCursorY = 0;
+
+  final _savedCursorStyle = CursorStyle();
+
+  final charset = Charset();
+
+  /// Width of the viewport in columns. Also the index of the last column.
+  int get viewWidth => terminal.viewWidth;
+
+  /// Height of the viewport in rows. Also the index of the last line.
+  int get viewHeight => terminal.viewHeight;
+
+  /// lines of the buffer. the length of [lines] should always be equal or
+  /// greater than [viewHeight].
+  late final lines = CircularList<BufferLine>(maxLines);
+
+  /// Total number of lines in the buffer. Always equal or greater than
+  /// [viewHeight].
+  int get height => lines.length;
+
+  /// Horizontal position of the cursor relative to the top-left cornor of the
+  /// screen, starting from 0.
+  int get cursorX => _cursorX.clamp(0, terminal.viewWidth - 1);
+
+  /// Vertical position of the cursor relative to the top-left cornor of the
+  /// screen, starting from 0.
+  int get cursorY => _cursorY;
+
+  /// Index of the first line in the scroll region.
+  int get marginTop => _marginTop;
+
+  /// Index of the last line in the scroll region.
+  int get marginBottom => _marginBottom;
+
+  /// The number of lines above the viewport.
+  int get scrollBack => height - viewHeight;
+
+  /// Vertical position of the cursor relative to the top of the buffer,
+  /// starting from 0.
+  int get absoluteCursorY => _cursorY + scrollBack;
+
+  /// Absolute index of the first line in the scroll region.
+  int get absoluteMarginTop => _marginTop + scrollBack;
+
+  /// Absolute index of the last line in the scroll region.
+  int get absoluteMarginBottom => _marginBottom + scrollBack;
+
+  /// Writes data to the _terminal. Terminal sequences or special characters are
+  /// not interpreted and directly added to the buffer.
+  ///
+  /// See also: [Terminal.write]
+  void write(String text) {
+    for (var char in text.runes) {
+      writeChar(char);
+    }
+  }
+
+  /// Writes a single character to the _terminal. Escape sequences or special
+  /// characters are not interpreted and directly added to the buffer.
+  ///
+  /// See also: [Terminal.writeChar]
+  void writeChar(int codePoint) {
+    codePoint = charset.translate(codePoint);
+
+    final cellWidth = unicodeV11.wcwidth(codePoint);
+    if (_cursorX >= terminal.viewWidth) {
+      index();
+      setCursorX(0);
+      if (terminal.autoWrapMode) {
+        currentLine.isWrapped = true;
+      }
+    }
+
+    final line = currentLine;
+    line.setCell(_cursorX, codePoint, cellWidth, terminal.cursor);
+
+    if (_cursorX < viewWidth) {
+      _cursorX++;
+    }
+
+    if (cellWidth == 2) {
+      writeChar(0);
+    }
+  }
+
+  /// The line at the current cursor position.
+  BufferLine get currentLine {
+    return lines[absoluteCursorY];
+  }
+
+  void backspace() {
+    if (_cursorX == 0 && currentLine.isWrapped) {
+      currentLine.isWrapped = false;
+      moveCursor(viewWidth - 1, -1);
+    } else if (_cursorX == viewWidth) {
+      moveCursor(-2, 0);
+    } else {
+      moveCursor(-1, 0);
+    }
+  }
+
+  /// Erases the viewport from the cursor position to the end of the buffer,
+  /// including the cursor position.
+  void eraseDisplayFromCursor() {
+    eraseLineFromCursor();
+
+    for (var i = absoluteCursorY; i < height; i++) {
+      final line = lines[i];
+      line.isWrapped = false;
+      line.eraseRange(0, viewWidth, terminal.cursor);
+    }
+  }
+
+  /// Erases the viewport from the top-left corner to the cursor, including the
+  /// cursor.
+  void eraseDisplayToCursor() {
+    eraseLineToCursor();
+
+    for (var i = 0; i < _cursorY; i++) {
+      final line = lines[i + scrollBack];
+      line.isWrapped = false;
+      line.eraseRange(0, viewWidth, terminal.cursor);
+    }
+  }
+
+  /// Erases the whole viewport.
+  void eraseDisplay() {
+    for (var i = 0; i < viewHeight; i++) {
+      final line = lines[i + scrollBack];
+      line.isWrapped = false;
+      line.eraseRange(0, viewWidth, terminal.cursor);
+    }
+  }
+
+  /// Erases the line from the cursor to the end of the line, including the
+  /// cursor position.
+  void eraseLineFromCursor() {
+    currentLine.isWrapped = false;
+    currentLine.eraseRange(_cursorX, viewWidth, terminal.cursor);
+  }
+
+  /// Erases the line from the start of the line to the cursor, including the
+  /// cursor.
+  void eraseLineToCursor() {
+    currentLine.isWrapped = false;
+    currentLine.eraseRange(0, _cursorX, terminal.cursor);
+  }
+
+  /// Erases the line at the current cursor position.
+  void eraseLine() {
+    currentLine.isWrapped = false;
+    currentLine.eraseRange(0, viewWidth, terminal.cursor);
+  }
+
+  /// Erases [count] cells starting at the cursor position.
+  void eraseChars(int count) {
+    final start = _cursorX;
+    currentLine.eraseRange(start, start + count, terminal.cursor);
+  }
+
+  void scrollDown(int lines) {
+    for (var i = absoluteMarginBottom; i >= absoluteMarginTop; i--) {
+      if (i >= absoluteMarginTop + lines) {
+        this.lines[i] = this.lines[i - lines];
+      } else {
+        this.lines[i] = _newEmptyLine();
+      }
+    }
+  }
+
+  void scrollUp(int lines) {
+    for (var i = absoluteMarginTop; i <= absoluteMarginBottom; i++) {
+      if (i <= absoluteMarginBottom - lines) {
+        this.lines[i] = this.lines[i + lines];
+      } else {
+        this.lines[i] = _newEmptyLine();
+      }
+    }
+  }
+
+  /// https://vt100.net/docs/vt100-ug/chapter3.html#IND IND – Index
+  ///
+  /// ESC D
+  ///
+  /// [index] causes the active position to move downward one line without
+  /// changing the column position. If the active position is at the bottom
+  /// margin, a scroll up is performed.
+  void index() {
+    if (isInVerticalMargin) {
+      if (_cursorY == _marginBottom) {
+        if (marginTop == 0 && !isAltBuffer) {
+          lines.insert(absoluteMarginBottom + 1, _newEmptyLine());
+        } else {
+          scrollUp(1);
+        }
+      } else {
+        moveCursorY(1);
+      }
+      return;
+    }
+
+    // the cursor is not in the scrollable region
+    if (_cursorY >= viewHeight - 1) {
+      // we are at the bottom
+      if (isAltBuffer) {
+        scrollUp(1);
+      } else {
+        lines.push(_newEmptyLine());
+      }
+    } else {
+      // there're still lines so we simply move cursor down.
+      moveCursorY(1);
+    }
+  }
+
+  void lineFeed() {
+    index();
+    if (terminal.lineFeedMode) {
+      setCursorX(0);
+    }
+  }
+
+  /// https://terminalguide.namepad.de/seq/a_esc_cm/
+  void reverseIndex() {
+    if (isInVerticalMargin) {
+      if (_cursorY == _marginTop) {
+        scrollDown(1);
+      } else {
+        moveCursorY(-1);
+      }
+    } else {
+      moveCursorY(-1);
+    }
+  }
+
+  void cursorGoForward() {
+    _cursorX = min(_cursorX + 1, viewWidth);
+  }
+
+  void setCursorX(int cursorX) {
+    _cursorX = cursorX.clamp(0, viewWidth - 1);
+  }
+
+  void setCursorY(int cursorY) {
+    _cursorY = cursorY.clamp(0, viewHeight - 1);
+  }
+
+  void moveCursorX(int offset) {
+    setCursorX(_cursorX + offset);
+  }
+
+  void moveCursorY(int offset) {
+    setCursorY(_cursorY + offset);
+  }
+
+  void setCursor(int cursorX, int cursorY) {
+    var maxCursorY = viewHeight - 1;
+
+    if (terminal.originMode) {
+      cursorY += _marginTop;
+      maxCursorY = _marginBottom;
+    }
+
+    _cursorX = cursorX.clamp(0, viewWidth - 1);
+    _cursorY = cursorY.clamp(0, maxCursorY);
+  }
+
+  void moveCursor(int offsetX, int offsetY) {
+    final cursorX = _cursorX + offsetX;
+    final cursorY = _cursorY + offsetY;
+    setCursor(cursorX, cursorY);
+  }
+
+  /// Save cursor position, charmap and text attributes.
+  void saveCursor() {
+    _savedCursorX = _cursorX;
+    _savedCursorY = _cursorY;
+    _savedCursorStyle.foreground = terminal.cursor.foreground;
+    _savedCursorStyle.background = terminal.cursor.background;
+    _savedCursorStyle.attrs = terminal.cursor.attrs;
+    charset.save();
+  }
+
+  /// Restore cursor position, charmap and text attributes.
+  void restoreCursor() {
+    _cursorX = _savedCursorX;
+    _cursorY = _savedCursorY;
+    terminal.cursor.foreground = _savedCursorStyle.foreground;
+    terminal.cursor.background = _savedCursorStyle.background;
+    terminal.cursor.attrs = _savedCursorStyle.attrs;
+    charset.restore();
+  }
+
+  /// Sets the vertical scrolling margin to [top] and [bottom].
+  /// Both values must be between 0 and [viewHeight] - 1.
+  void setVerticalMargins(int top, int bottom) {
+    _marginTop = top.clamp(0, viewHeight - 1);
+    _marginBottom = bottom.clamp(0, viewHeight - 1);
+
+    _marginTop = min(_marginTop, _marginBottom);
+    _marginBottom = max(_marginTop, _marginBottom);
+  }
+
+  bool get isInVerticalMargin {
+    return _cursorY >= _marginTop && _cursorY <= _marginBottom;
+  }
+
+  void resetVerticalMargins() {
+    setVerticalMargins(0, viewHeight - 1);
+  }
+
+  void deleteChars(int count) {
+    final start = _cursorX.clamp(0, viewWidth);
+    count = min(count, viewWidth - start);
+    currentLine.removeCells(start, count, terminal.cursor);
+  }
+
+  /// Remove all lines above the top of the viewport.
+  void clearScrollback() {
+    if (height <= viewHeight) {
+      return;
+    }
+
+    lines.trimStart(scrollBack);
+  }
+
+  /// Clears the viewport and scrollback buffer. Then fill with empty lines.
+  void clear() {
+    lines.clear();
+    for (int i = 0; i < viewHeight; i++) {
+      lines.push(_newEmptyLine());
+    }
+  }
+
+  void insertBlankChars(int count) {
+    currentLine.insertCells(_cursorX, count, terminal.cursor);
+  }
+
+  void insertLines(int count) {
+    if (!isInVerticalMargin) {
+      return;
+    }
+
+    setCursorX(0);
+
+    for (var i = 0; i < count; i++) {
+      final shiftStart = absoluteCursorY;
+      final shiftCount = absoluteMarginBottom - absoluteCursorY;
+      lines.shiftElements(shiftStart, shiftCount, 1);
+      lines[absoluteCursorY] = _newEmptyLine();
+    }
+  }
+
+  void deleteLines(int count) {
+    if (!isInVerticalMargin) {
+      return;
+    }
+
+    setCursorX(0);
+
+    for (var i = 0; i < count; i++) {
+      lines.insert(absoluteMarginBottom, _newEmptyLine());
+      lines.remove(absoluteCursorY);
+    }
+  }
+
+  void resize(int oldWidth, int oldHeight, int newWidth, int newHeight) {
+    if (newWidth > oldWidth) {
+      lines.forEach((item) => item.resize(newWidth));
+    }
+
+    if (newHeight > oldHeight) {
+      // Grow larger
+      for (var i = 0; i < newHeight - oldHeight; i++) {
+        if (newHeight > lines.length) {
+          lines.push(_newEmptyLine());
+        } else {
+          _cursorY++;
+        }
+      }
+    } else {
+      // Shrink smallerclear
+      for (var i = 0; i < oldHeight - newHeight; i++) {
+        if (_cursorY > newHeight - 1) {
+          _cursorY--;
+        } else {
+          lines.pop();
+        }
+      }
+    }
+
+    // Ensure cursor is within the screen.
+    _cursorX = _cursorX.clamp(0, newWidth - 1);
+    _cursorY = _cursorY.clamp(0, newHeight - 1);
+
+    if (!isAltBuffer && newWidth != oldWidth) {
+      final reflowResult = reflow(lines, oldWidth, newWidth);
+
+      while (reflowResult.length < newHeight) {
+        reflowResult.add(_newEmptyLine());
+      }
+
+      lines.replaceWith(reflowResult);
+    }
+  }
+
+  BufferLine _newEmptyLine() {
+    final line = BufferLine(viewWidth);
+    return line;
+  }
+
+  static final _kWordSeparators = <int>{
+    0,
+    r' '.codeUnitAt(0),
+    r'.'.codeUnitAt(0),
+    r':'.codeUnitAt(0),
+    r'-'.codeUnitAt(0),
+    r'\'.codeUnitAt(0),
+    r'"'.codeUnitAt(0),
+    r'*'.codeUnitAt(0),
+    r'+'.codeUnitAt(0),
+    r'/'.codeUnitAt(0),
+    r'\'.codeUnitAt(0),
+  };
+
+  BufferRange? getWordBoundary(BufferPosition position) {
+    if (position.y >= lines.length) {
+      return null;
+    }
+
+    var line = lines[position.y];
+    var start = position.x;
+    var end = position.x;
+
+    do {
+      if (start == 0) {
+        break;
+      }
+      final char = line.getCodePoint(start - 1);
+      if (_kWordSeparators.contains(char)) {
+        break;
+      }
+      start--;
+    } while (true);
+
+    do {
+      if (end >= viewWidth) {
+        break;
+      }
+      final char = line.getCodePoint(end);
+      if (_kWordSeparators.contains(char)) {
+        break;
+      }
+      end++;
+    } while (true);
+
+    return BufferRange(
+      BufferPosition(start, position.y),
+      BufferPosition(end, position.y),
+    );
+  }
+
+  String getText(BufferRange range) {
+    final builder = StringBuffer();
+    for (var i = range.begin.y; i <= range.end.y; i++) {
+      if (i < 0 || i >= lines.length) {
+        break;
+      }
+
+      final line = lines[i];
+      final start = i == range.begin.y ? range.begin.x : 0;
+      final end = i == range.end.y ? range.end.x : line.length;
+
+      if (i != range.begin.y && line.isWrapped) {
+        builder.write('\n');
+      }
+
+      builder.write(line.getText(start, end));
+    }
+
+    return builder.toString();
+  }
+
+  @override
+  String toString() {
+    final builder = StringBuffer();
+    final lineNumberLength = lines.length.toString().length;
+    for (var i = 0; i < lines.length; i++) {
+      builder.writeln('${i.toString().padLeft(lineNumberLength)}: ${lines[i]}');
+    }
+    return builder.toString();
+  }
+}

+ 0 - 0
lib/buffer/cell_flags.dart → lib/core/buffer/cell_flags.dart


+ 295 - 0
lib/core/buffer/line.dart

@@ -0,0 +1,295 @@
+import 'dart:math' show min;
+import 'dart:typed_data';
+
+import 'package:xterm/core/cell.dart';
+import 'package:xterm/core/cursor.dart';
+import 'package:xterm/utils/unicode_v11.dart';
+
+const _cellSize = 4;
+
+const _cellForeground = 0;
+
+const _cellBackground = 1;
+
+const _cellAttributes = 2;
+
+const _cellContent = 3;
+
+class BufferLine {
+  BufferLine(
+    this._length, {
+    this.isWrapped = false,
+  }) : _data = Uint32List(_calcCapacity(_length) * _cellSize);
+
+  int _length;
+
+  Uint32List _data;
+
+  Uint32List get data => _data;
+
+  var isWrapped = false;
+
+  int get length => _length;
+
+  int getForeground(int index) {
+    return _data[index * _cellSize + _cellForeground];
+  }
+
+  int getBackground(int index) {
+    return _data[index * _cellSize + _cellBackground];
+  }
+
+  int getAttributes(int index) {
+    return _data[index * _cellSize + _cellAttributes];
+  }
+
+  int getContent(int index) {
+    return _data[index * _cellSize + _cellContent];
+  }
+
+  int getCodePoint(int index) {
+    return _data[index * _cellSize + _cellContent] & CellContent.codepointMask;
+  }
+
+  int getWidth(int index) {
+    return _data[index * _cellSize + _cellContent] >> CellContent.widthShift;
+  }
+
+  void getCellData(int index, CellData cellData) {
+    final offset = index * _cellSize;
+    cellData.foreground = _data[offset + _cellForeground];
+    cellData.background = _data[offset + _cellBackground];
+    cellData.flags = _data[offset + _cellAttributes];
+    cellData.content = _data[offset + _cellContent];
+  }
+
+  CellData createCellData(int index) {
+    final cellData = CellData.empty();
+    final offset = index * _cellSize;
+    _data[offset + _cellForeground] = cellData.foreground;
+    _data[offset + _cellBackground] = cellData.background;
+    _data[offset + _cellAttributes] = cellData.flags;
+    _data[offset + _cellContent] = cellData.content;
+    return cellData;
+  }
+
+  void setForeground(int index, int value) {
+    _data[index * _cellSize + _cellForeground] = value;
+  }
+
+  void setBackground(int index, int value) {
+    _data[index * _cellSize + _cellBackground] = value;
+  }
+
+  void setAttributes(int index, int value) {
+    _data[index * _cellSize + _cellAttributes] = value;
+  }
+
+  void setContent(int index, int value) {
+    _data[index * _cellSize + _cellContent] = value;
+  }
+
+  void setCodePoint(int index, int char) {
+    final width = unicodeV11.wcwidth(char);
+    setContent(index, char | (width << CellContent.widthShift));
+  }
+
+  void setCell(int index, int char, int witdh, CursorStyle style) {
+    final offset = index * _cellSize;
+    _data[offset + _cellForeground] = style.foreground;
+    _data[offset + _cellBackground] = style.background;
+    _data[offset + _cellAttributes] = style.attrs;
+    _data[offset + _cellContent] = char | (witdh << CellContent.widthShift);
+  }
+
+  void setCellData(int index, CellData cellData) {
+    final offset = index * _cellSize;
+    _data[offset + _cellForeground] = cellData.foreground;
+    _data[offset + _cellBackground] = cellData.background;
+    _data[offset + _cellAttributes] = cellData.flags;
+    _data[offset + _cellContent] = cellData.content;
+  }
+
+  void eraseCell(int index, CursorStyle style) {
+    final offset = index * _cellSize;
+    _data[offset + _cellForeground] = style.foreground;
+    _data[offset + _cellBackground] = style.background;
+    _data[offset + _cellAttributes] = style.attrs;
+    _data[offset + _cellContent] = 0;
+  }
+
+  void resetCell(int index) {
+    final offset = index * _cellSize;
+    _data[offset + _cellForeground] = 0;
+    _data[offset + _cellBackground] = 0;
+    _data[offset + _cellAttributes] = 0;
+    _data[offset + _cellContent] = 0;
+  }
+
+  void eraseRange(int start, int end, CursorStyle style) {
+    // reset cell one to the left if start is second cell of a wide char
+    if (start > 0 && getWidth(start - 1) == 2) {
+      eraseCell(start - 1, style);
+    }
+
+    // reset cell one to the right if end is second cell of a wide char
+    if (end < _length && getWidth(end - 1) == 2) {
+      eraseCell(end - 1, style);
+    }
+
+    end = min(end, _length);
+    for (var i = start; i < end; i++) {
+      eraseCell(i, style);
+    }
+  }
+
+  void removeCells(int start, int count, [CursorStyle? style]) {
+    assert(start >= 0 && start < _length);
+    assert(count >= 0 && start + count <= _length);
+
+    style ??= CursorStyle.empty;
+
+    if (start + count < _length) {
+      final moveStart = start * _cellSize;
+      final moveEnd = (_length - count) * _cellSize;
+      final moveOffset = count * _cellSize;
+      for (var i = moveStart; i < moveEnd; i++) {
+        _data[i] = _data[i + moveOffset];
+      }
+    }
+
+    for (var i = _length - count; i < _length; i++) {
+      eraseCell(i, style);
+    }
+
+    if (start > 0 && getWidth(start - 1) == 2) {
+      eraseCell(start - 1, style);
+    }
+  }
+
+  void insertCells(int start, int count, [CursorStyle? style]) {
+    style ??= CursorStyle.empty;
+
+    if (start > 0 && getWidth(start - 1) == 2) {
+      eraseCell(start - 1, style);
+    }
+
+    if (start + count < _length) {
+      final moveStart = start * _cellSize;
+      final moveEnd = (_length - count) * _cellSize;
+      final moveOffset = count * _cellSize;
+      for (var i = moveEnd - 1; i >= moveStart; i--) {
+        _data[i + moveOffset] = _data[i];
+      }
+    }
+
+    final end = min(start + count, _length);
+    for (var i = start; i < end; i++) {
+      eraseCell(i, style);
+    }
+
+    if (getWidth(_length - 1) == 2) {
+      eraseCell(_length - 1, style);
+    }
+  }
+
+  void resize(int length) {
+    assert(length >= 0);
+
+    if (length == _length) {
+      return;
+    }
+
+    final newBufferSize = _calcCapacity(length) * _cellSize;
+
+    if (newBufferSize > _data.length) {
+      final newBuffer = Uint32List(newBufferSize);
+      newBuffer.setRange(0, _data.length, _data);
+      _data = newBuffer;
+    }
+
+    _length = length;
+  }
+
+  int getTrimmedLength([int? cols]) {
+    if (cols == null) {
+      cols = _data.length ~/ _cellSize;
+    }
+
+    for (var i = cols - 1; i >= 0; i--) {
+      var codePoint = getCodePoint(i);
+
+      if (codePoint != 0) {
+        // we are at the last cell in this line that has content.
+        // the length of this line is the index of this cell + 1
+        // the only exception is that if that last cell is wider
+        // than 1 then we have to add the diff
+        final lastCellWidth = getWidth(i);
+        return i + lastCellWidth;
+      }
+    }
+    return 0;
+  }
+
+  void copyFrom(BufferLine src, int srcCol, int dstCol, int len) {
+    resize(dstCol + len);
+
+    // data.setRange(
+    //   dstCol * _cellSize,
+    //   (dstCol + len) * _cellSize,
+    //   Uint32List.sublistView(src.data, srcCol * _cellSize, len * _cellSize),
+    // );
+
+    var srcOffset = srcCol * _cellSize;
+    var dstOffset = dstCol * _cellSize;
+
+    for (var i = 0; i < len * _cellSize; i++) {
+      _data[dstOffset++] = src._data[srcOffset++];
+    }
+  }
+
+  static int _calcCapacity(int length) {
+    assert(length >= 0);
+
+    var capacity = 64;
+
+    if (length < 256) {
+      while (capacity < length) {
+        capacity *= 2;
+      }
+    } else {
+      capacity = 256;
+      while (capacity < length) {
+        capacity += 32;
+      }
+    }
+
+    return capacity;
+  }
+
+  String getText([int? from, int? to]) {
+    if (from == null) {
+      from = 0;
+    }
+
+    if (to == null) {
+      to = _length;
+    }
+
+    final builder = StringBuffer();
+    for (var i = from; i < to; i++) {
+      final codePoint = getCodePoint(i);
+      final width = getWidth(i);
+      if (codePoint != 0 && i + width <= to) {
+        builder.writeCharCode(codePoint);
+      }
+    }
+
+    return builder.toString();
+  }
+
+  @override
+  String toString() {
+    return getText();
+  }
+}

+ 55 - 0
lib/core/buffer/position.dart

@@ -0,0 +1,55 @@
+import 'package:xterm/core/buffer/range.dart';
+
+class BufferPosition {
+  final int x;
+
+  final int y;
+
+  const BufferPosition(this.x, this.y);
+
+  bool isEqual(BufferPosition other) {
+    return other.x == x && other.y == y;
+  }
+
+  bool isBefore(BufferPosition other) {
+    return y < other.y || (y == other.y && x < other.x);
+  }
+
+  bool isAfter(BufferPosition other) {
+    return y > other.y || (y == other.y && x > other.x);
+  }
+
+  bool isBeforeOrSame(BufferPosition other) {
+    return y < other.y || (y == other.y && x <= other.x);
+  }
+
+  bool isAfterOrSame(BufferPosition other) {
+    return y > other.y || (y == other.y && x >= other.x);
+  }
+
+  bool isAtSameRow(BufferPosition other) {
+    return y == other.y;
+  }
+
+  bool isAtSameColumn(BufferPosition other) {
+    return x == other.x;
+  }
+
+  bool isWithin(BufferRange range) {
+    return range.begin.isBeforeOrSame(this) && range.end.isAfterOrSame(this);
+  }
+
+  @override
+  String toString() => 'Position($x, $y)';
+
+  @override
+  int get hashCode => x.hashCode ^ y.hashCode;
+
+  @override
+  bool operator ==(Object other) =>
+      identical(this, other) ||
+      other is BufferPosition &&
+          runtimeType == other.runtimeType &&
+          x == other.x &&
+          y == other.y;
+}

+ 71 - 0
lib/core/buffer/range.dart

@@ -0,0 +1,71 @@
+import 'package:xterm/core/buffer/position.dart';
+import 'package:xterm/core/buffer/segment.dart';
+
+class BufferRange {
+  final BufferPosition begin;
+
+  final BufferPosition end;
+
+  BufferRange(this.begin, this.end);
+
+  BufferRange.collapsed(this.begin) : end = begin;
+
+  bool get isNormalized {
+    return begin.isBefore(end) || begin.isEqual(end);
+  }
+
+  bool get isCollapsed {
+    return begin.isEqual(end);
+  }
+
+  Iterable<BufferSegment> toSegments() sync* {
+    var start = this.begin;
+    var end = this.end;
+
+    if (!isNormalized) {
+      end = this.begin;
+      start = this.end;
+    }
+
+    for (var i = start.y; i <= end.y; i++) {
+      var startX = i == start.y ? start.x : null;
+      var endX = i == end.y ? end.x : null;
+      yield BufferSegment(this, i, startX, endX);
+    }
+  }
+
+  bool isWithin(BufferPosition position) {
+    return begin.isBeforeOrSame(position) && end.isAfterOrSame(position);
+  }
+
+  BufferRange merge(BufferRange range) {
+    final begin = this.begin.isBefore(range.begin) ? this.begin : range.begin;
+    final end = this.end.isAfter(range.end) ? this.end : range.end;
+    return BufferRange(begin, end);
+  }
+
+  BufferRange extend(BufferPosition position) {
+    final begin = this.begin.isBefore(position) ? position : this.begin;
+    final end = this.end.isAfter(position) ? position : this.end;
+    return BufferRange(begin, end);
+  }
+
+  @override
+  operator ==(Object other) {
+    if (identical(this, other)) {
+      return true;
+    }
+
+    if (other is! BufferRange) {
+      return false;
+    }
+
+    return begin == other.begin && end == other.end;
+  }
+
+  @override
+  int get hashCode => begin.hashCode ^ end.hashCode;
+
+  @override
+  String toString() => 'Range($begin, $end)';
+}

+ 51 - 0
lib/core/buffer/segment.dart

@@ -0,0 +1,51 @@
+import 'package:xterm/core/buffer/position.dart';
+import 'package:xterm/core/buffer/range.dart';
+
+class BufferSegment {
+  /// The range that this segment belongs to.
+  final BufferRange range;
+
+  /// The line that this segment resides on.
+  final int line;
+
+  /// The start position of this segment.
+  final int? start;
+
+  /// The end position of this segment. [null] if this segment is not closed.
+  final int? end;
+
+  const BufferSegment(this.range, this.line, this.start, this.end);
+
+  bool isWithin(BufferPosition position) {
+    if (position.y != line) {
+      return false;
+    }
+
+    if (start != null && position.x < start!) {
+      return false;
+    }
+
+    if (end != null && position.x > end!) {
+      return false;
+    }
+
+    return true;
+  }
+
+  @override
+  String toString() => 'Segment($line, $start, $end)';
+
+  @override
+  int get hashCode =>
+      range.hashCode ^ line.hashCode ^ start.hashCode ^ end.hashCode;
+
+  @override
+  bool operator ==(Object other) =>
+      identical(this, other) ||
+      other is BufferSegment &&
+          runtimeType == other.runtimeType &&
+          range == other.range &&
+          line == other.line &&
+          start == other.start &&
+          end == other.end;
+}

+ 66 - 0
lib/core/cell.dart

@@ -0,0 +1,66 @@
+import 'package:xterm/utils/hash_values.dart';
+
+class CellData {
+  CellData({
+    required this.foreground,
+    required this.background,
+    required this.flags,
+    required this.content,
+  });
+
+  factory CellData.empty() {
+    return CellData(
+      foreground: 0,
+      background: 0,
+      flags: 0,
+      content: 0,
+    );
+  }
+
+  int foreground;
+
+  int background;
+
+  int flags;
+
+  int content;
+
+  int getHash() {
+    return hashValues(foreground, background, flags, content);
+  }
+
+  @override
+  String toString() {
+    return 'CellData{foreground: $foreground, background: $background, flags: $flags, content: $content}';
+  }
+}
+
+abstract class CellAttr {
+  static const bold = 1 << 0;
+  static const faint = 1 << 1;
+  static const italic = 1 << 2;
+  static const underline = 1 << 3;
+  static const blink = 1 << 4;
+  static const inverse = 1 << 5;
+  static const invisible = 1 << 6;
+  static const strikethrough = 1 << 7;
+}
+
+abstract class CellColor {
+  static const valueMask = 0xFFFFFF;
+
+  static const typeShift = 25;
+  static const typeMask = 3 << typeShift;
+
+  static const normal = 0 << typeShift;
+  static const named = 1 << typeShift;
+  static const palette = 2 << typeShift;
+  static const rgb = 3 << typeShift;
+}
+
+abstract class CellContent {
+  static const codepointMask = 0x1fffff;
+
+  static const widthShift = 22;
+  // static const widthMask = 3 << widthShift;
+}

+ 0 - 0
lib/terminal/charset.dart → lib/core/charset.dart


+ 19 - 0
lib/core/color.dart

@@ -0,0 +1,19 @@
+abstract class NamedColor {
+  static const black = 0;
+  static const red = 1;
+  static const green = 2;
+  static const yellow = 3;
+  static const blue = 4;
+  static const magenta = 5;
+  static const cyan = 6;
+  static const white = 7;
+
+  static const brightBlack = 8;
+  static const brightRed = 9;
+  static const brightGreen = 10;
+  static const brightYellow = 11;
+  static const brightBlue = 12;
+  static const brightMagenta = 13;
+  static const brightCyan = 14;
+  static const brightWhite = 15;
+}

+ 137 - 0
lib/core/cursor.dart

@@ -0,0 +1,137 @@
+import 'package:xterm/core/cell.dart';
+
+class CursorStyle {
+  int foreground;
+
+  int background;
+
+  int attrs;
+
+  CursorStyle({this.foreground = 0, this.background = 0, this.attrs = 0});
+
+  static final empty = CursorStyle();
+
+  void setBold() {
+    attrs |= CellAttr.bold;
+  }
+
+  void setFaint() {
+    attrs |= CellAttr.faint;
+  }
+
+  void setItalic() {
+    attrs |= CellAttr.italic;
+  }
+
+  void setUnderline() {
+    attrs |= CellAttr.underline;
+  }
+
+  void setBlink() {
+    attrs |= CellAttr.blink;
+  }
+
+  void setInverse() {
+    attrs |= CellAttr.inverse;
+  }
+
+  void setInvisible() {
+    attrs |= CellAttr.invisible;
+  }
+
+  void setStrikethrough() {
+    attrs |= CellAttr.strikethrough;
+  }
+
+  void unsetBold() {
+    attrs &= ~CellAttr.bold;
+  }
+
+  void unsetFaint() {
+    attrs &= ~CellAttr.faint;
+  }
+
+  void unsetItalic() {
+    attrs &= ~CellAttr.italic;
+  }
+
+  void unsetUnderline() {
+    attrs &= ~CellAttr.underline;
+  }
+
+  void unsetBlink() {
+    attrs &= ~CellAttr.blink;
+  }
+
+  void unsetInverse() {
+    attrs &= ~CellAttr.inverse;
+  }
+
+  void unsetInvisible() {
+    attrs &= ~CellAttr.invisible;
+  }
+
+  void unsetStrikethrough() {
+    attrs &= ~CellAttr.strikethrough;
+  }
+
+  bool get isBold => (attrs & CellAttr.bold) != 0;
+
+  bool get isFaint => (attrs & CellAttr.faint) != 0;
+
+  bool get isItalis => (attrs & CellAttr.italic) != 0;
+
+  bool get isUnderline => (attrs & CellAttr.underline) != 0;
+
+  bool get isBlink => (attrs & CellAttr.blink) != 0;
+
+  bool get isInverse => (attrs & CellAttr.inverse) != 0;
+
+  bool get isInvisible => (attrs & CellAttr.invisible) != 0;
+
+  void setForegroundColor16(int color) {
+    foreground = color | CellColor.named;
+  }
+
+  void setForegroundColor256(int color) {
+    foreground = color | CellColor.palette;
+  }
+
+  void setForegroundColorRgb(int r, int g, int b) {
+    foreground = (r << 16) | (g << 8) | b | CellColor.rgb;
+  }
+
+  void resetForegroundColor() {
+    foreground = 0; // | CellColor.normal;
+  }
+
+  void setBackgroundColor16(int color) {
+    background = color | CellColor.named;
+  }
+
+  void setBackgroundColor256(int color) {
+    background = color | CellColor.palette;
+  }
+
+  void setBackgroundColorRgb(int r, int g, int b) {
+    background = (r << 16) | (g << 8) | b | CellColor.rgb;
+  }
+
+  void resetBackgroundColor() {
+    background = 0; // | CellColor.normal;
+  }
+
+  void reset() {
+    foreground = 0;
+    background = 0;
+    attrs = 0;
+  }
+}
+
+class CursorPosition {
+  int x;
+
+  int y;
+
+  CursorPosition(this.x, this.y);
+}

+ 29 - 0
lib/core/escape/emitter.dart

@@ -0,0 +1,29 @@
+class EscapeEmitter {
+  const EscapeEmitter();
+
+  String primaryDeviceAttributes() {
+    return '\x1b[?1;2c';
+  }
+
+  String secondaryDeviceAttributes() {
+    const model = 0;
+    const version = 0;
+    return '\x1b[>$model;$version;0c';
+  }
+
+  String tertiaryDeviceAttributes() {
+    return '\x1bP!|00000000\x1b\\';
+  }
+
+  String operatingStatus() {
+    return '\x1b[0n';
+  }
+
+  String cursorPosition(int x, int y) {
+    return '\x1b[$y;${x}R';
+  }
+
+  String bracketedPaste(String text) {
+    return '\x1b[200~$text\x1b[201~';
+  }
+}

+ 211 - 0
lib/core/escape/handler.dart

@@ -0,0 +1,211 @@
+import 'package:xterm/core/mouse.dart';
+
+abstract class EscapeHandler {
+  void writeChar(int char);
+
+  /* SBC */
+
+  void bell();
+
+  void backspaceReturn();
+
+  void tab();
+
+  void lineFeed();
+
+  void carriageReturn();
+
+  void shiftOut();
+
+  void shiftIn();
+
+  void unknownSBC(int char);
+
+  /* ANSI sequence */
+
+  void saveCursor();
+
+  void restoreCursor();
+
+  void index();
+
+  void nextLine();
+
+  void setTapStop();
+
+  void reverseIndex();
+
+  void designateCharset(int charset);
+
+  void unkownEscape(int char);
+
+  /* CSI */
+
+  void repeatPreviousCharacter(int n);
+
+  void setCursor(int x, int y);
+
+  void setCursorX(int x);
+
+  void setCursorY(int y);
+
+  void sendPrimaryDeviceAttributes();
+
+  void clearTabStopUnderCursor();
+
+  void clearAllTabStops();
+
+  void moveCursorX(int offset);
+
+  void moveCursorY(int n);
+
+  void sendSecondaryDeviceAttributes();
+
+  void sendTertiaryDeviceAttributes();
+
+  void sendOperatingStatus();
+
+  void sendCursorPosition();
+
+  void setMargins(int i, [int? bottom]);
+
+  void cursorNextLine(int amount);
+
+  void cursorPrecedingLine(int amount);
+
+  void eraseDisplayBelow();
+
+  void eraseDisplayAbove();
+
+  void eraseDisplay();
+
+  void eraseScrollbackOnly();
+
+  void eraseLineRight();
+
+  void eraseLineLeft();
+
+  void eraseLine();
+
+  void insertLines(int amount);
+
+  void deleteLines(int amount);
+
+  void deleteChars(int amount);
+
+  void scrollUp(int amount);
+
+  void scrollDown(int amount);
+
+  void eraseChars(int amount);
+
+  void insertBlankChars(int amount);
+
+  void unknownCSI(int finalByte);
+
+  /* Modes */
+
+  void setInsertMode(bool enabled);
+
+  void setLineFeedMode(bool enabled);
+
+  void setUnknownMode(int mode, bool enabled);
+
+  /* DEC Private modes */
+
+  void setCursorKeysMode(bool enabled);
+
+  void setReverseDisplayMode(bool enabled);
+
+  void setOriginMode(bool enabled);
+
+  void setColumnMode(bool enabled);
+
+  void setAutoWrapMode(bool enabled);
+
+  void setMouseMode(MouseMode mode);
+
+  void setCursorBlinkMode(bool enabled);
+
+  void setCursorVisibleMode(bool enabled);
+
+  void useAltBuffer();
+
+  void useMainBuffer();
+
+  void clearAltBuffer();
+
+  void setAppKeypadMode(bool enabled);
+
+  void setReportFocusMode(bool enabled);
+
+  void setMouseReportMode(MouseReportMode mode);
+
+  void setAltBufferMouseScrollMode(bool enabled);
+
+  void setBracketedPasteMode(bool enabled);
+
+  void setUnknownDecMode(int mode, bool enabled);
+
+  /* Select Graphic Rendition (SGR) */
+
+  void resetCursorStyle();
+
+  void setCursorBold();
+
+  void setCursorFaint();
+
+  void setCursorItalic();
+
+  void setCursorUnderline();
+
+  void setCursorBlink();
+
+  void setCursorInverse();
+
+  void setCursorInvisible();
+
+  void setCursorStrikethrough();
+
+  void unsetCursorBold();
+
+  void unsetCursorFaint();
+
+  void unsetCursorItalic();
+
+  void unsetCursorUnderline();
+
+  void unsetCursorBlink();
+
+  void unsetCursorInverse();
+
+  void unsetCursorInvisible();
+
+  void unsetCursorStrikethrough();
+
+  void setForegroundColor16(int color);
+
+  void setForegroundColor256(int index);
+
+  void setForegroundColorRgb(int r, int g, int b);
+
+  void resetForeground();
+
+  void setBackgroundColor16(int color);
+
+  void setBackgroundColor256(int index);
+
+  void setBackgroundColorRgb(int r, int g, int b);
+
+  void resetBackground();
+
+  void unsupportedStyle(int param);
+
+  /* OSC */
+
+  void setTitle(String name);
+
+  void setIconName(String name);
+
+  void unknownOSC(String ps);
+}

+ 1095 - 0
lib/core/escape/parser.dart

@@ -0,0 +1,1095 @@
+import 'package:xterm/core/color.dart';
+import 'package:xterm/core/mouse.dart';
+import 'package:xterm/core/escape/handler.dart';
+import 'package:xterm/utils/ascii.dart';
+import 'package:xterm/utils/byte_consumer.dart';
+import 'package:xterm/utils/char_code.dart';
+import 'package:xterm/utils/lookup_table.dart';
+
+/// [EscapeParser] translates control characters and escape sequences into
+/// function calls that the terminal can handle.
+///
+/// Design goals:
+///  * Zero object allocation during processing.
+///  * No internal state. Same input will always produce same output.
+class EscapeParser {
+  final EscapeHandler handler;
+
+  EscapeParser(this.handler);
+
+  final _queue = ByteConsumer();
+
+  /// Start of sequence or character being processed. Useful for debugging.
+  var tokenBegin = 0;
+
+  /// End of sequence or character being processed. Useful for debugging.
+  int get tokenEnd => _queue.totalConsumed;
+
+  void write(String chunk) {
+    _queue.unrefConsumedBlocks();
+    _queue.add(chunk);
+    _process();
+  }
+
+  void _process() {
+    while (_queue.isNotEmpty) {
+      tokenBegin = _queue.totalConsumed;
+      final char = _queue.consume();
+
+      if (char == Ascii.ESC) {
+        final processed = _processEscape();
+        if (!processed) {
+          _queue.rollback(tokenEnd - tokenBegin);
+          return;
+        }
+      } else {
+        _processChar(char);
+      }
+    }
+  }
+
+  void _processChar(int char) {
+    if (char > _sbcHandlers.maxIndex) {
+      handler.writeChar(char);
+      return;
+    }
+
+    final sbcHandler = _sbcHandlers[char];
+    if (sbcHandler == null) {
+      handler.unkownEscape(char);
+      return;
+    }
+
+    sbcHandler();
+  }
+
+  /// Processes a sequence of characters that starts with an escape character.
+  /// Returns [true] if the sequence was processed, [false] if it was not.
+  bool _processEscape() {
+    if (_queue.isEmpty) return false;
+
+    final escapeChar = _queue.consume();
+    final escapeHandler = _escHandlers[escapeChar];
+
+    if (escapeHandler == null) {
+      handler.unkownEscape(escapeChar);
+      return true;
+    }
+
+    return escapeHandler();
+  }
+
+  late final _sbcHandlers = FastLookupTable<_SbcHandler>({
+    0x07: handler.bell,
+    0x08: handler.backspaceReturn,
+    0x09: handler.tab,
+    0x0a: handler.lineFeed,
+    0x0b: handler.lineFeed,
+    0x0c: handler.lineFeed,
+    0x0d: handler.carriageReturn,
+    0x0e: handler.shiftOut,
+    0x0f: handler.shiftIn,
+  });
+
+  late final _escHandlers = FastLookupTable<_EscHandler>({
+    '['.charCode: _escHandleCSI,
+    ']'.charCode: _escHandleOSC,
+    '7'.charCode: _escHandleSaveCursor,
+    '8'.charCode: _escHandleRestoreCursor,
+    'D'.charCode: _escHandleIndex,
+    'E'.charCode: _escHandleNextLine,
+    'H'.charCode: _escHandleTabSet,
+    'M'.charCode: _escHandleReverseIndex,
+    // 'P'.charCode: _unsupportedHandler, // Sixel
+    // 'c'.charCode: _unsupportedHandler,
+    // '#'.charCode: _unsupportedHandler,
+    '('.charCode: _escHandleDesignateCharset0, //  SCS - G0
+    ')'.charCode: _escHandleDesignateCharset1, //  SCS - G1
+    // '*'.charCode: _voidHandler(1), // TODO: G2 (vt220)
+    // '+'.charCode: _voidHandler(1), // TODO: G3 (vt220)
+    '>'.charCode: _escHandleResetAppKeypadMode, // TODO: Normal Keypad
+    '='.charCode: _escHandleSetAppKeypadMode, // TODO: Application Keypad
+  });
+
+  /// `ESC 7` Save Cursor (DECSC)
+  ///
+  /// https://terminalguide.namepad.de/seq/a_esc_a7/
+  bool _escHandleSaveCursor() {
+    handler.saveCursor();
+    return true;
+  }
+
+  /// `ESC 8` Restore Cursor (DECRC)
+  ///
+  /// https://terminalguide.namepad.de/seq/a_esc_a8/
+  bool _escHandleRestoreCursor() {
+    handler.restoreCursor();
+    return true;
+  }
+
+  /// `ESC D` Index (IND)
+  ///
+  /// https://terminalguide.namepad.de/seq/a_esc_cd/
+  bool _escHandleIndex() {
+    handler.index();
+    return true;
+  }
+
+  /// `ESC E` Next Line (NEL)
+  ///
+  /// https://terminalguide.namepad.de/seq/a_esc_ce/
+  bool _escHandleNextLine() {
+    handler.nextLine();
+    return true;
+  }
+
+  /// `ESC H` Horizontal Tab Set (HTS)
+  ///
+  /// https://terminalguide.namepad.de/seq/a_esc_ch/
+  bool _escHandleTabSet() {
+    handler.setTapStop();
+    return true;
+  }
+
+  /// `ESC M` Reverse Index (RI)
+  ///
+  /// https://terminalguide.namepad.de/seq/a_esc_cm/
+  bool _escHandleReverseIndex() {
+    handler.reverseIndex();
+    return true;
+  }
+
+  bool _escHandleDesignateCharset0() {
+    if (_queue.isEmpty) return false;
+    _queue.consume();
+    handler.designateCharset(0);
+    return true;
+  }
+
+  bool _escHandleDesignateCharset1() {
+    if (_queue.isEmpty) return false;
+    _queue.consume();
+    handler.designateCharset(1);
+    return true;
+  }
+
+  /// `ESC >` Reset Application Keypad Mode (DECKPNM)
+  ///
+  /// https://terminalguide.namepad.de/seq/a_esc_x3c_greater_than/
+  bool _escHandleSetAppKeypadMode() {
+    handler.setAppKeypadMode(true);
+    return true;
+  }
+
+  /// `ESC =` Set Application Keypad Mode (DECKPAM)
+  ///
+  /// https://terminalguide.namepad.de/seq/a_esc_x3d_equals/
+  bool _escHandleResetAppKeypadMode() {
+    handler.setAppKeypadMode(false);
+    return true;
+  }
+
+  bool _escHandleCSI() {
+    final consumed = _consumeCsi();
+    if (!consumed) return false;
+
+    final csiHandler = _csiHandlers[_csi.finalByte];
+
+    if (csiHandler == null) {
+      handler.unknownCSI(_csi.finalByte);
+    } else {
+      csiHandler();
+    }
+
+    return true;
+  }
+
+  /// The last parsed [_Csi]. This is a mutable singletion by design to reduce
+  /// object allocations.
+  final _csi = _Csi(finalByte: 0, params: []);
+
+  /// Parse a CSI from the head of the queue. Return false if the CSI isn't
+  /// complete. After a CSI is successfully parsed, [_csi] is updated.
+  bool _consumeCsi() {
+    if (_queue.isEmpty) {
+      return false;
+    }
+
+    _csi.params.clear();
+
+    // test whether the csi is a `CSI ? Ps ...` or `CSI Ps ...`
+    final prefix = _queue.peek();
+    if (prefix >= Ascii.colon && prefix <= Ascii.questionMark) {
+      _csi.prefix = prefix;
+      _queue.consume();
+    } else {
+      _csi.prefix = null;
+    }
+
+    var param = 0;
+    var hasParam = false;
+    while (true) {
+      // The sequence isn't completed, just ignore it.
+      if (_queue.isEmpty) {
+        return false;
+      }
+
+      final char = _queue.consume();
+
+      if (char == Ascii.semicolon) {
+        if (hasParam) {
+          _csi.params.add(param);
+        }
+        param = 0;
+        continue;
+      }
+
+      if (char >= Ascii.num0 && char <= Ascii.num9) {
+        hasParam = true;
+        param *= 10;
+        param += char - Ascii.num0;
+        continue;
+      }
+
+      if (char > Ascii.NULL && char < Ascii.num0) {
+        // intermediates.add(char);
+        continue;
+      }
+
+      if (char >= Ascii.atSign && char <= Ascii.tilde) {
+        if (hasParam) {
+          _csi.params.add(param);
+        }
+
+        _csi.finalByte = char;
+        return true;
+      }
+    }
+  }
+
+  late final _csiHandlers = FastLookupTable<_CsiHandler>({
+    // 'a'.codeUnitAt(0): _csiHandleCursorHorizontalRelative,
+    'b'.codeUnitAt(0): _csiHandleRepeatPreviousCharacter,
+    'c'.codeUnitAt(0): _csiHandleSendDeviceAttributes,
+    'd'.codeUnitAt(0): _csiHandleLinePositionAbsolute,
+    'f'.codeUnitAt(0): _csiHandleCursorPosition,
+    'g'.codeUnitAt(0): _csiHandelClearTabStop,
+    'h'.codeUnitAt(0): _csiHandleMode,
+    'l'.codeUnitAt(0): _csiHandleMode,
+    'm'.codeUnitAt(0): _csiHandleSgr,
+    'n'.codeUnitAt(0): _csiHandleDeviceStatusReport,
+    'r'.codeUnitAt(0): _csiHandleSetMargins,
+    't'.codeUnitAt(0): _csiWindowManipulation,
+    'A'.codeUnitAt(0): _csiHandleCursorUp,
+    'B'.codeUnitAt(0): _csiHandleCursorDown,
+    'C'.codeUnitAt(0): _csiHandleCursorForward,
+    'D'.codeUnitAt(0): _csiHandleCursorBackward,
+    'E'.codeUnitAt(0): _csiHandleCursorNextLine,
+    'F'.codeUnitAt(0): _csiHandleCursorPrecedingLine,
+    'G'.codeUnitAt(0): _csiHandleCursorHorizontalAbsolute,
+    'H'.codeUnitAt(0): _csiHandleCursorPosition,
+    'J'.codeUnitAt(0): _csiHandleEraseDisplay,
+    'K'.codeUnitAt(0): _csiHandleEraseLine,
+    'L'.codeUnitAt(0): _csiHandleInsertLines,
+    'M'.codeUnitAt(0): _csiHandleDeleteLines,
+    'P'.codeUnitAt(0): _csiHandleDelete,
+    'S'.codeUnitAt(0): _csiHandleScrollUp,
+    'T'.codeUnitAt(0): _csiHandleScrollDown,
+    'X'.codeUnitAt(0): _csiHandleEraseCharacters,
+    '@'.codeUnitAt(0): _csiHandleInsertBlankCharacters,
+  });
+
+  /// `ESC [ Ps a` Cursor Horizontal Position Relative (HPR)
+  ///
+  /// https://terminalguide.namepad.de/seq/csi_sa/
+  // void _csiHandleCursorHorizontalRelative() {
+  //   if (_csi.params.isEmpty) {
+  //     handler.cursorHorizontal(1);
+  //   } else {
+  //     handler.cursorHorizontal(_csi.params[0]);
+  //   }
+  // }
+
+  /// `ESC [ Ps b` Repeat Previous Character (REP)
+  ///
+  /// https://terminalguide.namepad.de/seq/csi_sb/
+  void _csiHandleRepeatPreviousCharacter() {
+    var amount = 1;
+
+    if (_csi.params.isNotEmpty) {
+      amount = _csi.params[0];
+      if (amount == 0) amount = 1;
+    }
+
+    handler.repeatPreviousCharacter(amount);
+  }
+
+  /// `ESC [ Ps c` Device Attributes (DA)
+  ///
+  /// https://terminalguide.namepad.de/seq/csi_sc/
+  void _csiHandleSendDeviceAttributes() {
+    switch (_csi.prefix) {
+      case Ascii.greaterThan:
+        return handler.sendSecondaryDeviceAttributes();
+      case Ascii.equal:
+        return handler.sendTertiaryDeviceAttributes();
+      default:
+        handler.sendPrimaryDeviceAttributes();
+    }
+  }
+
+  /// `ESC [ Ps d` Cursor Vertical Position Absolute (VPA)
+  ///
+  /// https://terminalguide.namepad.de/seq/csi_sd/
+  void _csiHandleLinePositionAbsolute() {
+    var y = 1;
+
+    if (_csi.params.isNotEmpty) {
+      y = _csi.params[0];
+    }
+
+    handler.setCursorY(y - 1);
+  }
+
+  /// `ESC [ Ps ; Ps f` Alias: Set Cursor Position
+  ///
+  /// https://terminalguide.namepad.de/seq/csi_sf/
+  void _csiHandleCursorPosition() {
+    var row = 1;
+    var col = 1;
+
+    if (_csi.params.length == 2) {
+      row = _csi.params[0];
+      col = _csi.params[1];
+    }
+
+    handler.setCursor(col - 1, row - 1);
+  }
+
+  /// `ESC [ Ps g` Tab Clear (TBC)
+  ///
+  /// https://terminalguide.namepad.de/seq/csi_sg/
+  void _csiHandelClearTabStop() {
+    var cmd = 0;
+
+    if (_csi.params.length == 1) {
+      cmd = _csi.params[0];
+    }
+
+    switch (cmd) {
+      case 0:
+        return handler.clearTabStopUnderCursor();
+      default:
+        return handler.clearAllTabStops();
+    }
+  }
+
+  /// - `ESC [ [ Pm ] h Set Mode (SM)` https://terminalguide.namepad.de/seq/csi_sm/
+  /// - `ESC [ ? [ Pm ] h` Set Mode (?) (SM) https://terminalguide.namepad.de/seq/csi_sh__p/
+  /// - `ESC [ [ Pm ] l` Reset Mode (RM) https://terminalguide.namepad.de/seq/csi_rm/
+  /// - `ESC [ ? [ Pm ] l` Reset Mode (?) (RM) https://terminalguide.namepad.de/seq/csi_sl__p/
+  void _csiHandleMode() {
+    final isEnabled = _csi.finalByte == Ascii.h;
+
+    final isDecModes = _csi.prefix == Ascii.questionMark;
+
+    if (isDecModes) {
+      for (var mode in _csi.params) {
+        _setDecMode(mode, isEnabled);
+      }
+    } else {
+      for (var mode in _csi.params) {
+        _setMode(mode, isEnabled);
+      }
+    }
+  }
+
+  /// `ESC [ [ Ps ] m` Select Graphic Rendition (SGR)
+  ///
+  /// https://terminalguide.namepad.de/seq/csi_sm/
+  void _csiHandleSgr() {
+    final params = _csi.params;
+
+    if (params.isEmpty) {
+      return handler.resetCursorStyle();
+    }
+
+    for (var i = 0; i < _csi.params.length; i++) {
+      final param = params[i];
+      switch (param) {
+        case 0:
+          handler.resetCursorStyle();
+          continue;
+        case 1:
+          handler.setCursorBold();
+          continue;
+        case 2:
+          handler.setCursorFaint();
+          continue;
+        case 3:
+          handler.setCursorItalic();
+          continue;
+        case 4:
+          handler.setCursorUnderline();
+          continue;
+        case 5:
+          handler.setCursorBlink();
+          continue;
+        case 7:
+          handler.setCursorInverse();
+          continue;
+        case 8:
+          handler.setCursorInvisible();
+          continue;
+        case 9:
+          handler.setCursorStrikethrough();
+          continue;
+
+        case 21:
+          handler.unsetCursorBold();
+          continue;
+        case 22:
+          handler.unsetCursorFaint();
+          continue;
+        case 23:
+          handler.unsetCursorItalic();
+          continue;
+        case 24:
+          handler.unsetCursorUnderline();
+          continue;
+        case 25:
+          handler.unsetCursorBlink();
+          continue;
+        case 27:
+          handler.unsetCursorInverse();
+          continue;
+        case 28:
+          handler.unsetCursorInvisible();
+          continue;
+        case 29:
+          handler.unsetCursorStrikethrough();
+          continue;
+
+        case 30:
+          handler.setForegroundColor16(NamedColor.black);
+          continue;
+        case 31:
+          handler.setForegroundColor16(NamedColor.red);
+          continue;
+        case 32:
+          handler.setForegroundColor16(NamedColor.green);
+          continue;
+        case 33:
+          handler.setForegroundColor16(NamedColor.yellow);
+          continue;
+        case 34:
+          handler.setForegroundColor16(NamedColor.blue);
+          continue;
+        case 35:
+          handler.setForegroundColor16(NamedColor.magenta);
+          continue;
+        case 36:
+          handler.setForegroundColor16(NamedColor.cyan);
+          continue;
+        case 37:
+          handler.setForegroundColor16(NamedColor.white);
+          continue;
+        case 38:
+          final mode = params[i + 1];
+          switch (mode) {
+            case 2:
+              final r = params[i + 2];
+              final g = params[i + 3];
+              final b = params[i + 4];
+              handler.setForegroundColorRgb(r, g, b);
+              i += 4;
+              break;
+            case 5:
+              final index = params[i + 2];
+              handler.setForegroundColor256(index);
+              i += 2;
+              break;
+          }
+          continue;
+        case 39:
+          handler.resetForeground();
+          continue;
+
+        case 40:
+          handler.setBackgroundColor16(NamedColor.black);
+          continue;
+        case 41:
+          handler.setBackgroundColor16(NamedColor.red);
+          continue;
+        case 42:
+          handler.setBackgroundColor16(NamedColor.green);
+          continue;
+        case 43:
+          handler.setBackgroundColor16(NamedColor.yellow);
+          continue;
+        case 44:
+          handler.setBackgroundColor16(NamedColor.blue);
+          continue;
+        case 45:
+          handler.setBackgroundColor16(NamedColor.magenta);
+          continue;
+        case 46:
+          handler.setBackgroundColor16(NamedColor.cyan);
+          continue;
+        case 47:
+          handler.setBackgroundColor16(NamedColor.white);
+          continue;
+        case 48:
+          final mode = params[i + 1];
+          switch (mode) {
+            case 2:
+              final r = params[i + 2];
+              final g = params[i + 3];
+              final b = params[i + 4];
+              handler.setBackgroundColorRgb(r, g, b);
+              i += 4;
+              break;
+            case 5:
+              final index = params[i + 2];
+              handler.setBackgroundColor256(index);
+              i += 2;
+              break;
+          }
+          continue;
+        case 49:
+          handler.resetBackground();
+          continue;
+
+        case 90:
+          handler.setForegroundColor16(NamedColor.brightBlack);
+          continue;
+        case 91:
+          handler.setForegroundColor16(NamedColor.brightRed);
+          continue;
+        case 92:
+          handler.setForegroundColor16(NamedColor.brightGreen);
+          continue;
+        case 93:
+          handler.setForegroundColor16(NamedColor.brightYellow);
+          continue;
+        case 94:
+          handler.setForegroundColor16(NamedColor.brightBlue);
+          continue;
+        case 95:
+          handler.setForegroundColor16(NamedColor.brightMagenta);
+          continue;
+        case 96:
+          handler.setForegroundColor16(NamedColor.brightCyan);
+          continue;
+        case 97:
+          handler.setForegroundColor16(NamedColor.brightWhite);
+          continue;
+
+        case 100:
+          handler.setBackgroundColor16(NamedColor.brightBlack);
+          continue;
+        case 101:
+          handler.setBackgroundColor16(NamedColor.brightRed);
+          continue;
+        case 102:
+          handler.setBackgroundColor16(NamedColor.brightGreen);
+          continue;
+        case 103:
+          handler.setBackgroundColor16(NamedColor.brightYellow);
+          continue;
+        case 104:
+          handler.setBackgroundColor16(NamedColor.brightBlue);
+          continue;
+        case 105:
+          handler.setBackgroundColor16(NamedColor.brightMagenta);
+          continue;
+        case 106:
+          handler.setBackgroundColor16(NamedColor.brightCyan);
+          continue;
+        case 107:
+          handler.setBackgroundColor16(NamedColor.brightWhite);
+          continue;
+
+        default:
+          handler.unsupportedStyle(param);
+          continue;
+      }
+    }
+  }
+
+  /// `ESC [ Ps n` Device Status Report [Dispatch] (DSR)
+  ///
+  /// https://terminalguide.namepad.de/seq/csi_sn/
+  void _csiHandleDeviceStatusReport() {
+    if (_csi.params.isEmpty) return;
+
+    switch (_csi.params[0]) {
+      case 5:
+        return handler.sendOperatingStatus();
+      case 6:
+        return handler.sendCursorPosition();
+    }
+  }
+
+  /// `ESC [ Ps ; Ps r` Set Top and Bottom Margins (DECSTBM)
+  ///
+  /// https://terminalguide.namepad.de/seq/csi_sr/
+  void _csiHandleSetMargins() {
+    var top = 1;
+    int? bottom;
+
+    if (_csi.params.length > 2) return;
+
+    if (_csi.params.isNotEmpty) {
+      top = _csi.params[0];
+
+      if (_csi.params.length == 2) {
+        bottom = _csi.params[1] - 1;
+      }
+    }
+
+    handler.setMargins(top - 1, bottom);
+  }
+
+  /// `ESC [ Ps t` Window operations [DISPATCH]
+  ///
+  /// https://terminalguide.namepad.de/seq/csi_st/
+  void _csiWindowManipulation() {
+    // Not supported.
+  }
+
+  /// `ESC [ Ps A` Cursor Up (CUU)
+  ///
+  /// https://terminalguide.namepad.de/seq/csi_ca/
+  void _csiHandleCursorUp() {
+    var amount = 1;
+
+    if (_csi.params.isNotEmpty) {
+      amount = _csi.params[0];
+      if (amount == 0) amount = 1;
+    }
+
+    handler.moveCursorY(-amount);
+  }
+
+  /// `ESC [ Ps B` Cursor Down (CUD)
+  ///
+  /// https://terminalguide.namepad.de/seq/csi_cb/
+  void _csiHandleCursorDown() {
+    var amount = 1;
+
+    if (_csi.params.isNotEmpty) {
+      amount = _csi.params[0];
+      if (amount == 0) amount = 1;
+    }
+
+    handler.moveCursorY(amount);
+  }
+
+  /// `ESC [ Ps C` Cursor Right (CUF)
+  ///
+  /// Cursor Right (CUF)
+  void _csiHandleCursorForward() {
+    var amount = 1;
+
+    if (_csi.params.isNotEmpty) {
+      amount = _csi.params[0];
+      if (amount == 0) amount = 1;
+    }
+
+    handler.moveCursorX(amount);
+  }
+
+  /// `ESC [ Ps D` Cursor Left (CUB)
+  ///
+  /// https://terminalguide.namepad.de/seq/csi_cd/
+  void _csiHandleCursorBackward() {
+    var amount = 1;
+
+    if (_csi.params.isNotEmpty) {
+      amount = _csi.params[0];
+      if (amount == 0) amount = 1;
+    }
+
+    handler.moveCursorX(-amount);
+  }
+
+  /// `ESC [ Ps E` Cursor Next Line (CNL)
+  ///
+  /// https://terminalguide.namepad.de/seq/csi_ce/
+  void _csiHandleCursorNextLine() {
+    var amount = 1;
+
+    if (_csi.params.isNotEmpty) {
+      amount = _csi.params[0];
+      if (amount == 0) amount = 1;
+    }
+
+    handler.cursorNextLine(amount);
+  }
+
+  /// `ESC [ Ps F` Cursor Previous Line (CPL)
+  ///
+  /// https://terminalguide.namepad.de/seq/csi_cf/
+  void _csiHandleCursorPrecedingLine() {
+    var amount = 1;
+
+    if (_csi.params.isNotEmpty) {
+      amount = _csi.params[0];
+      if (amount == 0) amount = 1;
+    }
+
+    handler.cursorPrecedingLine(amount);
+  }
+
+  void _csiHandleCursorHorizontalAbsolute() {
+    var x = 1;
+
+    if (_csi.params.isNotEmpty) {
+      x = _csi.params[0];
+      if (x == 0) x = 1;
+    }
+
+    handler.setCursorX(x - 1);
+  }
+
+  /// ESC [ Ps J Erase Display [Dispatch] (ED)
+  ///
+  /// https://terminalguide.namepad.de/seq/csi_cj/
+  void _csiHandleEraseDisplay() {
+    var cmd = 0;
+
+    if (_csi.params.length == 1) {
+      cmd = _csi.params[0];
+    }
+
+    switch (cmd) {
+      case 0:
+        return handler.eraseDisplayBelow();
+      case 1:
+        return handler.eraseDisplayAbove();
+      case 2:
+        return handler.eraseDisplay();
+      case 3:
+        return handler.eraseScrollbackOnly();
+    }
+  }
+
+  /// `ESC [ Ps K` Erase Line [Dispatch] (EL)
+  ///
+  /// https://terminalguide.namepad.de/seq/csi_ck/
+  void _csiHandleEraseLine() {
+    var cmd = 0;
+
+    if (_csi.params.length == 1) {
+      cmd = _csi.params[0];
+    }
+
+    switch (cmd) {
+      case 0:
+        return handler.eraseLineRight();
+      case 1:
+        return handler.eraseLineLeft();
+      case 2:
+        return handler.eraseLine();
+    }
+  }
+
+  /// `ESC [ Ps L` Insert Line (IL)
+  ///
+  /// https://terminalguide.namepad.de/seq/csi_cl/
+  void _csiHandleInsertLines() {
+    var amount = 1;
+
+    if (_csi.params.isNotEmpty) {
+      amount = _csi.params[0];
+    }
+
+    handler.insertLines(amount);
+  }
+
+  /// ESC [ Ps M Delete Line (DL)
+  ///
+  /// https://terminalguide.namepad.de/seq/csi_cm/
+  void _csiHandleDeleteLines() {
+    var amount = 1;
+
+    if (_csi.params.isNotEmpty) {
+      amount = _csi.params[0];
+    }
+
+    handler.deleteLines(amount);
+  }
+
+  /// ESC [ Ps P Delete Character (DCH)
+  ///
+  /// https://terminalguide.namepad.de/seq/csi_cp/
+  void _csiHandleDelete() {
+    var amount = 1;
+
+    if (_csi.params.isNotEmpty) {
+      amount = _csi.params[0];
+    }
+
+    handler.deleteChars(amount);
+  }
+
+  /// `ESC [ Ps S` Scroll Up (SU)
+  ///
+  /// https://terminalguide.namepad.de/seq/csi_cs/
+  void _csiHandleScrollUp() {
+    var amount = 1;
+
+    if (_csi.params.isNotEmpty) {
+      amount = _csi.params[0];
+    }
+
+    handler.scrollUp(amount);
+  }
+
+  /// `ESC [ Ps T `Scroll Down (SD)
+  ///
+  /// https://terminalguide.namepad.de/seq/csi_ct_1param/
+  void _csiHandleScrollDown() {
+    var amount = 1;
+
+    if (_csi.params.isNotEmpty) {
+      amount = _csi.params[0];
+    }
+
+    handler.scrollDown(amount);
+  }
+
+  /// `ESC [ Ps X` Erase Character (ECH)
+  ///
+  /// https://terminalguide.namepad.de/seq/csi_cx/
+  void _csiHandleEraseCharacters() {
+    var amount = 1;
+
+    if (_csi.params.isNotEmpty) {
+      amount = _csi.params[0];
+    }
+
+    handler.eraseChars(amount);
+  }
+
+  /// `ESC [ Ps @` Insert Blanks (ICH)
+  ///
+  /// https://terminalguide.namepad.de/seq/csi_x40_at/
+  ///
+  /// Inserts amount spaces at current cursor position moving existing cell
+  /// contents to the right. The contents of the amount right-most columns in
+  /// the scroll region are lost. The cursor position is not changed.
+  void _csiHandleInsertBlankCharacters() {
+    var amount = 1;
+
+    if (_csi.params.isNotEmpty) {
+      amount = _csi.params[0];
+    }
+
+    handler.insertBlankChars(amount);
+  }
+
+  void _setMode(int mode, bool enabled) {
+    switch (mode) {
+      case 4:
+        return handler.setInsertMode(enabled);
+      case 20:
+        return handler.setLineFeedMode(enabled);
+      default:
+        return handler.setUnknownMode(mode, enabled);
+    }
+  }
+
+  void _setDecMode(int mode, bool enabled) {
+    switch (mode) {
+      case 1:
+        return handler.setCursorKeysMode(enabled);
+      case 3:
+        return handler.setColumnMode(enabled);
+      case 5:
+        return handler.setReverseDisplayMode(enabled);
+      case 6:
+        return handler.setOriginMode(enabled);
+      case 7:
+        return handler.setAutoWrapMode(enabled);
+      case 9:
+        return enabled
+            ? handler.setMouseMode(MouseMode.clickOnly)
+            : handler.setMouseMode(MouseMode.none);
+      case 12:
+      case 13:
+        return handler.setCursorBlinkMode(enabled);
+      case 25:
+        return handler.setCursorVisibleMode(enabled);
+      case 47:
+        if (enabled) {
+          return handler.useAltBuffer();
+        } else {
+          return handler.useMainBuffer();
+        }
+      case 66:
+        return handler.setAppKeypadMode(enabled);
+      case 1000:
+      case 10061000:
+        return enabled
+            ? handler.setMouseMode(MouseMode.upDownScroll)
+            : handler.setMouseMode(MouseMode.none);
+      case 1001:
+        return enabled
+            ? handler.setMouseMode(MouseMode.upDownScroll)
+            : handler.setMouseMode(MouseMode.none);
+      case 1002:
+        return enabled
+            ? handler.setMouseMode(MouseMode.upDownScrollDrag)
+            : handler.setMouseMode(MouseMode.none);
+      case 1003:
+        return enabled
+            ? handler.setMouseMode(MouseMode.upDownScrollMove)
+            : handler.setMouseMode(MouseMode.none);
+      case 1004:
+        return handler.setReportFocusMode(enabled);
+      case 1005:
+        return enabled
+            ? handler.setMouseReportMode(MouseReportMode.utf)
+            : handler.setMouseReportMode(MouseReportMode.normal);
+      case 1006:
+        return enabled
+            ? handler.setMouseReportMode(MouseReportMode.sgr)
+            : handler.setMouseReportMode(MouseReportMode.normal);
+      case 1007:
+        return handler.setAltBufferMouseScrollMode(enabled);
+      case 1015:
+        return enabled
+            ? handler.setMouseReportMode(MouseReportMode.urxvt)
+            : handler.setMouseReportMode(MouseReportMode.normal);
+      case 1047:
+        if (enabled) {
+          handler.useAltBuffer();
+        } else {
+          handler.clearAltBuffer();
+          handler.useMainBuffer();
+        }
+        return;
+      case 1048:
+        if (enabled) {
+          return handler.saveCursor();
+        } else {
+          return handler.restoreCursor();
+        }
+      case 1049:
+        if (enabled) {
+          handler.saveCursor();
+          handler.clearAltBuffer();
+          handler.useAltBuffer();
+        } else {
+          handler.useMainBuffer();
+        }
+        return;
+      case 2004:
+        return handler.setBracketedPasteMode(enabled);
+      default:
+        return handler.setUnknownDecMode(mode, enabled);
+    }
+  }
+
+  bool _escHandleOSC() {
+    final consumed = _consumeOsc();
+    if (!consumed) return false;
+
+    if (_osc.length < 2) {
+      return true;
+    }
+
+    final ps = _osc[0];
+    final pt = _osc[1];
+
+    switch (ps) {
+      case '0':
+        handler.setTitle(pt);
+        handler.setIconName(pt);
+        break;
+      case '1':
+        handler.setIconName(pt);
+        break;
+      case '2':
+        handler.setTitle(pt);
+        break;
+      default:
+        handler.unknownOSC(ps);
+    }
+
+    return true;
+  }
+
+  final _osc = <String>[];
+
+  bool _consumeOsc() {
+    _osc.clear();
+    final param = StringBuffer();
+
+    while (true) {
+      if (_queue.isEmpty) {
+        return false;
+      }
+
+      final char = _queue.consume();
+
+      // OSC terminates with BEL
+      if (char == Ascii.BEL) {
+        _osc.add(param.toString());
+        return true;
+      }
+
+      /// OSC terminates with ST
+      if (char == Ascii.ESC) {
+        if (_queue.isEmpty) {
+          return false;
+        }
+
+        if (_queue.consume() == Ascii.backslash) {
+          _osc.add(param.toString());
+        }
+
+        return true;
+      }
+
+      /// Parse next parameter
+      if (char == Ascii.semicolon) {
+        _osc.add(param.toString());
+        param.clear();
+        continue;
+      }
+
+      param.writeCharCode(char);
+    }
+  }
+}
+
+class _Csi {
+  _Csi({
+    required this.params,
+    required this.finalByte,
+    // required this.intermediates,
+  });
+
+  int? prefix;
+
+  List<int> params;
+
+  int finalByte;
+  // final List<int> intermediates;
+
+  @override
+  String toString() {
+    return params.join(';') + String.fromCharCode(finalByte);
+  }
+}
+
+/// Function that handles a sequence of characters that starts with an escape.
+/// Returns [true] if the sequence was processed, [false] if it was not.
+typedef _EscHandler = bool Function();
+
+typedef _SbcHandler = void Function();
+
+typedef _CsiHandler = void Function();

+ 131 - 0
lib/core/input/handler.dart

@@ -0,0 +1,131 @@
+import 'package:xterm/core/input/keys.dart';
+import 'package:xterm/core/input/keytab/keytab.dart';
+import 'package:xterm/utils/platform.dart';
+import 'package:xterm/core/state.dart';
+
+class TerminalInputEvent {
+  final TerminalKey key;
+
+  final bool shift;
+
+  final bool ctrl;
+
+  final bool alt;
+
+  final TerminalState state;
+
+  final bool altBuffer;
+
+  final TerminalTargetPlatform platform;
+
+  TerminalInputEvent({
+    required this.key,
+    required this.shift,
+    required this.ctrl,
+    required this.alt,
+    required this.state,
+    required this.altBuffer,
+    required this.platform,
+  });
+}
+
+abstract class TerminalInputHandler {
+  String? call(TerminalInputEvent event);
+}
+
+class CascadeInputHandler implements TerminalInputHandler {
+  final List<TerminalInputHandler> _handlers;
+
+  const CascadeInputHandler(this._handlers);
+
+  @override
+  String? call(TerminalInputEvent event) {
+    for (var handler in _handlers) {
+      final result = handler(event);
+      if (result != null) {
+        return result;
+      }
+    }
+    return null;
+  }
+}
+
+const defaultInputHandler = CascadeInputHandler([
+  KeytabInputHandler(),
+  CtrlInputHandler(),
+  AltInputHandler(),
+]);
+
+final _keytab = Keytab.defaultKeytab();
+
+class KeytabInputHandler implements TerminalInputHandler {
+  const KeytabInputHandler();
+
+  @override
+  String? call(TerminalInputEvent event) {
+    final action = _keytab.find(
+      event.key,
+      ctrl: event.ctrl,
+      alt: event.alt,
+      shift: event.shift,
+      newLineMode: event.state.lineFeedMode,
+      appCursorKeys: event.state.appKeypadMode,
+      appKeyPad: event.state.appKeypadMode,
+      appScreen: event.altBuffer,
+      macos: event.platform == TerminalTargetPlatform.macos,
+    );
+
+    if (action == null) {
+      return null;
+    }
+
+    return action.action.unescapedValue();
+  }
+}
+
+class CtrlInputHandler implements TerminalInputHandler {
+  const CtrlInputHandler();
+
+  @override
+  String? call(TerminalInputEvent event) {
+    if (!event.ctrl) {
+      return null;
+    }
+
+    final key = event.key;
+
+    if (key.index >= TerminalKey.keyA.index &&
+        key.index <= TerminalKey.keyZ.index) {
+      final input = key.index - TerminalKey.keyA.index + 1;
+      return String.fromCharCode(input);
+    }
+
+    return null;
+  }
+}
+
+class AltInputHandler implements TerminalInputHandler {
+  const AltInputHandler();
+
+  @override
+  String? call(TerminalInputEvent event) {
+    if (!event.alt) {
+      return null;
+    }
+
+    if (event.platform == TerminalTargetPlatform.macos) {
+      return null;
+    }
+
+    final key = event.key;
+
+    if (key.index >= TerminalKey.keyA.index &&
+        key.index <= TerminalKey.keyZ.index) {
+      final charCode = key.index - TerminalKey.keyA.index + 65;
+      final input = [0x1b, charCode];
+      return String.fromCharCodes(input);
+    }
+
+    return null;
+  }
+}

+ 0 - 0
lib/input/keys.dart → lib/core/input/keys.dart


+ 110 - 0
lib/core/input/keytab/keytab.dart

@@ -0,0 +1,110 @@
+import 'package:xterm/core/input/keys.dart';
+import 'package:xterm/core/input/keytab/keytab_default.dart';
+import 'package:xterm/core/input/keytab/keytab_parse.dart';
+import 'package:xterm/core/input/keytab/keytab_record.dart';
+import 'package:xterm/core/input/keytab/keytab_token.dart';
+
+class Keytab {
+  Keytab({
+    required this.name,
+    required this.records,
+  });
+
+  factory Keytab.parse(String source) {
+    final tokens = tokenize(source).toList();
+    final parser = KeytabParser()..addTokens(tokens);
+    return parser.result;
+  }
+
+  factory Keytab.defaultKeytab() {
+    return Keytab.parse(kDefaultKeytab);
+  }
+
+  final String? name;
+
+  final List<KeytabRecord> records;
+
+  KeytabRecord? find(
+    TerminalKey key, {
+    bool ctrl = false,
+    bool alt = false,
+    bool shift = false,
+    bool newLineMode = false,
+    bool appCursorKeys = false,
+    bool appKeyPad = false,
+    bool appScreen = false,
+    bool macos = false,
+    // bool meta,
+  }) {
+    for (var record in records) {
+      if (record.key != key) {
+        continue;
+      }
+
+      if (record.anyModifier == true) {
+        if (ctrl == false && alt == false && shift == false) {
+          continue;
+        }
+      } else if (record.anyModifier == false) {
+        if (ctrl != false || alt != false || shift != false) {
+          continue;
+        }
+      } else {
+        if (record.ctrl != null && record.ctrl != ctrl) {
+          continue;
+        }
+
+        if (record.shift != null && record.shift != shift) {
+          continue;
+        }
+
+        if (record.alt != null && record.alt != alt) {
+          continue;
+        }
+      }
+
+      if (record.newLine != null && record.newLine != newLineMode) {
+        continue;
+      }
+
+      if (record.appCursorKeys != null &&
+          record.appCursorKeys != appCursorKeys) {
+        continue;
+      }
+
+      if (record.appKeyPad != null && record.appKeyPad != appKeyPad) {
+        continue;
+      }
+
+      if (record.appScreen != null && record.appScreen != appScreen) {
+        continue;
+      }
+
+      if (record.macos != null && record.macos != macos) {
+        continue;
+      }
+
+      // TODO: support VT52
+      if (record.ansi == false) {
+        continue;
+      }
+
+      return record;
+    }
+
+    return null;
+  }
+
+  @override
+  String toString() {
+    final buffer = StringBuffer();
+
+    buffer.writeln('keyboard "$name"');
+
+    for (var record in records) {
+      buffer.writeln(record);
+    }
+
+    return buffer.toString();
+  }
+}

+ 2 - 2
lib/input/keytab/keytab_default.dart → lib/core/input/keytab/keytab_default.dart

@@ -1,5 +1,5 @@
-import 'package:xterm/input/keytab/keytab_parse.dart';
-import 'package:xterm/input/keytab/keytab_token.dart';
+import 'package:xterm/core/input/keytab/keytab_parse.dart';
+import 'package:xterm/core/input/keytab/keytab_token.dart';
 
 
 const kDefaultKeytab = r'''
 const kDefaultKeytab = r'''
 # [README.default.Keytab] Default Keyboard Table
 # [README.default.Keytab] Default Keyboard Table

+ 1 - 1
lib/input/keytab/keytab_escape.dart → lib/core/input/keytab/keytab_escape.dart

@@ -4,7 +4,7 @@ String keytabUnescape(String str) {
   str = str
   str = str
       .replaceAll(r'\E', _esc)
       .replaceAll(r'\E', _esc)
       .replaceAll(r'\\', '\\')
       .replaceAll(r'\\', '\\')
-      .replaceAll(r'\"', '\"')
+      .replaceAll(r'\"', '"')
       .replaceAll(r'\t', '\t')
       .replaceAll(r'\t', '\t')
       .replaceAll(r'\r', '\r')
       .replaceAll(r'\r', '\r')
       .replaceAll(r'\n', '\n')
       .replaceAll(r'\n', '\n')

+ 5 - 5
lib/input/keytab/keytab_parse.dart → lib/core/input/keytab/keytab_parse.dart

@@ -1,7 +1,7 @@
-import 'package:xterm/input/keytab/keytab.dart';
-import 'package:xterm/input/keytab/keytab_record.dart';
-import 'package:xterm/input/keytab/keytab_token.dart';
-import 'package:xterm/input/keytab/qt_keyname.dart';
+import 'package:xterm/core/input/keytab/keytab.dart';
+import 'package:xterm/core/input/keytab/keytab_record.dart';
+import 'package:xterm/core/input/keytab/keytab_token.dart';
+import 'package:xterm/core/input/keytab/qt_keyname.dart';
 
 
 class ParseError {}
 class ParseError {}
 
 
@@ -178,7 +178,7 @@ class KeytabParser {
       appCursorKeys: appCursorKeys,
       appCursorKeys: appCursorKeys,
       appKeyPad: appKeyPad,
       appKeyPad: appKeyPad,
       newLine: newLine,
       newLine: newLine,
-      mac: mac,
+      macos: mac,
     );
     );
 
 
     _records.add(record);
     _records.add(record);

+ 34 - 26
lib/input/keytab/keytab_record.dart → lib/core/input/keytab/keytab_record.dart

@@ -1,4 +1,5 @@
-import 'package:xterm/input/keys.dart';
+import 'package:xterm/core/input/keys.dart';
+import 'package:xterm/core/input/keytab/keytab_escape.dart';
 
 
 enum KeytabActionType {
 enum KeytabActionType {
   input,
   input,
@@ -9,8 +10,17 @@ class KeytabAction {
   KeytabAction(this.type, this.value);
   KeytabAction(this.type, this.value);
 
 
   final KeytabActionType type;
   final KeytabActionType type;
+
   final String value;
   final String value;
 
 
+  String unescapedValue() {
+    if (type == KeytabActionType.input) {
+      return keytabUnescape(value);
+    } else {
+      return value;
+    }
+  }
+
   @override
   @override
   String toString() {
   String toString() {
     switch (type) {
     switch (type) {
@@ -18,8 +28,6 @@ class KeytabAction {
         return '"$value"';
         return '"$value"';
       case KeytabActionType.shortcut:
       case KeytabActionType.shortcut:
         return value;
         return value;
-      default:
-        return '(no value)';
     }
     }
   }
   }
 }
 }
@@ -39,7 +47,7 @@ class KeytabRecord {
     required this.appCursorKeys,
     required this.appCursorKeys,
     required this.appKeyPad,
     required this.appKeyPad,
     required this.newLine,
     required this.newLine,
-    required this.mac,
+    required this.macos,
   });
   });
 
 
   String qtKeyName;
   String qtKeyName;
@@ -56,7 +64,7 @@ class KeytabRecord {
   bool? appCursorKeys;
   bool? appCursorKeys;
   bool? appKeyPad;
   bool? appKeyPad;
   bool? newLine;
   bool? newLine;
-  bool? mac;
+  bool? macos;
 
 
   @override
   @override
   String toString() {
   String toString() {
@@ -64,63 +72,63 @@ class KeytabRecord {
     buffer.write('$qtKeyName ');
     buffer.write('$qtKeyName ');
 
 
     if (alt != null) {
     if (alt != null) {
-      buffer.write(modeStatus(alt!, 'Alt'));
+      buffer.write(_toMode(alt!, 'Alt'));
     }
     }
 
 
     if (ctrl != null) {
     if (ctrl != null) {
-      buffer.write(modeStatus(ctrl!, 'Control'));
+      buffer.write(_toMode(ctrl!, 'Control'));
     }
     }
 
 
     if (shift != null) {
     if (shift != null) {
-      buffer.write(modeStatus(shift!, 'Shift'));
+      buffer.write(_toMode(shift!, 'Shift'));
     }
     }
 
 
     if (anyModifier != null) {
     if (anyModifier != null) {
-      buffer.write(modeStatus(anyModifier!, 'AnyMod'));
+      buffer.write(_toMode(anyModifier!, 'AnyMod'));
     }
     }
 
 
     if (ansi != null) {
     if (ansi != null) {
-      buffer.write(modeStatus(ansi!, 'Ansi'));
+      buffer.write(_toMode(ansi!, 'Ansi'));
     }
     }
 
 
     if (appScreen != null) {
     if (appScreen != null) {
-      buffer.write(modeStatus(appScreen!, 'AppScreen'));
+      buffer.write(_toMode(appScreen!, 'AppScreen'));
     }
     }
 
 
     if (keyPad != null) {
     if (keyPad != null) {
-      buffer.write(modeStatus(keyPad!, 'KeyPad'));
+      buffer.write(_toMode(keyPad!, 'KeyPad'));
     }
     }
 
 
     if (appCursorKeys != null) {
     if (appCursorKeys != null) {
-      buffer.write(modeStatus(appCursorKeys!, 'AppCuKeys'));
+      buffer.write(_toMode(appCursorKeys!, 'AppCuKeys'));
     }
     }
 
 
     if (appKeyPad != null) {
     if (appKeyPad != null) {
-      buffer.write(modeStatus(appKeyPad!, 'AppKeyPad'));
+      buffer.write(_toMode(appKeyPad!, 'AppKeyPad'));
     }
     }
 
 
     if (newLine != null) {
     if (newLine != null) {
-      buffer.write(modeStatus(newLine!, 'NewLine'));
+      buffer.write(_toMode(newLine!, 'NewLine'));
     }
     }
 
 
-    if (mac != null) {
-      buffer.write(modeStatus(mac!, 'Mac'));
+    if (macos != null) {
+      buffer.write(_toMode(macos!, 'Mac'));
     }
     }
 
 
     buffer.write(' : $action');
     buffer.write(' : $action');
 
 
     return buffer.toString();
     return buffer.toString();
   }
   }
-}
 
 
-String modeStatus(bool status, String mode) {
-  if (status == true) {
-    return '+$mode';
-  }
+  static String _toMode(bool status, String mode) {
+    if (status == true) {
+      return '+$mode';
+    }
 
 
-  if (status == false) {
-    return '-$mode';
-  }
+    if (status == false) {
+      return '-$mode';
+    }
 
 
-  return '';
+    return '';
+  }
 }
 }

+ 0 - 0
lib/input/keytab/keytab_token.dart → lib/core/input/keytab/keytab_token.dart


+ 1 - 1
lib/input/keytab/qt_keyname.dart → lib/core/input/keytab/qt_keyname.dart

@@ -1,4 +1,4 @@
-import 'package:xterm/input/keys.dart';
+import 'package:xterm/core/input/keys.dart';
 
 
 /// See: https://doc.qt.io/qt-5/qt.html#Key-enum
 /// See: https://doc.qt.io/qt-5/qt.html#Key-enum
 const qtKeynameMap = <String, TerminalKey>{
 const qtKeynameMap = <String, TerminalKey>{

+ 23 - 0
lib/core/mouse.dart

@@ -0,0 +1,23 @@
+/// https://terminalguide.namepad.de/mouse/
+enum MouseMode {
+  none,
+
+  clickOnly,
+
+  upDownScroll,
+
+  upDownScrollDrag,
+
+  upDownScrollMove,
+}
+
+/// https://terminalguide.namepad.de/mouse/
+enum MouseReportMode {
+  normal,
+
+  utf,
+
+  sgr,
+
+  urxvt,
+}

+ 157 - 0
lib/core/reflow.dart

@@ -0,0 +1,157 @@
+import 'package:xterm/core/buffer/line.dart';
+import 'package:xterm/utils/circular_list.dart';
+
+class _LineBuilder {
+  _LineBuilder([this._capacity = 80]) {
+    _result = BufferLine(_capacity);
+  }
+
+  final int _capacity;
+
+  late BufferLine _result;
+
+  int _length = 0;
+
+  int get length => _length;
+
+  bool get isEmpty => _length == 0;
+
+  bool get isNotEmpty => _length != 0;
+
+  void add(BufferLine src, int start, int length) {
+    _result.copyFrom(src, start, _length, length);
+    _length += length;
+  }
+
+  void setBuffer(BufferLine line, int length) {
+    _result = line;
+    _length = length;
+  }
+
+  BufferLine take({required bool wrapped}) {
+    final result = _result;
+    result.isWrapped = wrapped;
+    result.resize(_length);
+
+    _result = BufferLine(_capacity);
+    _length = 0;
+
+    return result;
+  }
+}
+
+class _LineReflow {
+  final int oldWidth;
+
+  final int newWidth;
+
+  _LineReflow(this.oldWidth, this.newWidth);
+
+  final _lines = <BufferLine>[];
+
+  late final _builder = _LineBuilder(newWidth);
+
+  void add(BufferLine line) {
+    final length = line.getTrimmedLength(oldWidth);
+
+    if (length == 0) {
+      _lines.add(line);
+      return;
+    }
+
+    if (_lines.isNotEmpty || _builder.isNotEmpty) {
+      _addRange(line, 0, length);
+      return;
+    }
+
+    if (newWidth >= oldWidth) {
+      _builder.setBuffer(line, length);
+    } else {
+      _lines.add(line);
+
+      if (line.getWidth(newWidth - 1) == 2) {
+        _addRange(line, newWidth - 1, length);
+      } else {
+        _addRange(line, newWidth, length);
+      }
+    }
+
+    line.resize(newWidth);
+
+    if (line.getWidth(newWidth - 1) == 2) {
+      line.resetCell(newWidth - 1);
+    }
+  }
+
+  void _addRange(BufferLine line, int start, int end) {
+    var cellsLeft = end - start;
+
+    while (cellsLeft > 0) {
+      final spaceLeft = newWidth - _builder.length;
+
+      var lineFilled = false;
+
+      var cellsToCopy = cellsLeft;
+
+      if (cellsToCopy >= spaceLeft) {
+        cellsToCopy = spaceLeft;
+        lineFilled = true;
+      }
+
+      // Avoid breaking wide characters
+      if (cellsToCopy == spaceLeft &&
+          line.getWidth(start + cellsToCopy - 1) == 2) {
+        cellsToCopy--;
+      }
+
+      _builder.add(line, start, cellsToCopy);
+
+      start += cellsToCopy;
+      cellsLeft -= cellsToCopy;
+
+      if (lineFilled) {
+        _lines.add(_builder.take(wrapped: _lines.isNotEmpty));
+      }
+    }
+  }
+
+  List<BufferLine> finish() {
+    if (_builder.isNotEmpty) {
+      _lines.add(_builder.take(wrapped: _lines.isNotEmpty));
+    }
+
+    return _lines;
+  }
+}
+
+List<BufferLine> reflow(
+  CircularList<BufferLine> lines,
+  int oldWidth,
+  int newWidth,
+) {
+  final result = <BufferLine>[];
+
+  for (var i = 0; i < lines.length; i++) {
+    final line = lines[i];
+
+    final reflow = _LineReflow(oldWidth, newWidth);
+
+    reflow.add(line);
+
+    for (var offset = i + 1; offset < lines.length; offset++) {
+      final nextLine = lines[offset];
+
+      if (!nextLine.isWrapped) {
+        break;
+      }
+
+      i++;
+
+      reflow.add(nextLine);
+    }
+
+    result.addAll(reflow.finish());
+  }
+
+  return result;
+}

+ 3 - 0
lib/core/snapshot.dart

@@ -0,0 +1,3 @@
+abstract class TerminalSnapshot {
+  void trimScrollback();
+}

+ 40 - 0
lib/core/state.dart

@@ -0,0 +1,40 @@
+import 'package:xterm/core/cursor.dart';
+import 'package:xterm/core/mouse.dart';
+
+abstract class TerminalState {
+  int get viewWidth;
+
+  int get viewHeight;
+
+  CursorStyle get cursor;
+
+  /* Modes */
+
+  bool get insertMode;
+
+  bool get lineFeedMode;
+
+  /* DEC Private modes */
+
+  bool get cursorKeysMode;
+
+  bool get reverseDisplayMode;
+
+  bool get originMode;
+
+  bool get autoWrapMode;
+
+  MouseMode get mouseMode;
+
+  bool get cursorBlinkMode;
+
+  bool get cursorVisibleMode;
+
+  bool get appKeypadMode;
+
+  bool get reportFocusMode;
+
+  bool get altBufferMouseScrollMode;
+
+  bool get bracketedPasteMode;
+}

+ 46 - 0
lib/core/tabs.dart

@@ -0,0 +1,46 @@
+import 'dart:math' show min;
+
+const _kMaxColumns = 1024;
+
+class TabStops {
+  final _stops = List<bool>.filled(_kMaxColumns, false);
+
+  int? find(int start, int end) {
+    if (start >= end) {
+      return null;
+    }
+    end = min(end, _stops.length);
+    for (var i = start; i < end; i++) {
+      if (_stops[i]) {
+        return i;
+      }
+    }
+    return null;
+  }
+
+  void setAt(int index) {
+    assert(index >= 0 && index < _kMaxColumns);
+    _stops[index] = true;
+  }
+
+  void clearAt(int index) {
+    assert(index >= 0 && index < _kMaxColumns);
+    _stops[index] = false;
+  }
+
+  void clearAll() {
+    _stops.fillRange(0, _kMaxColumns, false);
+  }
+
+  bool isSetAt(int index) {
+    return _stops[index];
+  }
+
+  void reset() {
+    clearAll();
+    const interval = 8;
+    for (var i = 0; i < _kMaxColumns; i += interval) {
+      _stops[i] = true;
+    }
+  }
+}

+ 783 - 0
lib/core/terminal.dart

@@ -0,0 +1,783 @@
+import 'dart:math' show max;
+
+import 'package:xterm/core/input/handler.dart';
+import 'package:xterm/core/input/keys.dart';
+import 'package:xterm/core/buffer/buffer.dart';
+import 'package:xterm/core/cursor.dart';
+import 'package:xterm/core/escape/emitter.dart';
+import 'package:xterm/core/escape/handler.dart';
+import 'package:xterm/core/escape/parser.dart';
+import 'package:xterm/core/buffer/line.dart';
+import 'package:xterm/core/mouse.dart';
+import 'package:xterm/utils/platform.dart';
+import 'package:xterm/core/state.dart';
+import 'package:xterm/core/tabs.dart';
+import 'package:xterm/utils/ascii.dart';
+import 'package:xterm/utils/circular_list.dart';
+import 'package:xterm/utils/observable.dart';
+
+class Terminal with Observable implements TerminalState, EscapeHandler {
+  final int maxLines;
+
+  void Function()? onBell;
+
+  void Function(String)? onTitleChange;
+
+  void Function(String)? onIconChange;
+
+  void Function(String)? onOutput;
+
+  void Function(int width, int height, int pixelWidth, int pixelHeight)?
+      onResize;
+
+  /// Flag to toggle os specific behaviors.
+  final TerminalTargetPlatform platform;
+
+  final TerminalInputHandler inputHandler;
+
+  Terminal({
+    this.maxLines = 1000,
+    this.onBell,
+    this.onTitleChange,
+    this.onIconChange,
+    this.onOutput,
+    this.onResize,
+    this.platform = TerminalTargetPlatform.unknown,
+    this.inputHandler = defaultInputHandler,
+  });
+
+  late final _parser = EscapeParser(this);
+
+  final _emitter = const EscapeEmitter();
+
+  late var _buffer = _mainBuffer;
+
+  late final _mainBuffer = Buffer(this, maxLines: maxLines, isAltBuffer: false);
+
+  late final _altBuffer = Buffer(this, maxLines: maxLines, isAltBuffer: true);
+
+  final tabStops = TabStops();
+
+  var _precedingCodepoint = 0;
+
+  /* TerminalState */
+
+  int _viewWidth = 80;
+
+  int _viewHeight = 24;
+
+  final _cursorStyle = CursorStyle();
+
+  bool _insertMode = false;
+
+  bool _lineFeedMode = false;
+
+  bool _cursorKeysMode = false;
+
+  bool _reverseDisplayMode = false;
+
+  bool _originMode = false;
+
+  bool _autoWrapMode = true;
+
+  MouseMode _mouseMode = MouseMode.none;
+
+  MouseReportMode _mouseReportMode = MouseReportMode.normal;
+
+  bool _cursorBlinkMode = false;
+
+  bool _cursorVisibleMode = true;
+
+  bool _appKeypadMode = false;
+
+  bool _reportFocusMode = false;
+
+  bool _altBufferMouseScrollMode = false;
+
+  bool _bracketedPasteMode = false;
+
+  /* State getters */
+
+  @override
+  int get viewWidth => _viewWidth;
+
+  @override
+  int get viewHeight => _viewHeight;
+
+  @override
+  CursorStyle get cursor => _cursorStyle;
+
+  @override
+  bool get insertMode => _insertMode;
+
+  @override
+  bool get lineFeedMode => _lineFeedMode;
+
+  @override
+  bool get cursorKeysMode => _cursorKeysMode;
+
+  @override
+  bool get reverseDisplayMode => _reverseDisplayMode;
+
+  @override
+  bool get originMode => _originMode;
+
+  @override
+  bool get autoWrapMode => _autoWrapMode;
+
+  @override
+  MouseMode get mouseMode => _mouseMode;
+
+  MouseReportMode get mouseReportMode => _mouseReportMode;
+
+  @override
+  bool get cursorBlinkMode => _cursorBlinkMode;
+
+  @override
+  bool get cursorVisibleMode => _cursorVisibleMode;
+
+  @override
+  bool get appKeypadMode => _appKeypadMode;
+
+  @override
+  bool get reportFocusMode => _reportFocusMode;
+
+  @override
+  bool get altBufferMouseScrollMode => _altBufferMouseScrollMode;
+
+  @override
+  bool get bracketedPasteMode => _bracketedPasteMode;
+
+  Buffer get buffer => _buffer;
+
+  Buffer get mainBuffer => _mainBuffer;
+
+  Buffer get altBuffer => _altBuffer;
+
+  bool get isUsingAltBuffer => _buffer == _altBuffer;
+
+  CircularList<BufferLine> get lines => _buffer.lines;
+
+  void write(String data) {
+    _parser.write(data);
+    notifyListeners();
+  }
+
+  bool keyInput(
+    TerminalKey key, {
+    bool shift = false,
+    bool alt = false,
+    bool ctrl = false,
+  }) {
+    final output = inputHandler(
+      TerminalInputEvent(
+        key: key,
+        shift: shift,
+        alt: alt,
+        ctrl: ctrl,
+        state: this,
+        altBuffer: isUsingAltBuffer,
+        platform: platform,
+      ),
+    );
+
+    if (output != null) {
+      onOutput?.call(output);
+      return true;
+    }
+
+    return false;
+  }
+
+  bool charInput(
+    int charCode, {
+    bool alt = false,
+    bool ctrl = false,
+  }) {
+    if (ctrl) {
+      // a(97) ~ z(122)
+      if (charCode >= Ascii.a && charCode <= Ascii.z) {
+        final output = charCode - Ascii.a + 1;
+        onOutput?.call(String.fromCharCode(output));
+        return true;
+      }
+
+      // [(91) ~ _(95)
+      if (charCode >= Ascii.openBracket && charCode <= Ascii.underscore) {
+        final output = charCode - Ascii.openBracket + 27;
+        onOutput?.call(String.fromCharCode(output));
+        return true;
+      }
+    }
+
+    if (alt && platform != TerminalTargetPlatform.macos) {
+      if (charCode >= Ascii.a && charCode <= Ascii.z) {
+        final code = charCode - Ascii.a + 65;
+        final input = [0x1b, code];
+        onOutput?.call(String.fromCharCodes(input));
+        return true;
+      }
+    }
+
+    return false;
+  }
+
+  void textInput(String text) {
+    onOutput?.call(text);
+  }
+
+  void paste(String text) {
+    if (_bracketedPasteMode) {
+      onOutput?.call(_emitter.bracketedPaste(text));
+    } else {
+      textInput(text);
+    }
+  }
+
+  /// Resize the terminal screen. [newWidth] and [newHeight] should be greater
+  /// than 0. Text reflow is currently not implemented and will be avaliable in
+  /// the future.
+  void resize(
+    int newWidth,
+    int newHeight, [
+    int? pixelWidth,
+    int? pixelHeight,
+  ]) {
+    newWidth = max(newWidth, 1);
+    newHeight = max(newHeight, 1);
+
+    onResize?.call(newWidth, newHeight, pixelWidth ?? 0, pixelHeight ?? 0);
+
+    //we need to resize both buffers so that they are ready when we switch between them
+    _altBuffer.resize(_viewWidth, _viewHeight, newWidth, newHeight);
+    _mainBuffer.resize(_viewWidth, _viewHeight, newWidth, newHeight);
+
+    _viewWidth = newWidth;
+    _viewHeight = newHeight;
+
+    if (buffer == _altBuffer) {
+      buffer.clearScrollback();
+    }
+
+    _altBuffer.resetVerticalMargins();
+    _mainBuffer.resetVerticalMargins();
+  }
+
+  /* Handlers */
+
+  @override
+  void writeChar(int char) {
+    _precedingCodepoint = char;
+    _buffer.writeChar(char);
+  }
+
+  /* SBC */
+
+  @override
+  void bell() {
+    onBell?.call();
+  }
+
+  @override
+  void backspaceReturn() {
+    _buffer.moveCursorX(-1);
+  }
+
+  @override
+  void tab() {
+    final nextStop = tabStops.find(_buffer.cursorX, _viewWidth);
+
+    if (nextStop != null) {
+      _buffer.setCursorX(nextStop);
+    } else {
+      _buffer.setCursorX(_viewWidth);
+      _buffer.cursorGoForward(); // Enter pending-wrap state
+    }
+  }
+
+  @override
+  void lineFeed() {
+    _buffer.lineFeed();
+  }
+
+  @override
+  void carriageReturn() {
+    _buffer.setCursorX(0);
+  }
+
+  @override
+  void shiftOut() {
+    _buffer.charset.use(1);
+  }
+
+  @override
+  void shiftIn() {
+    _buffer.charset.use(0);
+  }
+
+  @override
+  void unknownSBC(int char) {
+    // no-op
+  }
+
+  /* ANSI sequence */
+
+  @override
+  void saveCursor() {
+    _buffer.saveCursor();
+  }
+
+  @override
+  void restoreCursor() {
+    _buffer.restoreCursor();
+  }
+
+  @override
+  void index() {
+    _buffer.index();
+  }
+
+  @override
+  void nextLine() {
+    _buffer.index();
+    _buffer.setCursorX(0);
+  }
+
+  @override
+  void setTapStop() {
+    tabStops.isSetAt(_buffer.cursorX);
+  }
+
+  @override
+  void reverseIndex() {
+    _buffer.reverseIndex();
+  }
+
+  @override
+  void designateCharset(int charset) {
+    _buffer.charset.use(charset);
+  }
+
+  @override
+  void unkownEscape(int char) {
+    // no-op
+  }
+
+  /* CSI */
+
+  @override
+  void repeatPreviousCharacter(int count) {
+    if (_precedingCodepoint == 0) {
+      return;
+    }
+
+    for (var i = 0; i < count; i++) {
+      _buffer.writeChar(_precedingCodepoint);
+    }
+  }
+
+  @override
+  void setCursor(int x, int y) {
+    _buffer.setCursor(x, y);
+  }
+
+  @override
+  void setCursorX(int x) {
+    _buffer.setCursorX(x);
+  }
+
+  @override
+  void setCursorY(int y) {
+    _buffer.setCursorY(y);
+  }
+
+  @override
+  void moveCursorX(int offset) {
+    _buffer.moveCursorX(offset);
+  }
+
+  @override
+  void moveCursorY(int n) {
+    _buffer.moveCursorY(n);
+  }
+
+  @override
+  void clearTabStopUnderCursor() {
+    tabStops.clearAt(_buffer.cursorX);
+  }
+
+  @override
+  void clearAllTabStops() {
+    tabStops.clearAll();
+  }
+
+  @override
+  void sendPrimaryDeviceAttributes() {
+    onOutput?.call(_emitter.primaryDeviceAttributes());
+  }
+
+  @override
+  void sendSecondaryDeviceAttributes() {
+    onOutput?.call(_emitter.secondaryDeviceAttributes());
+  }
+
+  @override
+  void sendTertiaryDeviceAttributes() {
+    onOutput?.call(_emitter.tertiaryDeviceAttributes());
+  }
+
+  @override
+  void sendOperatingStatus() {
+    onOutput?.call(_emitter.operatingStatus());
+  }
+
+  @override
+  void sendCursorPosition() {
+    onOutput?.call(_emitter.cursorPosition(_buffer.cursorX, _buffer.cursorY));
+  }
+
+  @override
+  void setMargins(int top, [int? bottom]) {
+    _buffer.setVerticalMargins(top, bottom ?? viewHeight - 1);
+  }
+
+  @override
+  void cursorNextLine(int amount) {
+    _buffer.moveCursorY(amount);
+    _buffer.setCursorX(0);
+  }
+
+  @override
+  void cursorPrecedingLine(int amount) {
+    _buffer.moveCursorY(-amount);
+    _buffer.setCursorX(0);
+  }
+
+  @override
+  void eraseDisplayBelow() {
+    _buffer.eraseDisplayFromCursor();
+  }
+
+  @override
+  void eraseDisplayAbove() {
+    _buffer.eraseDisplayToCursor();
+  }
+
+  @override
+  void eraseDisplay() {
+    _buffer.eraseDisplay();
+  }
+
+  @override
+  void eraseScrollbackOnly() {
+    _buffer.clearScrollback();
+  }
+
+  @override
+  void eraseLineRight() {
+    _buffer.eraseLineFromCursor();
+  }
+
+  @override
+  void eraseLineLeft() {
+    _buffer.eraseLineToCursor();
+  }
+
+  @override
+  void eraseLine() {
+    _buffer.eraseLine();
+  }
+
+  @override
+  void insertLines(int amount) {
+    _buffer.insertLines(amount);
+  }
+
+  @override
+  void deleteLines(int amount) {
+    _buffer.deleteLines(amount);
+  }
+
+  @override
+  void deleteChars(int amount) {
+    _buffer.deleteChars(amount);
+  }
+
+  @override
+  void scrollUp(int amount) {
+    _buffer.scrollUp(amount);
+  }
+
+  @override
+  void scrollDown(int amount) {
+    _buffer.scrollDown(amount);
+  }
+
+  @override
+  void eraseChars(int amount) {
+    _buffer.eraseChars(amount);
+  }
+
+  @override
+  void insertBlankChars(int amount) {
+    _buffer.insertBlankChars(amount);
+  }
+
+  @override
+  void unknownCSI(int finalByte) {
+    // no-op
+  }
+
+  /* Modes */
+
+  @override
+  void setInsertMode(bool enabled) {
+    _insertMode = enabled;
+  }
+
+  @override
+  void setLineFeedMode(bool enabled) {
+    _lineFeedMode = enabled;
+  }
+
+  @override
+  void setUnknownMode(int mode, bool enabled) {
+    // no-op
+  }
+
+  /* DEC Private modes */
+
+  @override
+  void setCursorKeysMode(bool enabled) {
+    _cursorKeysMode = enabled;
+  }
+
+  @override
+  void setReverseDisplayMode(bool enabled) {
+    _reverseDisplayMode = enabled;
+  }
+
+  @override
+  void setOriginMode(bool enabled) {
+    _originMode = enabled;
+  }
+
+  @override
+  void setColumnMode(bool enabled) {
+    // no-op
+  }
+
+  @override
+  void setAutoWrapMode(bool enabled) {
+    _autoWrapMode = enabled;
+  }
+
+  @override
+  void setMouseMode(MouseMode mode) {
+    _mouseMode = mode;
+  }
+
+  @override
+  void setCursorBlinkMode(bool enabled) {
+    _cursorBlinkMode = enabled;
+  }
+
+  @override
+  void setCursorVisibleMode(bool enabled) {
+    _cursorVisibleMode = enabled;
+  }
+
+  @override
+  void useAltBuffer() {
+    _buffer = _altBuffer;
+  }
+
+  @override
+  void useMainBuffer() {
+    _buffer = _mainBuffer;
+  }
+
+  @override
+  void clearAltBuffer() {
+    _altBuffer.clear();
+  }
+
+  @override
+  void setAppKeypadMode(bool enabled) {
+    _appKeypadMode = enabled;
+  }
+
+  @override
+  void setReportFocusMode(bool enabled) {
+    _reportFocusMode = enabled;
+  }
+
+  @override
+  void setMouseReportMode(MouseReportMode mode) {
+    _mouseReportMode = mode;
+  }
+
+  @override
+  void setAltBufferMouseScrollMode(bool enabled) {
+    _altBufferMouseScrollMode = enabled;
+  }
+
+  @override
+  void setBracketedPasteMode(bool enabled) {
+    _bracketedPasteMode = enabled;
+  }
+
+  @override
+  void setUnknownDecMode(int mode, bool enabled) {
+    // no-op
+  }
+
+  /* Select Graphic Rendition (SGR) */
+
+  @override
+  void resetCursorStyle() {
+    _cursorStyle.reset();
+  }
+
+  @override
+  void setCursorBold() {
+    _cursorStyle.setBold();
+  }
+
+  @override
+  void setCursorFaint() {
+    _cursorStyle.setFaint();
+  }
+
+  @override
+  void setCursorItalic() {
+    _cursorStyle.setItalic();
+  }
+
+  @override
+  void setCursorUnderline() {
+    _cursorStyle.setUnderline();
+  }
+
+  @override
+  void setCursorBlink() {
+    _cursorStyle.setBlink();
+  }
+
+  @override
+  void setCursorInverse() {
+    _cursorStyle.setInverse();
+  }
+
+  @override
+  void setCursorInvisible() {
+    _cursorStyle.setInvisible();
+  }
+
+  @override
+  void setCursorStrikethrough() {
+    _cursorStyle.setStrikethrough();
+  }
+
+  @override
+  void unsetCursorBold() {
+    _cursorStyle.unsetBold();
+  }
+
+  @override
+  void unsetCursorFaint() {
+    _cursorStyle.unsetFaint();
+  }
+
+  @override
+  void unsetCursorItalic() {
+    _cursorStyle.unsetItalic();
+  }
+
+  @override
+  void unsetCursorUnderline() {
+    _cursorStyle.unsetUnderline();
+  }
+
+  @override
+  void unsetCursorBlink() {
+    _cursorStyle.unsetBlink();
+  }
+
+  @override
+  void unsetCursorInverse() {
+    _cursorStyle.unsetInverse();
+  }
+
+  @override
+  void unsetCursorInvisible() {
+    _cursorStyle.unsetInvisible();
+  }
+
+  @override
+  void unsetCursorStrikethrough() {
+    _cursorStyle.unsetStrikethrough();
+  }
+
+  @override
+  void setForegroundColor16(int color) {
+    _cursorStyle.setForegroundColor16(color);
+  }
+
+  @override
+  void setForegroundColor256(int index) {
+    _cursorStyle.setForegroundColor256(index);
+  }
+
+  @override
+  void setForegroundColorRgb(int r, int g, int b) {
+    _cursorStyle.setForegroundColorRgb(r, g, b);
+  }
+
+  @override
+  void resetForeground() {
+    _cursorStyle.resetForegroundColor();
+  }
+
+  @override
+  void setBackgroundColor16(int color) {
+    _cursorStyle.setBackgroundColor16(color);
+  }
+
+  @override
+  void setBackgroundColor256(int index) {
+    _cursorStyle.setBackgroundColor256(index);
+  }
+
+  @override
+  void setBackgroundColorRgb(int r, int g, int b) {
+    _cursorStyle.setBackgroundColorRgb(r, g, b);
+  }
+
+  @override
+  void resetBackground() {
+    _cursorStyle.resetBackgroundColor();
+  }
+
+  @override
+  void unsupportedStyle(int param) {
+    // no-op
+  }
+
+  /* OSC */
+
+  @override
+  void setTitle(String name) {
+    onTitleChange?.call(name);
+  }
+
+  @override
+  void setIconName(String name) {
+    onIconChange?.call(name);
+  }
+
+  @override
+  void unknownOSC(String ps) {
+    // no-op
+  }
+}

+ 0 - 1
lib/flutter.dart

@@ -1 +0,0 @@
-export 'frontend/terminal_view.dart';

+ 0 - 30
lib/frontend/char_size.dart

@@ -1,30 +0,0 @@
-class CellSize {
-  CellSize({
-    required this.charWidth,
-    required this.charHeight,
-    required this.letterSpacing,
-    required this.lineSpacing,
-    required this.cellWidth,
-    required this.cellHeight,
-  });
-
-  final double charWidth;
-  final double charHeight;
-  final double cellWidth;
-  final double cellHeight;
-  final double letterSpacing;
-  final double lineSpacing;
-
-  @override
-  String toString() {
-    final data = {
-      'charWidth': charWidth,
-      'charHeight': charHeight,
-      'letterSpacing': letterSpacing,
-      'lineSpacing': lineSpacing,
-      'cellWidth': cellWidth,
-      'cellHeight': cellHeight,
-    };
-    return 'CellSize$data';
-  }
-}

+ 0 - 23
lib/frontend/helpers.dart

@@ -1,23 +0,0 @@
-import 'package:flutter/widgets.dart';
-
-Size textSize(Text text) {
-  var span = text.textSpan ?? TextSpan(text: text.data, style: text.style);
-
-  var tp = TextPainter(
-    text: span,
-    textAlign: text.textAlign ?? TextAlign.start,
-    textDirection: text.textDirection ?? TextDirection.ltr,
-    textScaleFactor: text.textScaleFactor ?? 1,
-    maxLines: text.maxLines,
-    locale: text.locale,
-    strutStyle: text.strutStyle,
-  );
-
-  tp.layout();
-
-  return Size(tp.width, tp.height);
-}
-
-bool isMonospace(List<String> fontFamily) {
-  return true; // TBD
-}

+ 0 - 17
lib/frontend/input_behavior.dart

@@ -1,17 +0,0 @@
-import 'package:flutter/services.dart';
-import 'package:xterm/xterm.dart';
-
-abstract class InputBehavior {
-  const InputBehavior();
-
-  bool get acceptKeyStroke;
-
-  TextEditingValue get initEditingState;
-
-  void onKeyStroke(RawKeyEvent event, TerminalUiInteraction terminal);
-
-  TextEditingValue? onTextEdit(
-      TextEditingValue value, TerminalUiInteraction terminal);
-
-  void onAction(TextInputAction action, TerminalUiInteraction terminal);
-}

+ 0 - 84
lib/frontend/input_behavior_default.dart

@@ -1,84 +0,0 @@
-import 'package:flutter/services.dart';
-import 'package:xterm/frontend/input_behavior.dart';
-import 'package:xterm/frontend/input_map.dart';
-import 'package:xterm/xterm.dart';
-
-class InputBehaviorDefault extends InputBehavior {
-  InputBehaviorDefault();
-
-  @override
-  bool get acceptKeyStroke => true;
-
-  @override
-  TextEditingValue get initEditingState => TextEditingValue.empty;
-
-  @override
-  void onKeyStroke(RawKeyEvent event, TerminalUiInteraction terminal) {
-    if (event is! RawKeyDownEvent) {
-      return;
-    }
-
-    final key = inputMap(event.logicalKey);
-
-    if (key != null) {
-      terminal.keyInput(
-        key,
-        ctrl: event.isControlPressed,
-        alt: event.isAltPressed,
-        shift: event.isShiftPressed,
-        mac: terminal.platform.useMacInputBehavior,
-        character: event.character,
-      );
-    }
-  }
-
-  String? _composingString;
-
-  TextEditingValue? _lastEditingState;
-
-  @override
-  TextEditingValue? onTextEdit(
-      TextEditingValue value, TerminalUiInteraction terminal) {
-    var inputText = value.text;
-    // we just want to detect if a composing is going on and notify the terminal
-    // about it
-    if (value.composing.start != value.composing.end) {
-      _composingString = inputText;
-      terminal.updateComposingString(_composingString!);
-      _lastEditingState = value;
-      return null;
-    }
-    //when we reach this point the composing state is over
-    if (_composingString != null) {
-      _composingString = null;
-      terminal.updateComposingString('');
-    }
-
-    //this is a hack to bypass some race condition in the input system
-    //we just take the last rune if there are more than one as it sometimes
-    //happens that the last value is still part of the new value
-
-    if (_lastEditingState?.text.isNotEmpty == true) {
-      if (inputText.length > _lastEditingState!.text.length) {
-        inputText = inputText.substring(_lastEditingState!.text.length);
-      }
-    }
-
-    if (inputText.isNotEmpty) {
-      terminal.raiseOnInput(inputText);
-    }
-
-    _lastEditingState = value;
-
-    if (value == TextEditingValue.empty || inputText == '') {
-      return null;
-    } else {
-      return TextEditingValue.empty;
-    }
-  }
-
-  @override
-  void onAction(TextInputAction action, TerminalUiInteraction terminal) {
-    //
-  }
-}

+ 0 - 5
lib/frontend/input_behavior_desktop.dart

@@ -1,5 +0,0 @@
-import 'package:xterm/frontend/input_behavior_default.dart';
-
-class InputBehaviorDesktop extends InputBehaviorDefault {
-  InputBehaviorDesktop();
-}

+ 0 - 43
lib/frontend/input_behavior_mobile.dart

@@ -1,43 +0,0 @@
-import 'package:flutter/services.dart';
-import 'package:xterm/frontend/input_behavior_default.dart';
-import 'package:xterm/input/keys.dart';
-import 'package:xterm/xterm.dart';
-
-class InputBehaviorMobile extends InputBehaviorDefault {
-  InputBehaviorMobile();
-
-  final acceptKeyStroke = false;
-
-  final initEditingState = const TextEditingValue(
-    text: '  ',
-    selection: TextSelection.collapsed(offset: 1),
-  );
-
-  TextEditingValue onTextEdit(
-      TextEditingValue value, TerminalUiInteraction terminal) {
-    if (value.text.length > initEditingState.text.length) {
-      terminal.raiseOnInput(value.text.substring(1, value.text.length - 1));
-    } else if (value.text.length < initEditingState.text.length) {
-      terminal.keyInput(TerminalKey.backspace);
-    } else {
-      if (value.selection.baseOffset < 1) {
-        terminal.keyInput(TerminalKey.arrowLeft);
-      } else if (value.selection.baseOffset > 1) {
-        terminal.keyInput(TerminalKey.arrowRight);
-      }
-    }
-
-    return initEditingState;
-  }
-
-  void onAction(TextInputAction action, TerminalUiInteraction terminal) {
-    print('action $action');
-    switch (action) {
-      case TextInputAction.done:
-        terminal.keyInput(TerminalKey.enter);
-        break;
-      default:
-        print('unknown action $action');
-    }
-  }
-}

+ 0 - 18
lib/frontend/input_behaviors.dart

@@ -1,18 +0,0 @@
-import 'package:platform_info/platform_info.dart';
-import 'package:xterm/frontend/input_behavior.dart';
-import 'package:xterm/frontend/input_behavior_desktop.dart';
-import 'package:xterm/frontend/input_behavior_mobile.dart';
-
-class InputBehaviors {
-  static final desktop = InputBehaviorDesktop();
-
-  static final mobile = InputBehaviorMobile();
-
-  static InputBehavior get platform {
-    if (Platform.I.isMobile) {
-      return mobile;
-    }
-
-    return desktop;
-  }
-}

+ 0 - 268
lib/frontend/input_listener.dart

@@ -1,268 +0,0 @@
-import 'package:flutter/foundation.dart';
-import 'package:flutter/material.dart';
-import 'package:flutter/scheduler.dart';
-import 'package:flutter/services.dart';
-
-typedef KeyStrokeHandler = void Function(RawKeyEvent);
-typedef InputHandler = TextEditingValue? Function(TextEditingValue);
-typedef ActionHandler = void Function(TextInputAction);
-typedef FocusHandler = void Function(bool);
-
-abstract class InputListenerController {
-  void requestKeyboard();
-  void setCaretRect(Rect rect);
-}
-
-class InputListener extends StatefulWidget {
-  InputListener({
-    required this.child,
-    required this.onKeyStroke,
-    required this.onTextInput,
-    required this.onAction,
-    required this.focusNode,
-    this.onFocus,
-    this.autofocus = false,
-    this.listenKeyStroke = true,
-    this.readOnly = false,
-    this.initEditingState = TextEditingValue.empty,
-    this.inputType = TextInputType.text,
-    this.enableSuggestions = false,
-    this.inputAction = TextInputAction.done,
-    this.keyboardAppearance = Brightness.light,
-    this.autocorrect = false,
-  });
-
-  final Widget child;
-  final InputHandler onTextInput;
-  final KeyStrokeHandler onKeyStroke;
-  final ActionHandler onAction;
-  final FocusHandler? onFocus;
-  final bool autofocus;
-  final FocusNode focusNode;
-  final bool listenKeyStroke;
-  final bool readOnly;
-  final TextEditingValue initEditingState;
-  final TextInputType inputType;
-  final bool enableSuggestions;
-  final TextInputAction inputAction;
-  final Brightness keyboardAppearance;
-  final bool autocorrect;
-
-  @override
-  InputListenerState createState() => InputListenerState();
-
-  static InputListenerController? of(BuildContext context) {
-    return context.findAncestorStateOfType<InputListenerState>();
-  }
-}
-
-class InputListenerState extends State<InputListener>
-    implements InputListenerController {
-  TextInputConnection? _conn;
-  FocusAttachment? _focusAttachment;
-  bool _didAutoFocus = false;
-
-  @override
-  void initState() {
-    _focusAttachment = widget.focusNode.attach(context);
-    widget.focusNode.addListener(onFocusChange);
-    super.initState();
-  }
-
-  @override
-  void didChangeDependencies() {
-    super.didChangeDependencies();
-
-    if (!_didAutoFocus && widget.autofocus) {
-      _didAutoFocus = true;
-      SchedulerBinding.instance.addPostFrameCallback((_) {
-        if (mounted) {
-          FocusScope.of(context).autofocus(widget.focusNode);
-        }
-      });
-    }
-  }
-
-  bool get _shouldCreateInputConnection => kIsWeb || !widget.readOnly;
-
-  bool get _hasInputConnection => _conn != null && _conn!.attached;
-
-  @override
-  void didUpdateWidget(InputListener oldWidget) {
-    super.didUpdateWidget(oldWidget);
-
-    if (widget.focusNode != oldWidget.focusNode) {
-      oldWidget.focusNode.removeListener(onFocusChange);
-      _focusAttachment?.detach();
-      _focusAttachment = widget.focusNode.attach(context);
-      widget.focusNode.addListener(onFocusChange);
-    }
-
-    if (!_shouldCreateInputConnection) {
-      closeInputConnectionIfNeeded();
-    } else {
-      if (oldWidget.readOnly && widget.focusNode.hasFocus) {
-        openInputConnection();
-      }
-    }
-  }
-
-  @override
-  void dispose() {
-    super.dispose();
-    _focusAttachment?.detach();
-  }
-
-  @override
-  Widget build(BuildContext context) {
-    _focusAttachment?.reparent();
-
-    if (widget.listenKeyStroke) {
-      return RawKeyboardListener(
-        focusNode: widget.focusNode,
-        onKey: widget.onKeyStroke,
-        autofocus: widget.autofocus,
-        child: widget.child,
-      );
-    }
-
-    return Focus(
-      focusNode: widget.focusNode,
-      autofocus: widget.autofocus,
-      includeSemantics: false,
-      child: widget.child,
-    );
-  }
-
-  @override
-  void requestKeyboard() {
-    if (widget.focusNode.hasFocus) {
-      openInputConnection();
-    } else {
-      widget.focusNode.requestFocus();
-    }
-  }
-
-  @override
-  void setCaretRect(Rect rect) {
-    _conn?.setCaretRect(rect);
-  }
-
-  void onFocusChange() {
-    if (widget.onFocus != null) {
-      widget.onFocus?.call(widget.focusNode.hasFocus);
-    }
-
-    openOrCloseInputConnectionIfNeeded();
-  }
-
-  void openOrCloseInputConnectionIfNeeded() {
-    if (widget.focusNode.hasFocus && widget.focusNode.consumeKeyboardToken()) {
-      openInputConnection();
-    } else if (!widget.focusNode.hasFocus) {
-      closeInputConnectionIfNeeded();
-    }
-  }
-
-  void openInputConnection() {
-    if (!_shouldCreateInputConnection) {
-      return;
-    }
-
-    if (_hasInputConnection) {
-      _conn!.show();
-    } else {
-      final config = TextInputConfiguration(
-        inputType: widget.inputType,
-        enableSuggestions: widget.enableSuggestions,
-        inputAction: widget.inputAction,
-        keyboardAppearance: widget.keyboardAppearance,
-        autocorrect: widget.autocorrect,
-      );
-      final client = TerminalTextInputClient(onInput, onAction);
-      _conn = TextInput.attach(client, config);
-
-      _conn!.show();
-
-      final dx = 0.0;
-      final dy = 0.0;
-      _conn!.setEditableSizeAndTransform(
-        Size(10, 10),
-        Matrix4.translationValues(dx, dy, 0.0),
-      );
-
-      _conn!.setEditingState(widget.initEditingState);
-    }
-  }
-
-  void closeInputConnectionIfNeeded() {
-    if (_conn != null && _conn!.attached) {
-      _conn!.close();
-      _conn = null;
-    }
-  }
-
-  void onInput(TextEditingValue value) {
-    final newValue = widget.onTextInput(value);
-
-    if (newValue != null) {
-      _conn?.setEditingState(newValue);
-    } else {
-      _conn?.setEditingState(TextEditingValue.empty);
-    }
-  }
-
-  void onAction(TextInputAction action) {
-    widget.onAction(action);
-  }
-}
-
-class TerminalTextInputClient extends TextInputClient {
-  TerminalTextInputClient(this.onInput, this.onAction);
-
-  final void Function(TextEditingValue) onInput;
-  final ActionHandler onAction;
-
-  TextEditingValue? _savedValue;
-
-  TextEditingValue? get currentTextEditingValue {
-    return _savedValue;
-  }
-
-  AutofillScope? get currentAutofillScope {
-    return null;
-  }
-
-  void updateEditingValue(TextEditingValue value) {
-    // print('updateEditingValue $value');
-
-    if (value.text != '') {
-      onInput(value);
-    }
-
-    _savedValue = value;
-    // print('updateEditingValue $value');
-  }
-
-  void performAction(TextInputAction action) {
-    // print('performAction $action');
-    onAction(action);
-  }
-
-  void updateFloatingCursor(RawFloatingCursorPoint point) {
-    // print('updateFloatingCursor');
-  }
-
-  void showAutocorrectionPromptRect(int start, int end) {
-    // print('showAutocorrectionPromptRect');
-  }
-
-  void connectionClosed() {
-    // print('connectionClosed');
-  }
-
-  @override
-  void performPrivateCommand(String action, Map<String, dynamic> data) {
-    // print('performPrivateCommand $action');
-  }
-}

+ 0 - 28
lib/frontend/mouse_listener.dart

@@ -1,28 +0,0 @@
-import 'package:flutter/gestures.dart';
-import 'package:flutter/widgets.dart';
-
-typedef ScrollHandler = void Function(Offset);
-
-class MouseListener extends StatelessWidget {
-  MouseListener({
-    required this.child,
-    required this.onScroll,
-  });
-
-  final Widget child;
-  final ScrollHandler onScroll;
-
-  @override
-  Widget build(BuildContext context) {
-    return Listener(
-      child: child,
-      onPointerSignal: onPointerSignal,
-    );
-  }
-
-  void onPointerSignal(PointerSignalEvent event) {
-    if (event is PointerScrollEvent) {
-      onScroll(event.scrollDelta);
-    }
-  }
-}

+ 0 - 78
lib/frontend/oscillator.dart

@@ -1,78 +0,0 @@
-import 'dart:async';
-
-import 'package:xterm/util/observable.dart';
-
-class Oscillator with Observable {
-  Oscillator(this.duration);
-
-  Oscillator.ms(int ms) : duration = Duration(milliseconds: ms);
-
-  final Duration duration;
-
-  var _value = true;
-  Timer? _timer;
-  var _shouldRun = false;
-
-  @override
-  void addListener(listener) {
-    super.addListener(listener);
-    resume();
-  }
-
-  @override
-  void removeListener(listener) {
-    super.removeListener(listener);
-    if (listeners.isEmpty) {
-      pause();
-    }
-  }
-
-  void _onOscillation(_) {
-    _value = !_value;
-    notifyListeners();
-  }
-
-  bool get value {
-    return _value;
-  }
-
-  void restart() {
-    stop();
-    start();
-  }
-
-  void start() {
-    _value = true;
-    _shouldRun = true;
-    // only start right away when anyone is listening.
-    // the moment a listener gets registered the Oscillator will start
-    if (listeners.isNotEmpty) {
-      _startInternal();
-    }
-  }
-
-  void _startInternal() {
-    if (_timer != null) return;
-    _timer = Timer.periodic(duration, _onOscillation);
-  }
-
-  void pause() {
-    _stopInternal();
-  }
-
-  void resume() {
-    if (_shouldRun) {
-      _startInternal();
-    }
-  }
-
-  void stop() {
-    _shouldRun = false;
-    _stopInternal();
-  }
-
-  void _stopInternal() {
-    _timer?.cancel();
-    _timer = null;
-  }
-}

+ 0 - 423
lib/frontend/terminal_painters.dart

@@ -1,423 +0,0 @@
-import 'dart:math';
-
-import 'package:flutter/material.dart';
-import 'package:xterm/buffer/cell_flags.dart';
-import 'package:xterm/buffer/line/line.dart';
-import 'package:xterm/mouse/position.dart';
-import 'package:xterm/terminal/terminal_search.dart';
-import 'package:xterm/terminal/terminal_ui_interaction.dart';
-import 'package:xterm/theme/terminal_style.dart';
-import 'package:xterm/util/bit_flags.dart';
-
-import 'cache.dart';
-import 'char_size.dart';
-
-class TerminalPainter extends CustomPainter {
-  TerminalPainter({
-    required this.terminal,
-    required this.style,
-    required this.charSize,
-    required this.textLayoutCache,
-  });
-
-  final TerminalUiInteraction terminal;
-  final TerminalStyle style;
-  final CellSize charSize;
-  final TextLayoutCache textLayoutCache;
-
-  @override
-  void paint(Canvas canvas, Size size) {
-    _paintBackground(canvas);
-
-    _paintText(canvas);
-
-    _paintUserSearchResult(canvas, size);
-
-    _paintSelection(canvas);
-  }
-
-  void _paintBackground(Canvas canvas) {
-    final lines = terminal.getVisibleLines();
-
-    for (var row = 0; row < lines.length; row++) {
-      final line = lines[row];
-      final offsetY = row * charSize.cellHeight;
-      final cellCount = terminal.terminalWidth;
-
-      for (var col = 0; col < cellCount; col++) {
-        final cellWidth = line.cellGetWidth(col);
-        if (cellWidth == 0) {
-          continue;
-        }
-
-        final cellFgColor = line.cellGetFgColor(col);
-        final cellBgColor = line.cellGetBgColor(col);
-        final effectBgColor = line.cellHasFlag(col, CellFlags.inverse)
-            ? cellFgColor
-            : cellBgColor;
-
-        if (effectBgColor == 0x00) {
-          continue;
-        }
-
-        // when a program reports black as background then it "really" means transparent
-        if (effectBgColor == 0xFF000000) {
-          continue;
-        }
-
-        final offsetX = col * charSize.cellWidth;
-        final effectWidth = charSize.cellWidth * cellWidth + 1;
-        final effectHeight = charSize.cellHeight + 1;
-
-        // background color is already painted with opacity by the Container of
-        // TerminalPainter so wo don't need to fallback to
-        // terminal.theme.background here.
-
-        final paint = Paint()..color = Color(effectBgColor);
-        canvas.drawRect(
-          Rect.fromLTWH(offsetX, offsetY, effectWidth, effectHeight),
-          paint,
-        );
-      }
-    }
-  }
-
-  void _paintUserSearchResult(Canvas canvas, Size size) {
-    final searchResult = terminal.userSearchResult;
-
-    //when there is no ongoing user search then directly return
-    if (!terminal.isUserSearchActive) {
-      return;
-    }
-
-    //make everything dim so that the search result can be seen better
-    final dimPaint = Paint()
-      ..color = Color(terminal.theme.background).withAlpha(128)
-      ..style = PaintingStyle.fill;
-
-    canvas.drawRect(Rect.fromLTWH(0, 0, size.width, size.height), dimPaint);
-
-    for (int i = 1; i <= searchResult.allHits.length; i++) {
-      _paintSearchHit(canvas, searchResult.allHits[i - 1], i);
-    }
-  }
-
-  void _paintSearchHit(Canvas canvas, TerminalSearchHit hit, int hitNum) {
-    //check if the hit is visible
-    if (hit.startLineIndex >=
-            terminal.scrollOffsetFromTop + terminal.terminalHeight ||
-        hit.endLineIndex < terminal.scrollOffsetFromTop) {
-      return;
-    }
-
-    final paint = Paint()
-      ..color = Color(terminal.currentSearchHit == hitNum
-          ? terminal.theme.searchHitBackgroundCurrent
-          : terminal.theme.searchHitBackground)
-      ..style = PaintingStyle.fill;
-
-    if (hit.startLineIndex == hit.endLineIndex) {
-      final double y =
-          (hit.startLineIndex.toDouble() - terminal.scrollOffsetFromTop) *
-              charSize.cellHeight;
-      final startX = charSize.cellWidth * hit.startIndex;
-      final endX = charSize.cellWidth * hit.endIndex;
-
-      canvas.drawRect(
-          Rect.fromLTRB(startX, y, endX, y + charSize.cellHeight), paint);
-    } else {
-      //draw first row: start - line end
-      final double yFirstRow =
-          (hit.startLineIndex.toDouble() - terminal.scrollOffsetFromTop) *
-              charSize.cellHeight;
-      final startXFirstRow = charSize.cellWidth * hit.startIndex;
-      final endXFirstRow = charSize.cellWidth * terminal.terminalWidth;
-      canvas.drawRect(
-          Rect.fromLTRB(startXFirstRow, yFirstRow, endXFirstRow,
-              yFirstRow + charSize.cellHeight),
-          paint);
-      //draw middle rows
-      final middleRowCount = hit.endLineIndex - hit.startLineIndex - 1;
-      if (middleRowCount > 0) {
-        final startYMiddleRows =
-            (hit.startLineIndex + 1 - terminal.scrollOffsetFromTop) *
-                charSize.cellHeight;
-        final startXMiddleRows = 0.toDouble();
-        final endYMiddleRows = min(
-                hit.endLineIndex - terminal.scrollOffsetFromTop,
-                terminal.terminalHeight) *
-            charSize.cellHeight;
-        final endXMiddleRows = terminal.terminalWidth * charSize.cellWidth;
-        canvas.drawRect(
-            Rect.fromLTRB(startXMiddleRows, startYMiddleRows, endXMiddleRows,
-                endYMiddleRows),
-            paint);
-      }
-      //draw end row: line start - end
-      if (hit.endLineIndex - terminal.scrollOffsetFromTop <
-          terminal.terminalHeight) {
-        final startXEndRow = 0.toDouble();
-        final startYEndRow = (hit.endLineIndex - terminal.scrollOffsetFromTop) *
-            charSize.cellHeight;
-        final endXEndRow = hit.endIndex * charSize.cellWidth;
-        final endYEndRow = startYEndRow + charSize.cellHeight;
-        canvas.drawRect(
-            Rect.fromLTRB(startXEndRow, startYEndRow, endXEndRow, endYEndRow),
-            paint);
-      }
-    }
-
-    final visibleLines = terminal.getVisibleLines();
-
-    //paint text
-    for (var rawRow = hit.startLineIndex;
-        rawRow <= hit.endLineIndex;
-        rawRow++) {
-      final start = rawRow == hit.startLineIndex ? hit.startIndex : 0;
-      final end =
-          rawRow == hit.endLineIndex ? hit.endIndex : terminal.terminalWidth;
-
-      final row = rawRow - terminal.scrollOffsetFromTop;
-
-      final offsetY = row * charSize.cellHeight;
-
-      if (row >= visibleLines.length || row < 0) {
-        continue;
-      }
-
-      final line = visibleLines[row];
-
-      for (var col = start; col < end; col++) {
-        final offsetX = col * charSize.cellWidth;
-        _paintCell(
-          canvas,
-          line,
-          col,
-          offsetX,
-          offsetY,
-          fgColorOverride: terminal.theme.searchHitForeground,
-          bgColorOverride: terminal.theme.searchHitForeground,
-        );
-      }
-    }
-  }
-
-  void _paintSelection(Canvas canvas) {
-    final paint = Paint()..color = Colors.white.withOpacity(0.3);
-
-    for (var y = 0; y < terminal.terminalHeight; y++) {
-      final offsetY = y * charSize.cellHeight;
-      final absoluteY = terminal.convertViewLineToRawLine(y) -
-          terminal.scrollOffsetFromBottom;
-
-      for (var x = 0; x < terminal.terminalWidth; x++) {
-        var cellCount = 0;
-
-        while (
-            (terminal.selection?.contains(Position(x + cellCount, absoluteY)) ??
-                    false) &&
-                x + cellCount < terminal.terminalWidth) {
-          cellCount++;
-        }
-
-        if (cellCount == 0) {
-          continue;
-        }
-
-        final offsetX = x * charSize.cellWidth;
-        final effectWidth = cellCount * charSize.cellWidth;
-        final effectHeight = charSize.cellHeight;
-
-        canvas.drawRect(
-          Rect.fromLTWH(offsetX, offsetY, effectWidth, effectHeight),
-          paint,
-        );
-
-        x += cellCount;
-      }
-    }
-  }
-
-  void _paintText(Canvas canvas) {
-    final lines = terminal.getVisibleLines();
-
-    for (var row = 0; row < lines.length; row++) {
-      final line = lines[row];
-      final offsetY = row * charSize.cellHeight;
-      // final cellCount = math.min(terminal.viewWidth, line.length);
-      final cellCount = terminal.terminalWidth;
-
-      for (var col = 0; col < cellCount; col++) {
-        final width = line.cellGetWidth(col);
-
-        if (width == 0) {
-          continue;
-        }
-        final offsetX = col * charSize.cellWidth;
-        _paintCell(
-          canvas,
-          line,
-          col,
-          offsetX,
-          offsetY,
-        );
-      }
-    }
-  }
-
-  int _getColor(int colorCode) {
-    return (colorCode == 0) ? 0xFF000000 : colorCode;
-  }
-
-  void _paintCell(
-    Canvas canvas,
-    BufferLine line,
-    int cell,
-    double offsetX,
-    double offsetY, {
-    int? fgColorOverride,
-    int? bgColorOverride,
-  }) {
-    final codePoint = line.cellGetContent(cell);
-    final fgColor = fgColorOverride ?? _getColor(line.cellGetFgColor(cell));
-    final bgColor = bgColorOverride ?? _getColor(line.cellGetBgColor(cell));
-    final flags = line.cellGetFlags(cell);
-
-    if (codePoint == 0 || flags.hasFlag(CellFlags.invisible)) {
-      return;
-    }
-
-    // final cellHash = line.cellGetHash(cell);
-    final cellHash = hashValues(codePoint, fgColor, bgColor, flags);
-
-    var character = textLayoutCache.getLayoutFromCache(cellHash);
-    if (character != null) {
-      canvas.drawParagraph(character, Offset(offsetX, offsetY));
-      return;
-    }
-
-    final cellColor = flags.hasFlag(CellFlags.inverse) ? bgColor : fgColor;
-
-    var color = Color(cellColor);
-
-    if (flags & CellFlags.faint != 0) {
-      color = color.withOpacity(0.5);
-    }
-
-    final styleToUse = PaintHelper.getStyleToUse(
-      style,
-      color,
-      bold: flags.hasFlag(CellFlags.bold),
-      italic: flags.hasFlag(CellFlags.italic),
-      underline: flags.hasFlag(CellFlags.underline),
-    );
-
-    character = textLayoutCache.performAndCacheLayout(
-        String.fromCharCode(codePoint), styleToUse, cellHash);
-
-    canvas.drawParagraph(character, Offset(offsetX, offsetY));
-  }
-
-  @override
-  bool shouldRepaint(CustomPainter oldDelegate) {
-    /// paint only when the terminal has changed since last paint.
-    return terminal.dirty;
-  }
-}
-
-class CursorPainter extends CustomPainter {
-  final bool visible;
-  final CellSize charSize;
-  final bool focused;
-  final bool blinkVisible;
-  final int cursorColor;
-  final int textColor;
-  final String composingString;
-  final TextLayoutCache textLayoutCache;
-  final TerminalStyle style;
-
-  CursorPainter({
-    required this.visible,
-    required this.charSize,
-    required this.focused,
-    required this.blinkVisible,
-    required this.cursorColor,
-    required this.textColor,
-    required this.composingString,
-    required this.textLayoutCache,
-    required this.style,
-  });
-
-  @override
-  void paint(Canvas canvas, Size size) {
-    bool isVisible =
-        visible && (blinkVisible || composingString != '' || !focused);
-    if (isVisible) {
-      _paintCursor(canvas);
-    }
-  }
-
-  @override
-  bool shouldRepaint(covariant CustomPainter oldDelegate) {
-    if (oldDelegate is CursorPainter) {
-      return blinkVisible != oldDelegate.blinkVisible ||
-          focused != oldDelegate.focused ||
-          visible != oldDelegate.visible ||
-          charSize.cellWidth != oldDelegate.charSize.cellWidth ||
-          charSize.cellHeight != oldDelegate.charSize.cellHeight ||
-          composingString != oldDelegate.composingString;
-    }
-    return true;
-  }
-
-  void _paintCursor(Canvas canvas) {
-    final paint = Paint()
-      ..color = Color(cursorColor)
-      ..strokeWidth = focused ? 0.0 : 1.0
-      ..style = focused ? PaintingStyle.fill : PaintingStyle.stroke;
-
-    canvas.drawRect(
-        Rect.fromLTWH(0, 0, charSize.cellWidth, charSize.cellHeight), paint);
-
-    if (composingString != '') {
-      final styleToUse = PaintHelper.getStyleToUse(style, Color(textColor));
-      final character = textLayoutCache.performAndCacheLayout(
-          composingString, styleToUse, null);
-      canvas.drawParagraph(character, Offset(0, 0));
-    }
-  }
-}
-
-class PaintHelper {
-  static TextStyle getStyleToUse(
-    TerminalStyle style,
-    Color color, {
-    bool bold = false,
-    bool italic = false,
-    bool underline = false,
-  }) {
-    return (style.textStyleProvider != null)
-        ? style.textStyleProvider!(
-            color: color,
-            fontSize: style.fontSize,
-            fontWeight: bold && !style.ignoreBoldFlag
-                ? FontWeight.bold
-                : FontWeight.normal,
-            fontStyle: italic ? FontStyle.italic : FontStyle.normal,
-            decoration:
-                underline ? TextDecoration.underline : TextDecoration.none,
-          )
-        : TextStyle(
-            color: color,
-            fontSize: style.fontSize,
-            fontWeight: bold && !style.ignoreBoldFlag
-                ? FontWeight.bold
-                : FontWeight.normal,
-            fontStyle: italic ? FontStyle.italic : FontStyle.normal,
-            decoration:
-                underline ? TextDecoration.underline : TextDecoration.none,
-            fontFamily: 'monospace',
-            fontFamilyFallback: style.fontFamily,
-          );
-  }
-}

+ 0 - 563
lib/frontend/terminal_view.dart

@@ -1,563 +0,0 @@
-import 'dart:math' as math;
-
-import 'package:flutter/cupertino.dart';
-import 'package:flutter/gestures.dart';
-import 'package:flutter/material.dart';
-import 'package:flutter/scheduler.dart';
-import 'package:xterm/frontend/cache.dart';
-import 'package:xterm/frontend/char_size.dart';
-import 'package:xterm/frontend/helpers.dart';
-import 'package:xterm/frontend/input_behavior.dart';
-import 'package:xterm/frontend/input_behaviors.dart';
-import 'package:xterm/frontend/input_listener.dart';
-import 'package:xterm/frontend/oscillator.dart';
-import 'package:xterm/frontend/terminal_painters.dart';
-import 'package:xterm/mouse/position.dart';
-import 'package:xterm/terminal/terminal_ui_interaction.dart';
-import 'package:xterm/theme/terminal_style.dart';
-
-class TerminalView extends StatefulWidget {
-  TerminalView({
-    Key? key,
-    required this.terminal,
-    this.style = const TerminalStyle(),
-    this.opacity = 1.0,
-    FocusNode? focusNode,
-    this.autofocus = false,
-    ScrollController? scrollController,
-    this.inputType = TextInputType.text,
-    this.enableSuggestions = false,
-    this.inputAction = TextInputAction.done,
-    this.keyboardAppearance = Brightness.light,
-    this.autocorrect = false,
-    InputBehavior? inputBehavior,
-    this.scrollBehavior,
-    this.padding = 0.0,
-  })  : focusNode = focusNode ?? FocusNode(),
-        scrollController = scrollController ?? ScrollController(),
-        inputBehavior = inputBehavior ?? InputBehaviors.platform,
-        super(key: key ?? ValueKey(terminal));
-
-  final TerminalUiInteraction terminal;
-  final FocusNode focusNode;
-  final bool autofocus;
-  final ScrollController scrollController;
-  final TextInputType inputType;
-  final bool enableSuggestions;
-  final TextInputAction inputAction;
-  final Brightness keyboardAppearance;
-  final bool autocorrect;
-
-  final TerminalStyle style;
-  final double opacity;
-
-  final double padding;
-
-  final InputBehavior inputBehavior;
-
-  final ScrollBehavior? scrollBehavior;
-
-  // get the dimensions of a rendered character
-  CellSize measureCellSize(double fontSize) {
-    final testString = 'xxxxxxxxxx' * 1000;
-
-    final text = Text(
-      testString,
-      maxLines: 1,
-      style: (style.textStyleProvider != null)
-          ? style.textStyleProvider!(
-              fontSize: fontSize,
-            )
-          : TextStyle(
-              fontFamily: 'monospace',
-              fontFamilyFallback: style.fontFamily,
-              fontSize: fontSize,
-            ),
-    );
-
-    final size = textSize(text);
-
-    final charWidth = (size.width / testString.length);
-    final charHeight = size.height;
-
-    final cellWidth = charWidth * style.fontWidthScaleFactor;
-    final cellHeight = size.height * style.fontHeightScaleFactor;
-
-    return CellSize(
-      charWidth: charWidth,
-      charHeight: charHeight,
-      cellWidth: cellWidth,
-      cellHeight: cellHeight,
-      letterSpacing: cellWidth - charWidth,
-      lineSpacing: cellHeight - charHeight,
-    );
-  }
-
-  @override
-  _TerminalViewState createState() => _TerminalViewState();
-}
-
-class _TerminalViewState extends State<TerminalView> {
-  /// blinking cursor and blinking character
-  final blinkOscillator = Oscillator.ms(600);
-
-  final textLayoutCache = TextLayoutCache(TextDirection.ltr, 10240);
-
-  bool get focused {
-    return widget.focusNode.hasFocus;
-  }
-
-  late CellSize _cellSize;
-  Position? _tapPosition;
-
-  /// Scroll position from the terminal. Not null if terminal scroll extent has
-  /// been updated and needs to be syncronized to flutter side.
-  double? _pendingTerminalScrollExtent;
-
-  void onTerminalChange() {
-    _pendingTerminalScrollExtent =
-        _cellSize.cellHeight * widget.terminal.scrollOffsetFromTop;
-
-    if (mounted) {
-      setState(() {});
-    }
-  }
-
-  // listen to oscillator to update mouse blink etc.
-  // void onTick() {
-  //   widget.terminal.refresh();
-  // }
-
-  @override
-  void initState() {
-    blinkOscillator.start();
-    // oscillator.addListener(onTick);
-
-    // measureCellSize is expensive so we cache the result.
-    _cellSize = widget.measureCellSize(widget.style.fontSize);
-
-    widget.terminal.addListener(onTerminalChange);
-
-    super.initState();
-  }
-
-  @override
-  void didUpdateWidget(TerminalView oldWidget) {
-    oldWidget.terminal.removeListener(onTerminalChange);
-    widget.terminal.addListener(onTerminalChange);
-
-    if (oldWidget.style != widget.style) {
-      _cellSize = widget.measureCellSize(widget.style.fontSize);
-      textLayoutCache.clear();
-      updateTerminalSize();
-    }
-
-    super.didUpdateWidget(oldWidget);
-  }
-
-  @override
-  void dispose() {
-    blinkOscillator.stop();
-    // oscillator.removeListener(onTick);
-
-    widget.terminal.removeListener(onTerminalChange);
-    super.dispose();
-  }
-
-  GlobalKey _keyCursor = GlobalKey();
-
-  @override
-  Widget build(BuildContext context) {
-    return InputListener(
-      listenKeyStroke: widget.inputBehavior.acceptKeyStroke,
-      onKeyStroke: onKeyStroke,
-      onTextInput: onInput,
-      onAction: onAction,
-      onFocus: onFocus,
-      focusNode: widget.focusNode,
-      autofocus: widget.autofocus,
-      initEditingState: widget.inputBehavior.initEditingState,
-      inputType: widget.inputType,
-      enableSuggestions: widget.enableSuggestions,
-      inputAction: widget.inputAction,
-      keyboardAppearance: widget.keyboardAppearance,
-      autocorrect: widget.autocorrect,
-      child: MouseRegion(
-        cursor: SystemMouseCursors.text,
-        child: LayoutBuilder(builder: (context, constraints) {
-          onWidgetSize(constraints.maxWidth - widget.padding * 2,
-              constraints.maxHeight - widget.padding * 2);
-
-          if (_keyCursor.currentContext != null) {
-            /// this gets set so that the accent selection menu on MacOS pops up
-            /// at the right spot
-            final RenderBox cursorRenderObj =
-                _keyCursor.currentContext!.findRenderObject() as RenderBox;
-            final offset = cursorRenderObj.localToGlobal(Offset.zero);
-            InputListener.of(context)!.setCaretRect(
-              Rect.fromLTWH(
-                offset.dx,
-                offset.dy,
-                _cellSize.cellWidth,
-                _cellSize.cellHeight,
-              ),
-            );
-          }
-
-          // use flutter's Scrollable to manage scrolling to better integrate
-          // with widgets such as Scrollbar.
-          return NotificationListener<ScrollNotification>(
-            onNotification: (notification) {
-              onScroll(notification.metrics.pixels);
-              return false;
-            },
-            child: ScrollConfiguration(
-              behavior: widget.scrollBehavior ??
-                  ScrollConfiguration.of(context).copyWith(scrollbars: false),
-              child: Scrollable(
-                controller: widget.scrollController,
-                viewportBuilder: (context, offset) {
-                  if (!widget.scrollController.hasClients) {
-                    return buildTerminal(context);
-                  }
-                  final position = widget.scrollController.position;
-
-                  /// use [_EmptyScrollActivity] to suppress unexpected behaviors
-                  /// that come from [applyViewportDimension].
-                  if (InputBehaviors.platform == InputBehaviors.desktop &&
-                      position is ScrollActivityDelegate) {
-                    position.beginActivity(
-                      _EmptyScrollActivity(position as ScrollActivityDelegate),
-                    );
-                  }
-
-                  final viewPortHeight =
-                      constraints.maxHeight - widget.padding * 2;
-
-                  // set viewport height.
-                  offset.applyViewportDimension(viewPortHeight);
-
-                  if (widget.terminal.isReady) {
-                    final minScrollExtent = 0.0;
-
-                    final maxScrollExtent = math.max(
-                        0.0,
-                        _cellSize.cellHeight *
-                            (widget.terminal.bufferHeight -
-                                widget.terminal.terminalHeight));
-
-                    // set how much the terminal can scroll
-                    offset.applyContentDimensions(
-                        minScrollExtent, maxScrollExtent);
-
-                    // synchronize pending terminal scroll extent to ScrollController
-                    if (_pendingTerminalScrollExtent != null) {
-                      position.correctPixels(_pendingTerminalScrollExtent!);
-                      _pendingTerminalScrollExtent = null;
-                    }
-                  }
-
-                  return buildTerminal(context);
-                },
-              ),
-            ),
-          );
-        }),
-      ),
-    );
-  }
-
-  Widget buildTerminal(BuildContext context) {
-    return GestureDetector(
-      behavior: HitTestBehavior.deferToChild,
-      dragStartBehavior: DragStartBehavior.down,
-      onDoubleTapDown: (detail) {
-        final pos = detail.localPosition;
-        _tapPosition = getMouseOffset(pos.dx, pos.dy);
-      },
-      onTapDown: (detail) {
-        final pos = detail.localPosition;
-        _tapPosition = getMouseOffset(pos.dx, pos.dy);
-      },
-      onDoubleTap: () {
-        if (_tapPosition != null) {
-          widget.terminal.onMouseDoubleTap(_tapPosition!);
-          widget.terminal.refresh();
-        }
-      },
-      onTap: () {
-        if (widget.terminal.selection?.isEmpty ?? true) {
-          InputListener.of(context)!.requestKeyboard();
-        } else {
-          widget.terminal.clearSelection();
-        }
-        if (_tapPosition != null) {
-          widget.terminal.onMouseTap(_tapPosition!);
-          widget.terminal.refresh();
-        }
-      },
-      onPanStart: (detail) {
-        final pos = detail.localPosition;
-        final offset = getMouseOffset(pos.dx, pos.dy);
-        widget.terminal.onPanStart(offset);
-        widget.terminal.refresh();
-      },
-      onPanUpdate: (detail) {
-        final pos = detail.localPosition;
-        final offset = getMouseOffset(pos.dx, pos.dy);
-        widget.terminal.onPanUpdate(offset);
-        widget.terminal.refresh();
-      },
-      child: Container(
-        constraints: BoxConstraints.expand(),
-        child: Padding(
-          padding: EdgeInsets.all(widget.padding),
-          child: Stack(
-            children: <Widget>[
-              CustomPaint(
-                painter: TerminalPainter(
-                  terminal: widget.terminal,
-                  style: widget.style,
-                  charSize: _cellSize,
-                  textLayoutCache: textLayoutCache,
-                ),
-                child: Container(), //to get the size
-              ),
-              Positioned(
-                key: _keyCursor,
-                child: CursorView(
-                  terminal: widget.terminal,
-                  cellSize: _cellSize,
-                  focusNode: widget.focusNode,
-                  blinkOscillator: blinkOscillator,
-                  style: widget.style,
-                  textLayoutCache: textLayoutCache,
-                ),
-                width: _cellSize.cellWidth,
-                height: _cellSize.cellHeight,
-                left: _getCursorOffset().dx,
-                top: _getCursorOffset().dy,
-              ),
-            ],
-          ),
-        ),
-        color: Color(widget.terminal.backgroundColor).withOpacity(
-          widget.opacity,
-        ),
-      ),
-    );
-  }
-
-  Offset _getCursorOffset() {
-    final screenCursorY = widget.terminal.cursorY;
-    final offsetX = _cellSize.cellWidth * widget.terminal.cursorX;
-    final offsetY = _cellSize.cellHeight * screenCursorY;
-
-    return Offset(offsetX, offsetY);
-  }
-
-  /// Get global cell position from mouse position.
-  Position getMouseOffset(double px, double py) {
-    final col = ((px - widget.padding) / _cellSize.cellWidth).floor();
-    final row = ((py - widget.padding) / _cellSize.cellHeight).floor();
-
-    final x = col;
-    final y = widget.terminal.convertViewLineToRawLine(row) -
-        widget.terminal.scrollOffsetFromBottom;
-
-    return Position(x, y);
-  }
-
-  double? _width;
-  double? _height;
-
-  void onWidgetSize(double width, double height) {
-    if (!widget.terminal.isReady) {
-      return;
-    }
-
-    _width = width;
-    _height = height;
-
-    updateTerminalSize();
-  }
-
-  int? _lastTerminalWidth;
-  int? _lastTerminalHeight;
-
-  void updateTerminalSize() {
-    assert(_width != null);
-    assert(_height != null);
-
-    final termWidth = (_width! / _cellSize.cellWidth).floor();
-    final termHeight = (_height! / _cellSize.cellHeight).floor();
-
-    if (_lastTerminalWidth == termWidth && _lastTerminalHeight == termHeight) {
-      return;
-    }
-
-    _lastTerminalWidth = termWidth;
-    _lastTerminalHeight = termHeight;
-
-    widget.terminal.resize(
-      termWidth,
-      termHeight,
-      (termWidth * _cellSize.cellWidth).floor(),
-      (termHeight * _cellSize.cellHeight).floor(),
-    );
-  }
-
-  TextEditingValue? onInput(TextEditingValue value) {
-    return widget.inputBehavior.onTextEdit(value, widget.terminal);
-  }
-
-  void onKeyStroke(RawKeyEvent event) {
-    blinkOscillator.restart();
-    // TODO: find a way to stop scrolling immediately after key stroke.
-    widget.inputBehavior.onKeyStroke(event, widget.terminal);
-    if (event.character?.isNotEmpty == true) {
-      widget.terminal.setScrollOffsetFromBottom(0);
-    }
-  }
-
-  void onFocus(bool focused) {
-    SchedulerBinding.instance.addPostFrameCallback((_) {
-      widget.terminal.refresh();
-    });
-  }
-
-  void onAction(TextInputAction action) {
-    widget.inputBehavior.onAction(action, widget.terminal);
-  }
-
-  // synchronize flutter scroll offset to terminal
-  void onScroll(double offset) {
-    final topOffset = (offset / _cellSize.cellHeight).ceil();
-    final bottomOffset = widget.terminal.invisibleHeight - topOffset;
-    widget.terminal.setScrollOffsetFromBottom(bottomOffset);
-  }
-}
-
-class CursorView extends StatefulWidget {
-  final CellSize cellSize;
-  final TerminalUiInteraction terminal;
-  final FocusNode? focusNode;
-  final Oscillator blinkOscillator;
-  final TerminalStyle style;
-  final TextLayoutCache textLayoutCache;
-
-  CursorView({
-    required this.terminal,
-    required this.cellSize,
-    required this.focusNode,
-    required this.blinkOscillator,
-    required this.style,
-    required this.textLayoutCache,
-  });
-
-  @override
-  State<StatefulWidget> createState() => _CursorViewState();
-}
-
-class _CursorViewState extends State<CursorView> {
-  bool get focused {
-    return widget.focusNode?.hasFocus ?? false;
-  }
-
-  var _isOscillatorCallbackRegistered = false;
-
-  @override
-  void initState() {
-    _isOscillatorCallbackRegistered = true;
-    widget.blinkOscillator.addListener(onOscillatorTick);
-
-    widget.terminal.addListener(onTerminalChange);
-
-    super.initState();
-  }
-
-  @override
-  Widget build(BuildContext context) {
-    return CustomPaint(
-      painter: CursorPainter(
-        visible: _isCursorVisible(),
-        focused: focused,
-        charSize: widget.cellSize,
-        blinkVisible: widget.blinkOscillator.value,
-        cursorColor: widget.terminal.cursorColor,
-        textColor: widget.terminal.backgroundColor,
-        style: widget.style,
-        composingString: widget.terminal.composingString,
-        textLayoutCache: widget.textLayoutCache,
-      ),
-    );
-  }
-
-  bool _isCursorVisible() {
-    final screenCursorY =
-        widget.terminal.cursorY + widget.terminal.scrollOffsetFromBottom;
-
-    if (screenCursorY < 0 || screenCursorY >= widget.terminal.terminalHeight) {
-      return false;
-    }
-    return widget.terminal.showCursor;
-  }
-
-  @override
-  void dispose() {
-    widget.terminal.removeListener(onTerminalChange);
-    widget.blinkOscillator.removeListener(onOscillatorTick);
-
-    super.dispose();
-  }
-
-  void onTerminalChange() {
-    if (!mounted) {
-      return;
-    }
-
-    setState(() {
-      if (_isCursorVisible() /*&& widget.terminal.blinkingCursor*/ && focused) {
-        if (!_isOscillatorCallbackRegistered) {
-          _isOscillatorCallbackRegistered = true;
-          widget.blinkOscillator.addListener(onOscillatorTick);
-        }
-      } else {
-        if (_isOscillatorCallbackRegistered) {
-          _isOscillatorCallbackRegistered = false;
-          widget.blinkOscillator.removeListener(onOscillatorTick);
-        }
-      }
-    });
-  }
-
-  void onOscillatorTick() {
-    setState(() {});
-  }
-}
-
-/// A scroll activity that does nothing. Used to suppress unexpected behaviors
-/// from [Scrollable] during viewport building process.
-class _EmptyScrollActivity extends IdleScrollActivity {
-  _EmptyScrollActivity(ScrollActivityDelegate delegate) : super(delegate);
-
-  @override
-  void applyNewDimensions() {}
-
-  /// set [isScrolling] to ture to prevent flutter from calling the old scroll
-  /// activity.
-  @override
-  final isScrolling = true;
-
-  void dispatchScrollStartNotification(
-      ScrollMetrics metrics, BuildContext? context) {}
-
-  void dispatchScrollUpdateNotification(
-      ScrollMetrics metrics, BuildContext context, double scrollDelta) {}
-
-  void dispatchOverscrollNotification(
-      ScrollMetrics metrics, BuildContext context, double overscroll) {}
-
-  void dispatchScrollEndNotification(
-      ScrollMetrics metrics, BuildContext context) {}
-}

+ 0 - 37
lib/input/keytab/keytab.dart

@@ -1,37 +0,0 @@
-import 'package:xterm/input/keytab/keytab_default.dart';
-import 'package:xterm/input/keytab/keytab_parse.dart';
-import 'package:xterm/input/keytab/keytab_record.dart';
-import 'package:xterm/input/keytab/keytab_token.dart';
-
-class Keytab {
-  Keytab({
-    required this.name,
-    required this.records,
-  });
-
-  factory Keytab.parse(String source) {
-    final tokens = tokenize(source).toList();
-    final parser = KeytabParser()..addTokens(tokens);
-    return parser.result;
-  }
-
-  factory Keytab.defaultKeytab() {
-    return Keytab.parse(kDefaultKeytab);
-  }
-
-  final String? name;
-  final List<KeytabRecord> records;
-
-  @override
-  String toString() {
-    final buffer = StringBuffer();
-
-    buffer.writeln('keyboard "$name"');
-
-    for (var record in records) {
-      buffer.writeln(record);
-    }
-
-    return buffer.toString();
-  }
-}

+ 0 - 1
lib/input/shortcut.dart

@@ -1 +0,0 @@
-// TBD

Vissa filer visades inte eftersom för många filer har ändrats