@@ -11,6 +11,7 @@ import {
1111 WebContents ,
1212} from 'electron'
1313import * as Fs from 'fs'
14+ import * as Path from 'path'
1415
1516import { AppWindow } from './app-window'
1617import { buildDefaultMenu , getAllMenuItems } from './menu'
@@ -189,7 +190,34 @@ function getTargetWindow() {
189190 return focusedAppWindow
190191 }
191192
192- return getAppWindows ( ) [ 0 ] ?? null
193+ return getAppWindows ( ) . at ( 0 ) ?? null
194+ }
195+
196+ function normalizeRepositoryPath ( path : string ) {
197+ // Strip trailing separator
198+ const normalized = Path . normalize ( path ) . replace ( / [ \\ / ] + $ / , '' )
199+ // Windows paths are case-insensitive
200+ return __WIN32__ ? normalized . toLowerCase ( ) : normalized
201+ }
202+
203+ function findWindowForRepositoryPath ( rawTargetPath : string ) : AppWindow | null {
204+ const targetPath = normalizeRepositoryPath ( rawTargetPath )
205+ const allWindows = getAppWindows ( ) . filter ( w => w . hasSelectedRepositoryPath ( ) )
206+ const windowsSortedFromMostSpecificToLeast = allWindows . sort (
207+ ( a , b ) => b . selectedRepositoryPath . length - a . selectedRepositoryPath . length
208+ )
209+
210+ for ( const window of windowsSortedFromMostSpecificToLeast ) {
211+ const candidatePath = normalizeRepositoryPath ( window . selectedRepositoryPath )
212+ if (
213+ targetPath === candidatePath ||
214+ targetPath . startsWith ( candidatePath + Path . sep )
215+ ) {
216+ return window
217+ }
218+ }
219+
220+ return null // fallback to getLoadedTargetWindow()
193221}
194222
195223function getLoadedTargetWindow ( ) {
@@ -250,22 +278,23 @@ if (!handlingSquirrelEvent) {
250278 const gotSingleInstanceLock = app . requestSingleInstanceLock ( )
251279 isDuplicateInstance = ! gotSingleInstanceLock
252280
253- app . on ( 'second-instance' , ( event , args , workingDirectory ) => {
254- // Someone tried to run a second instance, we should focus our window.
255- const targetWindow = getTargetWindow ( )
256- if ( targetWindow ) {
257- if ( targetWindow . isMinimized ( ) ) {
258- targetWindow . restore ( )
281+ app . on ( 'second-instance' , async ( event , args , workingDirectory ) => {
282+ const handledAction = await handleCommandLineArguments ( args )
283+ if ( handledAction ) {
284+ return
285+ }
286+ const mainWindow = getTargetWindow ( )
287+ if ( mainWindow ) {
288+ if ( mainWindow . isMinimized ( ) ) {
289+ mainWindow . restore ( )
259290 }
260291
261- if ( ! targetWindow . isVisible ( ) ) {
262- targetWindow . show ( )
292+ if ( ! mainWindow . isVisible ( ) ) {
293+ mainWindow . show ( )
263294 }
264295
265- targetWindow . focus ( )
296+ mainWindow . focus ( )
266297 }
267-
268- handleCommandLineArguments ( args )
269298 } )
270299
271300 if ( isDuplicateInstance ) {
@@ -311,7 +340,7 @@ if (__DARWIN__) {
311340 } )
312341}
313342
314- async function handleCommandLineArguments ( argv : string [ ] ) {
343+ async function handleCommandLineArguments ( argv : string [ ] ) : Promise < boolean > {
315344 const args = parseCommandLineArgs ( argv , {
316345 boolean : [ 'protocol-launcher' ] ,
317346 } )
@@ -347,30 +376,41 @@ async function handleCommandLineArguments(argv: string[]) {
347376
348377 if ( matchingUrl ) {
349378 handleAppURL ( matchingUrl )
350- return
379+ return true
351380 } else if ( __WIN32__ ) {
352381 log . error ( `Encountered --protocol-launcher without app url` )
353- return
382+ return false
354383 }
355384 // If --protocol-launcher is present we always want to bail and not
356385 // risk a smuggled cli switch
357386 }
358387
359388 if ( typeof args [ 'cli-open' ] === 'string' ) {
360389 handleCLIAction ( { kind : 'open-repository' , path : args [ 'cli-open' ] } )
390+ return true
361391 } else if ( typeof args [ 'cli-clone' ] === 'string' ) {
362392 handleCLIAction ( {
363393 kind : 'clone-url' ,
364394 url : args [ 'cli-clone' ] ,
365395 branch :
366396 typeof args [ 'cli-branch' ] === 'string' ? args [ 'cli-branch' ] : undefined ,
367397 } )
398+ return true
368399 }
369400
370- return
401+ return false
371402}
372403
373404function handleCLIAction ( action : CLIAction ) {
405+ if ( action . kind === 'open-repository' ) {
406+ const existingWindow = findWindowForRepositoryPath ( action . path )
407+ if ( existingWindow !== null ) {
408+ existingWindow . revealAndFocus ( )
409+ existingWindow . sendCLIAction ( action )
410+ return
411+ }
412+ }
413+
374414 onDidLoad ( window => {
375415 // This manual focus call _shouldn't_ be necessary, but is for Chrome on
376416 // macOS. See https://github.com/desktop/desktop/issues/973.
@@ -623,6 +663,10 @@ app.on('ready', () => {
623663 getAppWindowFromWebContents ( event . sender ) ?. setTitle ( title )
624664 )
625665
666+ ipcMain . on ( 'set-window-selected-repository' , ( event , path : string | null ) =>
667+ getAppWindowFromWebContents ( event . sender ) ?. setSelectedRepositoryPath ( path )
668+ )
669+
626670 ipcMain . on ( 'minimize-window' , event =>
627671 getAppWindowFromWebContents ( event . sender ) ?. minimizeWindow ( )
628672 )
0 commit comments