Explorar el Código

Merge pull request #15 from TerminalStudio/fix-broken-sequence

Fix broken terminal sequence problem
xuty hace 4 años
padre
commit
0356cf4635

+ 1 - 0
lib/buffer/buffer.dart

@@ -444,6 +444,7 @@ class Buffer {
 
   void clear() {
     lines.clear();
+    lines.addAll(List.generate(terminal.viewHeight, (_) => BufferLine()));
   }
 
   void insertBlankCharacters(int count) {

+ 2 - 2
lib/buffer/cell_attr.dart

@@ -13,8 +13,8 @@ class CellAttr {
     this.inverse = false,
     this.invisible = false,
   }) : hashCode = hashValues(
-          fgColor,
-          bgColor,
+          fgColor?.value,
+          bgColor?.value,
           bold,
           faint,
           italic,

+ 46 - 12
lib/terminal/ansi.dart

@@ -4,9 +4,17 @@ import 'package:xterm/terminal/csi.dart';
 import 'package:xterm/terminal/osc.dart';
 import 'package:xterm/terminal/terminal.dart';
 
-typedef AnsiHandler = void Function(Queue<int>, Terminal);
+/// Handler of terminal sequences. Returns true if the sequence is consumed,
+/// false to indicate that the sequence is not completed and no charater is
+/// consumed from the queue.
+typedef AnsiHandler = bool Function(Queue<int>, Terminal);
+
+bool ansiHandler(Queue<int> queue, Terminal terminal) {
+  // The sequence isn't completed, just ignore it.
+  if (queue.isEmpty) {
+    return false;
+  }
 
-void ansiHandler(Queue<int> queue, Terminal terminal) {
   final charAfterEsc = queue.removeFirst();
 
   final handler = _ansiHandlers[charAfterEsc];
@@ -14,10 +22,16 @@ void ansiHandler(Queue<int> queue, Terminal terminal) {
     if (handler != csiHandler && handler != oscHandler) {
       terminal.debug.onEsc(charAfterEsc);
     }
-    return handler(queue, terminal);
+
+    final finished = handler(queue, terminal);
+    if (!finished) {
+      queue.addFirst(charAfterEsc);
+    }
+    return finished;
   }
 
   terminal.debug.onError('unsupported ansi sequence: $charAfterEsc');
+  return true;
 }
 
 final _ansiHandlers = <int, AnsiHandler>{
@@ -42,20 +56,30 @@ final _ansiHandlers = <int, AnsiHandler>{
 
 AnsiHandler _voidHandler(int sequenceLength) {
   return (queue, terminal) {
-    queue.take(sequenceLength);
+    if (queue.length < sequenceLength) {
+      return false;
+    }
+
+    for (var i = 0; i < sequenceLength; i++) {
+      queue.removeFirst();
+    }
+    return true;
   };
 }
 
-void _unsupportedHandler(Queue<int> queue, Terminal terminal) async {
+bool _unsupportedHandler(Queue<int> queue, Terminal terminal) {
   // print('unimplemented ansi sequence.');
+  return true;
 }
 
-void _ansiSaveCursorHandler(Queue<int> queue, Terminal terminal) {
+bool _ansiSaveCursorHandler(Queue<int> queue, Terminal terminal) {
   terminal.buffer.saveCursor();
+  return true;
 }
 
-void _ansiRestoreCursorHandler(Queue<int> queue, Terminal terminal) {
+bool _ansiRestoreCursorHandler(Queue<int> queue, Terminal terminal) {
   terminal.buffer.restoreCursor();
+  return true;
 }
 
 /// https://vt100.net/docs/vt100-ug/chapter3.html#IND IND – Index
@@ -65,12 +89,14 @@ void _ansiRestoreCursorHandler(Queue<int> queue, Terminal terminal) {
 /// This sequence 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 _ansiIndexHandler(Queue<int> queue, Terminal terminal) {
+bool _ansiIndexHandler(Queue<int> queue, Terminal terminal) {
   terminal.buffer.index();
+  return true;
 }
 
-void _ansiReverseIndexHandler(Queue<int> queue, Terminal terminal) {
+bool _ansiReverseIndexHandler(Queue<int> queue, Terminal terminal) {
   terminal.buffer.reverseIndex();
+  return true;
 }
 
 /// SCS – Select Character Set
@@ -80,16 +106,24 @@ void _ansiReverseIndexHandler(Queue<int> queue, Terminal terminal) {
 /// SO (shift in and shift out) respectively.
 AnsiHandler _scsHandler(int which) {
   return (Queue<int> queue, Terminal terminal) {
-    final name = String.fromCharCode(queue.removeFirst());
+    // The sequence isn't completed, just ignore it.
+    if (queue.isEmpty) {
+      return false;
+    }
+
+    final name = queue.removeFirst();
     terminal.buffer.charset.designate(which, name);
+    return true;
   };
 }
 
-void _ansiNextLineHandler(Queue<int> queue, Terminal terminal) {
+bool _ansiNextLineHandler(Queue<int> queue, Terminal terminal) {
   terminal.buffer.newLine();
   terminal.buffer.setCursorX(0);
+  return true;
 }
 
-void _ansiTabSetHandler(Queue<int> queue, Terminal terminal) {
+bool _ansiTabSetHandler(Queue<int> queue, Terminal terminal) {
   terminal.tabSetAtCursor();
+  return true;
 }

+ 4 - 4
lib/terminal/charset.dart

@@ -1,8 +1,8 @@
 typedef CharsetTranslator = int Function(int);
 
-const _charsets = <String, CharsetTranslator>{
-  '0': decSpecGraphicsTranslator,
-  'B': asciiTranslator,
+final _charsets = <int, CharsetTranslator>{
+  '0'.codeUnitAt(0): decSpecGraphicsTranslator,
+  'B'.codeUnitAt(0): asciiTranslator,
 };
 
 class Charset {
@@ -22,7 +22,7 @@ class Charset {
     return _cached(codePoint);
   }
 
-  void designate(int index, String name) {
+  void designate(int index, int name) {
     final charset = _charsets[name];
     if (charset != null) {
       _charsetMap[index] = charset;

+ 20 - 7
lib/terminal/csi.dart

@@ -57,10 +57,17 @@ CSI? _parseCsi(Queue<int> queue) {
   final paramBuffer = StringBuffer();
   final intermediates = <int>[];
 
-  while (queue.isNotEmpty) {
-    // TODO: handle special case when queue is empty as this time.
+  // Keep track of how many characters should be taken from the queue.
+  var readOffset = 0;
 
-    final char = queue.removeFirst();
+  while (true) {
+    // The sequence isn't completed, just ignore it.
+    if (queue.length <= readOffset) {
+      return null;
+    }
+
+    // final char = queue.removeFirst();
+    final char = queue.elementAt(readOffset++);
 
     if (char >= 0x30 && char <= 0x3F) {
       paramBuffer.writeCharCode(char);
@@ -76,6 +83,11 @@ CSI? _parseCsi(Queue<int> queue) {
     const csiMax = 0x7e;
 
     if (char >= csiMin && char <= csiMax) {
+      // The sequence is complete. So we consume it from the queue.
+      for (var i = 0; i < readOffset; i++) {
+        queue.removeFirst();
+      }
+
       final params = paramBuffer.toString().split(';');
       return CSI(
         params: params,
@@ -86,11 +98,11 @@ CSI? _parseCsi(Queue<int> queue) {
   }
 }
 
-void csiHandler(Queue<int> queue, Terminal terminal) {
+bool csiHandler(Queue<int> queue, Terminal terminal) {
   final csi = _parseCsi(queue);
 
   if (csi == null) {
-    return;
+    return false;
   }
 
   terminal.debug.onCsi(csi);
@@ -99,10 +111,11 @@ void csiHandler(Queue<int> queue, Terminal terminal) {
 
   if (handler != null) {
     handler(csi, terminal);
-    return;
+  } else {
+    terminal.debug.onError('unknown: $csi');
   }
 
-  terminal.debug.onError('unknown: $csi');
+  return true;
 }
 
 /// DECSED - Selective Erase In Display

+ 32 - 6
lib/terminal/osc.dart

@@ -8,12 +8,24 @@ import 'package:xterm/terminal/terminal.dart';
 //   return terminator.contains(codePoint);
 // }
 
-List<String> _parseOsc(Queue<int> queue, Set<int> terminators) {
+List<String>? _parseOsc(Queue<int> queue, Set<int> terminators) {
+  // TODO: add tests for cases such as incomplete sequence.
+
   final params = <String>[];
   final param = StringBuffer();
 
-  while (queue.isNotEmpty) {
-    final char = queue.removeFirst();
+  // Keep track of how many characters should be taken from the queue.
+  var readOffset = 0;
+
+  while (true) {
+    // The sequence isn't completed, just ignore it.
+    if (queue.length <= readOffset) {
+      return null;
+    }
+
+    final char = queue.elementAt(readOffset++);
+
+    // final char = queue.removeFirst();
 
     if (terminators.contains(char)) {
       params.add(param.toString());
@@ -30,20 +42,32 @@ List<String> _parseOsc(Queue<int> queue, Set<int> terminators) {
     param.writeCharCode(char);
   }
 
+  // The sequence is complete. So we consume it from the queue.
+  for (var i = 0; i < readOffset; i++) {
+    queue.removeFirst();
+  }
+
   return params;
 }
 
-void oscHandler(Queue<int> queue, Terminal terminal) {
+/// OSC - Operating System Command: sequence starting with ESC ] (7bit) or OSC
+/// (\x9D, 8bit)
+bool oscHandler(Queue<int> queue, Terminal terminal) {
   final params = _parseOsc(queue, terminal.platform.oscTerminators);
+
+  if (params == null) {
+    return false;
+  }
+
   terminal.debug.onOsc(params);
 
   if (params.isEmpty) {
     terminal.debug.onError('osc with no params');
-    return;
+    return true;
   }
 
   if (params.length < 2) {
-    return;
+    return true;
   }
 
   final ps = params[0];
@@ -60,4 +84,6 @@ void oscHandler(Queue<int> queue, Terminal terminal) {
     default:
       terminal.debug.onError('unknown osc ps: $ps');
   }
+
+  return true;
 }

+ 10 - 5
lib/terminal/terminal.dart

@@ -199,10 +199,7 @@ class Terminal with Observable {
     refresh();
   }
 
-  /// Writes data to the terminal. Special characters are interpreted. To write
-  /// terminal sequences, [Terminal.write] should be used instead. Writing a
-  /// terminal sequence by calling [Terminal.writeChar] multiple times is not
-  /// supported.
+  /// Writes data to the terminal. Special characters are interpreted.
   ///
   /// See also: [Buffer.writeChar]
   void writeChar(int codePoint) {
@@ -225,7 +222,15 @@ class Terminal with Observable {
       final char = _queue.removeFirst();
 
       if (char == esc) {
-        ansiHandler(_queue, this);
+        final finished = ansiHandler(_queue, this);
+
+        // Terminal sequence in the queue is not completed, and no charater is
+        // consumed.
+        if (!finished) {
+          _queue.addFirst(esc);
+          break;
+        }
+
         continue;
       }