fix(coding-agent,tui): stabilize windows shell/path handling

fixes #1775

fixes #1769
This commit is contained in:
badlogic
2026-03-14 05:36:04 +01:00
parent 1a4d153d7a
commit 4f81c3c28d
10 changed files with 213 additions and 76 deletions

View File

@@ -6,6 +6,42 @@ import { fuzzyFilter } from "./fuzzy.js";
const PATH_DELIMITERS = new Set([" ", "\t", '"', "'", "="]);
function toDisplayPath(value: string): string {
return value.replace(/\\/g, "/");
}
function escapeRegex(value: string): string {
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
function buildFdPathQuery(query: string): string {
const normalized = toDisplayPath(query);
if (!normalized.includes("/")) {
return normalized;
}
const hasTrailingSeparator = normalized.endsWith("/");
const trimmed = normalized.replace(/^\/+|\/+$/g, "");
if (!trimmed) {
return normalized;
}
const separatorPattern = "[\\\\/]";
const segments = trimmed
.split("/")
.filter(Boolean)
.map((segment) => escapeRegex(segment));
if (segments.length === 0) {
return normalized;
}
let pattern = segments.join(separatorPattern);
if (hasTrailingSeparator) {
pattern += separatorPattern;
}
return pattern;
}
function findLastDelimiter(text: string): number {
for (let i = text.length - 1; i >= 0; i -= 1) {
if (PATH_DELIMITERS.has(text[i] ?? "")) {
@@ -112,7 +148,7 @@ function walkDirectoryWithFd(
// Add query as pattern if provided
if (query) {
args.push(query);
args.push(buildFdPathQuery(query));
}
const result = spawnSync(fdPath, args, {
@@ -129,15 +165,17 @@ function walkDirectoryWithFd(
const results: Array<{ path: string; isDirectory: boolean }> = [];
for (const line of lines) {
const normalizedPath = line.endsWith("/") ? line.slice(0, -1) : line;
const displayLine = toDisplayPath(line);
const hasTrailingSeparator = displayLine.endsWith("/");
const normalizedPath = hasTrailingSeparator ? displayLine.slice(0, -1) : displayLine;
if (normalizedPath === ".git" || normalizedPath.startsWith(".git/") || normalizedPath.includes("/.git/")) {
continue;
}
// fd outputs directories with trailing /
const isDirectory = line.endsWith("/");
const isDirectory = hasTrailingSeparator;
results.push({
path: line,
path: displayLine,
isDirectory,
});
}
@@ -448,13 +486,14 @@ export class CombinedAutocompleteProvider implements AutocompleteProvider {
}
private resolveScopedFuzzyQuery(rawQuery: string): { baseDir: string; query: string; displayBase: string } | null {
const slashIndex = rawQuery.lastIndexOf("/");
const normalizedQuery = toDisplayPath(rawQuery);
const slashIndex = normalizedQuery.lastIndexOf("/");
if (slashIndex === -1) {
return null;
}
const displayBase = rawQuery.slice(0, slashIndex + 1);
const query = rawQuery.slice(slashIndex + 1);
const displayBase = normalizedQuery.slice(0, slashIndex + 1);
const query = normalizedQuery.slice(slashIndex + 1);
let baseDir: string;
if (displayBase.startsWith("~/")) {
@@ -477,10 +516,11 @@ export class CombinedAutocompleteProvider implements AutocompleteProvider {
}
private scopedPathForDisplay(displayBase: string, relativePath: string): string {
const normalizedRelativePath = toDisplayPath(relativePath);
if (displayBase === "/") {
return `/${relativePath}`;
return `/${normalizedRelativePath}`;
}
return `${displayBase}${relativePath}`;
return `${toDisplayPath(displayBase)}${normalizedRelativePath}`;
}
// Get file/directory suggestions for a given path prefix
@@ -559,7 +599,7 @@ export class CombinedAutocompleteProvider implements AutocompleteProvider {
if (displayPrefix.endsWith("/")) {
// If prefix ends with /, append entry to the prefix
relativePath = displayPrefix + name;
} else if (displayPrefix.includes("/")) {
} else if (displayPrefix.includes("/") || displayPrefix.includes("\\")) {
// Preserve ~/ format for home directory paths
if (displayPrefix.startsWith("~/")) {
const homeRelativeDir = displayPrefix.slice(2); // Remove ~/
@@ -589,6 +629,7 @@ export class CombinedAutocompleteProvider implements AutocompleteProvider {
}
}
relativePath = toDisplayPath(relativePath);
const pathValue = isDirectory ? `${relativePath}/` : relativePath;
const value = buildCompletionValue(pathValue, {
isDirectory,