From fb40458983a7ceb23d8bc3f3ba5947970a5c5721 Mon Sep 17 00:00:00 2001 From: bones_was_here Date: Tue, 13 Aug 2024 14:28:34 +1000 Subject: [PATCH] con: support spaces in arguments and VFS tab completion via escapes This is consistent with how it's done in bash and other shells. Renames `completednamebufferlength` and a `t` for clarity. Changes GetMapList() to return the number of bytes written (and be slightly faster), though it wasn't needed in the final version of this patch. Signed-off-by: bones_was_here --- common.c | 8 +++-- console.c | 105 +++++++++++++++++++++++++++++++++++++++--------------- console.h | 2 +- qdefs.h | 2 ++ 4 files changed, 84 insertions(+), 33 deletions(-) diff --git a/common.c b/common.c index 0870a454..67c2a4fb 100644 --- a/common.c +++ b/common.c @@ -831,8 +831,9 @@ qbool COM_ParseToken_Console(const char **datapointer) } // skip whitespace +// unless it's an escaped space skipwhite: - for (;ISWHITESPACE(*data);data++) + for (;ISWHITESPACE_ANDNOTESCAPED(*data, data > *datapointer ? data[-1] : *data);data++) { if (*data == 0) { @@ -869,8 +870,9 @@ skipwhite: else { // regular word - for (;!ISWHITESPACE(*data);data++) - if (len < (int)sizeof(com_token) - 1) + for (;!ISWHITESPACE_ANDNOTESCAPED(*data, data > *datapointer ? data[-1] : *data); data++) + if (len < (int)sizeof(com_token) - 1 + && !(*data == '\\' && data[1] == ' ')) com_token[len++] = *data; com_token[len] = '\0'; com_token_len = len; diff --git a/console.c b/console.c index 6e6d1b2e..70f8fa20 100644 --- a/console.c +++ b/console.c @@ -2220,7 +2220,7 @@ its format (q1/q2/q3/hl) and even its message //LadyHavoc: rewrote bsp type detection, rewrote message extraction to do proper worldspawn parsing //LadyHavoc: added .ent file loading, and redesigned error handling to still try the .ent file even if the map format is not recognized, this also eliminated one goto //LadyHavoc: FIXME: man this GetMapList is STILL ugly code even after my cleanups... -qbool GetMapList (const char *s, char *completedname, int completednamebufferlength) +size_t GetMapList (const char *s, char *completedname, int completednamebuffersize) { fssearch_t *t; char message[1024]; @@ -2228,11 +2228,12 @@ qbool GetMapList (const char *s, char *completedname, int completednamebufferlen unsigned char *len; qfile_t *f; unsigned char buf[1024]; + size_t completednamelen = 0; dpsnprintf(message, sizeof(message), "maps/%s*.bsp", s); t = FS_Search(message, 1, true, NULL); if(!t) - return false; + return 0; if (t->numfilenames > 1) Con_Printf("^1 %i maps found :\n", t->numfilenames); len = (unsigned char *)Z_Malloc(t->numfilenames); @@ -2379,14 +2380,15 @@ qbool GetMapList (const char *s, char *completedname, int completednamebufferlen goto endcomplete; } endcomplete: - if(p > o && completedname && completednamebufferlength > 0) + if(p > o && completedname && completednamebuffersize > 0) { - memset(completedname, 0, completednamebufferlength); - memcpy(completedname, (t->filenames[0]+5), min(p, completednamebufferlength - 1)); + completednamelen = min(p, completednamebuffersize - 1); + memcpy(completedname, (t->filenames[0]+5), completednamelen); + completedname[completednamelen] = '\0'; } Z_Free(len); FS_FreeSearch(t); - return p > o; + return completednamelen; } /* @@ -2833,6 +2835,30 @@ static int Nicks_AddLastColor(char *buffer, int pos) return pos; } + +static size_t Con_UnescapeSpaces(char *dst, const char *src, size_t dsize) +{ + char *dstptr = dst; + char *end = dst + dsize - 1; + + for (; *src && dstptr < end; *dstptr++ = *src++) + if (*src == '\\' && src[1] == ' ') + ++src; + *dstptr = '\0'; + return dstptr - dst; +} +static size_t Con_EscapeSpaces(char *dst, const char *src, size_t dsize) +{ + char *dstptr = dst; + char *end = dst + dsize - 2; // allow for writing 2 chars per iteration + + for (; *src && dstptr < end; *dstptr++ = *src++) + if (*src == ' ') + *dstptr++ = '\\'; + *dstptr = '\0'; + return dstptr - dst; +} + /* Con_CompleteCommandLine @@ -2841,7 +2867,10 @@ static int Nicks_AddLastColor(char *buffer, int pos) Thanks to Fett erich@heintz.com Thanks to taniwha Enhanced to tab-complete map names by [515] - + Nick support by Blub + Pattern-matching file argument support by divVerent + Directory support by divVerent + Escaping support by bones_was_here */ int Con_CompleteCommandLine(cmd_state_t *cmd, qbool is_console) { @@ -2878,7 +2907,8 @@ int Con_CompleteCommandLine(cmd_state_t *cmd, qbool is_console) while(--pos >= linestart) { k = line[pos]; - if(k == '\"' || k == ';' || k == ' ' || k == '\'') + if (k == '\"' || k == ';' || k == '\'' + || (k == ' ' && (!is_console || pos == linestart || line[pos-1] != '\\'))) break; } pos++; @@ -2894,6 +2924,10 @@ int Con_CompleteCommandLine(cmd_state_t *cmd, qbool is_console) space = strchr(line + 1, ' '); if(space && pos == (space - line) + 1) { + char temp[MAX_QPATH]; + char searchstr[MAX_QPATH], result[MAX_QPATH * 2]; // enough to escape all possible spaces + size_t searchstrlen, resultlen; + // adding 1 to line drops the leading ] dp_ustr2stp(command, sizeof(command), line + 1, space - (line + 1)); @@ -2904,15 +2938,17 @@ int Con_CompleteCommandLine(cmd_state_t *cmd, qbool is_console) if(!strcmp(command, "map") || !strcmp(command, "changelevel") || (patterns && !strcmp(patterns, "map"))) { //maps search - char t[MAX_QPATH]; - if (GetMapList(s, t, sizeof(t))) + Con_UnescapeSpaces(searchstr, s, sizeof(searchstr)); + if (GetMapList(searchstr, temp, sizeof(temp))) { + resultlen = Con_EscapeSpaces(result, temp, sizeof(result)); + // first move the cursor - linepos += (int)strlen(t) - (int)strlen(s); + linepos += (int)resultlen - (int)strlen(s); // and now do the actual work *s = 0; - dp_strlcat(line, t, MAX_INPUTLINE); + dp_strlcat(line, result, MAX_INPUTLINE); dp_strlcat(line, s2, MAX_INPUTLINE); //add back chars after cursor // and fix the cursor @@ -2925,7 +2961,6 @@ int Con_CompleteCommandLine(cmd_state_t *cmd, qbool is_console) { if(patterns) { - char t[MAX_QPATH]; stringlist_t resultbuf, dirbuf; // Usage: @@ -2946,6 +2981,8 @@ int Con_CompleteCommandLine(cmd_state_t *cmd, qbool is_console) stringlistinit(&resultbuf); stringlistinit(&dirbuf); + searchstrlen = Con_UnescapeSpaces(searchstr, s, sizeof(searchstr)); + while(COM_ParseToken_Simple(&patterns, false, false, true)) { fssearch_t *search; @@ -2955,12 +2992,12 @@ int Con_CompleteCommandLine(cmd_state_t *cmd, qbool is_console) } else { - const char *slash = strrchr(s, '/'); + const char *slash = strrchr(searchstr, '/'); if(slash) { - dp_ustr2stp(t, sizeof(t), s, slash - s + 1); // + 1, because I want to include the slash - dp_strlcat(t, com_token, sizeof(t)); - search = FS_Search(t, true, true, NULL); + dp_ustr2stp(temp, sizeof(temp), searchstr, slash - searchstr + 1); // + 1, because I want to include the slash + dp_strlcat(temp, com_token, sizeof(temp)); + search = FS_Search(temp, true, true, NULL); } else search = FS_Search(com_token, true, true, NULL); @@ -2968,7 +3005,7 @@ int Con_CompleteCommandLine(cmd_state_t *cmd, qbool is_console) if(search) { for(i = 0; i < search->numfilenames; ++i) - if(!strncmp(search->filenames[i], s, strlen(s))) + if(!strncmp(search->filenames[i], searchstr, searchstrlen)) if(FS_FileType(search->filenames[i]) == FS_FILETYPE_FILE) stringlistappend(&resultbuf, search->filenames[i]); FS_FreeSearch(search); @@ -2978,19 +3015,19 @@ int Con_CompleteCommandLine(cmd_state_t *cmd, qbool is_console) // In any case, add directory names { fssearch_t *search; - const char *slash = strrchr(s, '/'); + const char *slash = strrchr(searchstr, '/'); if(slash) { - dp_ustr2stp(t, sizeof(t), s, slash - s + 1); // + 1, because I want to include the slash - dp_strlcat(t, "*", sizeof(t)); - search = FS_Search(t, true, true, NULL); + dp_ustr2stp(temp, sizeof(temp), searchstr, slash - searchstr + 1); // + 1, because I want to include the slash + dp_strlcat(temp, "*", sizeof(temp)); + search = FS_Search(temp, true, true, NULL); } else search = FS_Search("*", true, true, NULL); if(search) { for(i = 0; i < search->numfilenames; ++i) - if(!strncmp(search->filenames[i], s, strlen(s))) + if(!strncmp(search->filenames[i], searchstr, searchstrlen)) if(FS_FileType(search->filenames[i]) == FS_FILETYPE_DIRECTORY) stringlistappend(&dirbuf, search->filenames[i]); FS_FreeSearch(search); @@ -3001,15 +3038,18 @@ int Con_CompleteCommandLine(cmd_state_t *cmd, qbool is_console) { const char *p, *q; unsigned int matchchars; + bool appendspace = false; if(resultbuf.numstrings == 0 && dirbuf.numstrings == 1) { - dpsnprintf(t, sizeof(t), "%s/", dirbuf.strings[0]); + dpsnprintf(temp, sizeof(temp), "%s/", dirbuf.strings[0]); } else if(resultbuf.numstrings == 1 && dirbuf.numstrings == 0) { - dpsnprintf(t, sizeof(t), "%s ", resultbuf.strings[0]); + dp_strlcpy(temp, resultbuf.strings[0], sizeof(temp)); + // trailing space can't be appended yet as it would get escaped + appendspace = true; } else { @@ -3036,7 +3076,7 @@ int Con_CompleteCommandLine(cmd_state_t *cmd, qbool is_console) else for(; *p && *p == *q; ++p, ++q); matchchars = (unsigned int)(p - resultbuf.strings[0]); - dp_ustr2stp(t, sizeof(t), resultbuf.strings[0], matchchars); + dp_ustr2stp(temp, sizeof(temp), resultbuf.strings[0], matchchars); } else // matching directory only { @@ -3044,7 +3084,7 @@ int Con_CompleteCommandLine(cmd_state_t *cmd, qbool is_console) q = dirbuf.strings[dirbuf.numstrings - 1]; for(; *p && *p == *q; ++p, ++q); matchchars = (unsigned int)(p - dirbuf.strings[0]); - dp_ustr2stp(t, sizeof(t), dirbuf.strings[0], matchchars); + dp_ustr2stp(temp, sizeof(temp), dirbuf.strings[0], matchchars); } // now p points to the first non-equal character, or to the end // of resultbuf.strings[0]. We want to append the characters @@ -3052,12 +3092,19 @@ int Con_CompleteCommandLine(cmd_state_t *cmd, qbool is_console) // the unique prefix } + resultlen = Con_EscapeSpaces(result, temp, sizeof(result)); + if (appendspace && resultlen < sizeof(result) - 1) + { + result[resultlen++] = ' '; + result[resultlen] = '\0'; + } + // first move the cursor - linepos += (int)strlen(t) - (int)strlen(s); + linepos += (int)resultlen - (int)strlen(s); // and now do the actual work *s = 0; - dp_strlcat(line, t, MAX_INPUTLINE); + dp_strlcat(line, result, MAX_INPUTLINE); dp_strlcat(line, s2, MAX_INPUTLINE); //add back chars after cursor // and fix the cursor diff --git a/console.h b/console.h index 0f350376..7ea1ecd0 100644 --- a/console.h +++ b/console.h @@ -74,7 +74,7 @@ void Con_DrawNotify (void); void Con_ClearNotify (void); void Con_ToggleConsole_f(cmd_state_t *cmd); -qbool GetMapList (const char *s, char *completedname, int completednamebufferlength); +size_t GetMapList (const char *s, char *completedname, int completednamebuffersize); /// wrapper function to attempt to either complete the command line /// or to list possible matches grouped by type diff --git a/qdefs.h b/qdefs.h index 78f668d8..ad302bca 100644 --- a/qdefs.h +++ b/qdefs.h @@ -185,6 +185,8 @@ #define ISCOMMENT(ch, pos) ch[pos] == '/' && ch[pos + 1] == '/' && (pos == 0 || ISWHITESPACE(ch[pos - 1])) // This also includes extended characters, and ALL control chars #define ISWHITESPACEORCONTROL(ch) ((signed char) (ch) <= (signed char) ' ') +// Returns false for spaces if they're escaped +#define ISWHITESPACE_ANDNOTESCAPED(ch, prevch) (!(ch) || ((ch) == ' ' && (prevch) != '\\') || (ch) == '\t' || (ch) == '\r' || (ch) == '\n') #define DOUBLE_IS_TRUE_FOR_INT(x) ((x) & 0x7FFFFFFFFFFFFFFF) // also match "negative zero" doubles of value 0x8000000000000000 #define DOUBLE_LOSSLESS_FORMAT "%.17g" -- 2.39.2