1+ import * as path from 'path' ;
12import * as vscode from 'vscode' ;
2- import * as models from '../models' ;
33import * as elements from '../elements' ;
4- import * as path from 'path ' ;
4+ import * as models from '../models ' ;
55
66const namespaceSeparator = ':' ;
77
88export class TaskTreeDataProvider implements vscode . TreeDataProvider < elements . TreeItem > {
99 private _onDidChangeTreeData : vscode . EventEmitter < elements . TaskTreeItem | undefined > = new vscode . EventEmitter < elements . TaskTreeItem | undefined > ( ) ;
1010 readonly onDidChangeTreeData : vscode . Event < elements . TaskTreeItem | undefined > = this . _onDidChangeTreeData . event ;
1111 private _taskfiles ?: models . Taskfile [ ] ;
12+ private _treeViewMap : models . TaskMapping = { } ;
1213
1314 constructor (
1415 private nestingEnabled : boolean = false
@@ -44,6 +45,7 @@ export class TaskTreeDataProvider implements vscode.TreeDataProvider<elements.Tr
4445
4546 var tasks : models . Task [ ] | undefined ;
4647 var parentNamespace = "" ;
48+ var namespaceMap = this . _treeViewMap ;
4749 var workspace = "" ;
4850
4951 // If there is no parent and exactly one workspace folder or if the parent is a workspace
@@ -61,59 +63,61 @@ export class TaskTreeDataProvider implements vscode.TreeDataProvider<elements.Tr
6163 // If there is a parent and it is a namespace
6264 if ( parent instanceof elements . NamespaceTreeItem ) {
6365 tasks = parent . tasks ;
64- parentNamespace = parent . namespace ;
66+ parentNamespace = parent . label ;
67+ namespaceMap = parent . namespaceMap ;
6568 workspace = parent . workspace ;
6669 }
6770
68- if ( tasks ) {
69- let namespaceTreeItems = new Map < string , elements . NamespaceTreeItem > ( ) ;
70- let taskTreeItems : elements . TaskTreeItem [ ] = [ ] ;
71- for ( let task of tasks ) {
7271
73- let fullNamespacePath = getFullNamespacePath ( task ) ;
74- let namespacePath = trimParentNamespace ( fullNamespacePath , parentNamespace ) ;
72+ if ( tasks === undefined ) {
73+ return Promise . resolve ( [ ] ) ;
74+ }
75+
76+ let namespaceTreeItems = new Map < string , elements . NamespaceTreeItem > ( ) ;
77+ let taskTreeItems : elements . TaskTreeItem [ ] = [ ] ;
78+ tasks . forEach ( task => {
79+ let taskName = task . name . split ( ":" ) . pop ( ) ?? task . name ;
80+ let namespacePath = trimParentNamespace ( task . name , parentNamespace ) ;
81+ let namespaceName = getNamespaceName ( namespacePath ) ;
82+
83+ if ( taskName in namespaceMap ) {
84+ let item = new elements . TaskTreeItem (
85+ task . name . split ( namespaceSeparator ) . pop ( ) ?? task . name ,
86+ workspace ,
87+ task ,
88+ vscode . TreeItemCollapsibleState . None ,
89+ {
90+ command : 'vscode-task.goToDefinition' ,
91+ title : 'Go to Definition' ,
92+ arguments : [ task , true ]
93+ }
94+ ) ;
95+ taskTreeItems = taskTreeItems . concat ( item ) ;
96+ }
97+
98+ if ( namespaceName in namespaceMap && namespaceMap [ namespaceName ] !== null ) {
99+ let namespaceTreeItem = namespaceTreeItems . get ( namespaceName ) ;
75100
76- // Check if the task has a namespace
77- // If it does, add it to the namespace/tasks map
78- if ( this . nestingEnabled && namespacePath !== "" ) {
79- let namespaceLabel = getNamespaceLabel ( namespacePath ) ;
80- let namespaceTreeItem = namespaceTreeItems . get ( namespaceLabel ) ?? new elements . NamespaceTreeItem (
81- namespaceLabel ,
101+ if ( namespaceTreeItem === undefined ) {
102+ namespaceTreeItem = new elements . NamespaceTreeItem (
103+ namespaceName ,
82104 workspace ,
83- fullNamespacePath ,
105+ namespaceMap [ namespaceName ] ,
84106 [ ] ,
85107 vscode . TreeItemCollapsibleState . Collapsed
86108 ) ;
87- namespaceTreeItem . tasks . push ( task ) ;
88- namespaceTreeItems . set ( namespaceLabel , namespaceTreeItem ) ;
89- }
90-
91- // Otherwise, create a tree item for the task
92- else {
93- let taskLabel = getTaskLabel ( task , this . nestingEnabled ) ;
94- let taskTreeItem = new elements . TaskTreeItem (
95- taskLabel ,
96- workspace ,
97- task ,
98- vscode . TreeItemCollapsibleState . None ,
99- {
100- command : 'vscode-task.goToDefinition' ,
101- title : 'Go to Definition' ,
102- arguments : [ task , true ]
103- }
104- ) ;
105- taskTreeItems = taskTreeItems . concat ( taskTreeItem ) ;
106109 }
110+ namespaceTreeItem . tasks . push ( task ) ;
111+ namespaceTreeItems . set ( namespaceName , namespaceTreeItem ) ;
107112 }
108113
109- // Add the namespace and tasks to the tree
110- namespaceTreeItems . forEach ( namespace => {
111- treeItems = treeItems . concat ( namespace ) ;
112- } ) ;
113- treeItems = treeItems . concat ( taskTreeItems ) ;
114+ } ) ;
114115
115- return Promise . resolve ( treeItems ) ;
116- }
116+ // Add the namespace and tasks to the tree
117+ namespaceTreeItems . forEach ( namespace => {
118+ treeItems = treeItems . concat ( namespace ) ;
119+ } ) ;
120+ treeItems = treeItems . concat ( taskTreeItems ) ;
117121
118122 return Promise . resolve ( treeItems ) ;
119123 }
@@ -136,6 +140,37 @@ export class TaskTreeDataProvider implements vscode.TreeDataProvider<elements.Tr
136140 refresh ( taskfiles ?: models . Taskfile [ ] ) : void {
137141 if ( taskfiles ) {
138142 this . _taskfiles = taskfiles ;
143+ this . _treeViewMap = { } ;
144+
145+ // loop over all of the tasks in all of the task files and map their names into a set
146+ const taskNames = Array . from ( new Set (
147+ taskfiles . flatMap ( taskfile =>
148+ taskfile . tasks . flatMap ( task => task . name )
149+ )
150+ // and sort desc so we know that the namespace reduction sets child objects correctly.
151+ ) ) . sort ( ( a , b ) => ( a > b ? - 1 : 1 ) ) ;
152+
153+ taskNames . reduce ( ( acc : any , key : string ) => {
154+ const parts = key . split ( ':' ) ;
155+ let currentLevel = acc ;
156+
157+ parts . forEach ( ( part , index ) => {
158+ if ( part === "" ) {
159+ return ;
160+ } ;
161+
162+ if ( ! ( part in currentLevel ) ) {
163+ currentLevel [ part ] = { } ;
164+ if ( index === parts . length - 1 ) {
165+ currentLevel [ part ] = null ;
166+ }
167+ }
168+
169+ currentLevel = currentLevel [ part ] as models . TaskMapping ;
170+ } ) ;
171+
172+ return acc ;
173+ } , this . _treeViewMap ) ;
139174 }
140175 this . _onDidChangeTreeData . fire ( undefined ) ;
141176 }
@@ -151,31 +186,24 @@ function getFullNamespacePath(task: models.Task): string {
151186}
152187
153188function trimParentNamespace ( namespace : string , parentNamespace : string ) : string {
154- if ( namespace === parentNamespace ) {
155- return "" ;
189+ if ( parentNamespace === "" ) {
190+ return namespace ;
156191 }
157- parentNamespace += namespaceSeparator ;
158- // If the namespace is a direct child of the parent namespace, remove the parent namespace
159- if ( namespace . startsWith ( parentNamespace ) ) {
160- return namespace . substring ( parentNamespace . length ) ;
192+
193+ const index = namespace . indexOf ( parentNamespace + namespaceSeparator ) ;
194+
195+ if ( index === - 1 ) {
196+ return namespace ;
161197 }
162- return namespace ;
198+
199+ return namespace . substring ( index + parentNamespace . length + 1 ) ;
163200}
164201
165- function getNamespaceLabel ( namespacePath : string ) : string {
202+ function getNamespaceName ( namespacePath : string ) : string {
166203 // If the namespace has no separator, return the namespace
167204 if ( ! namespacePath . includes ( namespaceSeparator ) ) {
168205 return namespacePath ;
169206 }
170207 // Return the first element of the namespace
171208 return namespacePath . substring ( 0 , namespacePath . indexOf ( namespaceSeparator ) ) ;
172209}
173-
174- function getTaskLabel ( task : models . Task , nestingEnabled : boolean ) : string {
175- // If the task has no namespace, return the task's name
176- if ( ! task . name . includes ( namespaceSeparator ) || ! nestingEnabled ) {
177- return task . name ;
178- }
179- // Return the task's name by removing the namespace
180- return task . name . substring ( task . name . lastIndexOf ( namespaceSeparator ) + 1 ) ;
181- }
0 commit comments