Skip to content

Commit 82a9052

Browse files
noctrexDeusData
authored andcommitted
feat(mcp): add Windows support for code search via PowerShell
1 parent 1a4973d commit 82a9052

1 file changed

Lines changed: 62 additions & 18 deletions

File tree

src/mcp/mcp.c

Lines changed: 62 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,8 @@ enum {
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. */
29742977
static 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. */
34033451
static 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

Comments
 (0)