[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