[PATCH 3/3] programs/cmd: implement filename completion
Eric Pouech
eric.pouech at gmail.com
Thu Mar 24 08:46:52 CDT 2022
Signed-off-by: Eric Pouech <eric.pouech at 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) */
More information about the wine-devel
mailing list