@@ -8930,6 +8930,36 @@ bool cbm_search_code_file_pattern_can_prefilter(const char *file_pattern) {
89308930 return true;
89318931}
89328932
8933+ bool cbm_search_code_windows_path_matches_prefilter(const char *path, const char *file_pattern) {
8934+ if (!path || !cbm_search_code_file_pattern_can_prefilter(file_pattern)) {
8935+ return false;
8936+ }
8937+
8938+ const char *suffix = file_pattern + 1;
8939+ size_t path_len = strlen(path);
8940+ size_t suffix_len = strlen(suffix);
8941+ if (path_len < suffix_len) {
8942+ return false;
8943+ }
8944+
8945+ const unsigned char *candidate = (const unsigned char *)path + path_len - suffix_len;
8946+ const unsigned char *expected = (const unsigned char *)suffix;
8947+ for (size_t i = 0; i < suffix_len; i++) {
8948+ unsigned char left = candidate[i];
8949+ unsigned char right = expected[i];
8950+ if (left >= 'A' && left <= 'Z') {
8951+ left = (unsigned char)(left - 'A' + 'a');
8952+ }
8953+ if (right >= 'A' && right <= 'Z') {
8954+ right = (unsigned char)(right - 'A' + 'a');
8955+ }
8956+ if (left != right) {
8957+ return false;
8958+ }
8959+ }
8960+ return true;
8961+ }
8962+
89338963/* Build the grep/search command string based on scoped vs recursive mode.
89348964 * On Windows, uses PowerShell Select-String with tab-delimited output.
89358965 * On POSIX, uses grep with colon-delimited output. */
@@ -8950,30 +8980,16 @@ void cbm_search_code_build_grep_cmd(char *cmd, size_t cmd_sz, bool use_regex, bo
89508980 const char *sm = use_regex ? "" : " -SimpleMatch";
89518981 if (scoped) {
89528982 if (file_pattern) {
8953- if (cbm_search_code_file_pattern_can_prefilter(file_pattern)) {
8954- snprintf(
8955- cmd, cmd_sz,
8956- "powershell -Command \"" CBM_PS_UTF8_PRELUDE
8957- "$pat = Get-Content -Encoding UTF8 -LiteralPath '%s'; "
8958- "Get-Content -Encoding UTF8 -LiteralPath '%s'"
8959- " | Where-Object { $_ -like '%s' }"
8960- " | ForEach-Object { Select-String -LiteralPath $_ -Pattern $pat%s "
8961- "-ErrorAction SilentlyContinue }"
8962- " | Where-Object { $_.Path -like '*%s' }"
8963- " | ForEach-Object { $_.Path + [char]9 + $_.LineNumber + [char]9 + $_.Line }\"",
8964- tmpfile, filelist, file_pattern, sm, file_pattern);
8965- } else {
8966- snprintf(
8967- cmd, cmd_sz,
8968- "powershell -Command \"" CBM_PS_UTF8_PRELUDE
8969- "$pat = Get-Content -Encoding UTF8 -LiteralPath '%s'; "
8970- "Get-Content -Encoding UTF8 -LiteralPath '%s' | ForEach-Object { Select-String "
8971- "-LiteralPath $_ -Pattern $pat%s "
8972- "-ErrorAction SilentlyContinue }"
8973- " | Where-Object { $_.Path -like '*%s' }"
8974- " | ForEach-Object { $_.Path + [char]9 + $_.LineNumber + [char]9 + $_.Line }\"",
8975- tmpfile, filelist, sm, file_pattern);
8976- }
8983+ snprintf(
8984+ cmd, cmd_sz,
8985+ "powershell -Command \"" CBM_PS_UTF8_PRELUDE
8986+ "$pat = Get-Content -Encoding UTF8 -LiteralPath '%s'; "
8987+ "Get-Content -Encoding UTF8 -LiteralPath '%s' | ForEach-Object { Select-String "
8988+ "-LiteralPath $_ -Pattern $pat%s "
8989+ "-ErrorAction SilentlyContinue }"
8990+ " | Where-Object { $_.Path -like '*%s' }"
8991+ " | ForEach-Object { $_.Path + [char]9 + $_.LineNumber + [char]9 + $_.Line }\"",
8992+ tmpfile, filelist, sm, file_pattern);
89778993 } else {
89788994 snprintf(
89798995 cmd, cmd_sz,
@@ -9611,8 +9627,8 @@ static void classify_all_grep_hits(grep_match_t *gm, int gm_count, cbm_store_t *
96119627 * created inside the private scratch directory; this function never opens or
96129628 * closes it, so the list is never reachable through a predictable pathname. */
96139629static bool write_scoped_filelist(cbm_mcp_server_t *srv, const char *project, const char *root_path,
9614- FILE *fl, bool has_path_filter, cbm_regex_t *path_regex ,
9615- int *out_written) {
9630+ FILE *fl, const char *file_pattern, bool has_path_filter ,
9631+ cbm_regex_t *path_regex, int *out_written) {
96169632 *out_written = 0;
96179633 cbm_store_t *pre_store = resolve_store(srv, project);
96189634 if (!pre_store) {
@@ -9644,6 +9660,12 @@ static bool write_scoped_filelist(cbm_mcp_server_t *srv, const char *project, co
96449660 continue;
96459661 }
96469662 }
9663+ #ifdef _WIN32
9664+ if (cbm_search_code_file_pattern_can_prefilter(file_pattern) &&
9665+ !cbm_search_code_windows_path_matches_prefilter(indexed_files[fi], file_pattern)) {
9666+ continue;
9667+ }
9668+ #endif
96479669 /* Write "<root>/<file>" piece-by-piece (no fixed-size buffer, so an
96489670 * arbitrarily long absolute path cannot overflow). Forward slash join
96499671 * so xargs doesn't treat Windows backslashes as escapes; binary mode
@@ -10046,8 +10068,9 @@ static char *handle_search_code(cbm_mcp_server_t *srv, const char *args) {
1004610068 int scoped_written = 0;
1004710069
1004810070 uint64_t scope_t0 = metrics.include_phase_timings ? cbm_now_ms() : 0;
10049- scoped = write_scoped_filelist(srv, project, root_path, scratch.filelist, has_path_filter,
10050- has_path_filter ? &path_regex : NULL, &scoped_written);
10071+ scoped = write_scoped_filelist(srv, project, root_path, scratch.filelist, file_pattern,
10072+ has_path_filter, has_path_filter ? &path_regex : NULL,
10073+ &scoped_written);
1005110074 /* Close before grep runs: this is what flushes the records the helper wrote
1005210075 * through the descriptor. Clearing the field hands ownership to
1005310076 * search_scratch_close, which still unlinks the file itself. */
0 commit comments