@@ -24,6 +24,8 @@ import { FolderContext } from "../FolderContext";
24
24
import { getPlatformConfig , resolveTaskCwd } from "../utilities/tasks" ;
25
25
import { SwiftTask , TaskPlatformSpecificConfig } from "../tasks/SwiftTaskProvider" ;
26
26
import { convertPathToPattern , glob } from "fast-glob" ;
27
+ import { Version } from "../utilities/version" ;
28
+ import { existsSync } from "fs" ;
27
29
28
30
const LOADING_ICON = "loading~spin" ;
29
31
@@ -285,9 +287,13 @@ function snippetTaskName(name: string): string {
285
287
}
286
288
287
289
class TargetNode {
290
+ private newPluginLayoutVersion = new Version ( 6 , 0 , 0 ) ;
291
+
288
292
constructor (
289
293
public target : Target ,
290
- private activeTasks : Set < string >
294
+ private folder : FolderContext ,
295
+ private activeTasks : Set < string > ,
296
+ private fs ?: ( folder : string ) => Promise < string [ ] >
291
297
) { }
292
298
293
299
get name ( ) : string {
@@ -358,7 +364,41 @@ class TargetNode {
358
364
}
359
365
360
366
getChildren ( ) : TreeNode [ ] {
361
- return [ ] ;
367
+ return this . buildPluginOutputs ( this . folder . toolchain . swiftVersion ) ;
368
+ }
369
+
370
+ private buildToolGlobPattern ( version : Version ) : string {
371
+ const base = this . folder . folder . fsPath . replace ( / \\ / g, "/" ) ;
372
+ if ( version . isGreaterThanOrEqual ( this . newPluginLayoutVersion ) ) {
373
+ return `${ base } /.build/plugins/outputs/*/${ this . target . name } /*/*/**` ;
374
+ } else {
375
+ return `${ base } /.build/plugins/outputs/*/${ this . target . name } /*/**` ;
376
+ }
377
+ }
378
+
379
+ private buildPluginOutputs ( version : Version ) : TreeNode [ ] {
380
+ // Files in the `outputs` directory follow the pattern:
381
+ // .build/plugins/outputs/buildtoolplugin/<target-name>/destination/<build-tool-plugin-name>/*
382
+ // This glob will capture all the files in the outputs directory for this target.
383
+ const pattern = this . buildToolGlobPattern ( version ) ;
384
+ const base = this . folder . folder . fsPath . replace ( / \\ / g, "/" ) ;
385
+ const depth = version . isGreaterThanOrEqual ( this . newPluginLayoutVersion ) ? 4 : 3 ;
386
+ const matches = glob . sync ( pattern , { onlyFiles : false , cwd : base , deep : depth } ) ;
387
+ return matches . map ( filePath => {
388
+ const pluginName = path . basename ( filePath ) ;
389
+ return new HeaderNode (
390
+ `${ this . target . name } -${ pluginName } ` ,
391
+ `${ pluginName } - Generated Files` ,
392
+ "debug-disconnect" ,
393
+ ( ) =>
394
+ getChildren (
395
+ filePath ,
396
+ excludedFilesForProjectPanelExplorer ( ) ,
397
+ this . target . path ,
398
+ this . fs
399
+ )
400
+ ) ;
401
+ } ) ;
362
402
}
363
403
}
364
404
@@ -438,6 +478,8 @@ export class ProjectPanelProvider implements vscode.TreeDataProvider<TreeNode> {
438
478
private disposables : vscode . Disposable [ ] = [ ] ;
439
479
private activeTasks : Set < string > = new Set ( ) ;
440
480
private lastComputedNodes : TreeNode [ ] = [ ] ;
481
+ private buildPluginOutputWatcher ?: vscode . FileSystemWatcher ;
482
+ private buildPluginFolderWatcher ?: vscode . Disposable ;
441
483
442
484
onDidChangeTreeData = this . didChangeTreeDataEmitter . event ;
443
485
@@ -518,6 +560,7 @@ export class ProjectPanelProvider implements vscode.TreeDataProvider<TreeNode> {
518
560
if ( ! folder ) {
519
561
return ;
520
562
}
563
+ this . watchBuildPluginOutputs ( folder ) ;
521
564
treeView . title = `Swift Project (${ folder . name } )` ;
522
565
this . didChangeTreeDataEmitter . fire ( ) ;
523
566
break ;
@@ -540,6 +583,33 @@ export class ProjectPanelProvider implements vscode.TreeDataProvider<TreeNode> {
540
583
) ;
541
584
}
542
585
586
+ watchBuildPluginOutputs ( folderContext : FolderContext ) {
587
+ if ( this . buildPluginOutputWatcher ) {
588
+ this . buildPluginOutputWatcher . dispose ( ) ;
589
+ }
590
+ if ( this . buildPluginFolderWatcher ) {
591
+ this . buildPluginFolderWatcher . dispose ( ) ;
592
+ }
593
+
594
+ const fire = ( ) => this . didChangeTreeDataEmitter . fire ( ) ;
595
+ const buildPath = path . join ( folderContext . folder . fsPath , ".build/plugins/outputs" ) ;
596
+ this . buildPluginFolderWatcher = watchForFolder (
597
+ buildPath ,
598
+ ( ) => {
599
+ this . buildPluginOutputWatcher = vscode . workspace . createFileSystemWatcher (
600
+ new vscode . RelativePattern ( buildPath , "{*,*/*}" )
601
+ ) ;
602
+ this . buildPluginOutputWatcher . onDidCreate ( fire ) ;
603
+ this . buildPluginOutputWatcher . onDidDelete ( fire ) ;
604
+ this . buildPluginOutputWatcher . onDidChange ( fire ) ;
605
+ } ,
606
+ ( ) => {
607
+ this . buildPluginOutputWatcher ?. dispose ( ) ;
608
+ fire ( ) ;
609
+ }
610
+ ) ;
611
+ }
612
+
543
613
getTreeItem ( element : TreeNode ) : vscode . TreeItem {
544
614
return element . toTreeItem ( ) ;
545
615
}
@@ -556,7 +626,6 @@ export class ProjectPanelProvider implements vscode.TreeDataProvider<TreeNode> {
556
626
...this . lastComputedNodes ,
557
627
] ;
558
628
}
559
-
560
629
const nodes = await this . computeChildren ( folderContext , element ) ;
561
630
562
631
// If we're fetching the root nodes then save them in case we have an error later,
@@ -652,7 +721,7 @@ export class ProjectPanelProvider implements vscode.TreeDataProvider<TreeNode> {
652
721
// Snipepts are shown under the Snippets header
653
722
return targets
654
723
. filter ( target => target . type !== "snippet" )
655
- . map ( target => new TargetNode ( target , this . activeTasks ) )
724
+ . map ( target => new TargetNode ( target , folderContext , this . activeTasks ) )
656
725
. sort ( ( a , b ) => targetSort ( a ) . localeCompare ( targetSort ( b ) ) ) ;
657
726
}
658
727
@@ -708,7 +777,7 @@ export class ProjectPanelProvider implements vscode.TreeDataProvider<TreeNode> {
708
777
const targets = await folderContext . swiftPackage . targets ;
709
778
return targets
710
779
. filter ( target => target . type === "snippet" )
711
- . flatMap ( target => new TargetNode ( target , this . activeTasks ) )
780
+ . flatMap ( target => new TargetNode ( target , folderContext , this . activeTasks ) )
712
781
. sort ( ( a , b ) => a . name . localeCompare ( b . name ) ) ;
713
782
}
714
783
}
@@ -760,3 +829,35 @@ class TaskPoller implements vscode.Disposable {
760
829
}
761
830
}
762
831
}
832
+
833
+ /**
834
+ * Polls for the existence of a folder at the given path every 2.5 seconds.
835
+ * Notifies via the provided callbacks when the folder becomes available or is deleted.
836
+ */
837
+ function watchForFolder (
838
+ folderPath : string ,
839
+ onAvailable : ( ) => void ,
840
+ onDeleted : ( ) => void
841
+ ) : vscode . Disposable {
842
+ const POLL_INTERVAL = 2500 ;
843
+ let folderExists = existsSync ( folderPath ) ;
844
+
845
+ if ( folderExists ) {
846
+ onAvailable ( ) ;
847
+ }
848
+
849
+ const interval = setInterval ( ( ) => {
850
+ const nowExists = existsSync ( folderPath ) ;
851
+ if ( nowExists && ! folderExists ) {
852
+ folderExists = true ;
853
+ onAvailable ( ) ;
854
+ } else if ( ! nowExists && folderExists ) {
855
+ folderExists = false ;
856
+ onDeleted ( ) ;
857
+ }
858
+ } , POLL_INTERVAL ) ;
859
+
860
+ return {
861
+ dispose : ( ) => clearInterval ( interval ) ,
862
+ } ;
863
+ }
0 commit comments