@@ -8,13 +8,19 @@ public typealias Selectable = CustomStringConvertible & Sendable & Equatable
8
8
@MainActor
9
9
final class FuzzySelectorView < T: Selectable > {
10
10
private let appearance : Appearance
11
+ private let outTTY : OutTTY
12
+ private let tty : TTY
11
13
private let viewState : ViewState < T >
12
14
13
15
init (
14
16
appearance: Appearance ,
17
+ outTTY: OutTTY ,
18
+ tty: TTY ,
15
19
viewState: ViewState < T >
16
20
) {
17
21
self . appearance = appearance
22
+ self . outTTY = outTTY
23
+ self . tty = tty
18
24
self . viewState = viewState
19
25
}
20
26
}
@@ -348,45 +354,38 @@ private extension FuzzySelectorView {
348
354
case ( true , true ) : return self . appearance. highlightedTextAttributes
349
355
}
350
356
}
351
- }
352
357
353
- @MainActor
354
- func write( _ strings: [ String ] ) {
355
- for string in strings {
356
- try ! FileHandle . standardOutput. write ( contentsOf: Data ( string. utf8) )
358
+ func write( _ strings: [ String ] ) {
359
+ self . outTTY. write ( strings)
357
360
}
358
- try ! FileHandle . standardOutput. synchronize ( )
359
- }
360
361
361
- @MainActor
362
- func write( _ string: String ) {
363
- write ( [ string] )
364
- }
362
+ func write( _ string: String ) {
363
+ self . write ( [ string] )
364
+ }
365
365
366
- @MainActor
367
- func outputCode( _ code: ANSIControlCode ) {
368
- write ( code. ansiCommand. message)
369
- }
366
+ func outputCode( _ code: ANSIControlCode ) {
367
+ self . write ( code. ansiCommand. message)
368
+ }
370
369
371
- @MainActor
372
- func outputCodes( _ codes: [ ANSIControlCode ] ) {
373
- write ( codes. map ( \. ansiCommand. message) )
374
- }
370
+ func outputCodes( _ codes: [ ANSIControlCode ] ) {
371
+ self . write ( codes. map ( \. ansiCommand. message) )
372
+ }
375
373
376
- @MainActor
377
- func withSavedCursorPosition< T> ( _ body: ( ) throws -> T ) rethrows -> T {
378
- outputCodes ( [
379
- . setCursorHidden( true ) ,
380
- . saveCursorPosition,
381
- ] )
382
- defer {
383
- outputCodes ( [
384
- . restoreCursorPosition,
385
- . setCursorHidden( false ) ,
374
+ @discardableResult
375
+ func withSavedCursorPosition< V> ( _ body: ( ) throws -> V ) rethrows -> V {
376
+ self . outputCodes ( [
377
+ . setCursorHidden( true ) ,
378
+ . saveCursorPosition,
386
379
] )
387
- }
380
+ defer {
381
+ self . outputCodes ( [
382
+ . restoreCursorPosition,
383
+ . setCursorHidden( false ) ,
384
+ ] )
385
+ }
388
386
389
- return try body ( )
387
+ return try body ( )
388
+ }
390
389
}
391
390
392
391
func setGraphicsModes( textAttributes: Set < Appearance . TextAttributes > ) -> [ SetGraphicsRendition ] {
@@ -441,7 +440,9 @@ public final class FuzzySelector<T: Selectable, E: Error, Seq> where Seq: AsyncS
441
440
private let choices : Seq
442
441
private let installSignalHandlers : Bool
443
442
private let multipleSelection : Bool
443
+ private let outTTY : OutTTY
444
444
private let tty : TTY
445
+ private let ttyHandle : FileHandle
445
446
private let view : FuzzySelectorView < T >
446
447
private let viewState : ViewState < T >
447
448
@@ -455,8 +456,19 @@ public final class FuzzySelector<T: Selectable, E: Error, Seq> where Seq: AsyncS
455
456
reverse: Bool = true
456
457
) {
457
458
let appearance = appearance ?? . default
458
- let terminalSize = TerminalSize . current ( )
459
- guard let tty = TTY ( fileHandle: STDIN_FILENO) else {
459
+ guard let terminalSize = TerminalSize . current ( ) else {
460
+ // TODO: error
461
+ return nil
462
+ }
463
+ let ttyHandle = FileHandle ( forReadingAtPath: " /dev/tty " ) !
464
+ guard let tty = TTY ( fileHandle: ttyHandle) else {
465
+ // TODO: error
466
+ return nil
467
+ }
468
+
469
+ guard let outTTYHandle = FileHandle ( forWritingAtPath: " /dev/tty " ) ,
470
+ let outTTY = OutTTY ( fileHandle: outTTYHandle)
471
+ else {
460
472
// TODO: error
461
473
return nil
462
474
}
@@ -472,8 +484,15 @@ public final class FuzzySelector<T: Selectable, E: Error, Seq> where Seq: AsyncS
472
484
self . choices = choices
473
485
self . installSignalHandlers = installSignalHandlers
474
486
self . multipleSelection = multipleSelection
487
+ self . outTTY = outTTY
475
488
self . tty = tty
476
- self . view = FuzzySelectorView ( appearance: appearance, viewState: viewState)
489
+ self . ttyHandle = ttyHandle
490
+ self . view = FuzzySelectorView (
491
+ appearance: appearance,
492
+ outTTY: outTTY,
493
+ tty: tty,
494
+ viewState: viewState
495
+ )
477
496
self . viewState = viewState
478
497
}
479
498
@@ -482,7 +501,7 @@ public final class FuzzySelector<T: Selectable, E: Error, Seq> where Seq: AsyncS
482
501
/// The `run` method consumes the `choices` sequence given in init and asynchronously returns the selected items.
483
502
public func run( ) async throws -> [ T ] {
484
503
let keyReader = KeyReader ( tty: tty)
485
- outputCodes ( [
504
+ self . view . outputCodes ( [
486
505
. setCursorHidden( true ) ,
487
506
. saveCursorPosition,
488
507
. saveScreen,
@@ -539,7 +558,7 @@ public final class FuzzySelector<T: Selectable, E: Error, Seq> where Seq: AsyncS
539
558
self . view. showFilter ( )
540
559
self . view. showStatus ( )
541
560
case . key( . down) :
542
- withSavedCursorPosition {
561
+ self . view . withSavedCursorPosition {
543
562
self . view. moveDown ( )
544
563
}
545
564
self . view. showFilter ( )
@@ -559,7 +578,7 @@ public final class FuzzySelector<T: Selectable, E: Error, Seq> where Seq: AsyncS
559
578
break eventLoop
560
579
case . key( . suspend) :
561
580
try self . tty. unsetRaw ( )
562
- outputCodes ( [
581
+ self . view . outputCodes ( [
563
582
. disableAlternativeBuffer,
564
583
. restoreScreen,
565
584
] )
@@ -570,7 +589,7 @@ public final class FuzzySelector<T: Selectable, E: Error, Seq> where Seq: AsyncS
570
589
case . key( . tab) :
571
590
if self . multipleSelection {
572
591
self . viewState. toggleCurrentSelection ( )
573
- withSavedCursorPosition {
592
+ self . view . withSavedCursorPosition {
574
593
self . view. moveDown ( )
575
594
self . view. redrawChoices ( )
576
595
}
@@ -582,20 +601,20 @@ public final class FuzzySelector<T: Selectable, E: Error, Seq> where Seq: AsyncS
582
601
self . view. showFilter ( )
583
602
self . view. showStatus ( )
584
603
case . key( . up) :
585
- withSavedCursorPosition {
604
+ self . view . withSavedCursorPosition {
586
605
self . view. moveUp ( )
587
606
}
588
607
self . view. showFilter ( )
589
608
self . view. showStatus ( )
590
609
case . key( nil ) : break
591
610
case let . choice( choices) :
592
611
self . viewState. addChoices ( choices)
593
- withSavedCursorPosition {
612
+ self . view . withSavedCursorPosition {
594
613
self . view. redrawChoices ( )
595
614
}
596
615
self . view. showStatus ( )
597
616
case . viewStateChanged:
598
- withSavedCursorPosition {
617
+ self . view . withSavedCursorPosition {
599
618
self . view. redrawChoices ( )
600
619
}
601
620
self . view. showStatus ( )
@@ -608,7 +627,8 @@ public final class FuzzySelector<T: Selectable, E: Error, Seq> where Seq: AsyncS
608
627
}
609
628
}
610
629
try self . tty. unsetRaw ( )
611
- outputCodes ( [
630
+
631
+ self . view. outputCodes ( [
612
632
. disableAlternativeBuffer,
613
633
. restoreScreen,
614
634
. restoreCursorPosition,
0 commit comments