Skip to content

Commit 1adc276

Browse files
committed
Add replace functionality
1 parent 942af93 commit 1adc276

File tree

1 file changed

+111
-75
lines changed

1 file changed

+111
-75
lines changed

packages/code-editor/src/SearchForm/SearchForm.tsx

Lines changed: 111 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import {
1010
closeSearchPanel,
1111
findNext,
1212
findPrevious,
13+
replaceAll,
14+
replaceNext,
1315
SearchQuery,
1416
selectMatches,
1517
setSearchQuery,
@@ -43,40 +45,24 @@ import { SearchFormProps } from './SearchForm.types';
4345
export function SearchForm({ view }: SearchFormProps) {
4446
const [isOpen, setIsOpen] = useState(false);
4547
const [searchString, setSearchString] = useState('');
48+
const [replaceString, setReplaceString] = useState('');
49+
const [isCaseSensitive, setIsCaseSensitive] = useState(false);
50+
const [isWholeWord, setIsWholeWord] = useState(false);
51+
const [isRegex, setIsRegex] = useState(false);
52+
const [query, setQuery] = useState<SearchQuery>(
53+
new SearchQuery({
54+
search: searchString,
55+
caseSensitive: isCaseSensitive,
56+
regexp: isRegex,
57+
wholeWord: isWholeWord,
58+
replace: replaceString,
59+
}),
60+
);
4661
const [findCount, setFindCount] = useState(0);
4762
const { theme } = useDarkMode();
4863
const [selectedIndex, setSelectedIndex] = useState<number | null>(null);
4964

50-
const handleToggleButtonClick = useCallback(
51-
(_e: MouseEvent<HTMLButtonElement>) => {
52-
setIsOpen(currState => !currState);
53-
},
54-
[],
55-
);
56-
57-
const handleCloseButtonClick = useCallback(
58-
(_e: MouseEvent<HTMLButtonElement>) => {
59-
closeSearchPanel(view);
60-
},
61-
[view],
62-
);
63-
64-
const handleSearchQueryChange = useCallback(
65-
(_e: ChangeEvent<HTMLInputElement>) => {
66-
setSearchString(_e.target.value);
67-
},
68-
[],
69-
);
70-
71-
const computeSelectedIndex = useCallback(() => {
72-
const query = new SearchQuery({
73-
search: searchString,
74-
caseSensitive: true,
75-
regexp: false,
76-
wholeWord: false,
77-
replace: '',
78-
});
79-
65+
const updateSelectedIndex = useCallback(() => {
8066
const cursor = query.getCursor(view.state.doc);
8167
const selection = view.state.selection.main;
8268

@@ -88,26 +74,17 @@ export function SearchForm({ view }: SearchFormProps) {
8874
result.value.from === selection.from &&
8975
result.value.to === selection.to
9076
) {
91-
return index;
77+
setSelectedIndex(index);
78+
return;
9279
}
9380
index++;
9481
result = cursor.next();
9582
}
9683

97-
return null;
98-
}, [searchString, view]);
99-
100-
useEffect(() => {
101-
const query = new SearchQuery({
102-
search: searchString,
103-
caseSensitive: true,
104-
regexp: false,
105-
wholeWord: false,
106-
replace: '',
107-
});
108-
109-
view.dispatch({ effects: setSearchQuery.of(query) });
84+
setSelectedIndex(null);
85+
}, [query, view]);
11086

87+
const updateFindCount = useCallback(() => {
11188
const cursor = query.getCursor(view.state.doc);
11289
let count = 0;
11390
let result = cursor.next();
@@ -118,47 +95,97 @@ export function SearchForm({ view }: SearchFormProps) {
11895
}
11996

12097
setFindCount(count);
98+
}, [query, view]);
12199

122-
// Update selected index if current selection matches one of the results
123-
const selection = view.state.selection.main;
124-
const cursor2 = query.getCursor(view.state.doc);
125-
let idx = 1;
126-
let res2 = cursor2.next();
127-
let currentIndex: number | null = null;
100+
useEffect(() => {
101+
const newQuery = new SearchQuery({
102+
search: searchString,
103+
caseSensitive: isCaseSensitive,
104+
regexp: isRegex,
105+
wholeWord: isWholeWord,
106+
replace: replaceString,
107+
});
128108

129-
while (!res2.done) {
130-
if (
131-
res2.value.from === selection.from &&
132-
res2.value.to === selection.to
133-
) {
134-
currentIndex = idx;
135-
break;
136-
}
137-
idx++;
138-
res2 = cursor2.next();
139-
}
109+
setQuery(newQuery);
110+
view.dispatch({ effects: setSearchQuery.of(query) });
111+
updateFindCount();
112+
}, [
113+
replaceString,
114+
searchString,
115+
isCaseSensitive,
116+
isRegex,
117+
isWholeWord,
118+
view,
119+
updateFindCount,
120+
query,
121+
]);
122+
123+
const handleToggleButtonClick = useCallback(
124+
(_e: MouseEvent<HTMLButtonElement>) => {
125+
setIsOpen(currState => !currState);
126+
},
127+
[],
128+
);
140129

141-
setSelectedIndex(currentIndex);
142-
}, [searchString, view, computeSelectedIndex]);
130+
const handleCloseButtonClick = useCallback(
131+
(_e: MouseEvent<HTMLButtonElement>) => {
132+
closeSearchPanel(view);
133+
},
134+
[view],
135+
);
136+
137+
const handleSearchQueryChange = useCallback(
138+
(_e: ChangeEvent<HTMLInputElement>) => {
139+
setSearchString(_e.target.value);
140+
},
141+
[],
142+
);
143+
144+
const handleReplaceQueryChange = useCallback(
145+
(_e: ChangeEvent<HTMLInputElement>) => {
146+
setReplaceString(_e.target.value);
147+
},
148+
[],
149+
);
143150

144151
const handleFindFormSubmit = useCallback(
145152
(e: FormEvent<HTMLFormElement>) => {
146153
e.preventDefault();
147154
findNext(view);
148-
setSelectedIndex(computeSelectedIndex());
155+
updateSelectedIndex();
149156
},
150-
[view, computeSelectedIndex],
157+
[view, updateSelectedIndex],
151158
);
152159

153160
const handleNextClick = useCallback(() => {
154161
findNext(view);
155-
setSelectedIndex(computeSelectedIndex());
156-
}, [view, computeSelectedIndex]);
162+
updateSelectedIndex();
163+
}, [view, updateSelectedIndex]);
157164

