Signed-off-by: Eric Pouech eric.pouech@gmail.com
--- programs/cmd/batch.c | 252 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 246 insertions(+), 6 deletions(-)
diff --git a/programs/cmd/batch.c b/programs/cmd/batch.c index 9a262c5fec5..34385a12a0e 100644 --- a/programs/cmd/batch.c +++ b/programs/cmd/batch.c @@ -227,6 +227,158 @@ 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; +}; + +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) + { + if (dirlen) + { + memcpy(ret, path, (dirlen - 1) * sizeof(WCHAR)); + ret[dirlen - 1] = *last; + } + memcpy(ret + dirlen, in, len * sizeof(WCHAR)); + ret[dirlen + len] = L'\0'; + if (wcspbrk(ret, L" \t"")) /* need 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); + } +} + +static WCHAR* next_completion(struct completion_info* ci, const WCHAR* from, DWORD len, BOOL forward) +{ + if (!ci->subfiles || len != wcslen(ci->subfiles[ci->index_search]) || /* FIXME better check no wcslen, just check last char */ + memcmp(ci->subfiles[ci->index_search], from, len * sizeof(WCHAR))) + { + dispose_completion(ci); + if (!init_completion(ci, from, len, forward)) return NULL; + } + else + { + if (forward) + { + if (++ci->index_search >= ci->num_results) + ci->index_search = 0; + } + else + { + if (ci->index_search-- == 0) + ci->index_search = ci->num_results - 1; + } + } + return ci->subfiles[ci->index_search]; +} + /**************************************************************************** * WCMD_fgets * @@ -249,7 +401,7 @@ WCHAR *WCMD_fgets(WCHAR *buf, DWORD noChars, HANDLE h) /* 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 (!VerifyConsoleIoHandle(h)) { LARGE_INTEGER filepos; char *bufA; UINT cp; @@ -282,13 +434,101 @@ WCHAR *WCMD_fgets(WCHAR *buf, DWORD noChars, HANDLE h) heap_free(bufA); } else { - if (!charsRead) return NULL; + CONSOLE_READCONSOLE_CONTROL crc; + CONSOLE_SCREEN_BUFFER_INFO sbi; + struct completion_info ci = {NULL, 0, 0}; + DWORD nwritten; + HANDLE hout = GetStdHandle(STD_OUTPUT_HANDLE); + + crc.nLength = sizeof(crc); + crc.nInitialChars = 0; + crc.dwCtrlWakeupMask = 1 << '\t'; + crc.dwConsoleKeyState = 0; + GetConsoleScreenBufferInfo(hout, &sbi); + for (;;) + { + WCHAR* ptr; + /* we need to 0-terminate the string anyway */ + if (!ReadConsoleW(h, buf, noChars - 1, &charsRead, &crc) || !charsRead) + { + dispose_completion(&ci); + return NULL; + }
- /* Find first EOL */ - for (i = 0; i < charsRead; i++) { - if (buf[i] == '\n' || buf[i] == '\r') - break; + ptr = wmemchr(buf, L'\t', charsRead); + if (ptr) + { + int n = 0; + WCHAR* start; + WCHAR* completion; + + /* find start of word containing ptr (if any) */ + buf[charsRead] = '\0'; + for (;;) + { + WCHAR* next; + WCHAR* pmt = WCMD_parameter(buf, n, &next, TRUE, FALSE); + if (!pmt[0] || next > ptr) + { + start = ptr; + break; + } + if (ptr <= next + wcslen(pmt)) + { + start = next; + break; + } + n++; + } + completion = next_completion(&ci, start, ptr - start, !(crc.dwConsoleKeyState & SHIFT_PRESSED)); + if (completion) + { + DWORD newlen; + if (start - buf + wcslen(completion) + 1 > noChars) + { + memcpy(start, completion, (start - buf + noChars - 1) * sizeof(WCHAR)); + buf[noChars - 1] = L'\0'; + newlen = noChars - 1; + } + else + { + wcscpy(start, completion); + newlen = wcslen(buf); + } + SetConsoleCursorPosition(hout, sbi.dwCursorPosition); + WriteConsoleW(hout, buf, newlen, &nwritten, NULL); + if (newlen < charsRead) + { + /* clear remaining stuff on input line (FIXME doesn't handle escaped characters) */ + WCHAR space = L' '; + DWORD i; + CONSOLE_SCREEN_BUFFER_INFO saved_sbi; + + GetConsoleScreenBufferInfo(hout, &saved_sbi); + for (i = newlen; i < charsRead; i++) + WriteConsoleW(hout, &space, 1, &nwritten, NULL); + SetConsoleCursorPosition(hout, saved_sbi.dwCursorPosition); + } + charsRead = newlen; + buf[charsRead] = L'\0'; + } + else + { + /* Beep() -- not loud enough, maybe user won't hear it */ + charsRead = ptr - buf; + } + crc.nInitialChars = charsRead; + continue; + } + + /* Find first EOL */ + for (i = 0; i < charsRead; i++) { + if (buf[i] == '\n' || buf[i] == '\r') + break; + } + if (i < charsRead) break; } + dispose_completion(&ci); }
/* Truncate at EOL (or end of buffer) */