11import { useEffect , useRef , useState , type RefObject } from 'react' ;
2+ import { EditorView } from '@codemirror/view' ;
23import { motion , AnimatePresence } from 'framer-motion' ;
34import { Search , X , ChevronUp , ChevronDown , ChevronRight , CaseSensitive , Regex } from 'lucide-react' ;
45import {
@@ -36,13 +37,32 @@ function countMatches(view: ReturnType<DaxEditorHandle['getView']>, query: Searc
3637 }
3738}
3839
40+ function getCurrentMatchIndex ( view : ReturnType < DaxEditorHandle [ 'getView' ] > , query : SearchQuery ) : number {
41+ if ( ! view || ! query . search ) return 0 ;
42+ try {
43+ const cursorPos = view . state . selection . main . from ;
44+ const cursor = query . getCursor ( view . state . doc ) ;
45+ let index = 0 ;
46+ let result = cursor . next ( ) ;
47+ while ( ! result . done ) {
48+ if ( result . value . from >= cursorPos ) return index ;
49+ index ++ ;
50+ result = cursor . next ( ) ;
51+ }
52+ return 0 ;
53+ } catch {
54+ return 0 ;
55+ }
56+ }
57+
3958export function SearchBar ( { editorRef, onClose } : SearchBarProps ) {
4059 const [ searchText , setSearchText ] = useState ( '' ) ;
4160 const [ replaceText , setReplaceText ] = useState ( '' ) ;
4261 const [ caseSensitive , setCaseSensitive ] = useState ( false ) ;
4362 const [ useRegex , setUseRegex ] = useState ( false ) ;
4463 const [ showReplace , setShowReplace ] = useState ( false ) ;
4564 const [ matchCount , setMatchCount ] = useState ( 0 ) ;
65+ const [ currentMatchIndex , setCurrentMatchIndex ] = useState ( 0 ) ;
4666 const [ regexError , setRegexError ] = useState ( false ) ;
4767
4868 const searchInputRef = useRef < HTMLInputElement > ( null ) ;
@@ -78,13 +98,37 @@ export function SearchBar({ editorRef, onClose }: SearchBarProps) {
7898 const view = editorRef . current ?. getView ( ) ;
7999 if ( ! view ) return ;
80100 const q = buildQuery ( search ) ;
81- if ( ! q ) { setMatchCount ( 0 ) ; return ; }
101+ if ( ! q ) { setMatchCount ( 0 ) ; setCurrentMatchIndex ( 0 ) ; return ; }
82102 view . dispatch ( { effects : setSearchQuery . of ( q ) } ) ;
83- setMatchCount ( countMatches ( view , q ) ) ;
103+ const count = countMatches ( view , q ) ;
104+ setMatchCount ( count ) ;
105+
106+ if ( count > 0 ) {
107+ const cursor = q . getCursor ( view . state . doc ) ;
108+ const result = cursor . next ( ) ;
109+ if ( ! result . done ) {
110+ const { from, to } = result . value ;
111+ view . dispatch ( {
112+ selection : { anchor : from , head : to } ,
113+ effects : EditorView . scrollIntoView ( from )
114+ } ) ;
115+ setCurrentMatchIndex ( 0 ) ;
116+ }
117+ } else {
118+ setCurrentMatchIndex ( 0 ) ;
119+ }
84120 }
85121
86122 useEffect ( ( ) => {
87- applyQuery ( searchText ) ;
123+ const timer = setTimeout ( ( ) => {
124+ if ( searchText ) {
125+ applyQuery ( searchText ) ;
126+ } else {
127+ setMatchCount ( 0 ) ;
128+ setCurrentMatchIndex ( 0 ) ;
129+ }
130+ } , 150 ) ;
131+ return ( ) => clearTimeout ( timer ) ;
88132 // eslint-disable-next-line react-hooks/exhaustive-deps
89133 } , [ searchText , caseSensitive , useRegex ] ) ;
90134
@@ -93,13 +137,17 @@ export function SearchBar({ editorRef, onClose }: SearchBarProps) {
93137 if ( ! view || ! searchText ) return ;
94138 applyQuery ( searchText ) ;
95139 findNext ( view ) ;
140+ const q = buildQuery ( searchText ) ;
141+ if ( q ) setCurrentMatchIndex ( getCurrentMatchIndex ( view , q ) ) ;
96142 }
97143
98144 function handleFindPrev ( ) {
99145 const view = editorRef . current ?. getView ( ) ;
100146 if ( ! view || ! searchText ) return ;
101147 applyQuery ( searchText ) ;
102148 findPrevious ( view ) ;
149+ const q = buildQuery ( searchText ) ;
150+ if ( q ) setCurrentMatchIndex ( getCurrentMatchIndex ( view , q ) ) ;
103151 }
104152
105153 function handleReplaceAll ( ) {
@@ -174,14 +222,14 @@ export function SearchBar({ editorRef, onClose }: SearchBarProps) {
174222 type = "button"
175223 onClick = { ( ) => setShowReplace ( ( v ) => ! v ) }
176224 className = { cn (
177- 'inline-flex h-6 w-6 flex-shrink-0 items-center justify-center rounded border transition-all' ,
225+ 'group inline-flex h-6 w-6 flex-shrink-0 items-center justify-center rounded border transition-all' ,
178226 showReplace
179- ? 'border-primary/40 bg-primary/15 text-primary'
180- : 'border-border/60 bg-background/60 text-muted- foreground hover:border-border hover:bg-accent hover:text-foreground ' ,
227+ ? 'border-primary/60 bg-primary/25 text-primary drop-shadow-sm '
228+ : 'border-border/50 bg-input/40 text-foreground/60 drop-shadow-sm hover:bg-surface-elevated/50 hover:text-primary ' ,
181229 ) }
182230 >
183231 < ChevronRight
184- className = { cn ( 'h-3.5 w-3.5 transition-transform duration-150' , showReplace && 'rotate-90' ) }
232+ className = { cn ( 'h-3.5 w-3.5 transition-transform duration-150 group-hover:scale-110 ' , showReplace && 'rotate-90' ) }
185233 />
186234 </ button >
187235 </ TooltipTrigger >
@@ -192,7 +240,7 @@ export function SearchBar({ editorRef, onClose }: SearchBarProps) {
192240
193241 { /* Search input */ }
194242 < div className = { cn (
195- 'relative flex h-7 flex-1 items-center rounded border bg-background/60 px-2' ,
243+ 'relative flex h-7 flex-1 items-center rounded border bg-surface-elevated/40 px-2' ,
196244 regexError ? 'border-destructive' : 'border-border/60 focus-within:border-primary/60' ,
197245 ) } >
198246 < Search className = "mr-1.5 h-3 w-3 flex-shrink-0 text-muted-foreground" />
@@ -207,10 +255,10 @@ export function SearchBar({ editorRef, onClose }: SearchBarProps) {
207255 />
208256 { searchText && (
209257 < span className = { cn (
210- 'ml-1 flex-shrink-0 text-[10px] tabular-nums' ,
258+ 'ml-1 flex-shrink-0 whitespace-nowrap text-[10px] tabular-nums' ,
211259 matchCount > 0 ? 'text-muted-foreground' : 'text-destructive/70' ,
212260 ) } >
213- { matchCount > 0 ? `${ matchCount } ` : 'Nenhum resultado' }
261+ { matchCount > 0 ? `${ currentMatchIndex + 1 } / ${ matchCount } ` : 'Nenhum resultado' }
214262 </ span >
215263 ) }
216264 </ div >
@@ -324,7 +372,7 @@ export function SearchBar({ editorRef, onClose }: SearchBarProps) {
324372 { /* Spacer to align with search input */ }
325373 < span className = "h-6 w-6 flex-shrink-0" />
326374
327- < div className = "flex h-7 flex-1 items-center rounded border border-border/60 bg-background/60 px-2 focus-within:border-primary/60" >
375+ < div className = "flex h-7 flex-1 items-center rounded border border-border/60 bg-surface-elevated/40 px-2 focus-within:border-primary/60" >
328376 < input
329377 ref = { replaceInputRef }
330378 type = "text"
0 commit comments