Sfoglia il codice sorgente

Merge remote-tracking branch 'origin/master' into feature/reflow

devmil 4 anni fa
parent
commit
59056001d3

+ 17 - 2
lib/buffer/buffer.dart

@@ -106,7 +106,7 @@ class Buffer {
   }
 
   void newLine() {
-    if (terminal.lineFeed == false) {
+    if (terminal.newLineMode) {
       setCursorX(0);
     }
 
@@ -128,6 +128,10 @@ class Buffer {
   }
 
   List<BufferLine> getVisibleLines() {
+    if (height < terminal.viewHeight) {
+      return lines.toList();
+    }
+
     final result = <BufferLine>[];
 
     for (var i = height - terminal.viewHeight; i < height; i++) {
@@ -220,7 +224,13 @@ class Buffer {
     }
   }
 
-  /// https://vt100.net/docs/vt100-ug/chapter3.html#IND
+  /// 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) {
@@ -231,13 +241,18 @@ class Buffer {
       return;
     }
 
+    // the cursor is not in the scrollable region
     if (_cursorY >= terminal.viewHeight - 1) {
+      // we are ait the bottom so a new line is created.
       lines.add(BufferLine());
+
+      // clean extra lines if needed.
       final maxLines = terminal.maxLines;
       if (maxLines != null && lines.length > maxLines) {
         lines.removeRange(0, lines.length - maxLines);
       }
     } else {
+      // there're still lines so we simply move cursor down.
       moveCursorY(1);
     }
   }

+ 59 - 52
lib/frontend/terminal_view.dart

@@ -19,7 +19,7 @@ import 'package:xterm/terminal/terminal.dart';
 import 'package:xterm/theme/terminal_style.dart';
 import 'package:xterm/utli/hash_values.dart';
 
-typedef ResizeHandler = void Function(int width, int height);
+typedef TerminalResizeHandler = void Function(int width, int height);
 
 class TerminalView extends StatefulWidget {
   TerminalView({
@@ -38,7 +38,7 @@ class TerminalView extends StatefulWidget {
         super(key: key ?? ValueKey(terminal));
 
   final Terminal terminal;
-  final ResizeHandler? onResize;
+  final TerminalResizeHandler? onResize;
   final FocusNode focusNode;
   final bool autofocus;
   final ScrollController scrollController;
@@ -48,6 +48,7 @@ class TerminalView extends StatefulWidget {
 
   final InputBehavior inputBehavior;
 
+  // get the dimensions of a rendered character
   CellSize measureCellSize() {
     final testString = 'xxxxxxxxxx' * 1000;
 
@@ -87,6 +88,7 @@ class TerminalView extends StatefulWidget {
 }
 
 class _TerminalViewState extends State<TerminalView> {
+  /// blinking cursor and blinking character
   final oscillator = Oscillator.ms(600);
 
   bool get focused {
@@ -95,42 +97,41 @@ class _TerminalViewState extends State<TerminalView> {
 
   int? _lastTerminalWidth;
   int? _lastTerminalHeight;
-  late CellSize _cellSize;
-  late ViewportOffset _offset;
 
-  var _minScrollExtent = 0.0;
-  var _maxScrollExtent = 0.0;
+  late CellSize _cellSize;
 
   void onTerminalChange() {
-    // if (_offset != null) {
-    //   final currentScrollExtent =
-    //       _cellSize.cellHeight * widget.terminal.buffer.scrollOffsetFromTop;
+    final currentScrollExtent =
+        _cellSize.cellHeight * widget.terminal.buffer.scrollOffsetFromTop;
 
-    //   if (_offset.pixels != currentScrollExtent) {
-    //     _offset.correctBy(currentScrollExtent - _offset.pixels - 1);
-    //   }
-    // }
+    widget.scrollController.jumpTo(currentScrollExtent);
 
     if (mounted) {
       setState(() {});
     }
   }
 
-  void onTick() {
-    widget.terminal.refresh();
-  }
+  // listen to oscillator to update mouse blink etc.
+  // void onTick() {
+  //   widget.terminal.refresh();
+  // }
 
   @override
   void initState() {
     // oscillator.start();
     // oscillator.addListener(onTick);
+
+    // measureCellSize is expensive so we cache the result.
     _cellSize = widget.measureCellSize();
+
     widget.terminal.addListener(onTerminalChange);
+
     super.initState();
   }
 
   @override
   void didUpdateWidget(TerminalView oldWidget) {
+    oldWidget.terminal.removeListener(onTerminalChange);
     widget.terminal.addListener(onTerminalChange);
     super.didUpdateWidget(oldWidget);
   }
@@ -141,7 +142,6 @@ class _TerminalViewState extends State<TerminalView> {
     // oscillator.removeListener(onTick);
 
     widget.terminal.removeListener(onTerminalChange);
-
     super.dispose();
   }
 
@@ -160,29 +160,32 @@ class _TerminalViewState extends State<TerminalView> {
         cursor: SystemMouseCursors.text,
         child: LayoutBuilder(builder: (context, constraints) {
           onResize(constraints.maxWidth, constraints.maxHeight);
-          return Scrollable(
-            viewportBuilder: (context, offset) {
-              offset.applyViewportDimension(constraints.maxHeight);
-
-              _minScrollExtent = 0.0;
-
-              _maxScrollExtent = math.max(
-                  0.0,
-                  _cellSize.cellHeight * widget.terminal.buffer.height -
-                      constraints.maxHeight);
-
-              // final currentScrollExtent = _cellSize.cellHeight *
-              //     widget.terminal.buffer.scrollOffsetFromTop;
+          // use flutter's Scrollable to manage scrolling to better integrate
+          // with widgets such as Scrollbar.
+          return NotificationListener<UserScrollNotification>(
+            onNotification: (_) {
+              onScroll(_.metrics.pixels);
+              return false;
+            },
+            child: Scrollable(
+              controller: widget.scrollController,
+              viewportBuilder: (context, offset) {
+                // set viewport height.
+                offset.applyViewportDimension(constraints.maxHeight);
 
-              // offset.correctBy(currentScrollExtent - offset.pixels - 1);
+                final minScrollExtent = 0.0;
 
-              offset.applyContentDimensions(_minScrollExtent, _maxScrollExtent);
+                final maxScrollExtent = math.max(
+                    0.0,
+                    _cellSize.cellHeight * widget.terminal.buffer.height -
+                        constraints.maxHeight);
 
-              _offset = offset;
-              _offset.addListener(onScroll);
+                // set how much the terminal can scroll
+                offset.applyContentDimensions(minScrollExtent, maxScrollExtent);
 
-              return buildTerminal(context);
-            },
+                return buildTerminal(context);
+              },
+            ),
           );
         }),
       ),
@@ -272,7 +275,7 @@ class _TerminalViewState extends State<TerminalView> {
 
   void onKeyStroke(RawKeyEvent event) {
     widget.inputBehavior.onKeyStroke(event, widget.terminal);
-    _offset.moveTo(_maxScrollExtent);
+    widget.terminal.buffer.setScrollOffsetFromBottom(0);
   }
 
   void onFocus(bool focused) {
@@ -285,10 +288,14 @@ class _TerminalViewState extends State<TerminalView> {
     widget.inputBehavior.onAction(action, widget.terminal);
   }
 
-  void onScroll() {
-    final charOffset = (_offset.pixels / _cellSize.cellHeight).ceil();
-    final offset = widget.terminal.invisibleHeight - charOffset;
-    widget.terminal.buffer.setScrollOffsetFromBottom(offset);
+  // synchronize flutter scroll offset to terminal
+  void onScroll(double offset) {
+    final topOffset = (offset / _cellSize.cellHeight).ceil();
+    final bottomOffset = widget.terminal.invisibleHeight - topOffset;
+
+    setState(() {
+      widget.terminal.buffer.setScrollOffsetFromBottom(bottomOffset);
+    });
   }
 }
 
@@ -309,21 +316,21 @@ class TerminalPainter extends CustomPainter {
 
   @override
   void paint(Canvas canvas, Size size) {
-    paintBackground(canvas);
+    _paintBackground(canvas);
 
     // if (oscillator.value) {
     // }
 
     if (terminal.showCursor) {
-      paintCursor(canvas);
+      _paintCursor(canvas);
     }
 
-    paintText(canvas);
+    _paintText(canvas);
 
-    paintSelection(canvas);
+    _paintSelection(canvas);
   }
 
-  void paintBackground(Canvas canvas) {
+  void _paintBackground(Canvas canvas) {
     final lines = terminal.getVisibleLines();
 
     for (var i = 0; i < lines.length; i++) {
@@ -358,7 +365,7 @@ class TerminalPainter extends CustomPainter {
     }
   }
 
-  void paintSelection(Canvas canvas) {
+  void _paintSelection(Canvas canvas) {
     for (var y = 0; y < terminal.viewHeight; y++) {
       final offsetY = y * charSize.cellHeight;
       final absoluteY = terminal.buffer.convertViewLineToRawLine(y) -
@@ -392,7 +399,7 @@ class TerminalPainter extends CustomPainter {
     }
   }
 
-  void paintText(Canvas canvas) {
+  void _paintText(Canvas canvas) {
     final lines = terminal.getVisibleLines();
 
     for (var i = 0; i < lines.length; i++) {
@@ -408,12 +415,12 @@ class TerminalPainter extends CustomPainter {
         }
 
         final offsetX = i * charSize.cellWidth;
-        paintCell(canvas, cell, offsetX, offsetY);
+        _paintCell(canvas, cell, offsetX, offsetY);
       }
     }
   }
 
-  void paintCell(Canvas canvas, Cell cell, double offsetX, double offsetY) {
+  void _paintCell(Canvas canvas, Cell cell, double offsetX, double offsetY) {
     final attr = cell.attr!;
 
     if (cell.codePoint == null || attr.invisible) {
@@ -462,7 +469,7 @@ class TerminalPainter extends CustomPainter {
     tp.paint(canvas, Offset(offsetX, offsetY));
   }
 
-  void paintCursor(Canvas canvas) {
+  void _paintCursor(Canvas canvas) {
     final screenCursorY = terminal.cursorY + terminal.scrollOffset;
     if (screenCursorY < 0 || screenCursorY >= terminal.viewHeight) {
       return;
@@ -484,7 +491,7 @@ class TerminalPainter extends CustomPainter {
 
   @override
   bool shouldRepaint(CustomPainter oldDelegate) {
-    // print('shouldRepaint');
+    /// paint only when the terminal has changed since last paint.
     return terminal.dirty;
   }
 }

+ 14 - 2
lib/terminal/ansi.dart

@@ -32,8 +32,8 @@ final _ansiHandlers = <int, AnsiHandler>{
   'P'.codeUnitAt(0): _unsupportedHandler, // Sixel
   'c'.codeUnitAt(0): _unsupportedHandler,
   '#'.codeUnitAt(0): _unsupportedHandler,
-  '('.codeUnitAt(0): _scsHandler(0), //  G0
-  ')'.codeUnitAt(0): _scsHandler(1), //  G1
+  '('.codeUnitAt(0): _scsHandler(0), //  SCS - G0
+  ')'.codeUnitAt(0): _scsHandler(1), //  SCS - G1
   '*'.codeUnitAt(0): _voidHandler(1), // TODO: G2 (vt220)
   '+'.codeUnitAt(0): _voidHandler(1), // TODO: G3 (vt220)
   '>'.codeUnitAt(0): _voidHandler(0), // TODO: Normal Keypad
@@ -58,6 +58,13 @@ void _ansiRestoreCursorHandler(Queue<int> queue, Terminal terminal) {
   terminal.buffer.restoreCursor();
 }
 
+/// https://vt100.net/docs/vt100-ug/chapter3.html#IND IND – Index
+///
+/// ESC D  
+///
+/// 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) {
   terminal.buffer.index();
 }
@@ -66,6 +73,11 @@ void _ansiReverseIndexHandler(Queue<int> queue, Terminal terminal) {
   terminal.buffer.reverseIndex();
 }
 
+/// SCS – Select Character Set
+///
+/// The appropriate G0 and G1 character sets are designated from one of the five
+/// possible character sets. The G0 and G1 sets are invoked by the codes SI and
+/// SO (shift in and shift out) respectively.
 AnsiHandler _scsHandler(int which) {
   return (Queue<int> queue, Terminal terminal) {
     final name = String.fromCharCode(queue.removeFirst());

+ 12 - 1
lib/terminal/csi.dart

@@ -15,7 +15,7 @@ final _csiHandlers = <int, CsiSequenceHandler>{
   'l'.codeUnitAt(0): csiModeHandler,
   'm'.codeUnitAt(0): sgrHandler,
   'n'.codeUnitAt(0): csiDeviceStatusReportHandler,
-  'r'.codeUnitAt(0): csiSetMarginsHandler,
+  'r'.codeUnitAt(0): csiSetMarginsHandler, // DECSTBM
   't'.codeUnitAt(0): csiWindowManipulation,
   'A'.codeUnitAt(0): csiCursorUpHandler,
   'B'.codeUnitAt(0): csiCursorDownHandler,
@@ -266,6 +266,17 @@ void csiCursorDownHandler(CSI csi, Terminal terminal) {
   terminal.buffer.movePosition(0, distance);
 }
 
+/// DECSTBM – Set Top and Bottom Margins (DEC Private) 
+///
+/// ESC [ Pn; Pn r
+///
+/// This sequence sets the top and bottom margins to define the scrolling
+/// region. The first parameter is the line number of the first line in the
+/// scrolling region; the second parameter is the line number of the bottom line
+/// in the scrolling region. Default is the en tire screen (no margins). The
+/// minimum size of the scrolling region allowed is two lines, i.e., the top
+/// margin must be less than the bottom margin. The cursor is placed in the home
+/// position (see Origin Mode DECOM).  
 void csiSetMarginsHandler(CSI csi, Terminal terminal) {
   var top = 1;
   var bottom = terminal.viewHeight;

+ 19 - 12
lib/terminal/sbc.dart

@@ -2,18 +2,25 @@ import 'package:xterm/terminal/terminal.dart';
 
 typedef SbcHandler = void Function(int, Terminal);
 
-final sbcHandlers = <int, SbcHandler>{
-  0x05: _voidHandler,
-  0x07: _bellHandler,
-  0x08: _backspaceReturnHandler,
-  0x09: _tabHandler,
-  0x0a: _newLineHandler,
-  0x0b: _newLineHandler,
-  0x0c: _newLineHandler,
-  0x0d: _carriageReturnHandler,
-  0x0e: _shiftOutHandler,
-  0x0f: _shiftInHandler,
-};
+const sbcMaxCodepoint = 0x0f;
+
+final sbcHandlers = _sbcHandlers();
+
+// build single byte character lookup table
+List<SbcHandler?> _sbcHandlers() {
+  final result = List<SbcHandler?>.filled(sbcMaxCodepoint + 1, null);
+  result[0x05] = _voidHandler;
+  result[0x07] = _bellHandler;
+  result[0x08] = _backspaceReturnHandler;
+  result[0x09] = _tabHandler;
+  result[0x0a] = _newLineHandler;
+  result[0x0b] = _newLineHandler;
+  result[0x0c] = _newLineHandler;
+  result[0x0d] = _carriageReturnHandler;
+  result[0x0e] = _shiftOutHandler;
+  result[0x0f] = _shiftInHandler;
+  return result;
+}
 
 void _bellHandler(int code, Terminal terminal) {
   terminal.onBell();

+ 51 - 17
lib/terminal/terminal.dart

@@ -46,8 +46,6 @@ class Terminal with Observable {
     _buffer = _mainBuffer;
 
     tabs.reset();
-
-    // _buffer.write('this is magic!');
   }
 
   bool _dirty = false;
@@ -75,7 +73,7 @@ class Terminal with Observable {
   int get visibleHeight => min(_viewHeight, buffer.height);
   int get invisibleHeight => buffer.height - visibleHeight;
 
-  /// Insert/Replace Mode (IRM)
+  /// ### Insert/Replace Mode (IRM)
   ///
   /// The terminal displays received characters at the cursor position.
   /// Insert/Replace mode determines how the terminal adds characters to the
@@ -86,12 +84,10 @@ class Terminal with Observable {
   /// You can set or reset insert/replace mode as follows.
   bool _replaceMode = true;
 
-  bool _lineFeed = true;
   bool _screenMode = false; // DECSCNM (black on white background)
   bool _autoWrapMode = true;
-  bool _bracketedPasteMode = false;
 
-  /// DECOM – Origin Mode (DEC Private)
+  /// ### DECOM – Origin Mode (DEC Private)
   ///
   /// This is a private parameter applicable to set mode (SM) and reset mode
   /// (RM) control sequences. The reset state causes the origin to be at the
@@ -113,16 +109,49 @@ class Terminal with Observable {
   bool get originMode => _originMode;
   bool _originMode = false;
 
-  bool get lineFeed => _lineFeed;
-  bool get newLineMode => !_lineFeed;
+  /// ### LNM – Line Feed/New Line Mode
+  ///
+  /// This is a parameter applicable to set mode (SM) and reset mode (RM)
+  /// control sequences. The reset state causes the interpretation of the line
+  /// feed (LF), defined in ANSI Standard X3.4-1977, to imply only vertical
+  /// movement of the active position and causes the RETURN key (CR) to send the
+  /// single code CR. The set state causes the LF to imply movement to the first
+  /// position of the following line and causes the RETURN key to send the two
+  /// codes (CR, LF). This is the New Line (NL) option.
+  ///
+  /// This mode does not affect the index (IND), or next line (NEL) format
+  /// effectors.
+  bool get lineFeedMode => _lineFeedMode;
+  bool _lineFeedMode = true;
+
+  /// See: [lineFeedMode]
+  bool get newLineMode => !_lineFeedMode;
+
+  /// ### Bracketed Paste Mode
+  ///
+  /// When bracketed paste mode is set, pasted text is bracketed with control
+  /// sequences so that the program can differentiate pasted text from typed-in
+  /// text. When bracketed paste mode is set, the program will receive: `ESC
+  /// [200 ~`, followed by the pasted text, followed by `ESC [ 201 ~`.
   bool get bracketedPasteMode => _bracketedPasteMode;
+  bool _bracketedPasteMode = false;
 
   bool _showCursor = true;
-  bool _applicationCursorKeys = false;
-  bool _blinkingCursor = true;
-
   bool get showCursor => _showCursor;
+
+  /// DECCKM – Cursor Keys Mode (DEC Private)
+  ///
+  /// This is a private parameter applicable to set mode (SM) and reset mode
+  /// (RM) control sequences. This mode is only effective when the terminal is
+  /// in keypad application mode (see DECKPAM) and the ANSI/VT52 mode (DECANM)
+  /// is set (see DECANM). Under these conditions, if the cursor key mode is
+  /// reset, the four cursor function keys will send ANSI cursor control
+  /// commands. If cursor key mode is set, the four cursor function keys will
+  /// send application functions.
   bool get applicationCursorKeys => _applicationCursorKeys;
+  bool _applicationCursorKeys = false;
+
+  bool _blinkingCursor = true;
   bool get blinkingCursor => _blinkingCursor;
 
   late Buffer _buffer;
@@ -195,14 +224,19 @@ class Terminal with Observable {
   }
 
   void _processChar(int codePoint) {
-    final sbcHandler = sbcHandlers[codePoint];
+    // If the character doesn't have special effect. Write it directly to the
+    // buffer.
+    if (codePoint > sbcMaxCodepoint) {
+      debug.onChar(codePoint);
+      _buffer.writeChar(codePoint);
+      return;
+    }
 
+    // The character may have special effect.
+    final sbcHandler = sbcHandlers[codePoint];
     if (sbcHandler != null) {
       debug.onSbc(codePoint);
       sbcHandler(codePoint, this);
-    } else {
-      debug.onChar(codePoint);
-      _buffer.writeChar(codePoint);
     }
   }
 
@@ -253,11 +287,11 @@ class Terminal with Observable {
   }
 
   void setNewLineMode() {
-    _lineFeed = false;
+    _lineFeedMode = false;
   }
 
   void setLineFeedMode() {
-    _lineFeed = true;
+    _lineFeedMode = true;
   }
 
   void setMouseMode(MouseMode mode) {