Repository: TiddlyWiki/TiddlyWiki5 (JavaScript)

Issue:
[BUG] Search-Replace with $ in Search String
**Describe the bug**
The search-replace eats `$`

**To Reproduce**
Steps to reproduce the behavior:
1. Go to https://tiddlywiki.com/
2. Create a tiddler with content below

```
<$vars for="equation $$x$$ end." with="relation $$x$$  finish." source="This is equation $$x$$ end.">
{{{ [<source>search-replace<for>,<with>] }}}
</$vars>
```

See the result 

```
This is relation $x$ finish.
```

The `search-replace` operator eats one of `$`

NOTE: I also tested with `regexp-mode` with no success!

**Expected behavior**
Should keep both `$$`!

**TiddlyWiki Configuration (please complete the following information):**
 - Version 5.2.0
 - Saving mechanism -
 - Plugins installed -
 
**Desktop (please complete the following information):**
 - OS: Windows 10
 - Browser Chrome 90





Discussion:
This error reported by @sull-vitsy on https://talk.tiddlywiki.org/t/section-editor-plugin-first-stable-release/1060/97?u=mohammad
Are you sure you tried regexp mode?

```
<$vars for="equation $$x$$ end." with="relation $$x$$  finish." source="This is equation $$x$$ end.">
{{{ [<source>search-replace::regexp<for>,<with>] }}}
</$vars>

```
Thank you Saq! I think does not work always! but let me do some tests! As I referenced above I use it in more complex context but the problem is with `search-replcae`
> ```
> <$vars for="equation $$x$$ end." with="relation $$x$$  finish." source="This is equation $$x$$ end.">
> {{{ [<source>search-replace::regexp<for>,<with>] }}}
> </$vars>
> ```


This does not work at all!
I think the underlying issue is probably the escaping of special characters for regular expressions and a possible misunderstanding on my part as to what `$tw.utils.escapeRegExp` does. I will investigate when I get the opportunity but it might take a while.
Thank you Saq! In the mean time if there is a need to provide more examples let me know!
I think I know what the problem is. When I first wrote this for myself I used modern JS where its sufficient using string literals to escape a regular expression character like `$` with one backslash `\$`, however when re-writing this for older JS there is a need for double backslashes `\\$`

We probably need to add `.replace(/\\/g, '\\\\')` for input that is not a regular expression, but it will take time to write tests, confirm the issue and then patch.
Thank you Saq for clarification! No rush! I will put a note in Section Editor (SE) where I use search-replace and one my use SE with KaTex!
@kookma @saqimtiaz 

Something else must be screwy your end @kookma 

