@@ -4,8 +4,10 @@ import { EditorView, type ViewUpdate } from '@codemirror/view';
4
4
import { getDefaultExtensions } from './getDefaultExtensions' ;
5
5
import { getStatistics } from './utils' ;
6
6
import { type ReactCodeMirrorProps } from '.' ;
7
+ import { TimeoutLatch , getScheduler } from './timeoutLatch' ;
7
8
8
9
const External = Annotation . define < boolean > ( ) ;
10
+ const TYPING_TIMOUT = 200 ; // ms
9
11
10
12
export interface UseCodeMirror extends ReactCodeMirrorProps {
11
13
container ?: HTMLDivElement | null ;
@@ -41,6 +43,8 @@ export function useCodeMirror(props: UseCodeMirror) {
41
43
const [ container , setContainer ] = useState < HTMLDivElement | null > ( ) ;
42
44
const [ view , setView ] = useState < EditorView > ( ) ;
43
45
const [ state , setState ] = useState < EditorState > ( ) ;
46
+ const typingLatch = useState < { current : TimeoutLatch | null } > ( ( ) => ( { current : null } ) ) [ 0 ] ;
47
+ const pendingUpdate = useState < { current : ( ( ) => void ) | null } > ( ( ) => ( { current : null } ) ) [ 0 ] ;
44
48
const defaultThemeOption = EditorView . theme ( {
45
49
'&' : {
46
50
height,
@@ -62,6 +66,20 @@ export function useCodeMirror(props: UseCodeMirror) {
62
66
// If transaction is market as remote we don't have to call `onChange` handler again
63
67
! vu . transactions . some ( ( tr ) => tr . annotation ( External ) )
64
68
) {
69
+ if ( typingLatch . current ) {
70
+ typingLatch . current . reset ( ) ;
71
+ } else {
72
+ typingLatch . current = new TimeoutLatch ( ( ) => {
73
+ if ( pendingUpdate . current ) {
74
+ const forceUpdate = pendingUpdate . current ;
75
+ pendingUpdate . current = null ;
76
+ forceUpdate ( ) ;
77
+ }
78
+ typingLatch . current = null ;
79
+ } , TYPING_TIMOUT ) ;
80
+ getScheduler ( ) . add ( typingLatch . current ) ;
81
+ }
82
+
65
83
const doc = vu . state . doc ;
66
84
const value = doc . toString ( ) ;
67
85
onChange ( value , vu ) ;
@@ -126,6 +144,10 @@ export function useCodeMirror(props: UseCodeMirror) {
126
144
view . destroy ( ) ;
127
145
setView ( undefined ) ;
128
146
}
147
+ if ( typingLatch . current ) {
148
+ typingLatch . current . cancel ( ) ;
149
+ typingLatch . current = null ;
150
+ }
129
151
} ,
130
152
[ view ] ,
131
153
) ;
@@ -165,10 +187,22 @@ export function useCodeMirror(props: UseCodeMirror) {
165
187
}
166
188
const currentValue = view ? view . state . doc . toString ( ) : '' ;
167
189
if ( view && value !== currentValue ) {
168
- view . dispatch ( {
169
- changes : { from : 0 , to : currentValue . length , insert : value || '' } ,
170
- annotations : [ External . of ( true ) ] ,
171
- } ) ;
190
+ const isTyping = typingLatch . current && ! typingLatch . current . isDone ;
191
+
192
+ const forceUpdate = ( ) => {
193
+ if ( view && value !== view . state . doc . toString ( ) ) {
194
+ view . dispatch ( {
195
+ changes : { from : 0 , to : view . state . doc . toString ( ) . length , insert : value || '' } ,
196
+ annotations : [ External . of ( true ) ] ,
197
+ } ) ;
198
+ }
199
+ } ;
200
+
201
+ if ( ! isTyping ) {
202
+ forceUpdate ( ) ;
203
+ } else {
204
+ pendingUpdate . current = forceUpdate ;
205
+ }
172
206
}
173
207
} , [ value , view ] ) ;
174
208
0 commit comments