Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions packages/termansi/lib/src/term.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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'}';
6 changes: 4 additions & 2 deletions packages/termlib/lib/src/extensions/term.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
4 changes: 4 additions & 0 deletions packages/termlib/lib/src/termlib_base.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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<String, String>;

Expand Down
8 changes: 8 additions & 0 deletions packages/termparser/lib/src/parser.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,14 @@ import 'events.dart';
import 'parsers.dart' as parsers;
import 'provider.dart';

/// we want <C-?> as <C-?>.
bool ctrlQuestionMarkQuirk = false;

/// in this case we want "return", not "enter". and instead of <C-j>
/// mapped to "enter", we want <C-j> to have <C-j>/<C-k> for vim
/// style navigation.
bool rawModeReturnQuirk = false;

/// The ANSI escape sequence parser
///
/// This class implements the ANSI escape sequence parser allowing to parse
Expand Down
46 changes: 34 additions & 12 deletions packages/termparser/lib/src/parsers.dart
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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 <C-j>
// mapped to "enter", we want <C-j> to have <C-j>/<C-k> 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)),
Expand All @@ -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),
Expand Down Expand Up @@ -144,18 +162,22 @@ Event _parseKeyboardEnhancedMode(List<String> 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();
}

Expand Down
2 changes: 1 addition & 1 deletion packages/termparser/lib/termparser.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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;
41 changes: 40 additions & 1 deletion packages/termparser/test/parser_test.dart
Original file line number Diff line number Diff line change
@@ -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';

Expand Down Expand Up @@ -646,4 +646,43 @@ void main() {
expect(parser.current, equals(const NameAndVersionEvent('term v1-234')));
});
});

group('special cases?', () {
test('<C-j> is mapped to <enter>', () {
final parser = Parser()..advance([0x0a]);
expect(parser.moveNext(), true);
expect(parser.current, const KeyEvent(KeyCode(name: KeyCodeName.enter)));
});

test('<C-j> is mapped to <C-j>', () {
rawModeReturnQuirk = true;
final parser = Parser()..advance([0x0a]);
expect(parser.moveNext(), true);
expect(parser.current, const KeyEvent(KeyCode(char: 'j'), modifiers: KeyModifiers(KeyModifiers.ctrl)));
});

test('<C-?> is mapped to <C-7> 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('<C-?> 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;
});
});
}