158165
const handlePreviousClick = useCallback(() => {
159166
findPrevious(view);
160-
setSelectedIndex(computeSelectedIndex());
161-
}, [view, computeSelectedIndex]);
167+
updateSelectedIndex();
168+
}, [view, updateSelectedIndex]);
169+
170+
const handleReplace = useCallback(() => {
171+
replaceNext(view);
172+
updateSelectedIndex();
173+
updateFindCount();
174+
}, [view, updateSelectedIndex, updateFindCount]);
175+
176+
const handleReplaceAll = useCallback(() => {
177+
replaceAll(view);
178+
updateSelectedIndex();
179+
updateFindCount();
180+
}, [view, updateSelectedIndex, updateFindCount]);
181+
182+
const handleReplaceFormSubmit = useCallback(
183+
(e: FormEvent<HTMLFormElement>) => {
184+
e.preventDefault();
185+
handleReplace();
186+
},
187+
[handleReplace],
188+
);
162189

163190
return (
164191
<div
@@ -236,17 +263,26 @@ export function SearchForm({ view }: SearchFormProps) {
236263
aria-hidden={!isOpen}
237264
>
238265
<div className={getReplaceInnerSectionStyles(theme)}>
239-
<TextInput
240-
placeholder="Replace"
241-
aria-labelledby="replace"
242-
className={replaceInputContainerStyles}
243-
/>
244-
<Button aria-label="replace button" className={replaceButtonStyles}>
266+
<form onSubmit={handleReplaceFormSubmit}>
267+
<TextInput
268+
placeholder="Replace"
269+
aria-labelledby="replace"
270+
className={replaceInputContainerStyles}
271+
value={replaceString}
272+
onChange={handleReplaceQueryChange}
273+
/>
274+
</form>
275+
<Button
276+
aria-label="replace button"
277+
className={replaceButtonStyles}
278+
onClick={handleReplace}
279+
>
245280
Replace
246281
</Button>
247282
<Button
248283
aria-label="replace all button"
249284
className={replaceButtonStyles}
285+
onClick={handleReplaceAll}
250286
>
251287
Replace All
252288
</Button>

0 commit comments

Comments
 (0)