Forráskód Böngészése

Adds the composing state

The current composing value gets displayed inside the Cursor (doesn't work on Linux for now)
Also: Cursor blinking timer gets reset on input
Also: Sets CaretRect to InputMethod so that the accent selection PopUp on MacOS is positioned correctly
devmil 4 éve
szülő
commit
f346e4c7fa

+ 4 - 2
lib/frontend/cache.dart

@@ -18,7 +18,7 @@ class TextLayoutCache {
     return _cache[key];
   }
 
-  Paragraph performAndCacheLayout(String text, TextStyle style, int key) {
+  Paragraph performAndCacheLayout(String text, TextStyle style, int? key) {
     final builder = ParagraphBuilder(style.getParagraphStyle());
     builder.pushStyle(style.getTextStyle());
     builder.addText(text);
@@ -26,7 +26,9 @@ class TextLayoutCache {
     final paragraph = builder.build();
     paragraph.layout(ParagraphConstraints(width: double.infinity));
 
-    _cache[key] = paragraph;
+    if (key != null) {
+      _cache[key] = paragraph;
+    }
     return paragraph;
   }
 

+ 20 - 3
lib/frontend/input_behavior_default.dart

@@ -5,7 +5,7 @@ import 'package:xterm/frontend/input_map.dart';
 import 'package:xterm/xterm.dart';
 
 class InputBehaviorDefault extends InputBehavior {
-  const InputBehaviorDefault();
+  InputBehaviorDefault();
 
   @override
   bool get acceptKeyStroke => true;
@@ -32,11 +32,28 @@ class InputBehaviorDefault extends InputBehavior {
     }
   }
 
+  String? _composingString = null;
+
   @override
   TextEditingValue? onTextEdit(
       TextEditingValue value, TerminalUiInteraction terminal) {
-    terminal.raiseOnInput(value.text);
-    if (value == TextEditingValue.empty) {
+    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!);
+      return null;
+    }
+    //when we reach this point the composing state is over
+    if (_composingString != null) {
+      _composingString = null;
+      terminal.updateComposingString('');
+    }
+
+    terminal.raiseOnInput(inputText);
+
+    if (value == TextEditingValue.empty || inputText == '') {
       return null;
     } else {
       return TextEditingValue.empty;

+ 1 - 1
lib/frontend/input_behavior_desktop.dart

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

+ 1 - 1
lib/frontend/input_behavior_mobile.dart

@@ -5,7 +5,7 @@ import 'package:xterm/input/keys.dart';
 import 'package:xterm/xterm.dart';
 
 class InputBehaviorMobile extends InputBehaviorDefault {
-  const InputBehaviorMobile();
+  InputBehaviorMobile();
 
   final acceptKeyStroke = false;
 

+ 2 - 2
lib/frontend/input_behaviors.dart

@@ -4,9 +4,9 @@ import 'package:xterm/frontend/input_behavior_desktop.dart';
 import 'package:xterm/frontend/input_behavior_mobile.dart';
 
 class InputBehaviors {
-  static const desktop = InputBehaviorDesktop();
+  static final desktop = InputBehaviorDesktop();
 
-  static const mobile = InputBehaviorMobile();
+  static final mobile = InputBehaviorMobile();
 
   static InputBehavior get platform {
     if (Platform.I.isMobile) {

+ 12 - 8
lib/frontend/input_listener.dart

@@ -10,6 +10,7 @@ typedef FocusHandler = void Function(bool);
 
 abstract class InputListenerController {
   void requestKeyboard();
+  void setCaretRect(Rect rect);
 }
 
 class InputListener extends StatefulWidget {
@@ -123,6 +124,7 @@ class InputListenerState extends State<InputListener>
     );
   }
 
+  @override
   void requestKeyboard() {
     if (widget.focusNode.hasFocus) {
       openInputConnection();
@@ -131,6 +133,11 @@ class InputListenerState extends State<InputListener>
     }
   }
 
+  @override
+  void setCaretRect(Rect rect) {
+    _conn?.setCaretRect(rect);
+  }
+
   void onFocusChange() {
     if (widget.onFocus != null) {
       widget.onFocus?.call(widget.focusNode.hasFocus);
@@ -184,6 +191,8 @@ class InputListenerState extends State<InputListener>
 
     if (newValue != null) {
       _conn?.setEditingState(newValue);
+    } else {
+      _conn?.setEditingState(TextEditingValue.empty);
     }
   }
 
@@ -211,14 +220,9 @@ class TerminalTextInputClient extends TextInputClient {
   void updateEditingValue(TextEditingValue value) {
     // print('updateEditingValue $value');
 
-    onInput(value);
-
-    // if (_savedValue == null || _savedValue.text == '') {
-    //   onInput(value.text);
-    // } else if (_savedValue.text.length < value.text.length) {
-    //   final diff = value.text.substring(_savedValue.text.length);
-    //   onInput(diff);
-    // }
+    if (value.text != '') {
+      onInput(value);
+    }
 
     _savedValue = value;
     // print('updateEditingValue $value');

+ 6 - 0
lib/frontend/oscillator.dart

@@ -36,7 +36,13 @@ class Oscillator with Observable {
     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

+ 56 - 31
lib/frontend/terminal_painters.dart

@@ -177,35 +177,13 @@ class TerminalPainter extends CustomPainter {
       color = color.withOpacity(0.5);
     }
 
-    final styleToUse = (style.textStyleProvider != null)
-        ? style.textStyleProvider!(
-            color: color,
-            fontSize: style.fontSize,
-            fontWeight: flags.hasFlag(CellFlags.bold)
-                ? FontWeight.bold
-                : FontWeight.normal,
-            fontStyle: flags.hasFlag(CellFlags.italic)
-                ? FontStyle.italic
-                : FontStyle.normal,
-            decoration: flags.hasFlag(CellFlags.underline)
-                ? TextDecoration.underline
-                : TextDecoration.none,
-          )
-        : TextStyle(
-            color: color,
-            fontSize: style.fontSize,
-            fontWeight: flags.hasFlag(CellFlags.bold)
-                ? FontWeight.bold
-                : FontWeight.normal,
-            fontStyle: flags.hasFlag(CellFlags.italic)
-                ? FontStyle.italic
-                : FontStyle.normal,
-            decoration: flags.hasFlag(CellFlags.underline)
-                ? TextDecoration.underline
-                : TextDecoration.none,
-            fontFamily: 'monospace',
-            fontFamilyFallback: style.fontFamily,
-          );
+    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);
@@ -226,6 +204,10 @@ class CursorPainter extends CustomPainter {
   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,
@@ -233,11 +215,16 @@ class CursorPainter extends CustomPainter {
     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) {
-    if (blinkVisible && visible) {
+    bool isVisible = visible && (blinkVisible || composingString != '');
+    if (isVisible) {
       _paintCursor(canvas);
     }
   }
@@ -249,7 +236,8 @@ class CursorPainter extends CustomPainter {
           focused != oldDelegate.focused ||
           visible != oldDelegate.visible ||
           charSize.cellWidth != oldDelegate.charSize.cellWidth ||
-          charSize.cellHeight != oldDelegate.charSize.cellHeight;
+          charSize.cellHeight != oldDelegate.charSize.cellHeight ||
+          composingString != oldDelegate.composingString;
     }
     return true;
   }
@@ -262,5 +250,42 @@ class CursorPainter extends CustomPainter {
 
     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 ? FontWeight.bold : FontWeight.normal,
+            fontStyle: italic ? FontStyle.italic : FontStyle.normal,
+            decoration:
+                underline ? TextDecoration.underline : TextDecoration.none,
+          )
+        : TextStyle(
+            color: color,
+            fontSize: style.fontSize,
+            fontWeight: bold ? FontWeight.bold : FontWeight.normal,
+            fontStyle: italic ? FontStyle.italic : FontStyle.normal,
+            decoration:
+                underline ? TextDecoration.underline : TextDecoration.none,
+            fontFamily: 'monospace',
+            fontFamilyFallback: style.fontFamily,
+          );
   }
 }

+ 31 - 0
lib/frontend/terminal_view.dart

@@ -154,6 +154,8 @@ class _TerminalViewState extends State<TerminalView> {
     super.dispose();
   }
 
+  GlobalKey _keyCursor = GlobalKey();
+
   @override
   Widget build(BuildContext context) {
     return InputListener(
@@ -171,6 +173,22 @@ class _TerminalViewState extends State<TerminalView> {
           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>(
@@ -282,11 +300,14 @@ class _TerminalViewState extends State<TerminalView> {
                 ),
               ),
               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,
@@ -367,6 +388,7 @@ class _TerminalViewState extends State<TerminalView> {
   }
 
   void onKeyStroke(RawKeyEvent event) {
+    blinkOscillator.restart();
     // TODO: find a way to stop scrolling immediately after key stroke.
     widget.inputBehavior.onKeyStroke(event, widget.terminal);
     widget.terminal.setScrollOffsetFromBottom(0);
@@ -395,11 +417,16 @@ class CursorView extends StatefulWidget {
   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
@@ -432,6 +459,10 @@ class _CursorViewState extends State<CursorView> {
         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,
       ),
     );
   }

+ 11 - 0
lib/terminal/terminal.dart

@@ -722,4 +722,15 @@ class Terminal with Observable implements TerminalUiInteraction {
     _selection.init(Position(0, 0));
     _selection.update(Position(terminalWidth, bufferHeight));
   }
+
+  String _composingString = '';
+
+  @override
+  String get composingString => _composingString;
+
+  @override
+  void updateComposingString(String value) {
+    _composingString = value;
+    refresh();
+  }
 }

+ 18 - 1
lib/terminal/terminal_isolate.dart

@@ -31,7 +31,8 @@ enum _IsolateCommand {
   keyInput,
   requestNewStateWhenDirty,
   paste,
-  terminateBackend
+  terminateBackend,
+  updateComposingString
 }
 
 enum _IsolateEvent {
@@ -141,6 +142,7 @@ void terminalMain(SendPort port) async {
             _terminal.showCursor,
             _terminal.theme.cursor,
             _terminal.getVisibleLines(),
+            _terminal.composingString,
           );
           port.send([_IsolateEvent.newState, newState]);
           _needNotify = true;
@@ -151,6 +153,10 @@ void terminalMain(SendPort port) async {
         break;
       case _IsolateCommand.terminateBackend:
         _terminal?.terminateBackend();
+        break;
+      case _IsolateCommand.updateComposingString:
+        _terminal?.updateComposingString(msg[1]);
+        break;
     }
   }
 }
@@ -194,6 +200,8 @@ class TerminalState {
 
   bool consumed = false;
 
+  String composingString;
+
   TerminalState(
     this.scrollOffsetFromBottom,
     this.scrollOffsetFromTop,
@@ -209,6 +217,7 @@ class TerminalState {
     this.showCursor,
     this.cursorColor,
     this.visibleLines,
+    this.composingString,
   );
 }
 
@@ -509,4 +518,12 @@ class TerminalIsolate with Observable implements TerminalUiInteraction {
 
   @override
   bool get isTerminated => _isTerminated;
+
+  @override
+  String get composingString => _lastState?.composingString ?? '';
+
+  @override
+  void updateComposingString(String value) {
+    _sendPort?.send([_IsolateCommand.updateComposingString, value]);
+  }
 }

+ 7 - 0
lib/terminal/terminal_ui_interaction.dart

@@ -122,4 +122,11 @@ abstract class TerminalUiInteraction with Observable {
 
   /// flag that indicates if the backend is already terminated
   bool get isTerminated;
+
+  /// returns the current composing string. '' when there is no composing going on
+  String get composingString;
+
+  /// update the composing string. This gets called by the input handling
+  /// part of the terminal
+  void updateComposingString(String value);
 }