-
-
Notifications
You must be signed in to change notification settings - Fork 99
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix(mason_logger): arrow keys on windows (#1061)
- Loading branch information
Showing
11 changed files
with
437 additions
and
157 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
// coverage:ignore-file | ||
|
||
import 'dart:io'; | ||
|
||
import 'package:mason_logger/src/ffi/unix_terminal.dart'; | ||
import 'package:mason_logger/src/ffi/windows_terminal.dart'; | ||
|
||
/// {@template terminal} | ||
/// Interface for the underlying native terminal. | ||
/// {@endterminal} | ||
abstract class Terminal { | ||
/// {@macro terminal} | ||
factory Terminal() => Platform.isWindows ? WindowsTerminal() : UnixTerminal(); | ||
|
||
/// Enables raw mode which allows us to process each keypress as it comes in. | ||
/// https://viewsourcecode.org/snaptoken/kilo/02.enteringRawMode.html | ||
void enableRawMode(); | ||
|
||
/// Disables raw mode and restores the terminal’s original attributes. | ||
void disableRawMode(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
// coverage:ignore-file | ||
// ignore_for_file: public_member_api_docs, constant_identifier_names, camel_case_types, non_constant_identifier_names, lines_longer_than_80_chars | ||
|
||
import 'dart:ffi'; | ||
import 'dart:io'; | ||
|
||
import 'package:ffi/ffi.dart'; | ||
import 'package:mason_logger/src/ffi/terminal.dart'; | ||
|
||
class UnixTerminal implements Terminal { | ||
UnixTerminal() { | ||
_lib = Platform.isMacOS | ||
? DynamicLibrary.open('/usr/lib/libSystem.dylib') | ||
: DynamicLibrary.open('libc.so.6'); | ||
|
||
_tcgetattr = _lib.lookupFunction<TCGetAttrNative, TCGetAttrDart>( | ||
'tcgetattr', | ||
); | ||
_tcsetattr = _lib.lookupFunction<TCSetAttrNative, TCSetAttrDart>( | ||
'tcsetattr', | ||
); | ||
|
||
_origTermIOSPointer = calloc<TermIOS>(); | ||
_tcgetattr(_STDIN_FILENO, _origTermIOSPointer); | ||
} | ||
|
||
late final DynamicLibrary _lib; | ||
late final Pointer<TermIOS> _origTermIOSPointer; | ||
late final TCGetAttrDart _tcgetattr; | ||
late final TCSetAttrDart _tcsetattr; | ||
|
||
@override | ||
void enableRawMode() { | ||
final origTermIOS = _origTermIOSPointer.ref; | ||
final newTermIOSPointer = calloc<TermIOS>() | ||
..ref.c_iflag = | ||
origTermIOS.c_iflag & ~(_BRKINT | _ICRNL | _INPCK | _ISTRIP | _IXON) | ||
..ref.c_oflag = origTermIOS.c_oflag & ~_OPOST | ||
..ref.c_cflag = (origTermIOS.c_cflag & ~_CSIZE) | _CS8 | ||
..ref.c_lflag = origTermIOS.c_lflag & ~(_ECHO | _ICANON | _IEXTEN | _ISIG) | ||
..ref.c_cc = origTermIOS.c_cc | ||
..ref.c_cc[_VMIN] = 0 | ||
..ref.c_cc[_VTIME] = 1 | ||
..ref.c_ispeed = origTermIOS.c_ispeed | ||
..ref.c_oflag = origTermIOS.c_ospeed; | ||
|
||
_tcsetattr(_STDIN_FILENO, _TCSANOW, newTermIOSPointer); | ||
calloc.free(newTermIOSPointer); | ||
} | ||
|
||
@override | ||
void disableRawMode() { | ||
if (nullptr == _origTermIOSPointer.cast()) return; | ||
_tcsetattr(_STDIN_FILENO, _TCSANOW, _origTermIOSPointer); | ||
} | ||
} | ||
|
||
// Input Modes | ||
// https://ftp.gnu.org/old-gnu/Manuals/glibc-2.2.3/html_node/libc_352.html | ||
const int _BRKINT = 0x00000002; | ||
const int _INPCK = 0x00000010; | ||
const int _ISTRIP = 0x00000020; | ||
const int _ICRNL = 0x00000100; | ||
const int _IXON = 0x00000200; | ||
|
||
// Output Modes | ||
// https://ftp.gnu.org/old-gnu/Manuals/glibc-2.2.3/html_node/libc_353.html#SEC362 | ||
const int _OPOST = 0x00000001; | ||
|
||
// Control Modes | ||
// https://ftp.gnu.org/old-gnu/Manuals/glibc-2.2.3/html_node/libc_354.html#SEC363 | ||
const int _CSIZE = 0x00000300; | ||
const int _CS8 = 0x00000300; | ||
|
||
// Local Modes | ||
// https://ftp.gnu.org/old-gnu/Manuals/glibc-2.2.3/html_node/libc_355.html#SEC364 | ||
const int _ECHO = 0x00000008; | ||
const int _ISIG = 0x00000080; | ||
const int _ICANON = 0x00000100; | ||
const int _IEXTEN = 0x00000400; | ||
const int _TCSANOW = 0; | ||
const int _VMIN = 16; | ||
const int _VTIME = 17; | ||
|
||
typedef tcflag_t = UnsignedLong; | ||
typedef cc_t = UnsignedChar; | ||
typedef speed_t = UnsignedLong; | ||
|
||
// The default standard input file descriptor number which is 0. | ||
const _STDIN_FILENO = 0; | ||
|
||
// The number of elements in the control chars array. | ||
const _NCSS = 20; | ||
|
||
class TermIOS extends Struct { | ||
@tcflag_t() | ||
external int c_iflag; // input flags | ||
@tcflag_t() | ||
external int c_oflag; // output flags | ||
@tcflag_t() | ||
external int c_cflag; // control flags | ||
@tcflag_t() | ||
external int c_lflag; // local flags | ||
@Array(_NCSS) | ||
external Array<cc_t> c_cc; // control chars | ||
@speed_t() | ||
external int c_ispeed; // input speed | ||
@speed_t() | ||
external int c_ospeed; // output speed | ||
} | ||
|
||
// int tcgetattr(int, struct termios *); | ||
typedef TCGetAttrNative = Int32 Function( | ||
Int32 fildes, | ||
Pointer<TermIOS> termios, | ||
); | ||
typedef TCGetAttrDart = int Function(int fildes, Pointer<TermIOS> termios); | ||
|
||
// int tcsetattr(int, int, const struct termios *); | ||
typedef TCSetAttrNative = Int32 Function( | ||
Int32 fildes, | ||
Int32 optional_actions, | ||
Pointer<TermIOS> termios, | ||
); | ||
typedef TCSetAttrDart = int Function( | ||
int fildes, | ||
int optional_actions, | ||
Pointer<TermIOS> termios, | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
// coverage:ignore-file | ||
// ignore_for_file: public_member_api_docs | ||
|
||
import 'package:mason_logger/src/ffi/terminal.dart'; | ||
import 'package:win32/win32.dart'; | ||
|
||
class WindowsTerminal implements Terminal { | ||
WindowsTerminal() { | ||
outputHandle = GetStdHandle(STD_OUTPUT_HANDLE); | ||
inputHandle = GetStdHandle(STD_INPUT_HANDLE); | ||
} | ||
|
||
late final int inputHandle; | ||
late final int outputHandle; | ||
|
||
@override | ||
void enableRawMode() { | ||
const dwMode = (~ENABLE_ECHO_INPUT) & | ||
(~ENABLE_PROCESSED_INPUT) & | ||
(~ENABLE_LINE_INPUT) & | ||
(~ENABLE_WINDOW_INPUT); | ||
SetConsoleMode(inputHandle, dwMode); | ||
} | ||
|
||
@override | ||
void disableRawMode() { | ||
const dwMode = ENABLE_ECHO_INPUT | | ||
ENABLE_EXTENDED_FLAGS | | ||
ENABLE_INSERT_MODE | | ||
ENABLE_LINE_INPUT | | ||
ENABLE_MOUSE_INPUT | | ||
ENABLE_PROCESSED_INPUT | | ||
ENABLE_QUICK_EDIT_MODE | | ||
ENABLE_VIRTUAL_TERMINAL_INPUT; | ||
SetConsoleMode(inputHandle, dwMode); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
import 'dart:async'; | ||
import 'package:mason_logger/src/ffi/terminal.dart'; | ||
import 'package:mason_logger/src/io.dart' as io; | ||
|
||
const _asyncRunZoned = runZoned; | ||
|
||
/// This class facilitates overriding terminal utilities. | ||
/// It should be extended by another class in client code with overrides | ||
/// that construct a custom implementation. | ||
abstract class TerminalOverrides { | ||
static final _token = Object(); | ||
|
||
/// Returns the current [TerminalOverrides] instance. | ||
/// | ||
/// This will return `null` if the current [Zone] does not contain | ||
/// any [TerminalOverrides]. | ||
/// | ||
/// See also: | ||
/// * [TerminalOverrides.runZoned] to provide [TerminalOverrides] | ||
/// in a fresh [Zone]. | ||
/// | ||
static TerminalOverrides? get current { | ||
return Zone.current[_token] as TerminalOverrides?; | ||
} | ||
|
||
/// Runs [body] in a fresh [Zone] using the provided overrides. | ||
static R runZoned<R>( | ||
R Function() body, { | ||
io.KeyStroke Function()? readKey, | ||
Terminal Function()? createTerminal, | ||
}) { | ||
final overrides = _StdinOverridesScope(readKey, createTerminal); | ||
return _asyncRunZoned(body, zoneValues: {_token: overrides}); | ||
} | ||
|
||
/// The function used to read key strokes from stdin. | ||
io.KeyStroke Function() get readKey => io.readKey; | ||
|
||
/// The function used to create a [Terminal] instance. | ||
Terminal Function() get createTerminal => Terminal.new; | ||
} | ||
|
||
class _StdinOverridesScope extends TerminalOverrides { | ||
_StdinOverridesScope(this._readKey, this._createTerminal); | ||
|
||
final TerminalOverrides? _previous = TerminalOverrides.current; | ||
final io.KeyStroke Function()? _readKey; | ||
final Terminal Function()? _createTerminal; | ||
|
||
@override | ||
io.KeyStroke Function() get readKey { | ||
return _readKey ?? _previous?.readKey ?? super.readKey; | ||
} | ||
|
||
@override | ||
Terminal Function() get createTerminal { | ||
return _createTerminal ?? _previous?.createTerminal ?? super.createTerminal; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.