In native Windows, the COPY command will display the names of the files as they are copied. Wine should do the same. This change enables that.
-- v20: cmd: Allow '+' as delimiter for tab-completion, e.g. 'copy file+file2'.
From: Joe Souza jsouza@yahoo.com
--- programs/cmd/builtins.c | 29 +++++++++++++++++++++++++++-- programs/cmd/cmd.rc | 1 + programs/cmd/wcmd.h | 1 + 3 files changed, 29 insertions(+), 2 deletions(-)
diff --git a/programs/cmd/builtins.c b/programs/cmd/builtins.c index b78ae9d293b..c2379598976 100644 --- a/programs/cmd/builtins.c +++ b/programs/cmd/builtins.c @@ -656,6 +656,7 @@ RETURN_CODE WCMD_copy(WCHAR * args) WCHAR copycmd[4]; DWORD len; BOOL dstisdevice = FALSE; + unsigned numcopied = 0;
typedef struct _COPY_FILES { @@ -954,6 +955,8 @@ RETURN_CODE WCMD_copy(WCHAR * args) WCHAR *filenamepart; DWORD attributes; BOOL srcisdevice = FALSE; + BOOL havewildcards = FALSE; + BOOL displaynames = anyconcats; /* Display names if we are concatenating. */
/* If it was not explicit, we now know whether we are concatenating or not and hence whether to copy as binary or ascii */ @@ -965,6 +968,13 @@ RETURN_CODE WCMD_copy(WCHAR * args) return errorlevel = ERROR_INVALID_FUNCTION; WINE_TRACE("Full src name is '%s'\n", wine_dbgstr_w(srcpath));
+ havewildcards = wcspbrk(srcpath, L"*?") ? TRUE : FALSE; + /* If we are not already displaying file names due to concatenation, then display them + if using wildards. */ + if (!displaynames) { + displaynames = havewildcards; + } + /* If parameter is a directory, ensure it ends in * */ attributes = GetFileAttributesW(srcpath); if (ends_with_backslash( srcpath )) { @@ -972,17 +982,19 @@ RETURN_CODE WCMD_copy(WCHAR * args) /* We need to know where the filename part starts, so append * and recalculate the full resulting path */ lstrcatW(thiscopy->name, L"*"); + displaynames = TRUE; if (!WCMD_get_fullpath(thiscopy->name, ARRAY_SIZE(srcpath), srcpath, &filenamepart)) return errorlevel = ERROR_INVALID_FUNCTION; WINE_TRACE("Directory, so full name is now '%s'\n", wine_dbgstr_w(srcpath));
- } else if ((wcspbrk(srcpath, L"*?") == NULL) && + } else if (!havewildcards && (attributes != INVALID_FILE_ATTRIBUTES) && (attributes & FILE_ATTRIBUTE_DIRECTORY)) {
/* We need to know where the filename part starts, so append * and recalculate the full resulting path */ lstrcatW(thiscopy->name, L"\*"); + displaynames = TRUE; if (!WCMD_get_fullpath(thiscopy->name, ARRAY_SIZE(srcpath), srcpath, &filenamepart)) return errorlevel = ERROR_INVALID_FUNCTION; WINE_TRACE("Directory, so full name is now '%s'\n", wine_dbgstr_w(srcpath)); @@ -1064,6 +1076,10 @@ RETURN_CODE WCMD_copy(WCHAR * args)
/* Do the copy as appropriate */ if (overwrite) { + if (displaynames) { + WCMD_output_asis(srcpath); + WCMD_output_asis(L"\r\n"); + } if (anyconcats && WCMD_IsSameFile(srcpath, outname)) { /* behavior is as Unix 'touch' (change last-written time only) */ HANDLE file = CreateFileW(srcpath, GENERIC_WRITE, FILE_SHARE_WRITE, NULL, @@ -1097,7 +1113,12 @@ RETURN_CODE WCMD_copy(WCHAR * args) return_code = ERROR_INVALID_FUNCTION; } else { WINE_TRACE("Copied successfully\n"); - if (anyconcats) writtenoneconcat = TRUE; + if (anyconcats) { + writtenoneconcat = TRUE; + numcopied = 1; + } else { + numcopied++; + }
/* Append EOF if ascii destination and we are not going to add more onto the end Note: Testing shows windows has an optimization whereas if you have a binary @@ -1135,6 +1156,10 @@ RETURN_CODE WCMD_copy(WCHAR * args) } }
+ if (numcopied) { + WCMD_output(WCMD_LoadMessage(WCMD_NUMCOPIED), numcopied); + } + /* Exit out of the routine, freeing any remaining allocated memory */ exitreturn:
diff --git a/programs/cmd/cmd.rc b/programs/cmd/cmd.rc index 90091090e11..29d105fa7a2 100644 --- a/programs/cmd/cmd.rc +++ b/programs/cmd/cmd.rc @@ -406,4 +406,5 @@ Enter HELP <command> for further information on any of the above commands.\n" WCMD_BADTOKEN, "Syntax error: unexpected %1\n" WCMD_ENDOFLINE, "End of line" WCMD_ENDOFFILE, "End of file" + WCMD_NUMCOPIED, "%t%1!u! file(s) copied\n" } diff --git a/programs/cmd/wcmd.h b/programs/cmd/wcmd.h index 4c85d7ad8cb..2731743c27a 100644 --- a/programs/cmd/wcmd.h +++ b/programs/cmd/wcmd.h @@ -454,3 +454,4 @@ extern WCHAR version_string[]; #define WCMD_BADTOKEN 1047 #define WCMD_ENDOFLINE 1048 #define WCMD_ENDOFFILE 1049 +#define WCMD_NUMCOPIED 1050
From: Joe Souza jsouza@yahoo.com
--- programs/cmd/tests/test_builtins.cmd | 79 ++++++++++++++++++------ programs/cmd/tests/test_builtins.cmd.exp | 42 +++++++++++++ 2 files changed, 102 insertions(+), 19 deletions(-)
diff --git a/programs/cmd/tests/test_builtins.cmd b/programs/cmd/tests/test_builtins.cmd index 58d78194380..8ae733681eb 100644 --- a/programs/cmd/tests/test_builtins.cmd +++ b/programs/cmd/tests/test_builtins.cmd @@ -3416,6 +3416,30 @@ shift if not "%1"=="" goto :CheckNotExist goto :eof
+:CheckOutputExist +find /i "%1" test1.txt >nul 2>&1 +if errorlevel 0 ( + echo Passed: Found expected %1 in COPY output +) else ( + echo Failed: Did not find expected %1 in COPY output +) +shift +if not "%1"=="" goto :CheckOutputExist +del /q test1.txt >nul 2>&1 +goto :eof + +:CheckOutputNotExist +find /i "%1" test1.txt >nul 2>&1 +if errorlevel 1 ( + echo Passed: Did not find %1 in COPY output +) else ( + echo Failed: Unexpectedly found %1 in COPY output +) +shift +if not "%1"=="" goto :CheckOutputNotExist +del /q test1.txt >nul 2>&1 +goto :eof + rem Note: No way to check file size on NT4 so skip the test :CheckFileSize if not exist "%1" ( @@ -3444,28 +3468,33 @@ rem ----------------------- rem Simple single file copy rem ----------------------- rem Simple single file copy, normally used syntax -copy file1 dummy.file >nul 2>&1 +copy file1 dummy.file >test1.txt 2>&1 if errorlevel 1 echo Incorrect errorlevel +call :CheckOutputNotExist file1 call :CheckExist dummy.file
rem Simple single file copy, destination supplied as two forms of directory -copy file1 dir1 >nul 2>&1 +copy file1 dir1 >test1.txt 2>&1 if errorlevel 1 echo Incorrect errorlevel +call :CheckOutputNotExist file1 call :CheckExist dir1\file1
-copy file1 dir1\ >nul 2>&1 +copy file1 dir1\ >test1.txt 2>&1 if errorlevel 1 echo Incorrect errorlevel +call :CheckOutputNotExist file1 call :CheckExist dir1\file1
rem Simple single file copy, destination supplied as fully qualified destination -copy file1 dir1\file99 >nul 2>&1 +copy file1 dir1\file99 >test1.txt 2>&1 if errorlevel 1 echo Incorrect errorlevel +call :CheckOutputNotExist file1 call :CheckExist dir1\file99
rem Simple single file copy, destination not supplied cd dir1 -copy ..\file1 >nul 2>&1 +copy ..\file1 >test1.txt 2>&1 if errorlevel 1 echo Incorrect errorlevel +call :CheckOutputNotExist file1 call :CheckExist file1 cd ..
@@ -3477,19 +3506,22 @@ call :CheckNotExist dir2 dir2\file1 rem ----------------------- rem Wildcarded copy rem ----------------------- -rem Simple single file copy, destination supplied as two forms of directory -copy file? dir1 >nul 2>&1 +rem Simple wildcarded file copy, destination supplied as two forms of directory +copy file? dir1 >test1.txt 2>&1 if errorlevel 1 echo Incorrect errorlevel +call :CheckOutputExist file1 file2 file3 call :CheckExist dir1\file1 dir1\file2 dir1\file3
-copy file* dir1\ >nul 2>&1 +copy file* dir1\ >test1.txt 2>&1 if errorlevel 1 echo Incorrect errorlevel +call :CheckOutputExist file1 file2 file3 call :CheckExist dir1\file1 dir1\file2 dir1\file3
-rem Simple single file copy, destination not supplied +rem Simple wildcarded file copy, destination not supplied cd dir1 -copy ..\file*.* >nul 2>&1 +copy ..\file*.* >test1.txt 2>&1 if errorlevel 1 echo Incorrect errorlevel +call :CheckOutputExist file1 file2 file3 call :CheckExist file1 file2 file3 cd ..
@@ -3502,51 +3534,60 @@ rem ------------------------------------------------ rem Confirm overwrite works (cannot test prompting!) rem ------------------------------------------------ copy file1 testfile >nul 2>&1 -copy /y file2 testfile >nul 2>&1 +copy /y file2 testfile >test1.txt 2>&1 +call :CheckOutputNotExist file2 call :CheckExist testfile
rem ------------------------------------------------ rem Test concatenation rem ------------------------------------------------ rem simple case, no wildcards -copy file1+file2 testfile >nul 2>&1 +copy file1+file2 testfile >test1.txt 2>&1 if errorlevel 1 echo Incorrect errorlevel +call :CheckOutputExist file1 file2 call :CheckExist testfile
rem simple case, wildcards, no concatenation -copy file* testfile >nul 2>&1 +copy file* testfile >test1.txt 2>&1 if errorlevel 1 echo Incorrect errorlevel +call :CheckOutputExist file1 file2 file3 call :CheckExist testfile
rem simple case, wildcards, and concatenation echo ddddd > fred -copy file*+fred testfile >nul 2>&1 +copy file*+fred testfile >test1.txt 2>&1 if errorlevel 1 echo Incorrect errorlevel +call :CheckOutputExist file1 file2 file3 fred call :CheckExist testfile
rem simple case, wildcards, and concatenation -copy fred+file* testfile >nul 2>&1 +copy fred+file* testfile >test1.txt 2>&1 if errorlevel 1 echo Incorrect errorlevel +call :CheckOutputExist fred file1 file2 file3 call :CheckExist testfile
rem Calculate destination name -copy fred+file* dir1 >nul 2>&1 +copy fred+file* dir1 >test1.txt 2>&1 if errorlevel 1 echo Incorrect errorlevel +call :CheckOutputExist fred file1 file2 file3 call :CheckExist dir1\fred
rem Calculate destination name -copy fred+file* dir1\ >nul 2>&1 +copy fred+file* dir1\ >test1.txt 2>&1 if errorlevel 1 echo Incorrect errorlevel +call :CheckOutputExist fred file1 file2 file3 call :CheckExist dir1\fred
rem Calculate destination name (none supplied) cd dir1 -copy ..\fred+..\file* >nul 2>&1 +copy ..\fred+..\file* >test1.txt 2>&1 if errorlevel 1 echo Incorrect errorlevel +call :CheckOutputExist fred file1 file2 file3 call :CheckExist fred
-copy ..\fr*+..\file1 >nul 2>&1 +copy ..\fr*+..\file1 >test1.txt 2>&1 if errorlevel 1 echo Incorrect errorlevel +call :CheckOutputExist fred file1 call :CheckExist fred cd ..
diff --git a/programs/cmd/tests/test_builtins.cmd.exp b/programs/cmd/tests/test_builtins.cmd.exp index ff6f944cffb..95feb67602b 100644 --- a/programs/cmd/tests/test_builtins.cmd.exp +++ b/programs/cmd/tests/test_builtins.cmd.exp @@ -1936,19 +1936,33 @@ file correctly deleted --- a batch file can alter itself bar ---------- Testing copy +Passed: Did not find file1 in COPY output Passed: Found expected dummy.file +Passed: Did not find file1 in COPY output Passed: Found expected dir1\file1 +Passed: Did not find file1 in COPY output Passed: Found expected dir1\file1 +Passed: Did not find file1 in COPY output Passed: Found expected dir1\file99 +Passed: Did not find file1 in COPY output Passed: Found expected file1 Passed: Did not find dir2 Passed: Did not find dir2\file1 +Passed: Found expected file1 in COPY output +Passed: Found expected file2 in COPY output +Passed: Found expected file3 in COPY output Passed: Found expected dir1\file1 Passed: Found expected dir1\file2 Passed: Found expected dir1\file3 +Passed: Found expected file1 in COPY output +Passed: Found expected file2 in COPY output +Passed: Found expected file3 in COPY output Passed: Found expected dir1\file1 Passed: Found expected dir1\file2 Passed: Found expected dir1\file3 +Passed: Found expected file1 in COPY output +Passed: Found expected file2 in COPY output +Passed: Found expected file3 in COPY output Passed: Found expected file1 Passed: Found expected file2 Passed: Found expected file3 @@ -1956,14 +1970,42 @@ Passed: Did not find dir2 Passed: Did not find dir2\file1 Passed: Did not find dir2\file2 Passed: Did not find dir2\file3 +Passed: Did not find file2 in COPY output Passed: Found expected testfile +Passed: Found expected file1 in COPY output +Passed: Found expected file2 in COPY output Passed: Found expected testfile +Passed: Found expected file1 in COPY output +Passed: Found expected file2 in COPY output +Passed: Found expected file3 in COPY output Passed: Found expected testfile +Passed: Found expected file1 in COPY output +Passed: Found expected file2 in COPY output +Passed: Found expected file3 in COPY output +Passed: Found expected fred in COPY output Passed: Found expected testfile +Passed: Found expected fred in COPY output +Passed: Found expected file1 in COPY output +Passed: Found expected file2 in COPY output +Passed: Found expected file3 in COPY output Passed: Found expected testfile +Passed: Found expected fred in COPY output +Passed: Found expected file1 in COPY output +Passed: Found expected file2 in COPY output +Passed: Found expected file3 in COPY output Passed: Found expected dir1\fred +Passed: Found expected fred in COPY output +Passed: Found expected file1 in COPY output +Passed: Found expected file2 in COPY output +Passed: Found expected file3 in COPY output Passed: Found expected dir1\fred +Passed: Found expected fred in COPY output +Passed: Found expected file1 in COPY output +Passed: Found expected file2 in COPY output +Passed: Found expected file3 in COPY output Passed: Found expected fred +Passed: Found expected fred in COPY output +Passed: Found expected file1 in COPY output Passed: Found expected fred Passed: file size check on file1 [5]@or_broken@Skipping file size check on NT4 Passed: file size check on file2 [8]@or_broken@Skipping file size check on NT4
From: Joe Souza jsouza@yahoo.com
--- programs/cmd/wcmdmain.c | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-)
diff --git a/programs/cmd/wcmdmain.c b/programs/cmd/wcmdmain.c index e9c5be4ceb3..674ea8b1025 100644 --- a/programs/cmd/wcmdmain.c +++ b/programs/cmd/wcmdmain.c @@ -33,6 +33,10 @@
WINE_DEFAULT_DEBUG_CHANNEL(cmd);
+#define BASE_DELIMS L",=;~!^&()+{}[]" +#define PATH_SEPARATION_DELIMS L" " BASE_DELIMS +#define INTRA_PATH_DELIMS L"\" BASE_DELIMS + typedef struct _SEARCH_CONTEXT { WIN32_FIND_DATAW *fd; @@ -133,12 +137,12 @@ static void build_search_string(WCHAR *inputBuffer, int len, SEARCH_CONTEXT *sc)
sc->searchstr[0] = L'\0';
- /* If inputBuffer ends in a space then the user hit tab beyond the last - * parameter, so use that as the search pos (i.e. a wildcard search). + /* If inputBuffer ends in one of our delimiters then use that as the search + * pos (i.e. a wildcard search). * Otherwise, parse the buffer to find the last parameter in the buffer, * where tab was pressed. */ - if (len && inputBuffer[len-1] == L' ') { + if (len && wcschr(PATH_SEPARATION_DELIMS, inputBuffer[len-1])) { cc = len; } else { /* Handle spaces in directory names. Need to quote paths if they contain spaces. @@ -150,7 +154,7 @@ static void build_search_string(WCHAR *inputBuffer, int len, SEARCH_CONTEXT *sc) if (stripped_copy) { wcsncpy_s(last_stripped_copy, ARRAY_SIZE(last_stripped_copy), stripped_copy, _TRUNCATE); } - stripped_copy = WCMD_parameter(inputBuffer, nn++, ¶m, FALSE, FALSE); + stripped_copy = WCMD_parameter_with_delims(inputBuffer, nn++, ¶m, FALSE, FALSE, PATH_SEPARATION_DELIMS); } while (param);
if (last_param) { @@ -184,16 +188,16 @@ static void build_search_string(WCHAR *inputBuffer, int len, SEARCH_CONTEXT *sc)
static void find_insert_pos(const WCHAR *inputBuffer, int len, SEARCH_CONTEXT *sc) { - int cc = len; + int cc = len - 1;
- /* Handle paths here. Find last '\'. - * If '\' isn't found then insert pos is the same as search pos. + /* Handle paths here. Find last '\' or other delimiter. + * If not found then insert pos is the same as search pos. */ - while (cc > sc->search_pos && inputBuffer[cc] != L'\') { + while (cc > sc->search_pos && !wcschr(INTRA_PATH_DELIMS, inputBuffer[cc])) { cc--; }
- if (inputBuffer[cc] == L'"' || inputBuffer[cc] == L'\') { + if (inputBuffer[cc] == L'"' || wcschr(INTRA_PATH_DELIMS, inputBuffer[cc])) { cc++; }
On Mon Jun 16 00:37:54 2025 +0000, eric pouech wrote:
the copy part looks good to me (first 2 commits) handling of separators in completion still lacks a couple of items `dir "a+<TAB>` differs from native `dir a,<TAB>` differs from native `dir a"<TAB>` differs from native note there are characters (other than '+') that native (tested on Win10 only) uses for file separation: '\', '&', '(', ')', '/', '!', '~', '{', '}', '[', ']' and surprisingly '^' (as ^ is an escape character) what is not supported as separator: '<', '>' how strange!!! you want a filename after a redirection! '|' (that's strange I would have expected & and | to behave similarly) and `dir a><TAB>` the '>' is swallowed by completion... not sure we want that (esp. since you'd expect to type a filename after a redirection operator)
Latest changes pushed. This should be very close to what you are looking for. Notable exceptions are examples containing quote characters. I don't see a good way to resolve these issues if we are using WCMD_parameter to parse the line, since this function will remove all quotes it finds. Otherwise, tab-completion should be working with the remainder of these delimiter characters now.