| /* | 
 |  * line edition function for Win32 console | 
 |  * | 
 |  * Copyright 2001 Eric Pouech | 
 |  * | 
 |  * This library is free software; you can redistribute it and/or | 
 |  * modify it under the terms of the GNU Lesser General Public | 
 |  * License as published by the Free Software Foundation; either | 
 |  * version 2.1 of the License, or (at your option) any later version. | 
 |  * | 
 |  * This library is distributed in the hope that it will be useful, | 
 |  * but WITHOUT ANY WARRANTY; without even the implied warranty of | 
 |  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU | 
 |  * Lesser General Public License for more details. | 
 |  * | 
 |  * You should have received a copy of the GNU Lesser General Public | 
 |  * License along with this library; if not, write to the Free Software | 
 |  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA | 
 |  */ | 
 |  | 
 | #include "config.h" | 
 | #include "wine/port.h" | 
 |  | 
 | #include <stdarg.h> | 
 | #include <string.h> | 
 |  | 
 | #include "windef.h" | 
 | #include "winbase.h" | 
 | #include "wincon.h" | 
 | #include "wine/unicode.h" | 
 | #include "winnls.h" | 
 | #include "wine/debug.h" | 
 | #include "console_private.h" | 
 |  | 
 | WINE_DEFAULT_DEBUG_CHANNEL(console); | 
 |  | 
 | struct WCEL_Context; | 
 |  | 
 | typedef struct | 
 | { | 
 |     WCHAR			val;		/* vk or unicode char */ | 
 |     void			(*func)(struct WCEL_Context* ctx); | 
 | } KeyEntry; | 
 |  | 
 | typedef struct | 
 | { | 
 |     DWORD			keyState;	/* keyState (from INPUT_RECORD) to match */ | 
 |     BOOL			chkChar;	/* check vk or char */ | 
 |     const KeyEntry*		entries;	/* array of entries */ | 
 | } KeyMap; | 
 |  | 
 | typedef struct WCEL_Context { | 
 |     WCHAR*			line;		/* the line being edited */ | 
 |     size_t			alloc;		/* number of WCHAR in line */ | 
 |     unsigned    		len;		/* number of chars in line */ | 
 |     unsigned			ofs;		/* offset for cursor in current line */ | 
 |     WCHAR*			yanked;		/* yanked line */ | 
 |     unsigned			mark;		/* marked point (emacs mode only) */ | 
 |     CONSOLE_SCREEN_BUFFER_INFO	csbi;		/* current state (initial cursor, window size, attribute) */ | 
 |     HANDLE			hConIn; | 
 |     HANDLE			hConOut; | 
 |     unsigned			done : 1,	/* to 1 when we're done with editing */ | 
 | 	                        error : 1,	/* to 1 when an error occurred in the editing */ | 
 |                                 can_wrap : 1;   /* to 1 when multi-line edition can take place */ | 
 |     unsigned			histSize; | 
 |     unsigned			histPos; | 
 |     WCHAR*			histCurr; | 
 | } WCEL_Context; | 
 |  | 
 | #if 0 | 
 | static void WCEL_Dump(WCEL_Context* ctx, const char* pfx) | 
 | { | 
 |     MESSAGE("%s: [line=%s[alloc=%u] ofs=%u len=%u start=(%d,%d) mask=%c%c%c]\n" | 
 | 	    "\t\thist=(size=%u pos=%u curr=%s)\n" | 
 |             "\t\tyanked=%s\n", | 
 | 	    pfx, debugstr_w(ctx->line), ctx->alloc, ctx->ofs, ctx->len, | 
 | 	    ctx->csbi.dwCursorPosition.X, ctx->csbi.dwCursorPosition.Y, | 
 | 	    ctx->done ? 'D' : 'd', ctx->error ? 'E' : 'e', ctx->can_wrap ? 'W' : 'w', | 
 | 	    ctx->histSize, ctx->histPos, debugstr_w(ctx->histCurr), | 
 |             debugstr_w(ctx->yanked)); | 
 | } | 
 | #endif | 
 |  | 
 | /* ==================================================================== | 
 |  * | 
 |  * Console helper functions | 
 |  * | 
 |  * ====================================================================*/ | 
 |  | 
 | static BOOL WCEL_Get(WCEL_Context* ctx, INPUT_RECORD* ir) | 
 | { | 
 |     if (ReadConsoleInputW(ctx->hConIn, ir, 1, NULL)) return TRUE; | 
 |     ERR("hmm bad situation\n"); | 
 |     ctx->error = 1; | 
 |     return FALSE; | 
 | } | 
 |  | 
 | static inline void WCEL_Beep(WCEL_Context* ctx) | 
 | { | 
 |     Beep(400, 300); | 
 | } | 
 |  | 
 | static inline BOOL WCEL_IsSingleLine(WCEL_Context* ctx, size_t len) | 
 | { | 
 |     return ctx->csbi.dwCursorPosition.X + ctx->len + len <= ctx->csbi.dwSize.X; | 
 | } | 
 |  | 
 | static inline COORD WCEL_GetCoord(WCEL_Context* ctx, int ofs) | 
 | { | 
 |     COORD       c; | 
 |     int         len = ctx->csbi.dwSize.X - ctx->csbi.dwCursorPosition.X; | 
 |  | 
 |     c.Y = ctx->csbi.dwCursorPosition.Y; | 
 |     if (ofs >= len) | 
 |     { | 
 |         ofs -= len; | 
 |         c.X = ofs % ctx->csbi.dwSize.X; | 
 |         c.Y += 1 + ofs / ctx->csbi.dwSize.X; | 
 |     } | 
 |     else c.X = ctx->csbi.dwCursorPosition.X + ofs; | 
 |     return c; | 
 | } | 
 |  | 
 | static inline void WCEL_Update(WCEL_Context* ctx, int beg, int len) | 
 | { | 
 |     WriteConsoleOutputCharacterW(ctx->hConOut, &ctx->line[beg], len, | 
 |                                  WCEL_GetCoord(ctx, beg), NULL); | 
 |     FillConsoleOutputAttribute(ctx->hConOut, ctx->csbi.wAttributes, len, | 
 |                                WCEL_GetCoord(ctx, beg), NULL); | 
 | } | 
 |  | 
 | /* ==================================================================== | 
 |  * | 
 |  * context manipulation functions | 
 |  * | 
 |  * ====================================================================*/ | 
 |  | 
 | static BOOL WCEL_Grow(WCEL_Context* ctx, size_t len) | 
 | { | 
 |     if (!WCEL_IsSingleLine(ctx, len) && !ctx->can_wrap) | 
 |     { | 
 |         FIXME("Mode doesn't allow to wrap. However, we should allow to overwrite current string\n"); | 
 |         return FALSE; | 
 |     } | 
 |  | 
 |     if (ctx->len + len >= ctx->alloc) | 
 |     { | 
 | 	WCHAR*	newline; | 
 |         size_t  newsize; | 
 |  | 
 |         /* round up size to 32 byte-WCHAR boundary */ | 
 |         newsize = (ctx->len + len + 1 + 31) & ~31; | 
 |  | 
 | 	if (ctx->line) | 
 | 	    newline = HeapReAlloc(GetProcessHeap(), 0, ctx->line, sizeof(WCHAR) * newsize); | 
 | 	else | 
 | 	    newline = HeapAlloc(GetProcessHeap(), 0, sizeof(WCHAR) * newsize); | 
 |  | 
 | 	if (!newline) return FALSE; | 
 | 	ctx->line = newline; | 
 | 	ctx->alloc = newsize; | 
 |     } | 
 |     return TRUE; | 
 | } | 
 |  | 
 | static void WCEL_DeleteString(WCEL_Context* ctx, int beg, int end) | 
 | { | 
 |     unsigned    str_len = end - beg; | 
 |     COORD       cbeg = WCEL_GetCoord(ctx, ctx->len - str_len); | 
 |     COORD       cend = WCEL_GetCoord(ctx, ctx->len); | 
 |     CHAR_INFO   ci; | 
 |  | 
 |     if (end < ctx->len) | 
 | 	memmove(&ctx->line[beg], &ctx->line[end], (ctx->len - end) * sizeof(WCHAR)); | 
 |     /* we need to clean from ctx->len - str_len to ctx->len */ | 
 |  | 
 |     ci.Char.UnicodeChar = ' '; | 
 |     ci.Attributes = ctx->csbi.wAttributes; | 
 |  | 
 |     if (cbeg.Y == cend.Y) | 
 |     { | 
 |         /* partial erase of sole line */ | 
 |         CONSOLE_FillLineUniform(ctx->hConOut, cbeg.X, cbeg.Y, | 
 |                                 cend.X - cbeg.X, &ci); | 
 |     } | 
 |     else | 
 |     { | 
 |         int         i; | 
 |         /* erase til eol on first line */ | 
 |         CONSOLE_FillLineUniform(ctx->hConOut, cbeg.X, cbeg.Y, | 
 |                                 ctx->csbi.dwSize.X - cbeg.X, &ci); | 
 |         /* completely erase all the others (full lines) */ | 
 |         for (i = cbeg.Y + 1; i < cend.Y; i++) | 
 |             CONSOLE_FillLineUniform(ctx->hConOut, 0, i, ctx->csbi.dwSize.X, &ci); | 
 |         /* erase from beginning of line until last pos on last line */ | 
 |         CONSOLE_FillLineUniform(ctx->hConOut, 0, cend.Y, cend.X, &ci); | 
 |     } | 
 |     ctx->len -= str_len; | 
 |     WCEL_Update(ctx, 0, ctx->len); | 
 |     ctx->line[ctx->len] = 0; | 
 | } | 
 |  | 
 | static void WCEL_InsertString(WCEL_Context* ctx, const WCHAR* str) | 
 | { | 
 |     size_t	len = lstrlenW(str); | 
 |  | 
 |     if (!len || !WCEL_Grow(ctx, len)) return; | 
 |     if (ctx->len > ctx->ofs) | 
 | 	memmove(&ctx->line[ctx->ofs + len], &ctx->line[ctx->ofs], (ctx->len - ctx->ofs) * sizeof(WCHAR)); | 
 |     memcpy(&ctx->line[ctx->ofs], str, len * sizeof(WCHAR)); | 
 |     ctx->len += len; | 
 |     ctx->line[ctx->len] = 0; | 
 |     WCEL_Update(ctx, ctx->ofs, ctx->len - ctx->ofs); | 
 |  | 
 |     ctx->ofs += len; | 
 | } | 
 |  | 
 | static void WCEL_InsertChar(WCEL_Context* ctx, WCHAR c) | 
 | { | 
 |     WCHAR	buffer[2]; | 
 |  | 
 |     /* do not insert 0..31 control characters */ | 
 |     if (c < ' ' && c != '\t') return; | 
 |  | 
 |     buffer[0] = c; | 
 |     buffer[1] = 0; | 
 |     WCEL_InsertString(ctx, buffer); | 
 | } | 
 |  | 
 | static void WCEL_FreeYank(WCEL_Context* ctx) | 
 | { | 
 |     HeapFree(GetProcessHeap(), 0, ctx->yanked); | 
 |     ctx->yanked = NULL; | 
 | } | 
 |  | 
 | static void WCEL_SaveYank(WCEL_Context* ctx, int beg, int end) | 
 | { | 
 |     int len = end - beg; | 
 |     if (len <= 0) return; | 
 |  | 
 |     WCEL_FreeYank(ctx); | 
 |     /* After WCEL_FreeYank ctx->yanked is empty */ | 
 |     ctx->yanked = HeapAlloc(GetProcessHeap(), 0, (len + 1) * sizeof(WCHAR)); | 
 |     if (!ctx->yanked) return; | 
 |     memcpy(ctx->yanked, &ctx->line[beg], len * sizeof(WCHAR)); | 
 |     ctx->yanked[len] = 0; | 
 | } | 
 |  | 
 | /* FIXME NTDLL doesn't export iswalnum, and I don't want to link in msvcrt when most | 
 |  * of the data lay in unicode lib | 
 |  */ | 
 | static inline BOOL WCEL_iswalnum(WCHAR wc) | 
 | { | 
 |     return get_char_typeW(wc) & (C1_ALPHA|C1_DIGIT|C1_LOWER|C1_UPPER); | 
 | } | 
 |  | 
 | static int WCEL_GetLeftWordTransition(WCEL_Context* ctx, int ofs) | 
 | { | 
 |     ofs--; | 
 |     while (ofs >= 0 && !WCEL_iswalnum(ctx->line[ofs])) ofs--; | 
 |     while (ofs >= 0 && WCEL_iswalnum(ctx->line[ofs])) ofs--; | 
 |     if (ofs >= 0) ofs++; | 
 |     return max(ofs, 0); | 
 | } | 
 |  | 
 | static int WCEL_GetRightWordTransition(WCEL_Context* ctx, int ofs) | 
 | { | 
 |     ofs++; | 
 |     while (ofs <= ctx->len && WCEL_iswalnum(ctx->line[ofs])) ofs++; | 
 |     while (ofs <= ctx->len && !WCEL_iswalnum(ctx->line[ofs])) ofs++; | 
 |     return min(ofs, ctx->len); | 
 | } | 
 |  | 
 | static WCHAR* WCEL_GetHistory(WCEL_Context* ctx, int idx) | 
 | { | 
 |     WCHAR*	ptr; | 
 |  | 
 |     if (idx == ctx->histSize - 1) | 
 |     { | 
 | 	ptr = HeapAlloc(GetProcessHeap(), 0, (lstrlenW(ctx->histCurr) + 1) * sizeof(WCHAR)); | 
 | 	lstrcpyW(ptr, ctx->histCurr); | 
 |     } | 
 |     else | 
 |     { | 
 | 	int	len = CONSOLE_GetHistory(idx, NULL, 0); | 
 |  | 
 | 	if ((ptr = HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR)))) | 
 | 	{ | 
 | 	    CONSOLE_GetHistory(idx, ptr, len); | 
 | 	} | 
 |     } | 
 |     return ptr; | 
 | } | 
 |  | 
 | static void	WCEL_HistoryInit(WCEL_Context* ctx) | 
 | { | 
 |     ctx->histPos  = CONSOLE_GetNumHistoryEntries(); | 
 |     ctx->histSize = ctx->histPos + 1; | 
 |     ctx->histCurr = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(WCHAR)); | 
 | } | 
 |  | 
 | static void    WCEL_MoveToHist(WCEL_Context* ctx, int idx) | 
 | { | 
 |     WCHAR*	data = WCEL_GetHistory(ctx, idx); | 
 |     int		len = lstrlenW(data) + 1; | 
 |  | 
 |     /* save current line edition for recall when needed (FIXME seems broken to me) */ | 
 |     if (ctx->histPos == ctx->histSize - 1) | 
 |     { | 
 | 	HeapFree(GetProcessHeap(), 0, ctx->histCurr); | 
 | 	ctx->histCurr = HeapAlloc(GetProcessHeap(), 0, (ctx->len + 1) * sizeof(WCHAR)); | 
 | 	memcpy(ctx->histCurr, ctx->line, (ctx->len + 1) * sizeof(WCHAR)); | 
 |     } | 
 |     /* need to clean also the screen if new string is shorter than old one */ | 
 |     WCEL_DeleteString(ctx, 0, ctx->len); | 
 |     ctx->ofs = 0; | 
 |     /* insert new string */ | 
 |     if (WCEL_Grow(ctx, len)) | 
 |     { | 
 | 	WCEL_InsertString(ctx, data); | 
 | 	HeapFree(GetProcessHeap(), 0, data); | 
 | 	ctx->histPos = idx; | 
 |     } | 
 | } | 
 |  | 
 | static void    WCEL_FindPrevInHist(WCEL_Context* ctx) | 
 | { | 
 |     int startPos = ctx->histPos; | 
 |     WCHAR*	data; | 
 |     unsigned int    len, oldofs; | 
 |  | 
 |     if (ctx->histPos && ctx->histPos == ctx->histSize) { | 
 |         startPos--; | 
 |         ctx->histPos--; | 
 |     } | 
 |  | 
 |     do { | 
 |        data = WCEL_GetHistory(ctx, ctx->histPos); | 
 |  | 
 |        if (ctx->histPos) ctx->histPos--; | 
 |        else ctx->histPos = (ctx->histSize-1); | 
 |  | 
 |        len = lstrlenW(data) + 1; | 
 |        if ((len >= ctx->ofs) && | 
 |            (memcmp(ctx->line, data, ctx->ofs * sizeof(WCHAR)) == 0)) { | 
 |  | 
 |            /* need to clean also the screen if new string is shorter than old one */ | 
 |            WCEL_DeleteString(ctx, 0, ctx->len); | 
 |  | 
 |            if (WCEL_Grow(ctx, len)) | 
 |            { | 
 |               oldofs = ctx->ofs; | 
 |               ctx->ofs = 0; | 
 |               WCEL_InsertString(ctx, data); | 
 |               ctx->ofs = oldofs; | 
 |               SetConsoleCursorPosition(ctx->hConOut, WCEL_GetCoord(ctx, ctx->ofs)); | 
 |               HeapFree(GetProcessHeap(), 0, data); | 
 |               return; | 
 |            } | 
 |        } | 
 |     } while (ctx->histPos != startPos); | 
 |  | 
 |     return; | 
 | } | 
 |  | 
 | /* ==================================================================== | 
 |  * | 
 |  * basic edition functions | 
 |  * | 
 |  * ====================================================================*/ | 
 |  | 
 | static void WCEL_Done(WCEL_Context* ctx) | 
 | { | 
 |     WCHAR       nl = '\n'; | 
 |     if (!WCEL_Grow(ctx, 2)) return; | 
 |     ctx->line[ctx->len++] = '\r'; | 
 |     ctx->line[ctx->len++] = '\n'; | 
 |     ctx->line[ctx->len] = 0; | 
 |     WriteConsoleW(ctx->hConOut, &nl, 1, NULL, NULL); | 
 |     ctx->done = 1; | 
 | } | 
 |  | 
 | static void WCEL_MoveLeft(WCEL_Context* ctx) | 
 | { | 
 |     if (ctx->ofs > 0) ctx->ofs--; | 
 | } | 
 |  | 
 | static void WCEL_MoveRight(WCEL_Context* ctx) | 
 | { | 
 |     if (ctx->ofs < ctx->len) ctx->ofs++; | 
 | } | 
 |  | 
 | static void WCEL_MoveToLeftWord(WCEL_Context* ctx) | 
 | { | 
 |     unsigned int	new_ofs = WCEL_GetLeftWordTransition(ctx, ctx->ofs); | 
 |     if (new_ofs != ctx->ofs) ctx->ofs = new_ofs; | 
 | } | 
 |  | 
 | static void WCEL_MoveToRightWord(WCEL_Context* ctx) | 
 | { | 
 |     unsigned int	new_ofs = WCEL_GetRightWordTransition(ctx, ctx->ofs); | 
 |     if (new_ofs != ctx->ofs) ctx->ofs = new_ofs; | 
 | } | 
 |  | 
 | static void WCEL_MoveToBeg(WCEL_Context* ctx) | 
 | { | 
 |     ctx->ofs = 0; | 
 | } | 
 |  | 
 | static void WCEL_MoveToEnd(WCEL_Context* ctx) | 
 | { | 
 |     ctx->ofs = ctx->len; | 
 | } | 
 |  | 
 | static void WCEL_SetMark(WCEL_Context* ctx) | 
 | { | 
 |     ctx->mark = ctx->ofs; | 
 | } | 
 |  | 
 | static void WCEL_ExchangeMark(WCEL_Context* ctx) | 
 | { | 
 |     unsigned tmp; | 
 |  | 
 |     if (ctx->mark > ctx->len) return; | 
 |     tmp = ctx->ofs; | 
 |     ctx->ofs = ctx->mark; | 
 |     ctx->mark = tmp; | 
 | } | 
 |  | 
 | static void WCEL_CopyMarkedZone(WCEL_Context* ctx) | 
 | { | 
 |     unsigned beg, end; | 
 |  | 
 |     if (ctx->mark > ctx->len || ctx->mark == ctx->ofs) return; | 
 |     if (ctx->mark > ctx->ofs) | 
 |     { | 
 | 	beg = ctx->ofs;		end = ctx->mark; | 
 |     } | 
 |     else | 
 |     { | 
 | 	beg = ctx->mark;	end = ctx->ofs; | 
 |     } | 
 |     WCEL_SaveYank(ctx, beg, end); | 
 | } | 
 |  | 
 | static void WCEL_TransposeChar(WCEL_Context* ctx) | 
 | { | 
 |     WCHAR	c; | 
 |  | 
 |     if (!ctx->ofs || ctx->ofs == ctx->len) return; | 
 |  | 
 |     c = ctx->line[ctx->ofs]; | 
 |     ctx->line[ctx->ofs] = ctx->line[ctx->ofs - 1]; | 
 |     ctx->line[ctx->ofs - 1] = c; | 
 |  | 
 |     WCEL_Update(ctx, ctx->ofs - 1, 2); | 
 |     ctx->ofs++; | 
 | } | 
 |  | 
 | static void WCEL_TransposeWords(WCEL_Context* ctx) | 
 | { | 
 |     unsigned int	left_ofs = WCEL_GetLeftWordTransition(ctx, ctx->ofs), | 
 |         right_ofs = WCEL_GetRightWordTransition(ctx, ctx->ofs); | 
 |     if (left_ofs < ctx->ofs && right_ofs > ctx->ofs) | 
 |     { | 
 |         unsigned len_r = right_ofs - ctx->ofs; | 
 |         unsigned len_l = ctx->ofs - left_ofs; | 
 |  | 
 |         char*   tmp = HeapAlloc(GetProcessHeap(), 0, len_r * sizeof(WCHAR)); | 
 |         if (!tmp) return; | 
 |  | 
 |         memcpy(tmp, &ctx->line[ctx->ofs], len_r * sizeof(WCHAR)); | 
 |         memmove(&ctx->line[left_ofs + len_r], &ctx->line[left_ofs], len_l * sizeof(WCHAR)); | 
 |         memcpy(&ctx->line[left_ofs], tmp, len_r * sizeof(WCHAR)); | 
 |  | 
 |         HeapFree(GetProcessHeap(), 0, tmp); | 
 |         WCEL_Update(ctx, left_ofs, len_l + len_r); | 
 |         ctx->ofs = right_ofs; | 
 |     } | 
 | } | 
 |  | 
 | static void WCEL_LowerCaseWord(WCEL_Context* ctx) | 
 | { | 
 |     unsigned int	new_ofs = WCEL_GetRightWordTransition(ctx, ctx->ofs); | 
 |     if (new_ofs != ctx->ofs) | 
 |     { | 
 | 	unsigned int	i; | 
 | 	for (i = ctx->ofs; i <= new_ofs; i++) | 
 | 	    ctx->line[i] = tolowerW(ctx->line[i]); | 
 |         WCEL_Update(ctx, ctx->ofs, new_ofs - ctx->ofs + 1); | 
 | 	ctx->ofs = new_ofs; | 
 |     } | 
 | } | 
 |  | 
 | static void WCEL_UpperCaseWord(WCEL_Context* ctx) | 
 | { | 
 |     unsigned int	new_ofs = WCEL_GetRightWordTransition(ctx, ctx->ofs); | 
 |     if (new_ofs != ctx->ofs) | 
 |     { | 
 | 	unsigned int	i; | 
 | 	for (i = ctx->ofs; i <= new_ofs; i++) | 
 | 	    ctx->line[i] = toupperW(ctx->line[i]); | 
 | 	WCEL_Update(ctx, ctx->ofs, new_ofs - ctx->ofs + 1); | 
 | 	ctx->ofs = new_ofs; | 
 |     } | 
 | } | 
 |  | 
 | static void WCEL_CapitalizeWord(WCEL_Context* ctx) | 
 | { | 
 |     unsigned int	new_ofs = WCEL_GetRightWordTransition(ctx, ctx->ofs); | 
 |     if (new_ofs != ctx->ofs) | 
 |     { | 
 | 	unsigned int	i; | 
 |  | 
 | 	ctx->line[ctx->ofs] = toupperW(ctx->line[ctx->ofs]); | 
 | 	for (i = ctx->ofs + 1; i <= new_ofs; i++) | 
 | 	    ctx->line[i] = tolowerW(ctx->line[i]); | 
 | 	WCEL_Update(ctx, ctx->ofs, new_ofs - ctx->ofs + 1); | 
 | 	ctx->ofs = new_ofs; | 
 |     } | 
 | } | 
 |  | 
 | static void WCEL_Yank(WCEL_Context* ctx) | 
 | { | 
 |     WCEL_InsertString(ctx, ctx->yanked); | 
 | } | 
 |  | 
 | static void WCEL_KillToEndOfLine(WCEL_Context* ctx) | 
 | { | 
 |     WCEL_SaveYank(ctx, ctx->ofs, ctx->len); | 
 |     WCEL_DeleteString(ctx, ctx->ofs, ctx->len); | 
 | } | 
 |  | 
 | static void WCEL_KillMarkedZone(WCEL_Context* ctx) | 
 | { | 
 |     unsigned beg, end; | 
 |  | 
 |     if (ctx->mark > ctx->len || ctx->mark == ctx->ofs) return; | 
 |     if (ctx->mark > ctx->ofs) | 
 |     { | 
 | 	beg = ctx->ofs;		end = ctx->mark; | 
 |     } | 
 |     else | 
 |     { | 
 | 	beg = ctx->mark;	end = ctx->ofs; | 
 |     } | 
 |     WCEL_SaveYank(ctx, beg, end); | 
 |     WCEL_DeleteString(ctx, beg, end); | 
 |     ctx->ofs = beg; | 
 | } | 
 |  | 
 | static void WCEL_DeletePrevChar(WCEL_Context* ctx) | 
 | { | 
 |     if (ctx->ofs) | 
 |     { | 
 | 	WCEL_DeleteString(ctx, ctx->ofs - 1, ctx->ofs); | 
 | 	ctx->ofs--; | 
 |     } | 
 | } | 
 |  | 
 | static void WCEL_DeleteCurrChar(WCEL_Context* ctx) | 
 | { | 
 |     if (ctx->ofs < ctx->len) | 
 | 	WCEL_DeleteString(ctx, ctx->ofs, ctx->ofs + 1); | 
 | } | 
 |  | 
 | static void WCEL_DeleteLeftWord(WCEL_Context* ctx) | 
 | { | 
 |     unsigned int	new_ofs = WCEL_GetLeftWordTransition(ctx, ctx->ofs); | 
 |     if (new_ofs != ctx->ofs) | 
 |     { | 
 | 	WCEL_DeleteString(ctx, new_ofs, ctx->ofs); | 
 | 	ctx->ofs = new_ofs; | 
 |     } | 
 | } | 
 |  | 
 | static void WCEL_DeleteRightWord(WCEL_Context* ctx) | 
 | { | 
 |     unsigned int	new_ofs = WCEL_GetRightWordTransition(ctx, ctx->ofs); | 
 |     if (new_ofs != ctx->ofs) | 
 |     { | 
 | 	WCEL_DeleteString(ctx, ctx->ofs, new_ofs); | 
 |     } | 
 | } | 
 |  | 
 | static void WCEL_MoveToPrevHist(WCEL_Context* ctx) | 
 | { | 
 |     if (ctx->histPos) WCEL_MoveToHist(ctx, ctx->histPos - 1); | 
 | } | 
 |  | 
 | static void WCEL_MoveToNextHist(WCEL_Context* ctx) | 
 | { | 
 |     if (ctx->histPos < ctx->histSize - 1) WCEL_MoveToHist(ctx, ctx->histPos + 1); | 
 | } | 
 |  | 
 | static void WCEL_MoveToFirstHist(WCEL_Context* ctx) | 
 | { | 
 |     if (ctx->histPos != 0) WCEL_MoveToHist(ctx, 0); | 
 | } | 
 |  | 
 | static void WCEL_MoveToLastHist(WCEL_Context* ctx) | 
 | { | 
 |     if (ctx->histPos != ctx->histSize - 1) WCEL_MoveToHist(ctx, ctx->histSize - 1); | 
 | } | 
 |  | 
 | static void WCEL_Redraw(WCEL_Context* ctx) | 
 | { | 
 |     COORD       c = WCEL_GetCoord(ctx, ctx->len); | 
 |     CHAR_INFO   ci; | 
 |  | 
 |     WCEL_Update(ctx, 0, ctx->len); | 
 |  | 
 |     ci.Char.UnicodeChar = ' '; | 
 |     ci.Attributes = ctx->csbi.wAttributes; | 
 |  | 
 |     CONSOLE_FillLineUniform(ctx->hConOut, c.X, c.Y, ctx->csbi.dwSize.X - c.X, &ci); | 
 | } | 
 |  | 
 | static void WCEL_RepeatCount(WCEL_Context* ctx) | 
 | { | 
 | #if 0 | 
 | /* FIXME: wait until all console code is in kernel32 */ | 
 |     INPUT_RECORD        ir; | 
 |     unsigned            repeat = 0; | 
 |  | 
 |     while (WCEL_Get(ctx, &ir, FALSE)) | 
 |     { | 
 |         if (ir.EventType != KEY_EVENT) break; | 
 |         if (ir.Event.KeyEvent.bKeyDown) | 
 |         { | 
 |             if ((ir.Event.KeyEvent.dwControlKeyState & ~(NUMLOCK_ON|SCROLLLOCK_ON|CAPSLOCK_ON)) != 0) | 
 |                 break; | 
 |             if (ir.Event.KeyEvent.uChar.UnicodeChar < '0' || | 
 |                 ir.Event.KeyEvent.uChar.UnicodeChar > '9') | 
 |                 break; | 
 |             repeat = repeat * 10 + ir.Event.KeyEvent.uChar.UnicodeChar - '0'; | 
 |         } | 
 |         WCEL_Get(ctx, &ir, TRUE); | 
 |     } | 
 |     FIXME("=> %u\n", repeat); | 
 | #endif | 
 | } | 
 |  | 
 | /* ==================================================================== | 
 |  * | 
 |  * 		Key Maps | 
 |  * | 
 |  * ====================================================================*/ | 
 |  | 
 | #define CTRL(x)	((x) - '@') | 
 | static const KeyEntry StdKeyMap[] = | 
 | { | 
 |     {/*BACK*/0x08,	WCEL_DeletePrevChar 	}, | 
 |     {/*RETURN*/0x0d,	WCEL_Done		}, | 
 |     {/*DEL*/127,	WCEL_DeleteCurrChar 	}, | 
 |     {	0,		NULL			} | 
 | }; | 
 |  | 
 | static const KeyEntry Win32ExtraStdKeyMap[] = | 
 | { | 
 |     {/*VK_F8*/   0x77,	WCEL_FindPrevInHist	}, | 
 |     {	0,		NULL			} | 
 | }; | 
 |  | 
 |  | 
 | static const KeyEntry EmacsKeyMapCtrl[] = | 
 | { | 
 |     {	CTRL('@'),	WCEL_SetMark		}, | 
 |     {	CTRL('A'),	WCEL_MoveToBeg		}, | 
 |     {	CTRL('B'),	WCEL_MoveLeft		}, | 
 |     /* C: done in server */ | 
 |     {	CTRL('D'),	WCEL_DeleteCurrChar	}, | 
 |     {	CTRL('E'),	WCEL_MoveToEnd		}, | 
 |     {	CTRL('F'),	WCEL_MoveRight		}, | 
 |     {	CTRL('G'),	WCEL_Beep		}, | 
 |     {	CTRL('H'),	WCEL_DeletePrevChar	}, | 
 |     /* I: meaningless (or tab ???) */ | 
 |     {	CTRL('J'),	WCEL_Done		}, | 
 |     {	CTRL('K'),	WCEL_KillToEndOfLine	}, | 
 |     {   CTRL('L'),      WCEL_Redraw             }, | 
 |     {	CTRL('M'),	WCEL_Done		}, | 
 |     {	CTRL('N'),	WCEL_MoveToNextHist	}, | 
 |     /* O; insert line... meaningless */ | 
 |     {	CTRL('P'),	WCEL_MoveToPrevHist	}, | 
 |     /* Q: [NIY] quoting... */ | 
 |     /* R: [NIY] search backwards... */ | 
 |     /* S: [NIY] search forwards... */ | 
 |     {	CTRL('T'),	WCEL_TransposeChar	}, | 
 |     {   CTRL('U'),      WCEL_RepeatCount        }, | 
 |     /* V: paragraph down... meaningless */ | 
 |     {	CTRL('W'),	WCEL_KillMarkedZone	}, | 
 |     {	CTRL('X'),	WCEL_ExchangeMark	}, | 
 |     {	CTRL('Y'),	WCEL_Yank		}, | 
 |     /* Z: meaningless */ | 
 |     {	0,		NULL			} | 
 | }; | 
 |  | 
 | static const KeyEntry EmacsKeyMapAlt[] = | 
 | { | 
 |     {/*DEL*/127,	WCEL_DeleteLeftWord	}, | 
 |     {	'<',		WCEL_MoveToFirstHist	}, | 
 |     {	'>',		WCEL_MoveToLastHist	}, | 
 |     {	'?',		WCEL_Beep		}, | 
 |     {	'b',		WCEL_MoveToLeftWord	}, | 
 |     {   'c',		WCEL_CapitalizeWord	}, | 
 |     {	'd',		WCEL_DeleteRightWord	}, | 
 |     {	'f',		WCEL_MoveToRightWord	}, | 
 |     {	'l',		WCEL_LowerCaseWord	}, | 
 |     {   't',		WCEL_TransposeWords	}, | 
 |     {	'u',		WCEL_UpperCaseWord	}, | 
 |     {	'w', 		WCEL_CopyMarkedZone	}, | 
 |     {	0,		NULL			} | 
 | }; | 
 |  | 
 | static const KeyEntry EmacsKeyMapExtended[] = | 
 | { | 
 |     {/*RETURN*/  0x0d,	WCEL_Done }, | 
 |     {/*VK_PRIOR*/0x21, 	WCEL_MoveToPrevHist	}, | 
 |     {/*VK_NEXT*/ 0x22,	WCEL_MoveToNextHist 	}, | 
 |     {/*VK_END*/  0x23,	WCEL_MoveToEnd		}, | 
 |     {/*VK_HOME*/ 0x24,	WCEL_MoveToBeg		}, | 
 |     {/*VK_RIGHT*/0x27,	WCEL_MoveRight 		}, | 
 |     {/*VK_LEFT*/ 0x25,	WCEL_MoveLeft 		}, | 
 |     {/*VK_DEL*/  0x2e,  WCEL_DeleteCurrChar     }, | 
 |     {	0,		NULL 			} | 
 | }; | 
 |  | 
 | static const KeyMap EmacsKeyMap[] = | 
 | { | 
 |     {0x00000000, 1, StdKeyMap}, | 
 |     {0x00000001, 1, EmacsKeyMapAlt},	/* left  alt  */ | 
 |     {0x00000002, 1, EmacsKeyMapAlt},	/* right alt  */ | 
 |     {0x00000004, 1, EmacsKeyMapCtrl},	/* left  ctrl */ | 
 |     {0x00000008, 1, EmacsKeyMapCtrl},	/* right ctrl */ | 
 |     {0x00000100, 0, EmacsKeyMapExtended}, | 
 |     {0,		 0, 0} | 
 | }; | 
 |  | 
 | static const KeyEntry Win32KeyMapExtended[] = | 
 | { | 
 |     {/*VK_LEFT*/ 0x25, 	WCEL_MoveLeft 		}, | 
 |     {/*VK_RIGHT*/0x27,	WCEL_MoveRight		}, | 
 |     {/*VK_HOME*/ 0x24,	WCEL_MoveToBeg 		}, | 
 |     {/*VK_END*/  0x23,	WCEL_MoveToEnd 		}, | 
 |     {/*VK_UP*/   0x26, 	WCEL_MoveToPrevHist 	}, | 
 |     {/*VK_DOWN*/ 0x28,	WCEL_MoveToNextHist	}, | 
 |     {/*VK_DEL*/  0x2e,	WCEL_DeleteCurrChar	}, | 
 |     {	0,		NULL 			} | 
 | }; | 
 |  | 
 | static const KeyEntry Win32KeyMapCtrlExtended[] = | 
 | { | 
 |     {/*VK_LEFT*/ 0x25, 	WCEL_MoveToLeftWord 	}, | 
 |     {/*VK_RIGHT*/0x27,	WCEL_MoveToRightWord	}, | 
 |     {/*VK_END*/  0x23,	WCEL_KillToEndOfLine	}, | 
 |     {	0,		NULL 			} | 
 | }; | 
 |  | 
 | static const KeyMap Win32KeyMap[] = | 
 | { | 
 |     {0x00000000, 1, StdKeyMap}, | 
 |     {0x00000000, 0, Win32ExtraStdKeyMap}, | 
 |     {0x00000100, 0, Win32KeyMapExtended}, | 
 |     {0x00000104, 0, Win32KeyMapCtrlExtended}, | 
 |     {0x00000108, 0, Win32KeyMapCtrlExtended}, | 
 |     {0,		 0, 0} | 
 | }; | 
 | #undef CTRL | 
 |  | 
 | /* ==================================================================== | 
 |  * | 
 |  * 		Read line master function | 
 |  * | 
 |  * ====================================================================*/ | 
 |  | 
 | WCHAR* CONSOLE_Readline(HANDLE hConsoleIn) | 
 | { | 
 |     WCEL_Context	ctx; | 
 |     INPUT_RECORD	ir; | 
 |     const KeyMap*	km; | 
 |     const KeyEntry*	ke; | 
 |     unsigned		ofs; | 
 |     void		(*func)(struct WCEL_Context* ctx); | 
 |     DWORD               ks; | 
 |     int                 use_emacs; | 
 |  | 
 |     memset(&ctx, 0, sizeof(ctx)); | 
 |     ctx.hConIn = hConsoleIn; | 
 |     WCEL_HistoryInit(&ctx); | 
 |  | 
 |     if (!CONSOLE_GetEditionMode(hConsoleIn, &use_emacs)) | 
 |         use_emacs = 0; | 
 |  | 
 |     if ((ctx.hConOut = CreateFileA("CONOUT$", GENERIC_READ|GENERIC_WRITE, 0, NULL, | 
 | 				    OPEN_EXISTING, 0, 0 )) == INVALID_HANDLE_VALUE || | 
 | 	!GetConsoleScreenBufferInfo(ctx.hConOut, &ctx.csbi)) | 
 | 	return NULL; | 
 |     ctx.can_wrap = (GetConsoleMode(ctx.hConOut, &ks) && (ks & ENABLE_WRAP_AT_EOL_OUTPUT)) ? 1 : 0; | 
 |  | 
 |     if (!WCEL_Grow(&ctx, 1)) | 
 |     { | 
 | 	CloseHandle(ctx.hConOut); | 
 | 	return NULL; | 
 |     } | 
 |     ctx.line[0] = 0; | 
 |  | 
 | /* EPP     WCEL_Dump(&ctx, "init"); */ | 
 |  | 
 |     while (!ctx.done && !ctx.error && WCEL_Get(&ctx, &ir)) | 
 |     { | 
 | 	if (ir.EventType != KEY_EVENT) continue; | 
 | 	TRACE("key%s repeatCount=%u, keyCode=%02x scanCode=%02x char=%02x keyState=%08x\n", | 
 | 	      ir.Event.KeyEvent.bKeyDown ? "Down" : "Up  ", ir.Event.KeyEvent.wRepeatCount, | 
 | 	      ir.Event.KeyEvent.wVirtualKeyCode, ir.Event.KeyEvent.wVirtualScanCode, | 
 | 	      ir.Event.KeyEvent.uChar.UnicodeChar, ir.Event.KeyEvent.dwControlKeyState); | 
 | 	if (!ir.Event.KeyEvent.bKeyDown) continue; | 
 |  | 
 | /* EPP  	WCEL_Dump(&ctx, "before func"); */ | 
 | 	ofs = ctx.ofs; | 
 |         /* mask out some bits which don't interest us */ | 
 |         ks = ir.Event.KeyEvent.dwControlKeyState & ~(NUMLOCK_ON|SCROLLLOCK_ON|CAPSLOCK_ON|ENHANCED_KEY); | 
 |  | 
 | 	func = NULL; | 
 | 	for (km = (use_emacs) ? EmacsKeyMap : Win32KeyMap; km->entries != NULL; km++) | 
 | 	{ | 
 | 	    if (km->keyState != ks) | 
 | 		continue; | 
 | 	    if (km->chkChar) | 
 | 	    { | 
 | 		for (ke = &km->entries[0]; ke->func != 0; ke++) | 
 | 		    if (ke->val == ir.Event.KeyEvent.uChar.UnicodeChar) break; | 
 | 	    } | 
 | 	    else | 
 | 	    { | 
 | 		for (ke = &km->entries[0]; ke->func != 0; ke++) | 
 | 		    if (ke->val == ir.Event.KeyEvent.wVirtualKeyCode) break; | 
 |  | 
 | 	    } | 
 | 	    if (ke->func) | 
 | 	    { | 
 | 		func = ke->func; | 
 | 		break; | 
 | 	    } | 
 | 	} | 
 |  | 
 | 	if (func) | 
 | 	    (func)(&ctx); | 
 | 	else if (!(ir.Event.KeyEvent.dwControlKeyState & LEFT_ALT_PRESSED)) | 
 | 	    WCEL_InsertChar(&ctx, ir.Event.KeyEvent.uChar.UnicodeChar); | 
 | 	else TRACE("Dropped event\n"); | 
 |  | 
 | /* EPP         WCEL_Dump(&ctx, "after func"); */ | 
 | 	if (ctx.ofs != ofs) | 
 | 	    SetConsoleCursorPosition(ctx.hConOut, WCEL_GetCoord(&ctx, ctx.ofs)); | 
 |     } | 
 |     if (ctx.error) | 
 |     { | 
 | 	HeapFree(GetProcessHeap(), 0, ctx.line); | 
 | 	ctx.line = NULL; | 
 |     } | 
 |     WCEL_FreeYank(&ctx); | 
 |     if (ctx.line) | 
 | 	CONSOLE_AppendHistory(ctx.line); | 
 |  | 
 |     CloseHandle(ctx.hConOut); | 
 |     HeapFree(GetProcessHeap(), 0, ctx.histCurr); | 
 |     return ctx.line; | 
 | } |