@@ -13,6 +13,7 @@ struct TextPrompt {
1313 let collapseOnAnswer : Bool
1414 let renderer : Rendering
1515 let standardPipelines : StandardPipelines
16+ let keyStrokeListener : KeyStrokeListening
1617 let logger : Logger ?
1718 let validationRules : [ ValidatableRule ]
1819 let validator : InputValidating
@@ -26,39 +27,44 @@ struct TextPrompt {
2627 fatalError ( " ' \( prompt) ' can't be prompted in a non-interactive session. " )
2728 }
2829
29- var input = " "
30-
31- func isReturn( _ character: Character ) -> Bool {
32- #if os(Windows)
33- return character. unicodeScalars. first? . value == 10 || character. unicodeScalars. first? . value == 13
34- #else
35- return character == " \n "
36- #endif
37- }
30+ var input = [ Character] ( )
31+ var cursorIndex = 0
3832
3933 terminal. withoutCursor {
40- render ( input: input, errors: errors)
41- while let character = terminal. readCharacter ( ) , !isReturn( character) {
42- #if os(Windows)
43- // Handle Ctrl+C (character code 3)
44- // On Windows, Ctrl+C generates character code 3
45- // while "getch" is running it doesn't emit a signal
46- if character. unicodeScalars. first? . value == 3 {
47- exit ( 0 )
34+ render ( input: String ( input) , cursorIndex: cursorIndex, errors: errors)
35+ keyStrokeListener. listen ( terminal: terminal) { keyStroke in
36+ switch keyStroke {
37+ case . returnKey:
38+ return . abort
39+ case let . printable( character) :
40+ input. insert ( character, at: cursorIndex)
41+ cursorIndex += 1
42+ case . backspace:
43+ if cursorIndex > 0 {
44+ cursorIndex -= 1
45+ input. remove ( at: cursorIndex)
4846 }
49-
50- let isBackspace = character. unicodeScalars. first? . value == 8 || character. unicodeScalars. first? . value == 127
51- #else
52- let isBackspace = character == " \u{08} " || character == " \u{7F} "
53- #endif
54- if isBackspace { // Handle Backspace (Delete Last Character)
55- if !input. isEmpty {
56- input. removeLast ( ) // Remove last character from input
47+ case . delete:
48+ if cursorIndex < input. count {
49+ input. remove ( at: cursorIndex)
50+ }
51+ case . leftArrowKey:
52+ if cursorIndex > 0 {
53+ cursorIndex -= 1
5754 }
58- } else {
59- input. append ( character)
55+ case . rightArrowKey:
56+ if cursorIndex < input. count {
57+ cursorIndex += 1
58+ }
59+ case . home:
60+ cursorIndex = 0
61+ case . end:
62+ cursorIndex = input. count
63+ default :
64+ return . continue
6065 }
61- render ( input: input)
66+ render ( input: String ( input) , cursorIndex: cursorIndex)
67+ return . continue
6268 }
6369 }
6470
@@ -68,14 +74,14 @@ struct TextPrompt {
6874 if input. isEmpty, let defaultValue {
6975 resolvedInput = defaultValue
7076 } else {
71- resolvedInput = input
77+ resolvedInput = String ( input)
7278 }
7379
7480 let validationResult = validator. validate ( input: resolvedInput, rules: validationRules)
7581
7682 switch validationResult {
7783 case . success:
78- render ( input: input, withCursor: false )
84+ render ( input: String ( input) , cursorIndex : cursorIndex , withCursor: false )
7985 case let . failure( error) :
8086 return run ( errors: error. errors)
8187 }
@@ -87,7 +93,12 @@ struct TextPrompt {
8793 return resolvedInput
8894 }
8995
90- private func render( input: String , withCursor: Bool = true , errors: [ ValidatableError ] = [ ] ) {
96+ private func render(
97+ input: String ,
98+ cursorIndex: Int = 0 ,
99+ withCursor: Bool = true ,
100+ errors: [ ValidatableError ] = [ ]
101+ ) {
91102 let titleOffset = title != nil ? " " : " "
92103
93104 var message = " "
@@ -96,7 +107,14 @@ struct TextPrompt {
96107 . boldIfColoredTerminal ( terminal)
97108 }
98109
99- let inputDisplay = " \( input) \( withCursor ? " █ " : " " ) " . hexIfColoredTerminal ( theme. secondary, terminal)
110+ let inputDisplay : String
111+ if withCursor {
112+ let prefix = String ( input. prefix ( cursorIndex) )
113+ let suffix = String ( input. dropFirst ( cursorIndex) )
114+ inputDisplay = " \( prefix) █ \( suffix) " . hexIfColoredTerminal ( theme. secondary, terminal)
115+ } else {
116+ inputDisplay = input. hexIfColoredTerminal ( theme. secondary, terminal)
117+ }
100118
101119 message += " \( title != nil ? " \n " : " " ) \( titleOffset) \( prompt. formatted ( theme: theme, terminal: terminal) ) \( inputDisplay) "
102120
0 commit comments