6bd830fbfcca92d17ce8a087188b75c813c4d615
[reactos.git] / win32ss / user / winsrv / consrv / lineinput.c
1 /*
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
7 */
8
9 /* INCLUDES *******************************************************************/
10
11 #include "consrv.h"
12
13 #define NDEBUG
14 #include <debug.h>
15
16
17 BOOLEAN
18 ConvertInputAnsiToUnicode(PCONSOLE Console,
19 PVOID Source,
20 USHORT SourceLength,
21 // BOOLEAN IsUnicode,
22 PWCHAR* Target,
23 PUSHORT TargetLength);
24 BOOLEAN
25 ConvertInputUnicodeToAnsi(PCONSOLE Console,
26 PVOID Source,
27 USHORT SourceLength,
28 // BOOLEAN IsAnsi,
29 PCHAR/* * */ Target,
30 /*P*/USHORT TargetLength);
31
32
33 VOID
34 HistoryAddEntry(PCONSRV_CONSOLE Console,
35 PUNICODE_STRING ExeName,
36 PUNICODE_STRING Entry);
37 BOOL
38 HistoryRecallHistory(PCONSRV_CONSOLE Console,
39 PUNICODE_STRING ExeName,
40 INT Offset,
41 PUNICODE_STRING Entry);
42 VOID
43 HistoryGetCurrentEntry(PCONSRV_CONSOLE Console,
44 PUNICODE_STRING ExeName,
45 PUNICODE_STRING Entry);
46 VOID
47 HistoryDeleteCurrentBuffer(PCONSRV_CONSOLE Console,
48 PVOID ExeName);
49 BOOL
50 HistoryFindEntryByPrefix(PCONSRV_CONSOLE Console,
51 PUNICODE_STRING ExeName,
52 PUNICODE_STRING Prefix,
53 PUNICODE_STRING Entry);
54
55
56
57 /* PRIVATE FUNCTIONS **********************************************************/
58
59 static VOID
60 LineInputSetPos(PCONSRV_CONSOLE Console,
61 UINT Pos)
62 {
63 if (Pos != Console->LinePos && Console->InputBuffer.Mode & ENABLE_ECHO_INPUT)
64 {
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;
69
70 XY += (Pos - Console->LinePos);
71 if (XY < 0)
72 XY = 0;
73 else if (XY >= Buffer->ScreenBufferSize.Y * Buffer->ScreenBufferSize.X)
74 XY = Buffer->ScreenBufferSize.Y * Buffer->ScreenBufferSize.X - 1;
75
76 Buffer->CursorPosition.X = XY % Buffer->ScreenBufferSize.X;
77 Buffer->CursorPosition.Y = XY / Buffer->ScreenBufferSize.X;
78 TermSetScreenInfo(Console, Buffer, OldCursorX, OldCursorY);
79 }
80
81 Console->LinePos = Pos;
82 }
83
84 static VOID
85 LineInputEdit(PCONSRV_CONSOLE Console,
86 UINT NumToDelete,
87 UINT NumToInsert,
88 PWCHAR Insertion)
89 {
90 PTEXTMODE_SCREEN_BUFFER ActiveBuffer;
91 UINT Pos = Console->LinePos;
92 UINT NewSize = Console->LineSize - NumToDelete + NumToInsert;
93 UINT i;
94
95 if (GetType(Console->ActiveBuffer) != TEXTMODE_BUFFER) return;
96 ActiveBuffer = (PTEXTMODE_SCREEN_BUFFER)Console->ActiveBuffer;
97
98 /* Make sure there's always enough room for ending \r\n */
99 if (NewSize + 2 > Console->LineMaxSize)
100 return;
101
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));
106
107 if (Console->InputBuffer.Mode & ENABLE_ECHO_INPUT)
108 {
109 for (i = Pos; i < NewSize; i++)
110 {
111 TermWriteStream(Console, ActiveBuffer, &Console->LineBuffer[i], 1, TRUE);
112 }
113 for (; i < Console->LineSize; i++)
114 {
115 TermWriteStream(Console, ActiveBuffer, L" ", 1, TRUE);
116 }
117 Console->LinePos = i;
118 }
119
120 Console->LineSize = NewSize;
121 LineInputSetPos(Console, Pos + NumToInsert);
122 }
123
124 #if 0
125 static VOID
126 LineInputRecallHistory(PCONSRV_CONSOLE Console,
127 PUNICODE_STRING ExeName,
128 INT Offset)
129 {
130 PHISTORY_BUFFER Hist = HistoryCurrentBuffer(Console, ExeName);
131 UINT Position = 0;
132
133 if (!Hist || Hist->NumEntries == 0) return;
134
135 Position = Hist->Position + Offset;
136 Position = min(max(Position, 0), Hist->NumEntries - 1);
137 Hist->Position = Position;
138
139 LineInputSetPos(Console, 0);
140 LineInputEdit(Console, Console->LineSize,
141 Hist->Entries[Hist->Position].Length / sizeof(WCHAR),
142 Hist->Entries[Hist->Position].Buffer);
143 }
144 #else
145 static VOID
146 LineInputRecallHistory(PCONSRV_CONSOLE Console,
147 PUNICODE_STRING ExeName,
148 INT Offset)
149 {
150 UNICODE_STRING Entry;
151
152 if (!HistoryRecallHistory(Console, ExeName, Offset, &Entry)) return;
153
154 LineInputSetPos(Console, 0);
155 LineInputEdit(Console, Console->LineSize,
156 Entry.Length / sizeof(WCHAR),
157 Entry.Buffer);
158 }
159 #endif
160
161 VOID
162 LineInputKeyDown(PCONSRV_CONSOLE Console,
163 PUNICODE_STRING ExeName,
164 KEY_EVENT_RECORD *KeyEvent)
165 {
166 UINT Pos = Console->LinePos;
167 UNICODE_STRING Entry;
168 // INT HistPos;
169
170 /*
171 * First, deal with control keys...
172 */
173
174 switch (KeyEvent->wVirtualKeyCode)
175 {
176 case VK_ESCAPE:
177 /* Clear entire line */
178 LineInputSetPos(Console, 0);
179 LineInputEdit(Console, Console->LineSize, 0, NULL);
180 return;
181 case VK_HOME:
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);
186 return;
187 case VK_END:
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);
191 else
192 LineInputSetPos(Console, Console->LineSize);
193 return;
194 case VK_LEFT:
195 /* Move left. With CTRL, move to beginning of previous word */
196 if (KeyEvent->dwControlKeyState & (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED))
197 {
198 while (Pos > 0 && Console->LineBuffer[Pos - 1] == L' ') Pos--;
199 while (Pos > 0 && Console->LineBuffer[Pos - 1] != L' ') Pos--;
200 }
201 else
202 {
203 Pos -= (Pos > 0);
204 }
205 LineInputSetPos(Console, Pos);
206 return;
207 case VK_RIGHT:
208 case VK_F1:
209 /* Move right. With CTRL, move to beginning of next word */
210 if (KeyEvent->dwControlKeyState & (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED))
211 {
212 while (Pos < Console->LineSize && Console->LineBuffer[Pos] != L' ') Pos++;
213 while (Pos < Console->LineSize && Console->LineBuffer[Pos] == L' ') Pos++;
214 LineInputSetPos(Console, Pos);
215 }
216 else
217 {
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]);
224 }
225 return;
226 case VK_INSERT:
227 /* Toggle between insert and overstrike */
228 Console->LineInsertToggle = !Console->LineInsertToggle;
229 TermSetCursorInfo(Console, Console->ActiveBuffer);
230 return;
231 case VK_DELETE:
232 /* Remove character to right of cursor */
233 if (Pos != Console->LineSize)
234 LineInputEdit(Console, 1, 0, NULL);
235 return;
236 case VK_PRIOR:
237 /* Recall first history entry */
238 LineInputRecallHistory(Console, ExeName, -((WORD)-1));
239 return;
240 case VK_NEXT:
241 /* Recall last history entry */
242 LineInputRecallHistory(Console, ExeName, +((WORD)-1));
243 return;
244 case VK_UP:
245 case VK_F5:
246 /*
247 * Recall previous history entry. On first time, actually recall the
248 * current (usually last) entry; on subsequent times go back.
249 */
250 LineInputRecallHistory(Console, ExeName, Console->LineUpPressed ? -1 : 0);
251 Console->LineUpPressed = TRUE;
252 return;
253 case VK_DOWN:
254 /* Recall next history entry */
255 LineInputRecallHistory(Console, ExeName, +1);
256 return;
257 case VK_F3:
258 /* Recall remainder of current history entry */
259 HistoryGetCurrentEntry(Console, ExeName, &Entry);
260 if (Pos * sizeof(WCHAR) < Entry.Length)
261 {
262 UINT InsertSize = (Entry.Length / sizeof(WCHAR) - Pos);
263 UINT DeleteSize = min(Console->LineSize - Pos, InsertSize);
264 LineInputEdit(Console, DeleteSize, InsertSize, &Entry.Buffer[Pos]);
265 }
266 return;
267 case VK_F6:
268 /* Insert a ^Z character */
269 KeyEvent->uChar.UnicodeChar = 26;
270 break;
271 case VK_F7:
272 if (KeyEvent->dwControlKeyState & (LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED))
273 HistoryDeleteCurrentBuffer(Console, ExeName);
274 return;
275 case VK_F8:
276
277 {
278 UNICODE_STRING EntryFound;
279
280 Entry.Length = Console->LinePos * sizeof(WCHAR); // == Pos * sizeof(WCHAR)
281 Entry.Buffer = Console->LineBuffer;
282
283 if (HistoryFindEntryByPrefix(Console, ExeName, &Entry, &EntryFound))
284 {
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);
290 }
291 }
292 #if 0
293 PHISTORY_BUFFER Hist;
294
295 /* Search for history entries starting with input. */
296 Hist = HistoryCurrentBuffer(Console, ExeName);
297 if (!Hist || Hist->NumEntries == 0) return;
298
299 /*
300 * Like Up/F5, on first time start from current (usually last) entry,
301 * but on subsequent times start at previous entry.
302 */
303 if (Console->LineUpPressed)
304 Hist->Position = (Hist->Position ? Hist->Position : Hist->NumEntries) - 1;
305 Console->LineUpPressed = TRUE;
306
307 Entry.Length = Console->LinePos * sizeof(WCHAR); // == Pos * sizeof(WCHAR)
308 Entry.Buffer = Console->LineBuffer;
309
310 /*
311 * Keep going backwards, even wrapping around to the end,
312 * until we get back to starting point.
313 */
314 HistPos = Hist->Position;
315 do
316 {
317 if (RtlPrefixUnicodeString(&Entry, &Hist->Entries[HistPos], FALSE))
318 {
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);
325 return;
326 }
327 if (--HistPos < 0) HistPos += Hist->NumEntries;
328 } while (HistPos != Hist->Position);
329 #endif
330
331 return;
332 }
333
334
335 /*
336 * OK, we can continue...
337 */
338
339 if (KeyEvent->uChar.UnicodeChar == L'\b' && Console->InputBuffer.Mode & ENABLE_PROCESSED_INPUT)
340 {
341 /* backspace handling - if processed input enabled then we handle it here
342 * otherwise we treat it like a normal char. */
343 if (Pos > 0)
344 {
345 LineInputSetPos(Console, Pos - 1);
346 LineInputEdit(Console, 1, 0, NULL);
347 }
348 }
349 else if (KeyEvent->uChar.UnicodeChar == L'\r')
350 {
351 Entry.Length = Entry.MaximumLength = Console->LineSize * sizeof(WCHAR);
352 Entry.Buffer = Console->LineBuffer;
353 HistoryAddEntry(Console, ExeName, &Entry);
354
355 /* TODO: Expand aliases */
356 DPRINT1("TODO: Expand aliases\n");
357
358 LineInputSetPos(Console, Console->LineSize);
359 Console->LineBuffer[Console->LineSize++] = L'\r';
360 if (Console->InputBuffer.Mode & ENABLE_ECHO_INPUT)
361 {
362 if (GetType(Console->ActiveBuffer) == TEXTMODE_BUFFER)
363 {
364 TermWriteStream(Console, (PTEXTMODE_SCREEN_BUFFER)(Console->ActiveBuffer), L"\r", 1, TRUE);
365 }
366 }
367
368 /*
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.
372 */
373 if (Console->InputBuffer.Mode & ENABLE_PROCESSED_INPUT &&
374 Console->LineSize < Console->LineMaxSize)
375 {
376 Console->LineBuffer[Console->LineSize++] = L'\n';
377 if (Console->InputBuffer.Mode & ENABLE_ECHO_INPUT)
378 {
379 if (GetType(Console->ActiveBuffer) == TEXTMODE_BUFFER)
380 {
381 TermWriteStream(Console, (PTEXTMODE_SCREEN_BUFFER)(Console->ActiveBuffer), L"\n", 1, TRUE);
382 }
383 }
384 }
385 Console->LineComplete = TRUE;
386 Console->LinePos = 0;
387 }
388 else if (KeyEvent->uChar.UnicodeChar != L'\0')
389 {
390 if (KeyEvent->uChar.UnicodeChar < 0x20 &&
391 Console->LineWakeupMask & (1 << KeyEvent->uChar.UnicodeChar))
392 {
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;
398 }
399 else
400 {
401 /* Normal character */
402 BOOL Overstrike = !Console->LineInsertToggle && (Console->LinePos != Console->LineSize);
403 LineInputEdit(Console, (Overstrike ? 1 : 0), 1, &KeyEvent->uChar.UnicodeChar);
404 }
405 }
406 }
407
408
409 /* PUBLIC SERVER APIS *********************************************************/
410
411 /* EOF */