fix: preserve placeholder case when restoring project text

This commit is contained in:
muena
2026-10-06 13:43:33 +02:00
parent 7db3949d66
commit 818f9ed814
5 changed files with 54 additions and 5 deletions

17
src/lib/restore-case.ts Normal file
View File

@@ -0,0 +1,17 @@
/** Keep the saved spelling for the canonical placeholder and adapt edited case variants. */
export function restoreOriginalCase(match: string, key: string, original: string): string {
if (match === key) return original;
const letters = match.replace(/[^\p{L}]/gu, "");
if (!letters) return original;
if (letters === letters.toLocaleLowerCase("de")) return original.toLocaleLowerCase("de");
if (letters === letters.toLocaleUpperCase("de")) return original.toLocaleUpperCase("de");
if (/^\p{Lu}\p{Ll}+$/u.test(letters)) {
const originalLetters = original.replace(/[^\p{L}]/gu, "");
if (originalLetters && (originalLetters === originalLetters.toLocaleLowerCase("de")
|| originalLetters === originalLetters.toLocaleUpperCase("de"))) {
return original.toLocaleLowerCase("de").replace(/(^|[^\p{L}])(\p{L})/gu,
(_, prefix: string, letter: string) => prefix + letter.toLocaleUpperCase("de"));
}
}
return original;
}

View File

@@ -1,5 +1,6 @@
import type { PlaceholderMapping } from "../types";
import type { ProjectRun } from "./velum-project";
import { restoreOriginalCase } from "./restore-case";
function escapeRegExp(value: string): string {
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
@@ -98,6 +99,13 @@ function replaceNode(
session: boolean,
): { value: string; count: number } {
const candidates: Match[] = [];
const foldedCounts = new Map<string, number>();
if (!session) {
for (const key of Object.keys(mapping)) {
const folded = key.toUpperCase();
foldedCounts.set(folded, (foldedCounts.get(folded) ?? 0) + 1);
}
}
for (const [key, entry] of Object.entries(mapping)) {
const stripped = key.replace(/[<>]/g, "");
const bare = session ? stripped : key;
@@ -105,7 +113,7 @@ function replaceNode(
matchModes?.[key] === "anywhere"
? escapeRegExp(bare)
: `(?<![\\p{L}\\p{N}])${escapeRegExp(bare)}(?![\\p{L}\\p{N}])`;
const patterns = [new RegExp(boundary, "gu")];
const patterns = [new RegExp(boundary, session ? "gu" : "giu")];
if (session) {
patterns.unshift(
new RegExp(
@@ -116,10 +124,12 @@ function replaceNode(
}
for (const pattern of patterns) {
for (const match of value.matchAll(pattern)) {
if (!session && match[0] !== key && (Object.prototype.hasOwnProperty.call(mapping, match[0])
|| foldedCounts.get(key.toUpperCase()) !== 1)) continue;
candidates.push({
start: match.index,
end: match.index + match[0].length,
replacement: escapeHtml(entry.original),
replacement: escapeHtml(session ? entry.original : restoreOriginalCase(match[0], key, entry.original)),
});
}
}

View File

@@ -5,6 +5,7 @@ import { environment } from "@raycast/api";
import type { EntityType, PlaceholderMapping } from "../types";
import { whitelistKey } from "./project-whitelist";
import type { ProjectWhitelistEntry } from "./project-whitelist";
import { restoreOriginalCase } from "./restore-case";
export { whitelistKey } from "./project-whitelist";
export type { ProjectWhitelistEntry } from "./project-whitelist";
@@ -265,7 +266,7 @@ function restorePattern(mapping: PlaceholderMapping, matchModes: ProjectRun["mat
originalFor: (match) => {
const key = Object.prototype.hasOwnProperty.call(mapping, match)
? match : foldedKeys.get(match.toUpperCase());
return key ? mapping[key].original : undefined;
return key ? restoreOriginalCase(match, key, mapping[key].original) : undefined;
},
};
}