2 * LICENSE: GPL - See COPYING in the top level directory
3 * PROJECT: ReactOS Console Server DLL
4 * FILE: win32ss/user/winsrv/consrv/lineinput.c
5 * PURPOSE: Console line input functions
6 * PROGRAMMERS: Jeffrey Morlan
9 /* INCLUDES *******************************************************************/
18 ConvertInputAnsiToUnicode(PCONSOLE Console
,
23 PUSHORT TargetLength
);
25 ConvertInputUnicodeToAnsi(PCONSOLE Console
,
30 /*P*/USHORT TargetLength
);
34 HistoryAddEntry(PCONSRV_CONSOLE Console
,
35 PUNICODE_STRING ExeName
,
36 PUNICODE_STRING Entry
);
38 HistoryRecallHistory(PCONSRV_CONSOLE Console
,
39 PUNICODE_STRING ExeName
,
41 PUNICODE_STRING Entry
);
43 HistoryGetCurrentEntry(PCONSRV_CONSOLE Console
,
44 PUNICODE_STRING ExeName
,
45 PUNICODE_STRING Entry
);
47 HistoryDeleteCurrentBuffer(PCONSRV_CONSOLE Console
,
50 HistoryFindEntryByPrefix(PCONSRV_CONSOLE Console
,
51 PUNICODE_STRING ExeName
,
52 PUNICODE_STRING Prefix
,
53 PUNICODE_STRING Entry
);
57 /* PRIVATE FUNCTIONS **********************************************************/
60 LineInputSetPos(PCONSRV_CONSOLE Console
,
63 if (Pos
!= Console
->LinePos
&& Console
->InputBuffer
.Mode
& 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 (Console
->InputBuffer
.Mode
& 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
),
162 LineInputKeyDown(PCONSRV_CONSOLE Console
,
163 PUNICODE_STRING ExeName
,
164 KEY_EVENT_RECORD
*KeyEvent
)
166 UINT Pos
= Console
->LinePos
;
167 UNICODE_STRING Entry
;
171 * First, deal with control keys...
174 switch (KeyEvent
->wVirtualKeyCode
)
177 /* Clear entire line */
178 LineInputSetPos(Console
, 0);
179 LineInputEdit(Console
, Console
->LineSize
, 0, NULL
);
182 /* Move to start of line. With CTRL, erase everything left of cursor */
183 LineInputSetPos(Console
, 0);
184 if (KeyEvent
->dwControlKeyState
& (LEFT_CTRL_PRESSED
| RIGHT_CTRL_PRESSED
))
185 LineInputEdit(Console
, Pos
, 0, NULL
);
188 /* Move to end of line. With CTRL, erase everything right of cursor */
189 if (KeyEvent
->dwControlKeyState
& (LEFT_CTRL_PRESSED
| RIGHT_CTRL_PRESSED
))
190 LineInputEdit(Console
, Console
->LineSize
- Pos
, 0, NULL
);
192 LineInputSetPos(Console
, Console
->LineSize
);
195 /* Move left. With CTRL, move to beginning of previous word */
196 if (KeyEvent
->dwControlKeyState
& (LEFT_CTRL_PRESSED
| RIGHT_CTRL_PRESSED
))
198 while (Pos
> 0 && Console
->LineBuffer
[Pos
- 1] == L
' ') Pos
--;
199 while (Pos
> 0 && Console
->LineBuffer
[Pos
- 1] != L
' ') Pos
--;
205 LineInputSetPos(Console
, Pos
);
209 /* Move right. With CTRL, move to beginning of next word */
210 if (KeyEvent
->dwControlKeyState
& (LEFT_CTRL_PRESSED
| RIGHT_CTRL_PRESSED
))
212 while (Pos
< Console
->LineSize
&& Console
->LineBuffer
[Pos
] != L
' ') Pos
++;
213 while (Pos
< Console
->LineSize
&& Console
->LineBuffer
[Pos
] == L
' ') Pos
++;
214 LineInputSetPos(Console
, Pos
);
218 /* Recall one character (but don't overwrite current line) */
219 HistoryGetCurrentEntry(Console
, ExeName
, &Entry
);
220 if (Pos
< Console
->LineSize
)
221 LineInputSetPos(Console
, Pos
+ 1);
222 else if (Pos
* sizeof(WCHAR
) < Entry
.Length
)
223 LineInputEdit(Console
, 0, 1, &Entry
.Buffer
[Pos
]);
227 /* Toggle between insert and overstrike */
228 Console
->LineInsertToggle
= !Console
->LineInsertToggle
;
229 TermSetCursorInfo(Console
, Console
->ActiveBuffer
);
232 /* Remove character to right of cursor */
233 if (Pos
!= Console
->LineSize
)
234 LineInputEdit(Console
, 1, 0, NULL
);
237 /* Recall first history entry */
238 LineInputRecallHistory(Console
, ExeName
, -((WORD
)-1));
241 /* Recall last history entry */
242 LineInputRecallHistory(Console
, ExeName
, +((WORD
)-1));
247 * Recall previous history entry. On first time, actually recall the
248 * current (usually last) entry; on subsequent times go back.
250 LineInputRecallHistory(Console
, ExeName
, Console
->LineUpPressed
? -1 : 0);
251 Console
->LineUpPressed
= TRUE
;
254 /* Recall next history entry */
255 LineInputRecallHistory(Console
, ExeName
, +1);
258 /* Recall remainder of current history entry */
259 HistoryGetCurrentEntry(Console
, ExeName
, &Entry
);
260 if (Pos
* sizeof(WCHAR
) < Entry
.Length
)
262 UINT InsertSize
= (Entry
.Length
/ sizeof(WCHAR
) - Pos
);
263 UINT DeleteSize
= min(Console
->LineSize
- Pos
, InsertSize
);
264 LineInputEdit(Console
, DeleteSize
, InsertSize
, &Entry
.Buffer
[Pos
]);
268 /* Insert a ^Z character */
269 KeyEvent
->uChar
.UnicodeChar
= 26;
272 if (KeyEvent
->dwControlKeyState
& (LEFT_ALT_PRESSED
| RIGHT_ALT_PRESSED
))
273 HistoryDeleteCurrentBuffer(Console
, ExeName
);
278 UNICODE_STRING EntryFound
;
280 Entry
.Length
= Console
->LinePos
* sizeof(WCHAR
); // == Pos * sizeof(WCHAR)
281 Entry
.Buffer
= Console
->LineBuffer
;
283 if (HistoryFindEntryByPrefix(Console
, ExeName
, &Entry
, &EntryFound
))
285 LineInputEdit(Console
, Console
->LineSize
- Pos
,
286 EntryFound
.Length
/ sizeof(WCHAR
) - Pos
,
287 &EntryFound
.Buffer
[Pos
]);
288 /* Cursor stays where it was */
289 LineInputSetPos(Console
, Pos
);
293 PHISTORY_BUFFER Hist
;
295 /* Search for history entries starting with input. */
296 Hist
= HistoryCurrentBuffer(Console
, ExeName
);
297 if (!Hist
|| Hist
->NumEntries
== 0) return;
300 * Like Up/F5, on first time start from current (usually last) entry,
301 * but on subsequent times start at previous entry.
303 if (Console
->LineUpPressed
)
304 Hist
->Position
= (Hist
->Position
? Hist
->Position
: Hist
->NumEntries
) - 1;
305 Console
->LineUpPressed
= TRUE
;
307 Entry
.Length
= Console
->LinePos
* sizeof(WCHAR
); // == Pos * sizeof(WCHAR)
308 Entry
.Buffer
= Console
->LineBuffer
;
311 * Keep going backwards, even wrapping around to the end,
312 * until we get back to starting point.
314 HistPos
= Hist
->Position
;
317 if (RtlPrefixUnicodeString(&Entry
, &Hist
->Entries
[HistPos
], FALSE
))
319 Hist
->Position
= HistPos
;
320 LineInputEdit(Console
, Console
->LineSize
- Pos
,
321 Hist
->Entries
[HistPos
].Length
/ sizeof(WCHAR
) - Pos
,
322 &Hist
->Entries
[HistPos
].Buffer
[Pos
]);
323 /* Cursor stays where it was */
324 LineInputSetPos(Console
, Pos
);
327 if (--HistPos
< 0) HistPos
+= Hist
->NumEntries
;
328 } while (HistPos
!= Hist
->Position
);
336 * OK, we can continue...
339 if (KeyEvent
->uChar
.UnicodeChar
== L
'\b' && Console
->InputBuffer
.Mode
& ENABLE_PROCESSED_INPUT
)
341 /* backspace handling - if processed input enabled then we handle it here
342 * otherwise we treat it like a normal char. */
345 LineInputSetPos(Console
, Pos
- 1);
346 LineInputEdit(Console
, 1, 0, NULL
);
349 else if (KeyEvent
->uChar
.UnicodeChar
== L
'\r')
351 Entry
.Length
= Entry
.MaximumLength
= Console
->LineSize
* sizeof(WCHAR
);
352 Entry
.Buffer
= Console
->LineBuffer
;
353 HistoryAddEntry(Console
, ExeName
, &Entry
);
355 /* TODO: Expand aliases */
356 DPRINT1("TODO: Expand aliases\n");
358 LineInputSetPos(Console
, Console
->LineSize
);
359 Console
->LineBuffer
[Console
->LineSize
++] = L
'\r';
360 if (Console
->InputBuffer
.Mode
& ENABLE_ECHO_INPUT
)
362 if (GetType(Console
->ActiveBuffer
) == TEXTMODE_BUFFER
)
364 TermWriteStream(Console
, (PTEXTMODE_SCREEN_BUFFER
)(Console
->ActiveBuffer
), L
"\r", 1, TRUE
);
369 * Add \n if processed input. There should usually be room for it,
370 * but an exception to the rule exists: the buffer could have been
371 * pre-filled with LineMaxSize - 1 characters.
373 if (Console
->InputBuffer
.Mode
& ENABLE_PROCESSED_INPUT
&&
374 Console
->LineSize
< Console
->LineMaxSize
)
376 Console
->LineBuffer
[Console
->LineSize
++] = L
'\n';
377 if (Console
->InputBuffer
.Mode
& ENABLE_ECHO_INPUT
)
379 if (GetType(Console
->ActiveBuffer
) == TEXTMODE_BUFFER
)
381 TermWriteStream(Console
, (PTEXTMODE_SCREEN_BUFFER
)(Console
->ActiveBuffer
), L
"\n", 1, TRUE
);
385 Console
->LineComplete
= TRUE
;
386 Console
->LinePos
= 0;
388 else if (KeyEvent
->uChar
.UnicodeChar
!= L
'\0')
390 if (KeyEvent
->uChar
.UnicodeChar
< 0x20 &&
391 Console
->LineWakeupMask
& (1 << KeyEvent
->uChar
.UnicodeChar
))
393 /* Control key client wants to handle itself (e.g. for tab completion) */
394 Console
->LineBuffer
[Console
->LineSize
++] = L
' ';
395 Console
->LineBuffer
[Console
->LinePos
] = KeyEvent
->uChar
.UnicodeChar
;
396 Console
->LineComplete
= TRUE
;
397 Console
->LinePos
= 0;
401 /* Normal character */
402 BOOL Overstrike
= !Console
->LineInsertToggle
&& (Console
->LinePos
!= Console
->LineSize
);
403 LineInputEdit(Console
, (Overstrike
? 1 : 0), 1, &KeyEvent
->uChar
.UnicodeChar
);
409 /* PUBLIC SERVER APIS *********************************************************/