@@ -10,6 +10,8 @@ import {
10
10
closeSearchPanel ,
11
11
findNext ,
12
12
findPrevious ,
13
+ replaceAll ,
14
+ replaceNext ,
13
15
SearchQuery ,
14
16
selectMatches ,
15
17
setSearchQuery ,
@@ -43,40 +45,24 @@ import { SearchFormProps } from './SearchForm.types';
43
45
export function SearchForm ( { view } : SearchFormProps ) {
44
46
const [ isOpen , setIsOpen ] = useState ( false ) ;
45
47
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
+ ) ;
46
61
const [ findCount , setFindCount ] = useState ( 0 ) ;
47
62
const { theme } = useDarkMode ( ) ;
48
63
const [ selectedIndex , setSelectedIndex ] = useState < number | null > ( null ) ;
49
64
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 ( ( ) => {
80
66
const cursor = query . getCursor ( view . state . doc ) ;
81
67
const selection = view . state . selection . main ;
82
68
@@ -88,26 +74,17 @@ export function SearchForm({ view }: SearchFormProps) {
88
74
result . value . from === selection . from &&
89
75
result . value . to === selection . to
90
76
) {
91
- return index ;
77
+ setSelectedIndex ( index ) ;
78
+ return ;
92
79
}
93
80
index ++ ;
94
81
result = cursor . next ( ) ;
95
82
}
96
83
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 ] ) ;
110
86
87
+ const updateFindCount = useCallback ( ( ) => {
111
88
const cursor = query . getCursor ( view . state . doc ) ;
112
89
let count = 0 ;
113
90
let result = cursor . next ( ) ;
@@ -118,47 +95,97 @@ export function SearchForm({ view }: SearchFormProps) {
118
95
}
119
96
120
97
setFindCount ( count ) ;
98
+ } , [ query , view ] ) ;
121
99
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
+ } ) ;
128
108
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
+ ) ;
140
129
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
+ ) ;
143
150
144
151
const handleFindFormSubmit = useCallback (
145
152
( e : FormEvent < HTMLFormElement > ) => {
146
153
e . preventDefault ( ) ;
147
154
findNext ( view ) ;
148
- setSelectedIndex ( computeSelectedIndex ( ) ) ;
155
+ updateSelectedIndex ( ) ;
149
156
} ,
150
- [ view , computeSelectedIndex ] ,
157
+ [ view , updateSelectedIndex ] ,
151
158
) ;
152
159
153
160
const handleNextClick = useCallback ( ( ) => {
154
161
findNext ( view ) ;
155
- setSelectedIndex ( computeSelectedIndex ( ) ) ;
156
- } , [ view , computeSelectedIndex ] ) ;
162
+ updateSelectedIndex ( ) ;
163
+ } , [ view , updateSelectedIndex ] ) ;
157
164
158
165
const handlePreviousClick = useCallback ( ( ) => {
159
166
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
+ ) ;
162
189
163
190
return (
164
191
< div
@@ -236,17 +263,26 @@ export function SearchForm({ view }: SearchFormProps) {
236
263
aria-hidden = { ! isOpen }
237
264
>
238
265
< 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
+ >
245
280
Replace
246
281
</ Button >
247
282
< Button
248
283
aria-label = "replace all button"
249
284
className = { replaceButtonStyles }
285
+ onClick = { handleReplaceAll }
250
286
>
251
287
Replace All
252
288
</ Button >
0 commit comments