5858#include "pipeline/artifact.h"
5959
6060#ifdef _WIN32
61- #include <process.h> /* _getpid */
61+ #include <process.h>
62+ #define getpid _getpid
6263#else
6364#include <unistd.h>
6465#include <poll.h>
@@ -2970,10 +2971,45 @@ static int search_result_cmp(const void *a, const void *b) {
29702971 return rb -> score - ra -> score ; /* descending */
29712972}
29722973
2973- /* Build the grep command string based on scoped vs recursive mode */
2974+ /* Build the grep/search command string based on scoped vs recursive mode.
2975+ * On Windows, uses PowerShell Select-String with tab-delimited output.
2976+ * On POSIX, uses grep with colon-delimited output. */
29742977static void build_grep_cmd (char * cmd , size_t cmd_sz , bool use_regex , bool scoped ,
29752978 const char * file_pattern , const char * tmpfile , const char * filelist ,
29762979 const char * root_path ) {
2980+ #ifdef _WIN32
2981+ const char * sm = use_regex ? "" : " -SimpleMatch" ;
2982+ if (scoped ) {
2983+ if (file_pattern ) {
2984+ snprintf (cmd , cmd_sz ,
2985+ "powershell -Command \"$pat = Get-Content '%s'; "
2986+ "Get-Content '%s' | ForEach-Object { Select-String -LiteralPath $_ -Pattern $pat%s -ErrorAction SilentlyContinue }"
2987+ " | Where-Object { $_.Path -like '*%s' }"
2988+ " | ForEach-Object { $_.Path + [char]9 + $_.LineNumber + [char]9 + $_.Line }\"" ,
2989+ tmpfile , filelist , sm , file_pattern );
2990+ } else {
2991+ snprintf (cmd , cmd_sz ,
2992+ "powershell -Command \"$pat = Get-Content '%s'; "
2993+ "Get-Content '%s' | ForEach-Object { Select-String -LiteralPath $_ -Pattern $pat%s -ErrorAction SilentlyContinue }"
2994+ " | ForEach-Object { $_.Path + [char]9 + $_.LineNumber + [char]9 + $_.Line }\"" ,
2995+ tmpfile , filelist , sm );
2996+ }
2997+ } else {
2998+ if (file_pattern ) {
2999+ snprintf (cmd , cmd_sz ,
3000+ "powershell -Command \"Get-ChildItem -Recurse -Path '%s\\*' -Include '%s' -File -ErrorAction SilentlyContinue"
3001+ " | Select-String -Pattern (Get-Content '%s')%s -ErrorAction SilentlyContinue"
3002+ " | ForEach-Object { $_.Path + [char]9 + $_.LineNumber + [char]9 + $_.Line }\"" ,
3003+ root_path , file_pattern , tmpfile , sm );
3004+ } else {
3005+ snprintf (cmd , cmd_sz ,
3006+ "powershell -Command \"Get-ChildItem -Recurse -Path '%s\\*' -File -ErrorAction SilentlyContinue"
3007+ " | Select-String -Pattern (Get-Content '%s')%s -ErrorAction SilentlyContinue"
3008+ " | ForEach-Object { $_.Path + [char]9 + $_.LineNumber + [char]9 + $_.Line }\"" ,
3009+ root_path , tmpfile , sm );
3010+ }
3011+ }
3012+ #else
29773013 const char * flag = use_regex ? "-E" : "-F" ;
29783014 if (scoped ) {
29793015 if (file_pattern ) {
@@ -2991,6 +3027,7 @@ static void build_grep_cmd(char *cmd, size_t cmd_sz, bool use_regex, bool scoped
29913027 snprintf (cmd , cmd_sz , "grep -rn %s -f '%s' '%s' 2>/dev/null" , flag , tmpfile , root_path );
29923028 }
29933029 }
3030+ #endif
29943031}
29953032
29963033/* Build deduplicated file list from search results + raw matches. */
@@ -3211,19 +3248,27 @@ static grep_match_t *collect_grep_matches(FILE *fp, const char *root_path, size_
32113248 continue ;
32123249 }
32133250
3214- char * colon1 = strchr (line , ':' );
3215- if (!colon1 ) {
3251+ /* PowerShell output uses tab as delimiter (paths may contain colons
3252+ * on Windows, e.g. C:\dir\file). Unix grep uses colon. */
3253+ #ifdef _WIN32
3254+ char sep = '\t' ;
3255+ #else
3256+ char sep = ':' ;
3257+ #endif
3258+ char * sep1 = strchr (line , (unsigned char )sep );
3259+ if (!sep1 ) {
32163260 continue ;
32173261 }
3218- char * colon2 = strchr (colon1 + SKIP_ONE , ':' );
3219- if (!colon2 ) {
3262+ char * sep2 = strchr (sep1 + SKIP_ONE , ( unsigned char ) sep );
3263+ if (!sep2 ) {
32203264 continue ;
32213265 }
3266+ * sep1 = '\0' ;
3267+ * sep2 = '\0' ;
32223268
3223- * colon1 = '\0' ;
3224- * colon2 = '\0' ;
3225-
3226- /* After colon1 truncation, line contains only the file path portion. */
3269+ #ifdef _WIN32
3270+ cbm_normalize_path_sep (line );
3271+ #endif
32273272 const char * path = line ;
32283273 const char * file = strip_root_prefix (path , root_path , root_len );
32293274
@@ -3233,8 +3278,8 @@ static grep_match_t *collect_grep_matches(FILE *fp, const char *root_path, size_
32333278
32343279 safe_grow (gm , gm_count , gm_cap , PAIR_LEN );
32353280 snprintf (gm [gm_count ].file , sizeof (gm [0 ].file ), "%s" , file );
3236- gm [gm_count ].line = (int )strtol (colon1 + SKIP_ONE , NULL , CBM_DECIMAL_BASE );
3237- snprintf (gm [gm_count ].content , sizeof (gm [0 ].content ), "%s" , colon2 + SKIP_ONE );
3281+ gm [gm_count ].line = (int )strtol (sep1 + SKIP_ONE , NULL , CBM_DECIMAL_BASE );
3282+ snprintf (gm [gm_count ].content , sizeof (gm [0 ].content ), "%s" , sep2 + SKIP_ONE );
32383283 sanitize_ascii (gm [gm_count ].content );
32393284 gm_count ++ ;
32403285 }
@@ -3358,10 +3403,13 @@ static bool write_scoped_filelist(cbm_mcp_server_t *srv, const char *project, co
33583403 indexed_count == 0 ) {
33593404 return false;
33603405 }
3361- FILE * fl = fopen (filelist , "w " );
3406+ FILE * fl = fopen (filelist , "wb " );
33623407 bool ok = false;
33633408 if (fl ) {
33643409 for (int fi = 0 ; fi < indexed_count ; fi ++ ) {
3410+ /* Use forward slashes so xargs doesn't interpret Windows
3411+ * backslashes as escape sequences (e.g. \n becomes newline).
3412+ * Binary mode to prevent CRLF (xargs would see trailing \r). */
33653413 (void )fprintf (fl , "%s/%s\n" , root_path , indexed_files [fi ]);
33663414 }
33673415 (void )fclose (fl );
@@ -3401,11 +3449,7 @@ static bool validate_search_args(const char *root_path, const char *file_pattern
34013449
34023450/* Write pattern to a temp file for grep -f. Returns true on success. */
34033451static bool write_pattern_file (char * tmpfile , int tmpfile_sz , const char * pattern ) {
3404- #ifdef _WIN32
3405- snprintf (tmpfile , tmpfile_sz , "/tmp/cbm_search_%d.pat" , (int )_getpid ());
3406- #else
3407- snprintf (tmpfile , tmpfile_sz , "/tmp/cbm_search_%d.pat" , getpid ());
3408- #endif
3452+ snprintf (tmpfile , tmpfile_sz , "%s/cbm_search_%d.pat" , cbm_tmpdir (), (int )getpid ());
34093453 FILE * tf = fopen (tmpfile , "w" );
34103454 if (!tf ) {
34113455 return false;
0 commit comments