Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 10 additions & 6 deletions src/mcp/mcp.c
Original file line number Diff line number Diff line change
Expand Up @@ -4496,17 +4496,19 @@ static void build_grep_cmd(char *cmd, size_t cmd_sz, bool use_regex, bool scoped
if (file_pattern) {
snprintf(
cmd, cmd_sz,
"powershell -Command \"$pat = Get-Content '%s'; "
"Get-Content '%s' | ForEach-Object { Select-String -LiteralPath $_ -Pattern $pat%s "
"powershell -Command \"$pat = Get-Content -Encoding UTF8 -LiteralPath '%s'; "
"Get-Content -Encoding UTF8 -LiteralPath '%s' | ForEach-Object { Select-String "
"-LiteralPath $_ -Pattern $pat%s "
"-ErrorAction SilentlyContinue }"
" | Where-Object { $_.Path -like '*%s' }"
" | ForEach-Object { $_.Path + [char]9 + $_.LineNumber + [char]9 + $_.Line }\"",
tmpfile, filelist, sm, file_pattern);
} else {
snprintf(
cmd, cmd_sz,
"powershell -Command \"$pat = Get-Content '%s'; "
"Get-Content '%s' | ForEach-Object { Select-String -LiteralPath $_ -Pattern $pat%s "
"powershell -Command \"$pat = Get-Content -Encoding UTF8 -LiteralPath '%s'; "
"Get-Content -Encoding UTF8 -LiteralPath '%s' | ForEach-Object { Select-String "
"-LiteralPath $_ -Pattern $pat%s "
"-ErrorAction SilentlyContinue }"
" | ForEach-Object { $_.Path + [char]9 + $_.LineNumber + [char]9 + $_.Line }\"",
tmpfile, filelist, sm);
Expand All @@ -4517,15 +4519,17 @@ static void build_grep_cmd(char *cmd, size_t cmd_sz, bool use_regex, bool scoped
cmd, cmd_sz,
"powershell -Command \"Get-ChildItem -Recurse -Path '%s\\*' -Include '%s' -File "
"-ErrorAction SilentlyContinue"
" | Select-String -Pattern (Get-Content '%s')%s -ErrorAction SilentlyContinue"
" | Select-String -Pattern (Get-Content -Encoding UTF8 -LiteralPath '%s')%s "
"-ErrorAction SilentlyContinue"
" | ForEach-Object { $_.Path + [char]9 + $_.LineNumber + [char]9 + $_.Line }\"",
root_path, file_pattern, tmpfile, sm);
} else {
snprintf(
cmd, cmd_sz,
"powershell -Command \"Get-ChildItem -Recurse -Path '%s\\*' -File -ErrorAction "
"SilentlyContinue"
" | Select-String -Pattern (Get-Content '%s')%s -ErrorAction SilentlyContinue"
" | Select-String -Pattern (Get-Content -Encoding UTF8 -LiteralPath '%s')%s "
"-ErrorAction SilentlyContinue"
" | ForEach-Object { $_.Path + [char]9 + $_.LineNumber + [char]9 + $_.Line }\"",
root_path, tmpfile, sm);
}
Expand Down
77 changes: 77 additions & 0 deletions tests/test_mcp.c
Original file line number Diff line number Diff line change
Expand Up @@ -1755,6 +1755,80 @@ TEST(search_code_scoped_path_with_spaces_issue687) {
PASS();
}

#ifdef _WIN32
/* Issue #903 follow-up: scoped search_code on Windows writes a UTF-8 filelist
* containing absolute source paths, then reads it back through PowerShell.
* Windows PowerShell 5.1 treats UTF-8 without BOM as ANSI unless told
* otherwise, so a non-ASCII project root can be mojibaked before
* Select-String sees the LiteralPath. */
TEST(search_code_scoped_path_with_cjk_root_issue903) {
char tmp[512];
snprintf(tmp, sizeof(tmp), "%s/cbm_srch_cjk_XXXXXX", cbm_tmpdir());
if (!cbm_mkdtemp(tmp)) {
FAIL("cbm_mkdtemp failed");
}

char proj_dir[640];
snprintf(proj_dir, sizeof(proj_dir), "%s/%s", tmp,
"\xE4\xB8\xAD\xE6\x96\x87\xE9\xA1\xB9\xE7\x9B\xAE");
if (!cbm_mkdir_p(proj_dir, 0755)) {
cbm_rmdir(tmp);
FAIL("cannot create CJK project dir");
}

char src_path[768];
snprintf(src_path, sizeof(src_path), "%s/main.go", proj_dir);
FILE *fp = cbm_fopen(src_path, "wb");
if (!fp) {
cbm_rmdir(proj_dir);
cbm_rmdir(tmp);
FAIL("cannot write source file under CJK path");
}
fprintf(fp, "package main\n\nfunc HandleRequest() error {\n\treturn nil\n}\n");
fclose(fp);

cbm_mcp_server_t *srv = cbm_mcp_server_new(NULL);
ASSERT_NOT_NULL(srv);
cbm_store_t *st = cbm_mcp_server_store(srv);
ASSERT_NOT_NULL(st);
const char *proj = "cjk-search";
cbm_mcp_server_set_project(srv, proj);
cbm_store_upsert_project(st, proj, proj_dir);

cbm_node_t n = {.project = proj,
.label = "Function",
.name = "HandleRequest",
.qualified_name = "cjk-search.main.HandleRequest",
.file_path = "main.go",
.start_line = 3,
.end_line = 5};
ASSERT_GT(cbm_store_upsert_node(st, &n), 0);

char *resp = cbm_mcp_server_handle(
srv, "{\"jsonrpc\":\"2.0\",\"id\":903,\"method\":\"tools/call\","
"\"params\":{\"name\":\"search_code\","
"\"arguments\":{\"pattern\":\"HandleRequest\",\"project\":\"cjk-search\"}}}");
ASSERT_NOT_NULL(resp);
char *inner = extract_text_content(resp);
ASSERT_NOT_NULL(inner);

int grep_matches = -1;
const char *g = strstr(inner, "\"total_grep_matches\":");
if (g) {
sscanf(g, "\"total_grep_matches\":%d", &grep_matches);
}
ASSERT_TRUE(grep_matches > 0);

free(inner);
free(resp);
cbm_mcp_server_free(srv);
cbm_unlink(src_path);
cbm_rmdir(proj_dir);
cbm_rmdir(tmp);
PASS();
}
#endif

/* Shared fixture for the path_filter prefilter tests (PR #756 distilled):
* a project with two indexed files that both contain the search pattern —
* src/handler.go (inside the filter) and vendor/other.go (outside it). */
Expand Down Expand Up @@ -5072,6 +5146,9 @@ SUITE(mcp) {
RUN_TEST(tool_search_code_no_project);
RUN_TEST(search_code_multi_word);
RUN_TEST(search_code_scoped_path_with_spaces_issue687);
#ifdef _WIN32
RUN_TEST(search_code_scoped_path_with_cjk_root_issue903);
#endif
RUN_TEST(search_code_path_filter_prefilter_keeps_matches);
RUN_TEST(search_code_path_filter_matches_nothing);
RUN_TEST(search_code_invalid_regex_errors_issue283);
Expand Down
Loading