@@ -26,6 +26,7 @@ import { useFileTree } from './hooks/useFileTree';
2626import { useTerminals } from './hooks/useTerminals' ;
2727import { useEditors } from './hooks/useEditors' ;
2828import { useAgents } from './hooks/useAgents' ;
29+ import { useLayout } from './hooks/useLayout' ;
2930import { type AcpPermissionMode } from './types' ;
3031import { useTerminalPanes } from './features/terminal/useTerminalPanes' ;
3132import { useAgentPanes } from './features/agents/useAgentPanes' ;
@@ -37,11 +38,6 @@ import { AgentPanel } from './features/agents/AgentPanel';
3738const App : React . FC = ( ) => {
3839 const [ diffEnabled , setDiffEnabled ] = useState < boolean > ( loadDiffEnabled ( ) ) ;
3940 const [ editorDiffEnabledByPane , setEditorDiffEnabledByPane ] = useState < Record < string , boolean > > ( { } ) ;
40- const [ focusRequest , setFocusRequest ] = useState < {
41- target : PanelId ;
42- panelKey ?: string ;
43- nonce : number ;
44- } | null > ( null ) ;
4541 // const [followEnabled, setFollowEnabled] = useState<boolean>(loadFollowEnabled());
4642 const [ permissionMode , setPermissionMode ] = useState < AcpPermissionMode > ( loadAcpPermissionMode ( ) ) ;
4743
@@ -67,13 +63,6 @@ const App: React.FC = () => {
6763 const git = useGit ( { wsRef, isConnected } ) ;
6864 const search = useSearch ( { wsRef, isConnected } ) ;
6965 const wasConnectedRef = useRef < boolean > ( false ) ;
70- const activePanelRef = useRef < { panelId : PanelId ; panelKey : string } | null > ( null ) ;
71- const activeAgentPaneIdRef = useRef < string > ( 'agent' ) ;
72- const panelKeysByIdRef = useRef < Record < PanelId , string [ ] > > ( {
73- toolbar : [ 'toolbar' ] , files : [ 'files' ] ,
74- search : [ 'search' ] , changes : [ 'changes' ] ,
75- editor : [ ] , terminal : [ ] , agent : [ ] ,
76- } ) ;
7766 const agents = useAgents ( {
7867 wsRef,
7968 isConnected,
@@ -187,97 +176,6 @@ const App: React.FC = () => {
187176 saveItem ( 'terminals' , terminals . terminals ) ;
188177 } , [ terminals . terminals ] ) ;
189178
190- const handleCtrlFocusShortcut = useCallback ( ( e : KeyboardEvent ) : boolean => {
191- if ( ! e . ctrlKey || e . metaKey || e . altKey || e . shiftKey ) {
192- return false ;
193- }
194-
195- const focusTarget : PanelId | null =
196- e . code === 'Digit1' ? 'files'
197- : e . code === 'Digit2' ? 'editor'
198- : e . code === 'Digit3' ? 'terminal'
199- : e . code === 'Digit4' ? 'agent'
200- : null ;
201-
202- if ( ! focusTarget ) {
203- return false ;
204- }
205-
206- let targetPanelKey : string | undefined ;
207-
208- if ( focusTarget === 'editor' || focusTarget === 'terminal' || focusTarget === 'agent' ) {
209- const panelKeys = panelKeysByIdRef . current [ focusTarget ] ?? [ ] ;
210- if ( panelKeys . length > 0 ) {
211- const activePanel = activePanelRef . current ;
212- const isTargetFocused = activePanel ?. panelId === focusTarget
213- && panelKeys . includes ( activePanel . panelKey ) ;
214-
215- if ( isTargetFocused && activePanel ) {
216- const currentIndex = panelKeys . indexOf ( activePanel . panelKey ) ;
217- targetPanelKey = panelKeys [ ( currentIndex + 1 ) % panelKeys . length ] ;
218- } else {
219- const preferredPanelKey = focusTarget === 'editor'
220- ? editors . activeEditorPaneId
221- : focusTarget === 'terminal'
222- ? terminalPanes . activePaneId
223- : activeAgentPaneIdRef . current ;
224- targetPanelKey = preferredPanelKey && panelKeys . includes ( preferredPanelKey )
225- ? preferredPanelKey
226- : panelKeys [ 0 ] ;
227- }
228- }
229- }
230-
231- e . preventDefault ( ) ;
232- setFocusRequest ( { target : focusTarget , panelKey : targetPanelKey , nonce : Date . now ( ) } ) ;
233- return true ;
234- } , [ editors . activeEditorPaneId , terminalPanes . activePaneId ] ) ;
235-
236- useEffect ( ( ) => {
237- const handleKeyDown = ( e : KeyboardEvent ) => {
238- const activePaneId = editors . activeEditorPaneId ;
239- if ( activePaneId && editors . handleReferencesPeekKeyDown ( activePaneId , e ) ) {
240- return ;
241- }
242-
243- if ( e . metaKey && e . key === 'f' ) {
244- e . preventDefault ( ) ;
245- }
246-
247- if ( ( e . ctrlKey || e . metaKey ) && e . key === 's' ) {
248- e . preventDefault ( ) ;
249- if ( editors . activeFileId ) {
250- editors . saveFile ( editors . activeFileId ) ;
251- }
252- }
253-
254- if ( handleCtrlFocusShortcut ( e ) ) {
255- return ;
256- }
257-
258- if ( e . ctrlKey && e . key === '-' ) {
259- e . preventDefault ( ) ;
260- editors . undoCursor ( ) ;
261- } else if ( e . ctrlKey && e . key === '_' ) {
262- e . preventDefault ( ) ;
263- editors . redoCursor ( ) ;
264- }
265- } ;
266-
267- document . addEventListener ( 'keydown' , handleKeyDown , true ) ;
268- return ( ) => {
269- document . removeEventListener ( 'keydown' , handleKeyDown , true ) ;
270- } ;
271- } , [
272- editors . activeEditorPaneId ,
273- editors . activeFileId ,
274- editors . handleReferencesPeekKeyDown ,
275- handleCtrlFocusShortcut ,
276- editors . redoCursor ,
277- editors . saveFile ,
278- editors . undoCursor ,
279- ] ) ;
280-
281179 const handleSearch = ( { pattern } : { id : string ; pattern : string } ) => {
282180 search . startSearch ( pattern ) ;
283181 } ;
@@ -330,42 +228,58 @@ const App: React.FC = () => {
330228 setSelectedAgentId : agents . setSelectedAgentId ,
331229 } ) ;
332230
231+ const layout = useLayout ( {
232+ activeEditorPaneId : editors . activeEditorPaneId ,
233+ activeTerminalPaneId : terminalPanes . activePaneId ,
234+ activeAgentPaneId : agentPanes . activePaneId ,
235+ onFocusEditorPane : editors . focusEditorInPane ,
236+ onActivateTerminalPane : terminalPanes . setActivePaneId ,
237+ onActivateAgentPane : agentPanes . setActivePaneId ,
238+ } ) ;
239+
333240 useEffect ( ( ) => {
334- if ( ! focusRequest ) {
335- return ;
336- }
241+ const handleKeyDown = ( e : KeyboardEvent ) => {
242+ const activePaneId = editors . activeEditorPaneId ;
243+ if ( activePaneId && editors . handleReferencesPeekKeyDown ( activePaneId , e ) ) {
244+ return ;
245+ }
337246
338- if ( focusRequest . target === 'editor' && editors . activeEditorPaneId ) {
339- editors . focusEditorInPane ( focusRequest . panelKey ?? editors . activeEditorPaneId ) ;
340- return ;
341- }
247+ if ( e . metaKey && e . key === 'f' ) {
248+ e . preventDefault ( ) ;
249+ }
342250
343- if ( focusRequest . target === 'terminal' ) {
344- const knownTerminalPanels = panelKeysByIdRef . current . terminal ;
345- const requestedKey = focusRequest . panelKey ;
346- const resolvedKey = requestedKey && knownTerminalPanels . includes ( requestedKey )
347- ? requestedKey
348- : terminalPanes . activePaneId ;
349- terminalPanes . setActivePaneId ( resolvedKey || 'terminal' ) ;
350- return ;
351- }
251+ if ( ( e . ctrlKey || e . metaKey ) && e . key === 's' ) {
252+ e . preventDefault ( ) ;
253+ if ( editors . activeFileId ) {
254+ editors . saveFile ( editors . activeFileId ) ;
255+ }
256+ }
352257
353- if ( focusRequest . target === 'agent' ) {
354- const knownAgentPanels = panelKeysByIdRef . current . agent ;
355- const requestedKey = focusRequest . panelKey ;
356- const resolvedKey = requestedKey && knownAgentPanels . includes ( requestedKey )
357- ? requestedKey
358- : agentPanes . activePaneId ;
359- agentPanes . setActivePaneId ( resolvedKey || 'agent' ) ;
360- }
258+ if ( layout . handleCtrlFocusShortcut ( e ) ) {
259+ return ;
260+ }
261+
262+ if ( e . ctrlKey && e . key === '-' ) {
263+ e . preventDefault ( ) ;
264+ editors . undoCursor ( ) ;
265+ } else if ( e . ctrlKey && e . key === '_' ) {
266+ e . preventDefault ( ) ;
267+ editors . redoCursor ( ) ;
268+ }
269+ } ;
270+
271+ document . addEventListener ( 'keydown' , handleKeyDown , true ) ;
272+ return ( ) => {
273+ document . removeEventListener ( 'keydown' , handleKeyDown , true ) ;
274+ } ;
361275 } , [
362- agentPanes . activePaneId ,
363- agentPanes . setActivePaneId ,
364276 editors . activeEditorPaneId ,
365- editors . focusEditorInPane ,
366- focusRequest ,
367- terminalPanes . activePaneId ,
368- terminalPanes . setActivePaneId ,
277+ editors . activeFileId ,
278+ editors . handleReferencesPeekKeyDown ,
279+ editors . redoCursor ,
280+ editors . saveFile ,
281+ editors . undoCursor ,
282+ layout ,
369283 ] ) ;
370284
371285 const handleStartSpecificAgent = useCallback ( ( agent : AcpAgent ) => {
@@ -400,7 +314,7 @@ const App: React.FC = () => {
400314 < FilesPanel
401315 fileTree = { fileTree . fileTree }
402316 activeNodeId = { fileTree . activeNodeId }
403- focusRequestToken = { focusRequest ?. target === 'files' ? focusRequest . nonce : null }
317+ focusRequestToken = { layout . getFocusRequestToken ( 'files' ) }
404318 onActivateNode = { fileTree . setActiveNode }
405319 onToggle = { fileTree . toggleNode }
406320 onSelect = { fileTree . selectNode }
@@ -440,9 +354,7 @@ const App: React.FC = () => {
440354 return (
441355 < TerminalPanel
442356 panelKey = { panelKey }
443- focusRequestToken = { focusRequest ?. target === 'terminal' && ( ! focusRequest . panelKey || focusRequest . panelKey === panelKey )
444- ? focusRequest . nonce
445- : null }
357+ focusRequestToken = { layout . getFocusRequestToken ( 'terminal' , panelKey ) }
446358 isConnected = { isConnected }
447359 terminals = { terminals . terminals }
448360 terminalPanes = { terminalPanes }
@@ -456,9 +368,7 @@ const App: React.FC = () => {
456368 return (
457369 < AgentPanel
458370 panelKey = { panelKey }
459- focusRequestToken = { focusRequest ?. target === 'agent' && ( ! focusRequest . panelKey || focusRequest . panelKey === panelKey )
460- ? focusRequest . nonce
461- : null }
371+ focusRequestToken = { layout . getFocusRequestToken ( 'agent' , panelKey ) }
462372 isConnected = { isConnected }
463373 agentPanes = { agentPanes }
464374 agents = { agents }
@@ -499,10 +409,7 @@ const App: React.FC = () => {
499409 } ;
500410
501411 const handlePanelAdded = useCallback ( ( panelId : PanelId , panelKey : string ) => {
502- const current = panelKeysByIdRef . current [ panelId ] ?? [ ] ;
503- if ( ! current . includes ( panelKey ) ) {
504- panelKeysByIdRef . current [ panelId ] = [ ...current , panelKey ] ;
505- }
412+ layout . handlePanelAdded ( panelId , panelKey ) ;
506413
507414 if ( panelId === 'changes' ) {
508415 git . fetchGitStatus ( ) ;
@@ -520,13 +427,10 @@ const App: React.FC = () => {
520427 if ( panelId === 'terminal' ) {
521428 terminalPanes . registerPane ( panelKey ) ;
522429 }
523- } , [ agentPanes , editors , git . fetchGitStatus , terminalPanes ] ) ;
430+ } , [ agentPanes , editors , git . fetchGitStatus , layout , terminalPanes ] ) ;
524431
525432 const handlePanelRemoved = useCallback ( ( panelId : PanelId , panelKey : string ) => {
526- const current = panelKeysByIdRef . current [ panelId ] ?? [ ] ;
527- if ( current . includes ( panelKey ) ) {
528- panelKeysByIdRef . current [ panelId ] = current . filter ( ( key ) => key !== panelKey ) ;
529- }
433+ layout . handlePanelRemoved ( panelId , panelKey ) ;
530434
531435 if ( panelId === 'editor' ) {
532436 editors . unregisterEditorPane ( panelKey ) ;
@@ -547,10 +451,10 @@ const App: React.FC = () => {
547451 if ( panelId === 'terminal' ) {
548452 terminalPanes . unregisterPane ( panelKey ) ;
549453 }
550- } , [ agentPanes , editors , terminalPanes ] ) ;
454+ } , [ agentPanes , editors , layout , terminalPanes ] ) ;
551455
552456 const handlePanelActivated = useCallback ( ( panelId : PanelId , panelKey : string ) => {
553- activePanelRef . current = { panelId, panelKey } ;
457+ layout . handlePanelActivated ( panelId , panelKey ) ;
554458
555459 if ( panelId === 'editor' ) {
556460 editors . setActiveEditorPaneId ( panelKey ) ;
@@ -568,14 +472,13 @@ const App: React.FC = () => {
568472 return ;
569473 }
570474 if ( panelId === 'agent' ) {
571- activeAgentPaneIdRef . current = panelKey ;
572475 agentPanes . setActivePaneId ( panelKey ) ;
573476 return ;
574477 }
575478 if ( panelId === 'terminal' ) {
576479 terminalPanes . setActivePaneId ( panelKey ) ;
577480 }
578- } , [ agentPanes , editors , terminalPanes ] ) ;
481+ } , [ agentPanes , editors , layout , terminalPanes ] ) ;
579482
580483 return (
581484 < div className = "app-container toolbar-header-compact" >
0 commit comments