11import React , { useState , useEffect , useRef , useCallback } from 'react' ;
22import { io , Socket } from 'socket.io-client' ;
33import { AnycodeEditorReact , AnycodeEditor } from 'anycode-react' ;
4- import type { Change , Position } from '../anycode-base/src/code' ;
4+ import type { Change , Position , Edit } from '../anycode-base/src/code' ;
55import { WatcherCreate , WatcherEdits , WatcherRemove ,
66 type CursorHistory , type Terminal , type AcpSession ,
77 type AcpMessage , type AcpPromptStateMessage , type AcpToolCallMessage ,
8- type AcpOpenFileMessage , type SearchResult , type SearchEnd , type SearchMatch
8+ type AcpOpenFileMessage , type SearchResult , type SearchEnd , type SearchMatch ,
9+ type PendingBatch
910} from './types' ;
1011import { loadTerminals , loadTerminalSelected , loadBottomVisible ,
1112 loadLeftPanelVisible , loadRightPanelVisible , loadCenterPaneVisible ,
@@ -23,7 +24,7 @@ import { getAllAgents, getDefaultAgent, updateAgents, getDefaultAgentId,
2324 ensureDefaultAgents
2425} from './agents' ;
2526import { AcpAgent } from './types' ;
26- import { DEFAULT_FILE , DEFAULT_FILE_CONTENT , BACKEND_URL } from './constants' ;
27+ import { DEFAULT_FILE , DEFAULT_FILE_CONTENT , BACKEND_URL , BATCH_DELAY_MS } from './constants' ;
2728import './App.css' ;
2829import {
2930 Completion , CompletionRequest , Diagnostic , DiagnosticResponse ,
@@ -85,6 +86,9 @@ const App: React.FC = () => {
8586 const reconnectAttemptsRef = useRef < number > ( 0 ) ;
8687 const reconnectDelay = 1000 ;
8788
89+ // Batching for file changes
90+ const pendingChangesRef = useRef < Map < string , PendingBatch > > ( new Map ( ) ) ;
91+
8892 const handleLeftPanelVisibleChange = ( index : number , visible : boolean ) => {
8993 console . log ( 'handleLeftPanelVisibleChange' , index , visible ) ;
9094 if ( index === 0 ) {
@@ -221,11 +225,55 @@ const App: React.FC = () => {
221225 }
222226 } , [ activeFileId ] ) ;
223227
228+ const flushChanges = ( filename : string ) => {
229+ const batch = pendingChangesRef . current . get ( filename ) ;
230+ if ( ! batch || batch . changes . length === 0 ) return ;
231+
232+ // Merge all edits from batched changes
233+ const allEdits = batch . changes . flatMap ( c => c . edits ) ;
234+
235+ // Send all batched edits in one message
236+ if ( wsRef . current && isConnected ) {
237+ wsRef . current . emit ( "file:change" , {
238+ file : filename ,
239+ edits : allEdits
240+ } ) ;
241+ }
242+
243+ // Clear the batch
244+ batch . changes = [ ] ;
245+ batch . timerId = null ;
246+ } ;
247+
224248 const handleChange = ( filename : string , change : Change ) => {
225249 console . log ( 'handleChange' , filename , change ) ;
226250
227- if ( wsRef . current && isConnected ) {
228- wsRef . current . emit ( "file:change" , { file : filename , ...change } ) ;
251+ // Handle undo/redo immediately (flush pending changes first)
252+ if ( change . isUndo || change . isRedo ) {
253+ flushChanges ( filename ) ;
254+ if ( wsRef . current && isConnected ) {
255+ wsRef . current . emit ( "file:change" , { file : filename , ...change } ) ;
256+ }
257+ } else {
258+ // Batch regular edits
259+ let batch = pendingChangesRef . current . get ( filename ) ;
260+ if ( ! batch ) {
261+ batch = { changes : [ ] , timerId : null } ;
262+ pendingChangesRef . current . set ( filename , batch ) ;
263+ }
264+
265+ // Add change to batch
266+ batch . changes . push ( change ) ;
267+
268+ // Clear old timer
269+ if ( batch . timerId ) {
270+ clearTimeout ( batch . timerId ) ;
271+ }
272+
273+ // Set new timer to flush changes
274+ batch . timerId = setTimeout ( ( ) => {
275+ flushChanges ( filename ) ;
276+ } , BATCH_DELAY_MS ) ;
229277 }
230278
231279 const file = files . find ( f => f . id === filename ) ;
@@ -269,6 +317,9 @@ const App: React.FC = () => {
269317 } ;
270318
271319 const closeFile = ( fileId : string ) => {
320+ // Flush any pending changes before closing
321+ flushChanges ( fileId ) ;
322+
272323 if ( wsRef . current && isConnected ) {
273324 wsRef . current . emit ( "file:close" , { file : fileId } ) ;
274325 }
@@ -320,6 +371,9 @@ const App: React.FC = () => {
320371 } ;
321372
322373 const saveFile = ( fileId : string ) => {
374+ // Flush any pending changes before saving
375+ flushChanges ( fileId ) ;
376+
323377 const editor = editorRefs . current . get ( fileId ) ;
324378 if ( ! editor ) return ;
325379
@@ -452,6 +506,14 @@ const App: React.FC = () => {
452506 } ;
453507
454508 const disconnectFromBackend = ( ) => {
509+ // Flush all pending changes before disconnecting
510+ pendingChangesRef . current . forEach ( ( batch , filename ) => {
511+ if ( batch . timerId ) {
512+ clearTimeout ( batch . timerId ) ;
513+ }
514+ } ) ;
515+ pendingChangesRef . current . clear ( ) ;
516+
455517 if ( reconnectTimeoutRef . current ) {
456518 clearTimeout ( reconnectTimeoutRef . current ) ;
457519 reconnectTimeoutRef . current = null ;
0 commit comments