@@ -6,6 +6,9 @@ public static partial class Program
66{
77 private static void HandleInput ( AppState state )
88 {
9+ bool appendedInputInBatch = false ;
10+ int pastedLineBreaksInBatch = 0 ;
11+
912 while ( Console . KeyAvailable )
1013 {
1114 ConsoleKeyInfo key = Console . ReadKey ( intercept : true ) ;
@@ -22,6 +25,11 @@ private static void HandleInput(AppState state)
2225 return ;
2326 }
2427
28+ if ( TrySkipLineFeedAfterCarriageReturn ( state , key ) )
29+ {
30+ continue ;
31+ }
32+
2533 if ( key . Key == ConsoleKey . C &&
2634 key . Modifiers . HasFlag ( ConsoleModifiers . Control ) )
2735 {
@@ -52,17 +60,45 @@ private static void HandleInput(AppState state)
5260
5361 if ( IsEnterKey ( key ) )
5462 {
63+ if ( IsMultilineEnterKey ( key ) )
64+ {
65+ AppendInputLineBreak ( state , key ) ;
66+ appendedInputInBatch = true ;
67+ continue ;
68+ }
69+
70+ if ( IsLikelyPastedLineBreak ( key , appendedInputInBatch , pastedLineBreaksInBatch ) )
71+ {
72+ AppendInputLineBreak ( state , key ) ;
73+ appendedInputInBatch = true ;
74+ pastedLineBreaksInBatch ++ ;
75+ continue ;
76+ }
77+
5578 SubmitInput ( state ) ;
5679 return ;
5780 }
5881
5982 if ( ! char . IsControl ( key . KeyChar ) )
6083 {
6184 state . Input . Append ( key . KeyChar ) ;
85+ state . SkipNextInputLineFeed = false ;
86+ appendedInputInBatch = true ;
6287 }
6388 }
6489 }
6590
91+ private static bool TrySkipLineFeedAfterCarriageReturn ( AppState state , ConsoleKeyInfo key )
92+ {
93+ if ( ! state . SkipNextInputLineFeed )
94+ {
95+ return false ;
96+ }
97+
98+ state . SkipNextInputLineFeed = false ;
99+ return key . KeyChar == '\n ' ;
100+ }
101+
66102 private static bool IsBackspaceKey ( ConsoleKeyInfo key )
67103 {
68104 return key . Key == ConsoleKey . Backspace ||
@@ -75,6 +111,26 @@ private static bool IsEnterKey(ConsoleKeyInfo key)
75111 key . KeyChar is '\r ' or '\n ' ;
76112 }
77113
114+ private static bool IsMultilineEnterKey ( ConsoleKeyInfo key )
115+ {
116+ return IsEnterKey ( key ) &&
117+ ( key . Modifiers . HasFlag ( ConsoleModifiers . Shift ) ||
118+ key . Modifiers . HasFlag ( ConsoleModifiers . Control ) ||
119+ IsShiftKeyPressed ( ) ||
120+ IsControlKeyPressed ( ) ) ;
121+ }
122+
123+ private static bool IsLikelyPastedLineBreak (
124+ ConsoleKeyInfo key ,
125+ bool appendedInputInBatch ,
126+ int pastedLineBreaksInBatch )
127+ {
128+ return IsEnterKey ( key ) &&
129+ ( HasBufferedInputAfterDelay ( PasteContinuationReadTimeoutMilliseconds ) ||
130+ pastedLineBreaksInBatch > 0 ||
131+ ( appendedInputInBatch && key . KeyChar == '\n ' ) ) ;
132+ }
133+
78134 private static bool IsEscapeKey ( ConsoleKeyInfo key )
79135 {
80136 return key . Key == ConsoleKey . Escape ||
@@ -242,6 +298,12 @@ private static void ConsumeSs3Input(AppState state)
242298
243299 private static void HandleCsiKeySequence ( AppState state , string sequence )
244300 {
301+ if ( sequence == "200~" )
302+ {
303+ ConsumeBracketedPasteInput ( state ) ;
304+ return ;
305+ }
306+
245307 if ( TryDispatchModalTerminalKeySequence ( state , sequence ) )
246308 {
247309 return ;
@@ -252,6 +314,12 @@ private static void HandleCsiKeySequence(AppState state, string sequence)
252314
253315 private static void HandleTerminalKeySequence ( AppState state , string sequence )
254316 {
317+ if ( IsMultilineEnterTerminalSequence ( sequence ) )
318+ {
319+ AppendInputLineBreak ( state ) ;
320+ return ;
321+ }
322+
255323 switch ( sequence )
256324 {
257325 case "A" :
@@ -282,6 +350,96 @@ private static void HandleTerminalKeySequence(AppState state, string sequence)
282350 }
283351 }
284352
353+ private static bool IsMultilineEnterTerminalSequence ( string sequence )
354+ {
355+ return sequence is "13;2u" or "13;4u" or "13;5u" or "13;6u" or "13;7u" or "13;8u" ;
356+ }
357+
358+ private static void AppendInputLineBreak ( AppState state )
359+ {
360+ AppendInputLineBreak ( state , default ) ;
361+ }
362+
363+ private static void AppendInputLineBreak ( AppState state , ConsoleKeyInfo key )
364+ {
365+ if ( state . ActiveModal is null )
366+ {
367+ state . Input . Append ( '\n ' ) ;
368+ }
369+
370+ state . SkipNextInputLineFeed = key . KeyChar == '\r ' ;
371+ }
372+
373+ private static void AppendInputText ( AppState state , string text )
374+ {
375+ if ( state . ActiveModal is not null || string . IsNullOrEmpty ( text ) )
376+ {
377+ return ;
378+ }
379+
380+ string normalized = text
381+ . Replace ( "\r \n " , "\n " , StringComparison . Ordinal )
382+ . Replace ( '\r ' , '\n ' ) ;
383+
384+ state . Input . Append ( normalized ) ;
385+ state . SkipNextInputLineFeed = false ;
386+ }
387+
388+ private static void ConsumeBracketedPasteInput ( AppState state )
389+ {
390+ StringBuilder pastedText = new ( ) ;
391+
392+ while ( TryReadBufferedKey ( out ConsoleKeyInfo key ) )
393+ {
394+ if ( key . KeyChar == '\u001b ' &&
395+ TryConsumeBracketedPasteTerminator ( pastedText ) )
396+ {
397+ AppendInputText ( state , pastedText . ToString ( ) ) ;
398+ return ;
399+ }
400+
401+ pastedText . Append ( key . KeyChar ) ;
402+ }
403+
404+ AppendInputText ( state , pastedText . ToString ( ) ) ;
405+ }
406+
407+ private static bool TryConsumeBracketedPasteTerminator ( StringBuilder pastedText )
408+ {
409+ if ( ! TryReadBufferedKey ( out ConsoleKeyInfo prefixKey ) )
410+ {
411+ pastedText . Append ( '\u001b ' ) ;
412+ return false ;
413+ }
414+
415+ if ( prefixKey . KeyChar != '[' )
416+ {
417+ pastedText . Append ( '\u001b ' ) ;
418+ pastedText . Append ( prefixKey . KeyChar ) ;
419+ return false ;
420+ }
421+
422+ StringBuilder sequence = new ( ) ;
423+ while ( TryReadBufferedKey ( out ConsoleKeyInfo sequenceKey ) )
424+ {
425+ sequence . Append ( sequenceKey . KeyChar ) ;
426+ if ( IsAnsiFinalByte ( sequenceKey . KeyChar ) )
427+ {
428+ break ;
429+ }
430+ }
431+
432+ if ( sequence . ToString ( ) == "201~" )
433+ {
434+ return true ;
435+ }
436+
437+ pastedText . Append ( '\u001b ' ) ;
438+ pastedText . Append ( '[' ) ;
439+ pastedText . Append ( sequence ) ;
440+ return false ;
441+ }
442+
285443 private static bool TryDispatchModalTerminalKeySequence ( AppState state , string sequence )
286444 {
287445 if ( state . ActiveModal is null )
@@ -332,6 +490,23 @@ private static bool TryReadBufferedKey(out ConsoleKeyInfo key)
332490 return true ;
333491 }
334492
493+ private static bool HasBufferedInputAfterDelay ( int timeoutMilliseconds )
494+ {
495+ DateTimeOffset deadline = DateTimeOffset . UtcNow . AddMilliseconds ( timeoutMilliseconds ) ;
496+
497+ while ( ! Console . KeyAvailable )
498+ {
499+ if ( DateTimeOffset . UtcNow >= deadline )
500+ {
501+ return false ;
502+ }
503+
504+ Thread . Sleep ( 1 ) ;
505+ }
506+
507+ return true ;
508+ }
509+
335510 private static bool IsAnsiFinalByte ( char character )
336511 {
337512 return character is >= '@' and <= '~' ;
0 commit comments