2 * LICENSE: GPL - See COPYING in the top level directory
3 * PROJECT: ReactOS Console Server DLL
4 * FILE: consrv/lineinput.c
5 * PURPOSE: Console line input functions
6 * PROGRAMMERS: Jeffrey Morlan
9 /* INCLUDES *******************************************************************/
19 ConvertInputAnsiToUnicode(PCONSRV_CONSOLE Console
,
24 PUSHORT TargetLength
);
26 ConvertInputUnicodeToAnsi(PCONSRV_CONSOLE Console
,
31 /*P*/USHORT TargetLength
);
35 HistoryAddEntry(PCONSRV_CONSOLE Console
,
36 PUNICODE_STRING ExeName
,
37 PUNICODE_STRING Entry
);
39 HistoryRecallHistory(PCONSRV_CONSOLE Console
,
40 PUNICODE_STRING ExeName
,
42 PUNICODE_STRING Entry
);
44 HistoryGetCurrentEntry(PCONSRV_CONSOLE Console
,
45 PUNICODE_STRING ExeName
,
46 PUNICODE_STRING Entry
);
48 HistoryDeleteCurrentBuffer(PCONSRV_CONSOLE Console
,
49 PUNICODE_STRING ExeName
);
51 HistoryFindEntryByPrefix(PCONSRV_CONSOLE Console
,
52 PUNICODE_STRING ExeName
,
53 PUNICODE_STRING Prefix
,
54 PUNICODE_STRING Entry
);
57 /* PRIVATE FUNCTIONS **********************************************************/
60 LineInputSetPos(PCONSRV_CONSOLE Console
,
63 if (Pos
!= Console
->LinePos
&& GetConsoleInputBufferMode(Console
) & ENABLE_ECHO_INPUT
)
65 PCONSOLE_SCREEN_BUFFER Buffer
= Console
->ActiveBuffer
;
66 SHORT OldCursorX
= Buffer
->CursorPosition
.X
;
67 SHORT OldCursorY
= Buffer
->CursorPosition
.Y
;
68 INT XY
= OldCursorY
* Buffer
->ScreenBufferSize
.X
+ OldCursorX
;
70 XY
+= (Pos
- Console
->LinePos
);
73 else if (XY
>= Buffer
->ScreenBufferSize
.Y
* Buffer
->ScreenBufferSize
.X
)
74 XY
= Buffer
->ScreenBufferSize
.Y
* Buffer
->ScreenBufferSize
.X
- 1;
76 Buffer
->CursorPosition
.X
= XY
% Buffer
->ScreenBufferSize
.X
;
77 Buffer
->CursorPosition
.Y
= XY
/ Buffer
->ScreenBufferSize
.X
;
78 TermSetScreenInfo(Console
, Buffer
, OldCursorX
, OldCursorY
);
81 Console
->LinePos
= Pos
;
85 LineInputEdit(PCONSRV_CONSOLE Console
,
90 PTEXTMODE_SCREEN_BUFFER ActiveBuffer
;
91 UINT Pos
= Console
->LinePos
;
92 UINT NewSize
= Console
->LineSize
- NumToDelete
+ NumToInsert
;
95 if (GetType(Console
->ActiveBuffer
) != TEXTMODE_BUFFER
) return;
96 ActiveBuffer
= (PTEXTMODE_SCREEN_BUFFER
)Console
->ActiveBuffer
;
98 /* Make sure there's always enough room for ending \r\n */
99 if (NewSize
+ 2 > Console
->LineMaxSize
)
102 memmove(&Console
->LineBuffer
[Pos
+ NumToInsert
],
103 &Console
->LineBuffer
[Pos
+ NumToDelete
],
104 (Console
->LineSize
- (Pos
+ NumToDelete
)) * sizeof(WCHAR
));
105 memcpy(&Console
->LineBuffer
[Pos
], Insertion
, NumToInsert
* sizeof(WCHAR
));
107 if (GetConsoleInputBufferMode(Console
) & ENABLE_ECHO_INPUT
)
109 for (i
= Pos
; i
< NewSize
; i
++)
111 TermWriteStream(Console
, ActiveBuffer
, &Console
->LineBuffer
[i
], 1, TRUE
);
113 for (; i
< Console
->LineSize
; i
++)
115 TermWriteStream(Console
, ActiveBuffer
, L
" ", 1, TRUE
);
117 Console
->LinePos
= i
;
120 Console
->LineSize
= NewSize
;
121 LineInputSetPos(Console
, Pos
+ NumToInsert
);
126 LineInputRecallHistory(PCONSRV_CONSOLE Console
,
127 PUNICODE_STRING ExeName
,
130 PHISTORY_BUFFER Hist
= HistoryCurrentBuffer(Console
, ExeName
);
133 if (!Hist
|| Hist
->NumEntries
== 0) return;
135 Position
= Hist
->Position
+ Offset
;
136 Position
= min(max(Position
, 0), Hist
->NumEntries
- 1);
137 Hist
->Position
= Position
;
139 LineInputSetPos(Console
, 0);
140 LineInputEdit(Console
, Console
->LineSize
,
141 Hist
->Entries
[Hist
->Position
].Length
/ sizeof(WCHAR
),
142 Hist
->Entries
[Hist
->Position
].Buffer
);
146 LineInputRecallHistory(PCONSRV_CONSOLE Console
,
147 PUNICODE_STRING ExeName
,
150 UNICODE_STRING Entry
;
152 if (!HistoryRecallHistory(Console
, ExeName
, Offset
, &Entry
)) return;
154 LineInputSetPos(Console
, 0);
155 LineInputEdit(Console
, Console
->LineSize
,
156 Entry
.Length
/ sizeof(WCHAR
),
163 PPOPUP_WINDOW Popup
= NULL
;
166 HistoryDisplayCurrentHistory(PCONSRV_CONSOLE Console
,
167 PUNICODE_STRING ExeName
);
170 LineInputKeyDown(PCONSRV_CONSOLE Console
,
171 PUNICODE_STRING ExeName
,
172 KEY_EVENT_RECORD
*KeyEvent
)
174 UINT Pos
= Console
->LinePos
;
175 UNICODE_STRING Entry
;
178 * First, deal with control keys...
181 switch (KeyEvent
->wVirtualKeyCode
)
185 /* Clear entire line */
186 LineInputSetPos(Console
, 0);
187 LineInputEdit(Console
, Console
->LineSize
, 0, NULL
);
192 DestroyPopupWindow(Popup
);
200 /* Move to start of line. With CTRL, erase everything left of cursor */
201 LineInputSetPos(Console
, 0);
202 if (KeyEvent
->dwControlKeyState
& (LEFT_CTRL_PRESSED
| RIGHT_CTRL_PRESSED
))
203 LineInputEdit(Console
, Pos
, 0, NULL
);
209 /* Move to end of line. With CTRL, erase everything right of cursor */
210 if (KeyEvent
->dwControlKeyState
& (LEFT_CTRL_PRESSED
| RIGHT_CTRL_PRESSED
))
211 LineInputEdit(Console
, Console
->LineSize
- Pos
, 0, NULL
);
213 LineInputSetPos(Console
, Console
->LineSize
);
219 /* Move left. With CTRL, move to beginning of previous word */
220 if (KeyEvent
->dwControlKeyState
& (LEFT_CTRL_PRESSED
| RIGHT_CTRL_PRESSED
))
222 while (Pos
> 0 && Console
->LineBuffer
[Pos
- 1] == L
' ') Pos
--;
223 while (Pos
> 0 && Console
->LineBuffer
[Pos
- 1] != L
' ') Pos
--;
229 LineInputSetPos(Console
, Pos
);
236 /* Move right. With CTRL, move to beginning of next word */
237 if (KeyEvent
->dwControlKeyState
& (LEFT_CTRL_PRESSED
| RIGHT_CTRL_PRESSED
))
239 while (Pos
< Console
->LineSize
&& Console
->LineBuffer
[Pos
] != L
' ') Pos
++;
240 while (Pos
< Console
->LineSize
&& Console
->LineBuffer
[Pos
] == L
' ') Pos
++;
241 LineInputSetPos(Console
, Pos
);
245 /* Recall one character (but don't overwrite current line) */
246 HistoryGetCurrentEntry(Console
, ExeName
, &Entry
);
247 if (Pos
< Console
->LineSize
)
248 LineInputSetPos(Console
, Pos
+ 1);
249 else if (Pos
* sizeof(WCHAR
) < Entry
.Length
)
250 LineInputEdit(Console
, 0, 1, &Entry
.Buffer
[Pos
]);
257 /* Toggle between insert and overstrike */
258 Console
->LineInsertToggle
= !Console
->LineInsertToggle
;
259 TermSetCursorInfo(Console
, Console
->ActiveBuffer
);
265 /* Remove character to right of cursor */
266 if (Pos
!= Console
->LineSize
)
267 LineInputEdit(Console
, 1, 0, NULL
);
273 /* Recall first history entry */
274 LineInputRecallHistory(Console
, ExeName
, -((WORD
)-1));
280 /* Recall last history entry */
281 LineInputRecallHistory(Console
, ExeName
, +((WORD
)-1));
289 * Recall previous history entry. On first time, actually recall the
290 * current (usually last) entry; on subsequent times go back.
292 LineInputRecallHistory(Console
, ExeName
, Console
->LineUpPressed
? -1 : 0);
293 Console
->LineUpPressed
= TRUE
;
299 /* Recall next history entry */
300 LineInputRecallHistory(Console
, ExeName
, +1);
306 /* Recall remainder of current history entry */
307 HistoryGetCurrentEntry(Console
, ExeName
, &Entry
);
308 if (Pos
* sizeof(WCHAR
) < Entry
.Length
)
310 UINT InsertSize
= (Entry
.Length
/ sizeof(WCHAR
) - Pos
);
311 UINT DeleteSize
= min(Console
->LineSize
- Pos
, InsertSize
);
312 LineInputEdit(Console
, DeleteSize
, InsertSize
, &Entry
.Buffer
[Pos
]);
319 /* Insert a ^Z character */
320 KeyEvent
->uChar
.UnicodeChar
= 26;
326 if (KeyEvent
->dwControlKeyState
& (LEFT_ALT_PRESSED
| RIGHT_ALT_PRESSED
))
327 HistoryDeleteCurrentBuffer(Console
, ExeName
);
330 if (Popup
) DestroyPopupWindow(Popup
);
331 Popup
= HistoryDisplayCurrentHistory(Console
, ExeName
);
338 UNICODE_STRING EntryFound
;
340 Entry
.Length
= Console
->LinePos
* sizeof(WCHAR
); // == Pos * sizeof(WCHAR)
341 Entry
.Buffer
= Console
->LineBuffer
;
343 if (HistoryFindEntryByPrefix(Console
, ExeName
, &Entry
, &EntryFound
))
345 LineInputEdit(Console
, Console
->LineSize
- Pos
,
346 EntryFound
.Length
/ sizeof(WCHAR
) - Pos
,
347 &EntryFound
.Buffer
[Pos
]);
348 /* Cursor stays where it was */
349 LineInputSetPos(Console
, Pos
);
356 PHISTORY_BUFFER Hist
;
359 /* Search for history entries starting with input. */
360 Hist
= HistoryCurrentBuffer(Console
, ExeName
);
361 if (!Hist
|| Hist
->NumEntries
== 0) return;
364 * Like Up/F5, on first time start from current (usually last) entry,
365 * but on subsequent times start at previous entry.
367 if (Console
->LineUpPressed
)
368 Hist
->Position
= (Hist
->Position
? Hist
->Position
: Hist
->NumEntries
) - 1;
369 Console
->LineUpPressed
= TRUE
;
371 Entry
.Length
= Console
->LinePos
* sizeof(WCHAR
); // == Pos * sizeof(WCHAR)
372 Entry
.Buffer
= Console
->LineBuffer
;
375 * Keep going backwards, even wrapping around to the end,
376 * until we get back to starting point.
378 HistPos
= Hist
->Position
;
381 if (RtlPrefixUnicodeString(&Entry
, &Hist
->Entries
[HistPos
], FALSE
))
383 Hist
->Position
= HistPos
;
384 LineInputEdit(Console
, Console
->LineSize
- Pos
,
385 Hist
->Entries
[HistPos
].Length
/ sizeof(WCHAR
) - Pos
,
386 &Hist
->Entries
[HistPos
].Buffer
[Pos
]);
387 /* Cursor stays where it was */
388 LineInputSetPos(Console
, Pos
);
391 if (--HistPos
< 0) HistPos
+= Hist
->NumEntries
;
392 } while (HistPos
!= Hist
->Position
);
403 * OK, we deal with normal keys, we can continue...
406 if (KeyEvent
->uChar
.UnicodeChar
== L
'\b' && GetConsoleInputBufferMode(Console
) & ENABLE_PROCESSED_INPUT
)
408 /* backspace handling - if processed input enabled then we handle it here
409 * otherwise we treat it like a normal char. */
412 LineInputSetPos(Console
, Pos
- 1);
413 LineInputEdit(Console
, 1, 0, NULL
);
416 else if (KeyEvent
->uChar
.UnicodeChar
== L
'\r')
418 Entry
.Length
= Entry
.MaximumLength
= Console
->LineSize
* sizeof(WCHAR
);
419 Entry
.Buffer
= Console
->LineBuffer
;
420 HistoryAddEntry(Console
, ExeName
, &Entry
);
422 /* TODO: Expand aliases */
423 DPRINT1("TODO: Expand aliases\n");
425 LineInputSetPos(Console
, Console
->LineSize
);
426 Console
->LineBuffer
[Console
->LineSize
++] = L
'\r';
427 if (GetConsoleInputBufferMode(Console
) & ENABLE_ECHO_INPUT
)
429 if (GetType(Console
->ActiveBuffer
) == TEXTMODE_BUFFER
)
431 TermWriteStream(Console
, (PTEXTMODE_SCREEN_BUFFER
)(Console
->ActiveBuffer
), L
"\r", 1, TRUE
);
436 * Add \n if processed input. There should usually be room for it,
437 * but an exception to the rule exists: the buffer could have been
438 * pre-filled with LineMaxSize - 1 characters.
440 if (GetConsoleInputBufferMode(Console
) & ENABLE_PROCESSED_INPUT
&&
441 Console
->LineSize
< Console
->LineMaxSize
)
443 Console
->LineBuffer
[Console
->LineSize
++] = L
'\n';
444 if (GetConsoleInputBufferMode(Console
) & ENABLE_ECHO_INPUT
)
446 if (GetType(Console
->ActiveBuffer
) == TEXTMODE_BUFFER
)
448 TermWriteStream(Console
, (PTEXTMODE_SCREEN_BUFFER
)(Console
->ActiveBuffer
), L
"\n", 1, TRUE
);
452 Console
->LineComplete
= TRUE
;
453 Console
->LinePos
= 0;
455 else if (KeyEvent
->uChar
.UnicodeChar
!= L
'\0')
457 if (KeyEvent
->uChar
.UnicodeChar
< 0x20 &&
458 Console
->LineWakeupMask
& (1 << KeyEvent
->uChar
.UnicodeChar
))
460 /* Control key client wants to handle itself (e.g. for tab completion) */
461 Console
->LineBuffer
[Console
->LineSize
++] = L
' ';
462 Console
->LineBuffer
[Console
->LinePos
] = KeyEvent
->uChar
.UnicodeChar
;
463 Console
->LineComplete
= TRUE
;
464 Console
->LinePos
= 0;
468 /* Normal character */
469 BOOL Overstrike
= !Console
->LineInsertToggle
&& (Console
->LinePos
!= Console
->LineSize
);
470 DPRINT("Overstrike = %s\n", Overstrike
? "true" : "false");
471 LineInputEdit(Console
, (Overstrike
? 1 : 0), 1, &KeyEvent
->uChar
.UnicodeChar
);
477 /* PUBLIC SERVER APIS *********************************************************/