![image](https://user-images.githubusercontent.com/436812/137902885-025b7ce6-13a1-45f6-a68a-34960fd5a74e.png)

@CodaCodr - your screenshot shows `search-replace` does not work! Look at for and with vars closely!
nothing happened in the above screenshot!
Ah... I see. Anomaly there is the difference in results. Strange.
@CodaCodr - I appreciate to post the other case here (the one with `@`). As you reported `search-replace` does not work also with `@`
I'm not sure what you mean, @kookma -- the replacement worked.
@kookma this has now been fixed in the pre-release. Please do test.
Much appreciated! Thank you @saqimtiaz 



Patch:
diff --git a/core/modules/filters/strings.js b/core/modules/filters/strings.js
index 96efc0760e8..60a36265d95 100644
--- a/core/modules/filters/strings.js
+++ b/core/modules/filters/strings.js
@@ -121,21 +121,23 @@ exports["search-replace"] = function(source,operator,options) {
 		flagSuffix = (suffixes[0] ? (suffixes[0][0] || "") : ""),
 		flags = (flagSuffix.indexOf("g") !== -1 ? "g" : "") + (flagSuffix.indexOf("i") !== -1 ? "i" : "") + (flagSuffix.indexOf("m") !== -1 ? "m" : ""),
 		isRegExp = (suffixes[1] && suffixes[1][0] === "regexp") ? true : false,
-		searchTerm,
+		//Escape regexp characters if the operand is not a regular expression
+		searchTerm = isRegExp ? operator.operand : $tw.utils.escapeRegExp(operator.operand),
+		//Escape $ character in replacement string if not in regular expression mode
+		replacement = isRegExp ? operator.operands[1] : (operator.operands[1]||"").replace(/\$/g,"$$$$"),
 		regExp;
+	try {
+		regExp = new RegExp(searchTerm,flags);
+	} catch(ex) {
+		return ["RegExp error: " + ex];
+	}
 
 	source(function(tiddler,title) {
 		if(title && (operator.operands.length > 1)) {
-			//Escape regexp characters if the operand is not a regular expression
-			searchTerm = isRegExp ? operator.operand : $tw.utils.escapeRegExp(operator.operand);
-			try {
-				regExp = new RegExp(searchTerm,flags);
-			} catch(ex) {
-				return ["RegExp error: " + ex];
-			}
 			results.push(
-				title.replace(regExp,operator.operands[1])
+				title.replace(regExp,replacement)
 			);
+			regExp.lastIndex = 0;
 		} else {
 			results.push(title);
 		}
diff --git a/editions/tw5.com/tiddlers/filters/examples/search-replace Operator (Examples).tid b/editions/tw5.com/tiddlers/filters/examples/search-replace Operator (Examples).tid
index cb8050dbba5..25b4d1ea78f 100644
--- a/editions/tw5.com/tiddlers/filters/examples/search-replace Operator (Examples).tid
+++ b/editions/tw5.com/tiddlers/filters/examples/search-replace Operator (Examples).tid
@@ -1,5 +1,5 @@
 created: 20201107112846692
-modified: 20201118103305351
+modified: 20211101125225197
 tags: [[Operator Examples]] [[search-replace Operator]]
 title: search-replace Operator (Examples)
 type: text/vnd.tiddlywiki
@@ -25,8 +25,14 @@ You can also use regular expression capture groups in the replacement string:
 `\define names() (\w+)\s(\w+)`
 <<.operator-example 4 """[[John Smith]search-replace::regexp<names>,[$2,$1]]""" >>
 
+You can reference the portion of the input that matches the regular expression with `$&`:
+<<.operator-example 5 """[[John Smith]search-replace::regexp[John .*],[His name is $&]]""">>
+
+<<.operator-example 6 """[[This is an exciting feature]search-replace::regexp[exciting],[amazing and $&]]""">>
+
 To replace everything but a match using a regular expression and the ''multiline'' (m) flag:
 `\define myregexp2() ^(?!Unlike).*$`
-<<.operator-example 5 """[[HelloThere]get[text]search-replace:gm:regexp<myregexp2>,[]]""">>
+<<.operator-example 7 """[[HelloThere]get[text]search-replace:gm:regexp<myregexp2>,[]]""">>
+
 
 {{How to remove stop words}}


Test patch:
diff --git a/editions/test/tiddlers/tests/test-filters.js b/editions/test/tiddlers/tests/test-filters.js
index 04328f58f35..05c5e3c8364 100644
--- a/editions/test/tiddlers/tests/test-filters.js
+++ b/editions/test/tiddlers/tests/test-filters.js
@@ -815,6 +815,10 @@ Tests the filtering mechanism.
 			expect(wiki.filterTiddlers("[[Unlike conventional online services, TiddlyWiki lets you choose where to keep your data\nUnlike conventional online services, TiddlyWiki lets you choose where to keep your data\nUnlike conventional online services, TiddlyWiki lets you choose where to keep your data\nUnlike conventional online services, TiddlyWiki lets you choose where to keep your data]search-replace:g:regexp<myregexp2>,[]]",anchorWidget).join(",")).toBe("conventional online services, TiddlyWiki lets you choose where to keep your data\nUnlike conventional online services, TiddlyWiki lets you choose where to keep your data\nUnlike conventional online services, TiddlyWiki lets you choose where to keep your data\nUnlike conventional online services, TiddlyWiki lets you choose where to keep your data");
 			expect(wiki.filterTiddlers("[[Unlike conventional online services, TiddlyWiki lets you choose where to keep your data\nUnlike conventional online services, TiddlyWiki lets you choose where to keep your data\nUnlike conventional online services, TiddlyWiki lets you choose where to keep your data\nUnlike conventional online services, TiddlyWiki lets you choose where to keep your data]search-replace:gm:regexp<myregexp2>,[]]",anchorWidget).join(",")).toBe("conventional online services, TiddlyWiki lets you choose where to keep your data\nconventional online services, TiddlyWiki lets you choose where to keep your data\nconventional online services, TiddlyWiki lets you choose where to keep your data\nconventional online services, TiddlyWiki lets you choose where to keep your data");
 			expect(wiki.filterTiddlers("[[Hello There\nUnlike conventional online services, TiddlyWiki lets you choose where to keep your data\nguaranteeing that in the decades to come you will still be able to use the notes you take today.]search-replace:gm:regexp<myregexp3>,[]]",anchorWidget).join(",")).toBe("\nUnlike conventional online services, TiddlyWiki lets you choose where to keep your data\n");
+			expect(wiki.filterTiddlers("[[This is equation $$x$$ end.]search-replace[equation $$x$$ end.],[relation $$x$$ finish.]]").join(",")).toBe("This is relation $$x$$ finish.");
+			expect(wiki.filterTiddlers("[[This is an amazing TiddlyWiki]] [[How old is TiddlyWiki?. TiddlyWiki is great]] [[My TiddlyWiki is so fast.]] +[search-replace:g:regexp[TiddlyWiki],[TW]]").join(",")).toBe("This is an amazing TW,How old is TW?. TW is great,My TW is so fast.");
+			expect(wiki.filterTiddlers("[[This is an amazing TiddlyWiki]] [[How old is TiddlyWiki?. TiddlyWiki is great]] [[My TiddlyWiki is so fast.]] +[search-replace::regexp[TiddlyWiki],[TW]]").join(",")).toBe("This is an amazing TW,How old is TW?. TiddlyWiki is great,My TW is so fast.");
+			expect(wiki.filterTiddlers("[[This is an amazing TiddlyWiki]] [[How old is TiddlyWiki?. TiddlyWiki is great]] [[My TiddlyWiki is so fast.]] +[search-replace:g[TiddlyWiki],[TW]]").join(",")).toBe("This is an amazing TW,How old is TW?. TW is great,My TW is so fast.");
 		});
 		
 		it("should handle the pad operator", function() {