Repository: microsoft/vscode (TypeScript)
Date: 2021-03-20 01:28:26+00:00
Task type: Bug Fix

Issue:
Preserve case find replace appears broken
<!-- ⚠️⚠️ Do Not Delete This! bug_report_template ⚠️⚠️ -->
<!-- Please read our Rules of Conduct: https://opensource.microsoft.com/codeofconduct/ -->
<!-- 🕮 Read our guide about submitting issues: https://github.com/microsoft/vscode/wiki/Submitting-Bugs-and-Suggestions -->
<!-- 🔎 Search existing issues to avoid creating duplicates. -->
<!-- 🧪 Test using the latest Insiders build to see if your issue has already been fixed: https://code.visualstudio.com/insiders/ -->
<!-- 💡 Instead of creating your report here, use 'Report Issue' from the 'Help' menu in VS Code to pre-fill useful information. -->
- VS Code Version: 1.54.3
- OS Version: Windows 10 20H2

Steps to Reproduce:

1. Make a file with the same words with different casing
2. Do a find replace with preserve casing switched on
3. Watch the preview does not work properly (and the replace also doesn't)
4. Toggling preserve casing does not do anything

![image](https://user-images.githubusercontent.com/6927344/111368905-b718bc00-8696-11eb-901e-c05a743782f0.png)

I'm pretty sure this worked as expected before..


<!-- 🔧 Launch with `code --disable-extensions` to check. -->
Does this issue occur when all extensions are disabled?: Yes

<!-- 🪓 If you answered No above, use 'Help: Start Extension Bisect' from Command Palette to try to identify the cause. -->
<!-- 📣 Issues caused by an extension need to be reported direct to the extension publisher. The 'Help > Report Issue' dialog can assist with this. -->



Issue discussion:
The feature came via #60311
This case is not handled. When the match is mixed-case and starts with a lowercase character, I think the replace pattern should have its first character lowercased. Would take a PR that adds this.

Solution patch:
diff --git a/src/vs/base/common/search.ts b/src/vs/base/common/search.ts
--- a/src/vs/base/common/search.ts
+++ b/src/vs/base/common/search.ts
@@ -20,6 +20,8 @@ export function buildReplaceStringWithCasePreserved(matches: string[] | null, pa
 			return pattern.toLowerCase();
 		} else if (strings.containsUppercaseCharacter(matches[0][0]) && pattern.length > 0) {
 			return pattern[0].toUpperCase() + pattern.substr(1);
+		} else if (matches[0][0].toUpperCase() !== matches[0][0] && pattern.length > 0) {
+			return pattern[0].toLowerCase() + pattern.substr(1);
 		} else {
 			// we don't understand its pattern yet.
 			return pattern;


Test patch:
diff --git a/src/vs/editor/contrib/find/test/replacePattern.test.ts b/src/vs/editor/contrib/find/test/replacePattern.test.ts
--- a/src/vs/editor/contrib/find/test/replacePattern.test.ts
+++ b/src/vs/editor/contrib/find/test/replacePattern.test.ts
@@ -200,13 +200,16 @@ suite('Replace Pattern test', () => {
 		assertReplace(['abc', 'Abc'], 'Def', 'def');
 		assertReplace(['Abc', 'abc'], 'Def', 'Def');
 		assertReplace(['ABC', 'abc'], 'Def', 'DEF');
+		assertReplace(['aBc', 'abc'], 'Def', 'def');
 		assertReplace(['AbC'], 'Def', 'Def');
-		assertReplace(['aBC'], 'Def', 'Def');
+		assertReplace(['aBC'], 'Def', 'def');
+		assertReplace(['aBc'], 'DeF', 'deF');
 		assertReplace(['Foo-Bar'], 'newfoo-newbar', 'Newfoo-Newbar');
 		assertReplace(['Foo-Bar-Abc'], 'newfoo-newbar-newabc', 'Newfoo-Newbar-Newabc');
 		assertReplace(['Foo-Bar-abc'], 'newfoo-newbar', 'Newfoo-newbar');
 		assertReplace(['foo-Bar'], 'newfoo-newbar', 'newfoo-Newbar');
 		assertReplace(['foo-BAR'], 'newfoo-newbar', 'newfoo-NEWBAR');
+		assertReplace(['foO-BAR'], 'NewFoo-NewBar', 'newFoo-NEWBAR');
 		assertReplace(['Foo_Bar'], 'newfoo_newbar', 'Newfoo_Newbar');
 		assertReplace(['Foo_Bar_Abc'], 'newfoo_newbar_newabc', 'Newfoo_Newbar_Newabc');
 		assertReplace(['Foo_Bar_abc'], 'newfoo_newbar', 'Newfoo_newbar');
@@ -228,13 +231,16 @@ suite('Replace Pattern test', () => {
 		assertReplace(['abc', 'Abc'], 'Def', 'def');
 		assertReplace(['Abc', 'abc'], 'Def', 'Def');
 		assertReplace(['ABC', 'abc'], 'Def', 'DEF');
+		assertReplace(['aBc', 'abc'], 'Def', 'def');
 		assertReplace(['AbC'], 'Def', 'Def');
-		assertReplace(['aBC'], 'Def', 'Def');
+		assertReplace(['aBC'], 'Def', 'def');
+		assertReplace(['aBc'], 'DeF', 'deF');
 		assertReplace(['Foo-Bar'], 'newfoo-newbar', 'Newfoo-Newbar');
 		assertReplace(['Foo-Bar-Abc'], 'newfoo-newbar-newabc', 'Newfoo-Newbar-Newabc');
 		assertReplace(['Foo-Bar-abc'], 'newfoo-newbar', 'Newfoo-newbar');
 		assertReplace(['foo-Bar'], 'newfoo-newbar', 'newfoo-Newbar');
 		assertReplace(['foo-BAR'], 'newfoo-newbar', 'newfoo-NEWBAR');
+		assertReplace(['foO-BAR'], 'NewFoo-NewBar', 'newFoo-NEWBAR');
 		assertReplace(['Foo_Bar'], 'newfoo_newbar', 'Newfoo_Newbar');
 		assertReplace(['Foo_Bar_Abc'], 'newfoo_newbar_newabc', 'Newfoo_Newbar_Newabc');
 		assertReplace(['Foo_Bar_abc'], 'newfoo_newbar', 'Newfoo_newbar');