Signed-off-by: Eric Pouech eric.pouech@gmail.com
--- programs/cmd/batch.c | 279 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 273 insertions(+), 6 deletions(-)
diff --git a/programs/cmd/batch.c b/programs/cmd/batch.c index 9a262c5fec5..934cbcc7a39 100644 --- a/programs/cmd/batch.c +++ b/programs/cmd/batch.c @@ -227,6 +227,211 @@ WCHAR *WCMD_parameter (WCHAR *s, int n, WCHAR **start, BOOL raw, return WCMD_parameter_with_delims (s, n, start, raw, wholecmdline, L" \t,=;"); }
+struct completion_info +{ + WCHAR** subfiles; + DWORD index_search; + DWORD num_results; +}; + +/* gets directory name out of 'path', appends 'in' and surround with quotes if needed */ +static WCHAR* dup_quoted(const WCHAR* path, const WCHAR* in) +{ + const WCHAR* last = wcsrchr(path, L'/'); + size_t dirlen; + size_t len; + WCHAR* ret; + + if (last) + { + WCHAR* last1 = wcschr(last + 1, L'\'); + if (last1) last = last1; + } + else last = wcsrchr(path, L'\'); + dirlen = last ? (last - path) + 1 : 0; + len = wcslen(in); + ret = malloc((dirlen + len + 1) * sizeof(WCHAR)); + if (ret) + { + memcpy(ret, path, dirlen * sizeof(WCHAR)); + wcscpy(ret + dirlen, in); + if (wcspbrk(ret, L" \t")) /* do we need to surround with quotes? */ + { + WCHAR* new = realloc(ret, (1 + dirlen + len + 1 + 1) * sizeof(WCHAR)); + if (!new) + { + free(ret); + return NULL; + } + ret = new; + memmove(ret + 1, ret, (dirlen + len) * sizeof(WCHAR)); + ret[0] = ret[1 + dirlen + len] = L'"'; + ret[1 + dirlen + len + 1] = L'\0'; + } + } + return ret; +} + +static BOOL init_completion(struct completion_info* ci, const WCHAR* from, DWORD len, BOOL forward) +{ + WCHAR* ptr; + HANDLE hff; + WIN32_FIND_DATAW fd; + BOOL in_quotes = FALSE; + DWORD i, j = 0; + + ptr = malloc((len + 1 + 1) * sizeof(WCHAR)); + if (!ptr) return FALSE; + for (i = 0; i < len; i++) + { + switch (from[i]) + { + case L'"': + in_quotes = !in_quotes; + continue; + case L'\': + if (!in_quotes && i + 1 < len && wcschr(L" \t"\", from[i + 1]) != NULL) + ++i; + break; + case L' ': + case L'\t': + /* shouldn't happen, as 'from' should contain a single argument */ + if (!in_quotes) + { + free(ptr); + return FALSE; + } + break; + } + ptr[j++] = from[i]; + } + ptr[j] = L'*'; + ptr[j + 1] = L'\0'; + hff = FindFirstFileW(ptr, &fd); + ci->num_results = 0; + ci->subfiles = NULL; + if (hff != INVALID_HANDLE_VALUE) + { + do + { + WCHAR** new; + if (!wcscmp(fd.cFileName, L".") || !wcscmp(fd.cFileName, L"..")) + continue; + new = realloc(ci->subfiles, (ci->num_results + 1) * sizeof(*ci->subfiles)); + if (!new) + { + FindClose(hff); + free(ptr); + return FALSE; + } + ci->subfiles = new; + if (!(ci->subfiles[ci->num_results++] = dup_quoted(ptr, fd.cFileName))) + { + FindClose(hff); + free(ptr); + return FALSE; + } + } while (FindNextFileW(hff, &fd)); + FindClose(hff); + } + free(ptr); + if (!ci->num_results) return FALSE; + ci->index_search = forward ? 0 : ci->num_results - 1; + return TRUE; +} + +static void dispose_completion(struct completion_info* ci) +{ + if (ci->subfiles) + { + unsigned i; + for (i = 0; i < ci->num_results; i++) + free(ci->subfiles[i]); + free(ci->subfiles); + ci->subfiles = NULL; + } +} + +static WCHAR* next_completion(struct completion_info* ci, const WCHAR* from, DWORD len, BOOL forward) +{ + if (!ci->subfiles || + memcmp(ci->subfiles[ci->index_search], from, len * sizeof(WCHAR)) || + ci->subfiles[ci->index_search][len]) + { + dispose_completion(ci); + if (!init_completion(ci, from, len, forward)) return NULL; + } + else + { + ci->index_search += forward ? 1 : (ci->num_results - 1); + ci->index_search %= ci->num_results; + } + return ci->subfiles[ci->index_search]; +} + +/* backup cursor position by 'len' (display) characters */ +static BOOL backup_cursor_position(HANDLE hout, int len) +{ + CONSOLE_SCREEN_BUFFER_INFO sbi; + if (GetConsoleScreenBufferInfo(hout, &sbi)) + { + if (len > sbi.dwCursorPosition.X) + { + len -= sbi.dwCursorPosition.X + 1; + sbi.dwCursorPosition.X = sbi.dwSize.X - 1 - (len % sbi.dwSize.X); + sbi.dwCursorPosition.Y -= len / sbi.dwSize.X + 1; + if (sbi.dwCursorPosition.Y < 0) + sbi.dwCursorPosition.Y = 0; + } + else + sbi.dwCursorPosition.X -= len; + return SetConsoleCursorPosition(hout, sbi.dwCursorPosition); + } + return FALSE; +} + +/* write numspaces the character ' ' from cursor position, without altering cursor position */ +static void clear_spaces(HANDLE hout, int numspaces) +{ + static const WCHAR spaces[] = {L' ',L' ',L' ',L' ',L' ',L' ',L' ',L' ',L' ',L' ',L' ',L' ',L' ',L' ',L' ',L' '}; + int ns = numspaces; + + while (ns > 0) + { + DWORD nwritten; + + if (!WriteConsoleW(hout, spaces, min(ns, ARRAY_SIZE(spaces)), &nwritten, NULL) || !nwritten) + break; + ns -= nwritten; + } + backup_cursor_position(hout, numspaces - ns); +} + +/* number of characters to display a string (including escape for control characters */ +static DWORD get_display_length(const WCHAR* str, unsigned len) +{ + DWORD ret = len; + unsigned i; + + for (i = 0; i < len; i++) if (str[i] < ' ') ++ret; + return ret; +} + +static WCHAR *find_start_of_parameter(WCHAR *buf, WCHAR *ptr) +{ + int n = 0; + for (;; n++) + { + WCHAR* next; + WCHAR* pmt = WCMD_parameter(buf, n, &next, TRUE, FALSE); + if (!pmt[0] || next > ptr) + return ptr; + if (ptr <= next + wcslen(pmt)) + return next; + } + return NULL; /* shouldn't happen */ +} + /**************************************************************************** * WCMD_fgets * @@ -242,14 +447,19 @@ WCHAR *WCMD_parameter (WCHAR *s, int n, WCHAR **start, BOOL raw,
WCHAR *WCMD_fgets(WCHAR *buf, DWORD noChars, HANDLE h) { + CONSOLE_READCONSOLE_CONTROL crc; DWORD charsRead; BOOL status; DWORD i;
+ crc.nLength = sizeof(crc); + crc.nInitialChars = 0; + crc.dwCtrlWakeupMask = 1 << '\t'; + crc.dwConsoleKeyState = 0; /* We can't use the native f* functions because of the filename syntax differences between DOS and Unix. Also need to lose the LF (or CRLF) from the line. */
- if (!ReadConsoleW(h, buf, noChars, &charsRead, NULL)) { + if (!ReadConsoleW(h, buf, noChars - 1, &charsRead, &crc)) { LARGE_INTEGER filepos; char *bufA; UINT cp; @@ -282,13 +492,70 @@ WCHAR *WCMD_fgets(WCHAR *buf, DWORD noChars, HANDLE h) heap_free(bufA); } else { + struct completion_info ci = {NULL, 0, 0}; + HANDLE hout = GetStdHandle(STD_OUTPUT_HANDLE); + DWORD last_display_length = 0; + if (!charsRead) return NULL; + do + { + WCHAR* tabptr = wmemchr(buf, L'\t', charsRead); + if (tabptr) + { + WCHAR* start; + WCHAR* completion; + DWORD nwritten; + unsigned display_input_length; + + /* we still have <tab> in buf, so don't include it in count */ + display_input_length = get_display_length(buf, charsRead) - 2; + /* find start of word containing tabptr (if any) */ + buf[charsRead] = '\0'; + start = find_start_of_parameter(buf, tabptr); + completion = next_completion(&ci, start, tabptr - start, !(crc.dwConsoleKeyState & SHIFT_PRESSED)); + if (completion) + { + if (start - buf + wcslen(completion) + 1 > noChars) + { + memcpy(start, completion, (start - buf + noChars - 1) * sizeof(WCHAR)); + charsRead = noChars - 1; + } + else + { + wcscpy(start, completion); + charsRead = wcslen(buf); + } + } + else + { + /* Beep() -- not loud enough, maybe user won't hear it */ + charsRead = tabptr - buf; + } + /* scrolling or spurious write could have happened in ReadConsole + * so re-compute coordinates of start of input relative to current cursor position + */ + if (backup_cursor_position(hout, display_input_length)) + { + DWORD display_completion_length; + + WriteConsoleW(hout, buf, charsRead, &nwritten, NULL); + display_completion_length = get_display_length(buf, charsRead); + if (last_display_length > display_completion_length) + clear_spaces(hout, last_display_length - display_completion_length); + last_display_length = display_completion_length; + } + crc.nInitialChars = charsRead; + } + /* Find first EOL */ + for (i = 0; i < charsRead; i++) { + if (buf[i] == '\n' || buf[i] == '\r') + break; + } + if (i < charsRead) break;
- /* Find first EOL */ - for (i = 0; i < charsRead; i++) { - if (buf[i] == '\n' || buf[i] == '\r') - break; - } + } while (ReadConsoleW(h, buf, noChars - 1, &charsRead, &crc) && charsRead); + + dispose_completion(&ci); }
/* Truncate at EOL (or end of buffer) */