diff --git a/packages/termansi/lib/src/term.dart b/packages/termansi/lib/src/term.dart index d4f42a0..c676527 100644 --- a/packages/termansi/lib/src/term.dart +++ b/packages/termansi/lib/src/term.dart @@ -79,6 +79,12 @@ abstract class Term { /// Stop receiving mouse events static String get disableMouseEvents => '$CSI?1000;1003;1006l'; + /// Start receiving mouse events inside default configuration zellij multiplexer + static String get enableZellijMouseEvents => _mouseModeCode(true); + + /// Stop receiving mouse events inside default configuration zellij multiplexer + static String get disableZellijMouseEvents => _mouseModeCode(false); + /// Start receiving mouse events as pixels static String get enableMousePixelEvents => '$CSI?1000;1003;1016h'; @@ -116,3 +122,5 @@ abstract class Term { // read window size in pixels // CSI 14 t } + +String _mouseModeCode(bool value) => '$CSI?1000;1002;1003;1006;1015${value ? 'h' : 'l'}'; diff --git a/packages/termlib/lib/src/extensions/term.dart b/packages/termlib/lib/src/extensions/term.dart index 69bba07..6a641b9 100644 --- a/packages/termlib/lib/src/extensions/term.dart +++ b/packages/termlib/lib/src/extensions/term.dart @@ -20,10 +20,12 @@ extension TermUtils on TermLib { void setTerminalTitle(String title) => write(ansi.Term.setTerminalTitle(title)); /// Start receiving mouse events - void enableMouseEvents() => write(ansi.Term.enableMouseEvents); + void enableMouseEvents() => + write(zellijMouseMotionQuirk ? ansi.Term.enableZellijMouseEvents : ansi.Term.enableMouseEvents); /// Stop receiving mouse events - void disableMouseEvents() => write(ansi.Term.disableMouseEvents); + void disableMouseEvents() => + write(zellijMouseMotionQuirk ? ansi.Term.disableZellijMouseEvents : ansi.Term.disableMouseEvents); /// Start receiving focus events void startFocusTracking() => write(ansi.Term.enableFocusTracking); diff --git a/packages/termlib/lib/src/termlib_base.dart b/packages/termlib/lib/src/termlib_base.dart index df15d99..e51a859 100644 --- a/packages/termlib/lib/src/termlib_base.dart +++ b/packages/termlib/lib/src/termlib_base.dart @@ -12,6 +12,10 @@ import './ffi/termos.dart'; import './readline.dart'; import './style.dart'; +/// Send additional control codes to have mouse motion codes in default zellij +/// configuration. +bool zellijMouseMotionQuirk = false; + /// Type similar to Platform.environment, used for dependency injection typedef EnvironmentData = Map; diff --git a/packages/termparser/lib/src/parser.dart b/packages/termparser/lib/src/parser.dart index 53470a8..adbe23e 100644 --- a/packages/termparser/lib/src/parser.dart +++ b/packages/termparser/lib/src/parser.dart @@ -3,6 +3,14 @@ import 'events.dart'; import 'parsers.dart' as parsers; import 'provider.dart'; +/// we want as . +bool ctrlQuestionMarkQuirk = false; + +/// in this case we want "return", not "enter". and instead of +/// mapped to "enter", we want to have / for vim +/// style navigation. +bool rawModeReturnQuirk = false; + /// The ANSI escape sequence parser /// /// This class implements the ANSI escape sequence parser allowing to parse diff --git a/packages/termparser/lib/src/parsers.dart b/packages/termparser/lib/src/parsers.dart index 202b013..0324331 100644 --- a/packages/termparser/lib/src/parsers.dart +++ b/packages/termparser/lib/src/parsers.dart @@ -1,5 +1,7 @@ import 'dart:convert'; +import 'package:termparser/src/parser.dart'; + import 'events.dart'; import 'events_types.dart'; import 'extensions/string_extension.dart'; @@ -16,6 +18,18 @@ Event? parseChar(String char, {bool escO = false}) { _ => const KeyEvent(KeyCode()) // none }; } + + // in this case we want "return", not "enter". and instead of + // mapped to "enter", we want to have / for vim + // style navigation. + if (rawModeReturnQuirk) { + switch (char) { + case '\r': + return const KeyEvent(KeyCode(name: KeyCodeName.enter)); + case '\n': + return _ctrlOrKey(char); + } + } return switch (char) { '\r' || '\n' => const KeyEvent(KeyCode(name: KeyCodeName.enter)), '\t' => const KeyEvent(KeyCode(name: KeyCodeName.tab)), @@ -29,6 +43,10 @@ Event? parseChar(String char, {bool escO = false}) { KeyEvent _ctrlOrKey(String char) { final code = char.codeUnitAt(0); return switch (code) { + 0x1F when ctrlQuestionMarkQuirk => const KeyEvent( + KeyCode(char: '?'), + modifiers: KeyModifiers(KeyModifiers.ctrl), + ), >= 0x01 && <= 0x1A => KeyEvent( KeyCode(char: String.fromCharCode(code - 0x01 + 0x61)), modifiers: const KeyModifiers(KeyModifiers.ctrl), @@ -144,18 +162,22 @@ Event _parseKeyboardEnhancedMode(List parameters, String char) { final c = StringExtension.tryFromCharCode(codePoint); if (c == null) return const NoneEvent(); - keyCode = switch (codePoint) { - 0x1b => const KeyCode(name: KeyCodeName.escape), - 0xd => const KeyCode(name: KeyCodeName.enter), - // if the terminal is in raw mode, the enter key sends \r - // we need to handle this case. How to receive the raw mode status? - 0xa => const KeyCode(name: KeyCodeName.enter), - 0x9 => modifiers.has(KeyModifiers.shift) - ? const KeyCode(name: KeyCodeName.backTab) - : const KeyCode(name: KeyCodeName.tab), - 0x7f => const KeyCode(name: KeyCodeName.backSpace), - _ => KeyCode(char: String.fromCharCode(codePoint)) - }; + if (rawModeReturnQuirk && codePoint == 0xa) { + keyCode = KeyCode(char: String.fromCharCode(codePoint)); + } else { + keyCode = switch (codePoint) { + 0x1b => const KeyCode(name: KeyCodeName.escape), + 0xd => const KeyCode(name: KeyCodeName.enter), + // if the terminal is in raw mode, the enter key sends \r + // we need to handle this case. How to receive the raw mode status? + 0xa => const KeyCode(name: KeyCodeName.enter), + 0x9 => modifiers.has(KeyModifiers.shift) + ? const KeyCode(name: KeyCodeName.backTab) + : const KeyCode(name: KeyCodeName.tab), + 0x7f => const KeyCode(name: KeyCodeName.backSpace), + _ => KeyCode(char: String.fromCharCode(codePoint)) + }; + } stateFromKeyCode = KeyEventState.none(); } diff --git a/packages/termparser/lib/termparser.dart b/packages/termparser/lib/termparser.dart index 0fd957d..5e3feec 100644 --- a/packages/termparser/lib/termparser.dart +++ b/packages/termparser/lib/termparser.dart @@ -10,4 +10,4 @@ /// consistent state even if the input data is not complete. library; -export 'src/parser.dart' show Parser; +export 'src/parser.dart' show Parser, ctrlQuestionMarkQuirk, rawModeReturnQuirk; diff --git a/packages/termparser/test/parser_test.dart b/packages/termparser/test/parser_test.dart index f6ab19e..1d1a300 100644 --- a/packages/termparser/test/parser_test.dart +++ b/packages/termparser/test/parser_test.dart @@ -1,6 +1,6 @@ import 'dart:convert'; -import 'package:termparser/termparser.dart'; +import 'package:termparser/src/parser.dart'; import 'package:termparser/termparser_events.dart'; import 'package:test/test.dart'; @@ -646,4 +646,43 @@ void main() { expect(parser.current, equals(const NameAndVersionEvent('term v1-234'))); }); }); + + group('special cases?', () { + test(' is mapped to ', () { + final parser = Parser()..advance([0x0a]); + expect(parser.moveNext(), true); + expect(parser.current, const KeyEvent(KeyCode(name: KeyCodeName.enter))); + }); + + test(' is mapped to ', () { + rawModeReturnQuirk = true; + final parser = Parser()..advance([0x0a]); + expect(parser.moveNext(), true); + expect(parser.current, const KeyEvent(KeyCode(char: 'j'), modifiers: KeyModifiers(KeyModifiers.ctrl))); + }); + + test(' is mapped to by default (why?)', () { + final parser = Parser()..advance([0x1f]); + expect(parser.moveNext(), true); + expect( + parser.current, + const KeyEvent(KeyCode(char: '7'), modifiers: KeyModifiers(KeyModifiers.ctrl)), + ); + }); + + test(' is mapped properly with quirk active', () { + ctrlQuestionMarkQuirk = true; + final parser = Parser()..advance([0x1f]); + expect(parser.moveNext(), true); + expect( + parser.current, + const KeyEvent(KeyCode(char: '?'), modifiers: KeyModifiers(KeyModifiers.ctrl)), + ); + }); + + tearDown(() { + rawModeReturnQuirk = false; + ctrlQuestionMarkQuirk = false; + }); + }); }