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
38 changes: 36 additions & 2 deletions lib/src/backend/win32_ansi_stdin.dart
Original file line number Diff line number Diff line change
Expand Up @@ -54,21 +54,39 @@ class Win32AnsiStdin extends Stream<List<int>> implements Stdin {
void startEventLoop() {
if (_running) return;
_running = true;
// Dart's stdin.lineMode = false clears ENABLE_PROCESSED_INPUT, which is
// what makes Windows generate SIGINT for Ctrl+C.
_ensureProcessedInput();
_eventLoop();
}

void _ensureProcessedInput() {
final modePtr = calloc<Uint32>();
try {
if (_getConsoleMode(_inputHandle, modePtr) != 0) {
_setConsoleMode(_inputHandle, modePtr.value | _enableProcessedInput);
}
} finally {
calloc.free(modePtr);
}
}
Comment thread
FXschwartz marked this conversation as resolved.

Future<void> _eventLoop() async {
final pInputRecord = calloc<_InputRecord>();
final pEventsRead = calloc<Uint32>();

try {
while (_running) {
// Yield to Dart event loop
await Future.delayed(Duration.zero);
if (!_running) break;

// Bounded wait keeps the Dart event loop responsive. Without this,
// ReadConsoleInputW parks the isolate until the next keystroke,
// starving timers and signal handlers.
final waitResult = _waitForSingleObject(_inputHandle, _pollIntervalMs);
if (!_running) break;
if (waitResult != _waitObject0) continue;

// Read one input event
final result =
_readConsoleInputW(_inputHandle, pInputRecord, 1, pEventsRead);
if (result != 0 && pEventsRead.value > 0) {
Expand Down Expand Up @@ -377,10 +395,17 @@ class Win32AnsiStdin extends Stream<List<int>> implements Stdin {

// Windows API Constants
const int _stdInputHandle = -10;
const int _enableProcessedInput = 0x0001;
const int _enableMouseInput = 0x0010;
const int _enableExtendedFlags = 0x0080;
const int _enableQuickEditMode = 0x0040;

// WaitForSingleObject return value: object is signaled.
const int _waitObject0 = 0x00000000;

// 16ms poll keeps the input loop responsive.
const int _pollIntervalMs = 16;

// Event types
const int _keyEvent = 0x0001;
const int _mouseEvent = 0x0002;
Expand Down Expand Up @@ -494,6 +519,11 @@ typedef _ReadConsoleInputDart = int Function(
int nLength,
Pointer<Uint32> lpNumberOfEventsRead);

typedef _WaitForSingleObjectNative = Uint32 Function(
IntPtr hHandle, Uint32 dwMilliseconds);
typedef _WaitForSingleObjectDart = int Function(
int hHandle, int dwMilliseconds);

final _kernel32 = DynamicLibrary.open('kernel32.dll');

final _getStdHandle = _kernel32
Expand All @@ -510,3 +540,7 @@ final _setConsoleMode =
final _readConsoleInputW =
_kernel32.lookupFunction<_ReadConsoleInputNative, _ReadConsoleInputDart>(
'ReadConsoleInputW');

final _waitForSingleObject = _kernel32.lookupFunction<
_WaitForSingleObjectNative,
_WaitForSingleObjectDart>('WaitForSingleObject');
18 changes: 6 additions & 12 deletions lib/src/utils/nocterm_paths.dart
Original file line number Diff line number Diff line change
Expand Up @@ -28,22 +28,16 @@ String getNoctermDirectory() {
}

String getProjectDirectory() {
final start = Directory.current.path;
var parent = Directory.current;
while (true) {
final newParent = parent.parent;

if (newParent == parent) {
throw StateError('Could not determine project directory');
}

final pubspec = File(p.join(parent.path, 'pubspec.yaml'));
if (pubspec.existsSync()) {
return parent.path;
}
if (newParent.path == '/') {
return '/';
}
if (pubspec.existsSync()) return parent.path;

final newParent = parent.parent;
// Compare paths, not Directory identity — terminates at Windows drive
// roots where parent.parent returns the same path.
if (newParent.path == parent.path) return start;
parent = newParent;
}
}
Comment thread
FXschwartz marked this conversation as resolved.
Expand Down
48 changes: 48 additions & 0 deletions test/utils/nocterm_paths_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import 'dart:io';

import 'package:nocterm/src/utils/nocterm_paths.dart';
import 'package:path/path.dart' as p;
import 'package:test/test.dart';

void main() {
group('getProjectDirectory', () {
late Directory tempRoot;
late Directory previousCwd;

setUp(() {
previousCwd = Directory.current;
tempRoot = Directory.systemTemp.createTempSync('nocterm_paths_test_');
});

tearDown(() {
Directory.current = previousCwd;
if (tempRoot.existsSync()) tempRoot.deleteSync(recursive: true);
});

test('returns the closest ancestor containing pubspec.yaml', () {
final project = Directory(p.join(tempRoot.path, 'project'))..createSync();
File(p.join(project.path, 'pubspec.yaml')).writeAsStringSync('name: x');
final nested = Directory(p.join(project.path, 'a', 'b'))
..createSync(recursive: true);
Directory.current = nested;

expect(getProjectDirectory(), equals(project.path));
});

test('returns cwd when no pubspec.yaml ancestor exists', () {
final dir = Directory(p.join(tempRoot.path, 'a', 'b', 'c'))
..createSync(recursive: true);
Directory.current = dir;

expect(getProjectDirectory(), equals(dir.path));
});

test('terminates when walking past the filesystem root', () {
final dir = Directory(p.join(tempRoot.path, 'deep', 'no', 'project'))
..createSync(recursive: true);
Directory.current = dir;

expect(getProjectDirectory(), equals(dir.path));
});
});
}
Loading