[PATCH v3 4/4] programs/cmd: implement filename completion
Eric Pouech
eric.pouech at gmail.com
Tue Mar 1 02:26:14 CST 2022
Signed-off-by: Eric Pouech <eric.pouech at 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) */
More information about the wine-devel
mailing list