@@ -4,8 +4,10 @@ import { EditorView, type ViewUpdate } from '@codemirror/view';
44import { getDefaultExtensions } from './getDefaultExtensions' ;
55import { getStatistics } from './utils' ;
66import { type ReactCodeMirrorProps } from '.' ;
7+ import { TimeoutLatch , getScheduler } from './timeoutLatch' ;
78
89const External = Annotation . define < boolean > ( ) ;
10+ const TYPING_TIMOUT = 200 ; // ms
911
1012export interface UseCodeMirror extends ReactCodeMirrorProps {
1113 container ?: HTMLDivElement | null ;
@@ -41,6 +43,8 @@ export function useCodeMirror(props: UseCodeMirror) {
4143 const [ container , setContainer ] = useState < HTMLDivElement | null > ( ) ;
4244 const [ view , setView ] = useState < EditorView > ( ) ;
4345 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 ] ;
4448 const defaultThemeOption = EditorView . theme ( {
4549 '&' : {
4650 height,
@@ -62,6 +66,20 @@ export function useCodeMirror(props: UseCodeMirror) {
6266 // If transaction is market as remote we don't have to call `onChange` handler again
6367 ! vu . transactions . some ( ( tr ) => tr . annotation ( External ) )
6468 ) {
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+
6583 const doc = vu . state . doc ;
6684 const value = doc . toString ( ) ;
6785 onChange ( value , vu ) ;
@@ -126,6 +144,10 @@ export function useCodeMirror(props: UseCodeMirror) {
126144 view . destroy ( ) ;
127145 setView ( undefined ) ;
128146 }
147+ if ( typingLatch . current ) {
148+ typingLatch . current . cancel ( ) ;
149+ typingLatch . current = null ;
150+ }
129151 } ,
130152 [ view ] ,
131153 ) ;
@@ -165,10 +187,22 @@ export function useCodeMirror(props: UseCodeMirror) {
165187 }
166188 const currentValue = view ? view . state . doc . toString ( ) : '' ;
167189 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+ }
172206 }
173207 } , [ value , view ] ) ;
174208
0 commit comments