Files
openidea/src/platform/gitRefName.ts
T
JawandClaude Opus 5.5 d1acf9f9ac feat(vcs): replace spaces typed in a branch name with a separator (#1571)
Ref-name inputs only validated after the fact, so a space typed or pasted into a branch name showed "Branch name contains invalid characters" and disabled Create. Branch-name fields (New Branch, from a revision and in several repositories, Rename Branch, New Worktree, GitHub New Remote Branch) now clean the name while typing: forbidden characters are dropped and whitespace becomes '-', without stacking separators, keeping the caret in place. Shared via src/platform/gitRefName.ts and the useSanitizedField hook in src/ui.

Closes #1553

Co-authored-by: jaw52 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 23:09:55 +08:00

33 lines
1.5 KiB
TypeScript

/** Ports IntelliJ's `GitRefNameValidator.cleanUpBranchNameOnTyping` (git4idea). */
/** IntelliJ's advanced setting `git.branch.cleanup.symbol` (default `-`). */
export const BRANCH_CLEANUP_SYMBOL = "-";
// Characters and sequences git forbids that cannot be repaired by substitution are dropped.
const DROP = /^\.|^-|^\/|[~:^?*"[\\]+|(?:@\{)+|\/(?=\/)|(?:\.(?=\.))+|\.(?=\/)/g;
// IntelliJ drops a dot after a slash with a lookbehind, which pre-16.4 WebKit cannot parse.
const DOT_AFTER_SLASH = /\/\./g;
// A run of whitespace / control characters becomes one separator. It also absorbs separators
// mixed into or next to it, so pressing Space again after the substituted one doesn't stack them (IntelliJ
// collapses only spaces typed in one go).
const SEPARATOR = BRANCH_CLEANUP_SYMBOL.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
const REPLACE = new RegExp(`(?:${SEPARATOR}|[ \\p{Cc}])*[ \\p{Cc}](?:${SEPARATOR}|[ \\p{Cc}])*`, "gu");
function cleanOnce(name: string): string {
return name.replace(DROP, "").replace(DOT_AFTER_SLASH, "/").replace(REPLACE, BRANCH_CLEANUP_SYMBOL);
}
/**
* Makes `name` typable as a branch name: forbidden characters are dropped and spaces become the
* cleanup symbol, so a space never reaches the validator. Runs to a fixed point because the
* substitution can itself form a forbidden start (" x" → "-x" → "x").
*/
export function cleanUpBranchNameOnTyping(name: string): string {
let cleaned = name;
for (;;) {
const next = cleanOnce(cleaned);
if (next === cleaned) return cleaned;
cleaned = next;
}
}