import { Event , EventEmitter , ExtensionContext , Task , TaskDefinition , TaskRevealKind , TextDocument , TreeDataProvider , TreeItem , TreeItemCollapsibleState , Uri , TaskStartEvent , TaskEndEvent , commands , window , workspace , tasks , Selection , WorkspaceFolder , InputBoxOptions , ShellExecution , Terminal , StatusBarItem , StatusBarAlignment , CustomExecution } from "vscode" ; import * as path from "path" ; import * as util from "../common/utils" ; import * as assert from "assert" ; import * as nls from "vscode-nls" ; import constants from "../common/constants" ; import * as log from "../common/log" ; import TaskItem from "./item" ; import TaskFile from "./file" ; import TaskFolder from "./folder" ; import { visit , JSONVisitor } from "jsonc-parser" ; import { storage } from "../common/storage" ; import { views } from "../views" ; import { rebuildCache } from "../cache" ; import { configuration } from "../common/configuration" ; import { providers } from "../extension" ; const localize = nls . loadMessageBundle ( ) ; class NoScripts extends TreeItem { constructor ( ) { super ( localize ( "noScripts" , "No scripts found" ) , TreeItemCollapsibleState . None ) ; this . contextValue = "noscripts" ; } } const noScripts = new NoScripts ( ) ; class export class TaskTreeDataProvider implements TreeDataProvider < TreeItem > { private static statusBarSpace : StatusBarItem ; private tasks : Task [ ] | null = null ; private treeBuilding = false ; private busy = false ; private extensionContext : ExtensionContext ; private name : string ; private taskTree : TaskFolder [ ] | NoScripts [ ] | null = null ; private currentInvalidation : string | undefined ; private _onDidChangeTreeData : EventEmitter < TreeItem | null > = new EventEmitter < TreeItem | null > ( ) ; readonly onDidChangeTreeData : Event < TreeItem | null > = this . _onDidChangeTreeData . event ; constructor ( name , context ) { const subscriptions = context . subscriptions ; this . extensionContext = context ; this . name = name ; subscriptions . push ( commands . registerCommand ( name + ".run" , async ( item ) => { await this . run ( item ) ; } , this ) ) ; subscriptions . push ( commands . registerCommand ( name + ".runNoTerm" , async ( item ) => { await this . run ( item , true , false ) ; } , this ) ) ; subscriptions . push ( commands . registerCommand ( name + ".runWithArgs" , async ( item , args ) => { await this . run ( item , false , true , args ) ; } , this ) ) ; subscriptions . push ( commands . registerCommand ( name + ".runLastTask" , async ( ) => { await this . runLastTask ( ) ; } , this ) ) ; subscriptions . push ( commands . registerCommand ( name + ".stop" , ( item ) => { this . stop ( item ) ; } , this ) ) ; subscriptions . push ( commands . registerCommand ( name + ".restart" , async ( item ) => { await this . restart ( item ) ; } , this ) ) ; subscriptions . push ( commands . registerCommand ( name + ".pause" , ( item ) => { this . pause ( item ) ; } , this ) ) ; subscriptions . push ( commands . registerCommand ( name + ".open" , async ( item ) => { await this . open ( item ) ; } , this ) ) ; subscriptions . push ( commands . registerCommand ( name + ".openTerminal" , ( item ) => { this . openTerminal ( item ) ; } , this ) ) ; subscriptions . push ( commands . registerCommand ( name + ".refresh" , async ( ) => { await this . refresh ( true , false ) ; } , this ) ) ; subscriptions . push ( commands . registerCommand ( name + ".runInstall" , async ( taskFile ) => { await this . runNpmCommand ( taskFile , "install" ) ; } , this ) ) ; subscriptions . push ( commands . registerCommand ( name + ".runUpdate" , async ( taskFile ) => { await this . runNpmCommand ( taskFile , "update" ) ; } , this ) ) ; subscriptions . push ( commands . registerCommand ( name + ".runUpdatePackage" , async ( taskFile ) => { await this . runNpmCommand ( taskFile , "update <packagename>" ) ; } , this ) ) ; subscriptions . push ( commands . registerCommand ( name + ".runAudit" , async ( taskFile ) => { await this . runNpmCommand ( taskFile , "audit" ) ; } , this ) ) ; subscriptions . push ( commands . registerCommand ( name + ".runAuditFix" , async ( taskFile ) => { await this . runNpmCommand ( taskFile , "audit fix" ) ; } , this ) ) ; subscriptions . push ( commands . registerCommand ( name + ".addToExcludes" , async ( taskFile ) => { await this . addToExcludes ( taskFile ) ; } , this ) ) ; subscriptions . push ( commands . registerCommand ( name + ".addRemoveFromFavorites" , async ( taskItem ) => { await this . addRemoveFavorite ( taskItem ) ; } , this ) ) ; subscriptions . push ( commands . registerCommand ( name + ".clearSpecialFolder" , async ( taskFolder ) => { await this . clearSpecialFolder ( taskFolder ) ; } , this ) ) ; tasks . onDidStartTask ( async ( _e ) => this . taskStartEvent ( _e ) ) ; tasks . onDidEndTask ( async ( _e ) => this . taskFinishedEvent ( _e ) ) ; } method since param taskItem private async addRemoveFavorite ( taskItem ) { const favTasks = storage . get < string [ ] > ( constants . FAV_TASKS_STORE , [ ] ) ; const favId = this . getTaskItemId ( taskItem ) ; log . methodStart ( "add/remove favorite" , 1 , "" , false , [ [ "id" , taskItem . id ] , [ "current fav count" , favTasks . length ] ] ) ; if ( util . existsInArray ( favTasks , favId ) === false ) { await this . saveTask ( taskItem , - 1 , true ) ; } else { await util . removeFromArrayAsync ( favTasks , favId ) ; log . value ( "   new fav count" , favTasks . length , 2 ) ; await storage . update ( constants . FAV_TASKS_STORE , favTasks ) ; await this . showSpecialTasks ( true , true , undefined , "   " ) ; } log . methodDone ( "add/remove favorite" , 1 ) ; } private async addToExcludes ( selection ) { const me = this ; let pathValue = "" ; let uri ; log . methodStart ( "add to excludes" , 1 , "" , true , [ [ "global" , global ] ] ) ; if ( selection instanceof TaskFile ) { uri = selection . resourceUri ; if ( selection . isGroup ) { log . write ( "  file group" ) ; pathValue = "" ; for ( const each of selection . treeNodes ) { if ( each . resourceUri ) { pathValue += each . resourceUri . path ; pathValue += "," ; } } if ( pathValue ) { pathValue = pathValue . substring ( 0 , pathValue . length - 1 ) ; } } else if ( uri ) { log . value ( "  file glob" , uri . path ) ; pathValue = uri . path ; } } else { pathValue = selection ; } if ( ! pathValue ) { return ; } log . value ( "   path value" , pathValue , 2 ) ; let excludes = [ ] ; const excludes2 = configuration . get < string [ ] > ( "exclude" ) ; if ( excludes2 && excludes2 instanceof Array ) { excludes = excludes2 ; } else if ( typeof excludes2 === "string" ) { excludes . push ( excludes2 ) ; } const paths = pathValue . split ( "," ) ; for ( const s in paths ) { if ( paths . hasOwnProperty ( s ) ) { util . pushIfNotExists ( excludes , paths [ s ] ) ; } } configuration . updateWs ( "exclude" , excludes ) ; await me . refresh ( selection instanceof TaskFile ? selection . taskSource : false , uri ) ; log . methodDone ( "add to excludes" , 1 ) ; } private addToSpecialFolder ( taskItem , folder , tasks , label ) { if ( taskItem && taskItem . id && folder && tasks && label && taskItem . task && tasks . includes ( taskItem . id ) ) { const taskItem2 = new TaskItem ( this . extensionContext , taskItem . taskFile , taskItem . task ) ; taskItem2 . id = label + ":" + taskItem2 . id ; taskItem2 . label = this . getSpecialTaskName ( taskItem2 ) ; folder . insertTaskFile ( taskItem2 , 0 ) ; } } private async buildGroupings ( folders , logPad = "" , logLevel = 1 ) { log . methodStart ( "build tree node groupings" , logLevel , logPad ) ; for ( const [ key , folder ] of folders ) { if ( key === constants . LAST_TASKS_LABEL || key === constants . FAV_TASKS_LABEL ) { continue ; } this . sortFolder ( folder , logPad + "   " , logLevel + 1 ) ; if ( configuration . get ( "groupWithSeparator" ) ) { await this . createTaskGroupings ( folder , logPad + "   " , logLevel + 1 ) ; } } log . methodDone ( "build tree node groupings" , logLevel , logPad ) ; } private async buildTaskTree ( tasksList , logPad = "" , logLevel = 1 ) { let taskCt = 0 ; const folders = new Map ( ) ; const files = new Map ( ) ; let ltFolder , favFolder ; log . methodStart ( "build task tree" , logLevel , logPad ) ; this . treeBuilding = true ; const lastTasks = storage . get < string [ ] > ( constants . LAST_TASKS_STORE , [ ] ) ; if ( configuration . get < boolean > ( "showLastTasks" ) === true ) { if ( lastTasks && lastTasks . length > 0 ) { ltFolder = new TaskFolder ( constants . LAST_TASKS_LABEL ) ; folders . set ( constants . LAST_TASKS_LABEL , ltFolder ) ; } } const favTasks = storage . get < string [ ] > ( constants . FAV_TASKS_STORE , [ ] ) ; if ( favTasks && favTasks . length > 0 ) { favFolder = new TaskFolder ( constants . FAV_TASKS_LABEL ) ; folders . set ( constants . FAV_TASKS_LABEL , favFolder ) ; } for ( const each of tasksList ) { log . blank ( 1 ) ; log . write ( "   Processing task " + ( ++ taskCt ) . toString ( ) + " of " + tasksList . length . toString ( ) , logLevel , logPad ) ; this . buildTaskTreeList ( each , folders , files , ltFolder , favFolder , lastTasks , favTasks , logPad + "   " ) ; } await this . buildGroupings ( folders , logPad + "   " , logLevel ) ; this . sortLastTasks ( ltFolder ?. taskFiles , lastTasks , logPad + "   " , logLevel ) ; this . sortTasks ( favFolder ?. taskFiles , logPad + "   " , logLevel ) ; const sortedFolders = this . getSortedRoot ( folders ) ; log . methodDone ( "build task tree" , logLevel , logPad ) ; this . treeBuilding = false ; return sortedFolders ; } method param each param folders param files param ltFolder param favFolder param lastTasks param favTasks param logPad private buildTaskTreeList ( each , folders , files , ltFolder , favFolder , lastTasks , favTasks , logPad = "" ) { let folder , scopeName ; log . methodStart ( "build task tree list" , 2 , logPad , true , [ [ "name" , each . name ] , [ "source" , each . source ] , [ "scope" , each . scope ] , [ "definition type" , each . definition . type ] , [ "definition path" , each . definition . path ] ] ) ; const definition = each . definition ; let relativePath = definition . path ?? "" ; const include = this . isTaskIncluded ( each , relativePath , logPad + "   " ) ; if ( ! include ) { log . methodDone ( "build task tree list" , 2 , logPad ) ; return ; } const isNpmInstallTask = include === "npm-install" ; if ( typeof include === "string" && ! isNpmInstallTask ) { relativePath = include ; log . value ( logPad + "   set relative path" , relativePath , 2 ) ; } if ( this . isWorkspaceFolder ( each . scope ) ) { scopeName = each . scope . name ; folder = folders . get ( scopeName ) ; if ( ! folder ) { folder = new TaskFolder ( each . scope ) ; folders . set ( scopeName , folder ) ; } } else { scopeName = constants . USER_TASKS_LABEL ; folder = folders . get ( scopeName ) ; if ( ! folder ) { folder = new TaskFolder ( scopeName ) ; folders . set ( scopeName , folder ) ; } } this . logTask ( each , scopeName , logPad + "   " ) ; const taskFile = this . getTaskFileNode ( each , folder , files , relativePath , scopeName , logPad + "   " ) ; if ( ! isNpmInstallTask ) { const taskItem = new TaskItem ( this . extensionContext , taskFile , each ) ; taskFile . addTreeNode ( taskItem ) ; this . addToSpecialFolder ( taskItem , ltFolder , lastTasks , constants . LAST_TASKS_LABEL ) ; this . addToSpecialFolder ( taskItem , favFolder , favTasks , constants . FAV_TASKS_LABEL ) ; } log . methodDone ( "build task tree list" , 2 , logPad ) ; } method param folder since private async clearSpecialFolder ( folder ) { const choice = typeof folder === "string" ? "Yes" : await window . showInformationMessage ( "Clear all tasks from this folder?" , "Yes" , "No" ) , label = typeof folder === "string" ? folder : folder . label ; if ( choice === "Yes" ) { if ( label === constants . FAV_TASKS_LABEL ) { await storage . update ( constants . FAV_TASKS_STORE , [ ] ) ; await this . showSpecialTasks ( false , true ) ; } else if ( label === constants . LAST_TASKS_LABEL ) { await storage . update ( constants . LAST_TASKS_STORE , [ ] ) ; await this . showSpecialTasks ( false ) ; } } } method param storeName see param label param treeIndex param sort param logPad private async createSpecialFolder ( storeName , label , treeIndex , sort , logPad = "" ) { const lTasks = storage . get < string [ ] > ( storeName , [ ] ) ; const folder = new TaskFolder ( label ) ; log . methodStart ( "create special tasks folder" , 1 , logPad , true , [ [ "store" , storeName ] , [ "name" , label ] ] ) ; if ( ! this . taskTree ) { return ; } this . taskTree . splice ( treeIndex , 0 , folder ) ; for ( const tId of lTasks ) { const taskItem2 = await this . getTaskItems ( tId , logPad + "   " ) ; if ( taskItem2 && taskItem2 instanceof TaskItem && taskItem2 . task ) { const taskItem3 = new TaskItem ( this . extensionContext , taskItem2 . taskFile , taskItem2 . task ) ; taskItem3 . id = label + ":" + taskItem3 . id ; taskItem3 . label = this . getSpecialTaskName ( taskItem3 ) ; folder . insertTaskFile ( taskItem3 , 0 ) ; } } if ( sort ) { this . sortTasks ( folder . taskFiles , logPad + "   " ) ; } log . methodDone ( "create special tasks folder" , 1 , logPad ) ; } method since 1.28.0
     *
     * Creates main task groupings, i.e. 'npm', 'vscode', 'batch', etc, for a given  TaskFolder param folder private async createTaskGroupings ( folder , logPad = "" , logLevel = 1 ) { let prevTaskFile ; const subfolders = new Map ( ) ; log . methodStart ( "create tree node folder grouping" , logLevel , logPad , true , [ [ "project folder" , folder . label ] ] ) ; for ( const each of folder . taskFiles ) { if ( ! ( each instanceof TaskFile ) ) { continue ; } if ( prevTaskFile && prevTaskFile . taskSource === each . taskSource ) { const id = folder . label + each . taskSource ; let subfolder = subfolders . get ( id ) ; if ( ! subfolder ) { log . values ( logLevel + 2 , logPad , [ [ "   Add source file sub-container" , each . path ] , [ "      id" , id ] ] , true ) ; const definition = ( each . treeNodes [ 0 ] as TaskItem ) ?. task ?. definition ; if ( definition ) { subfolder = new TaskFile ( this . extensionContext , folder , definition , each . taskSource , each . path , 0 , true , undefined , "   " ) ; subfolders . set ( id , subfolder ) ; folder . addTaskFile ( subfolder ) ; subfolder . addTreeNode ( prevTaskFile ) ; } } if ( subfolder && subfolder . nodePath !== each . nodePath ) { subfolder . addTreeNode ( each ) ; } } prevTaskFile = each ; await this . createTaskGroupingsBySep ( folder , each , subfolders , 0 , logPad + "   " , logLevel + 1 ) ; } this . removeGroupedTasks ( folder , subfolders , logPad + "   " , logLevel + 1 ) ; log . write ( logPad + "   rename grouped tasks" , logLevel ) ; for ( const each of folder . taskFiles ) { if ( each instanceof TaskFile ) { await this . renameGroupedTasks ( each ) ; } } this . sortFolder ( folder , logPad + "   " , logLevel + 1 ) ; log . methodDone ( "create tree node folder grouping" , logLevel , logPad ) ; } method since param folder param each param prevTaskFile param subfolders param groupSeparator private async createTaskGroupingsBySep ( folder , taskFile , subfolders , treeLevel = 0 , logPad = "" , logLevel = 2 ) { let prevName ; let prevTaskItem ; const newNodes = [ ] ; const groupSeparator = util . getGroupSeparator ( ) ; const atMaxLevel = configuration . get < number > ( "groupMaxLevel" ) <= treeLevel + 1 ; log . methodStart ( "create task groupings by separator" , logLevel , logPad , true , [ [ "folder" , folder . label ] , [ "label (node name)" , taskFile . label ] , [ "grouping level" , treeLevel ] , [ "is group" , taskFile . isGroup ] , [ "file name" , taskFile . fileName ] , [ "folder" , folder . label ] , [ "path" , taskFile . path ] , [ "tree level" , treeLevel ] ] ) ; const _setNodePath = ( t , cPath ) => { if ( t && ! atMaxLevel && prevName ) { log . write ( "   setting node path" , logLevel + 2 , logPad ) ; log . value ( "      current" , t . nodePath , logLevel + 2 , logPad ) ; if ( ! t . nodePath && taskFile . taskSource === "Workspace" ) { t . nodePath = path . join ( ".vscode" , prevName [ treeLevel ] ) ; } else if ( ! t . nodePath ) { t . nodePath = prevName [ treeLevel ] ; } else { t . nodePath = path . join ( cPath , prevName [ treeLevel ] ) ; } log . value ( "      new" , t . nodePath , logLevel + 2 , logPad ) ; } } ; for ( const each of taskFile . treeNodes ) { if ( ! ( each instanceof TaskItem ) || ! each . task || ! each . label ) { continue ; } const label = each . label . toString ( ) ; let subfolder ; const prevNameThis = label ?. split ( groupSeparator ) ; const prevNameOk = prevName && prevName . length > treeLevel && prevName [ treeLevel ] ; log . write ( "   process task item" , logLevel + 1 , logPad ) ; log . values ( logLevel + 2 , logPad + "      " , [ [ "id" , each . id ] , [ "label" , label ] , [ "node path" , each . nodePath ] , [ "command" , each . command ?. command ] , [ "previous name [tree level]" , prevName && prevNameOk ? prevName [ treeLevel ] : "undefined" ] , [ "this previous name" , prevNameThis ] ] ) ; let foundGroup = false ; if ( prevName && prevNameOk && prevNameThis && prevNameThis . length > treeLevel ) { for ( let i = 0 ; i <= treeLevel ; i ++ ) { if ( prevName [ i ] === prevNameThis [ i ] ) { log . write ( "   found group" , 4 , logPad ) ; foundGroup = true ; } else { foundGroup = false ; break ; } } } if ( foundGroup && prevName ) { const id = this . getGroupedId ( folder , taskFile , label , treeLevel ) ; subfolder = subfolders . get ( id ) ; if ( ! subfolder ) { subfolder = new TaskFile ( this . extensionContext , folder , each . task . definition , taskFile . taskSource , each . taskFile . path , treeLevel , true , prevName [ treeLevel ] , logPad ) ; subfolders . set ( id , subfolder ) ; _setNodePath ( prevTaskItem , each . nodePath ) ; subfolder . addTreeNode ( prevTaskItem ) ; newNodes . push ( subfolder ) ; } _setNodePath ( each , each . nodePath ) ; subfolder . addTreeNode ( each ) ; } if ( label . includes ( groupSeparator ) ) { prevName = label . split ( groupSeparator ) ; } prevTaskItem = each ; } if ( newNodes . length > 0 ) { let numGrouped = 0 ; for ( const n of newNodes ) { taskFile . insertTreeNode ( n , numGrouped ++ ) ; if ( ! atMaxLevel ) { await this . createTaskGroupingsBySep ( folder , n , subfolders , treeLevel + 1 , logPad + "   " , logLevel + 1 ) ; } } } log . methodDone ( "create task groupings by separator" , logLevel , logPad ) ; } private findJsonDocumentPosition ( documentText , taskItem ) { const me = this ; let inScripts = false ; let inTasks = false ; let inTaskLabel ; let scriptOffset = 0 ; const visitor = { onError : ( ) => { return scriptOffset ; } , onObjectEnd : ( ) => { if ( inScripts ) { inScripts = false ; } } , onLiteralValue : ( value , offset , _length ) => { if ( inTaskLabel ) { if ( typeof value === "string" ) { if ( inTaskLabel === "label" || inTaskLabel === "script" ) { if ( taskItem . task ?. name === value ) { scriptOffset = offset ; } } } inTaskLabel = undefined ; } } , onObjectProperty : ( property , offset , _length ) => { if ( property === "scripts" ) { inScripts = true ; if ( ! taskItem ) { scriptOffset = offset ; } } else if ( inScripts && taskItem ) { const label = me . getTaskName ( property , taskItem . task ?. definition . path ) ; if ( taskItem . task ?. name === label ) { scriptOffset = offset ; } } else if ( property === "tasks" ) { inTasks = true ; if ( ! inTaskLabel ) { scriptOffset = offset ; } } else if ( ( property === "label" || property === "script" ) && inTasks && ! inTaskLabel ) { inTaskLabel = "label" ; if ( ! inTaskLabel ) { scriptOffset = offset ; } } else { inTaskLabel = undefined ; } } } ; visit ( documentText , visitor ) ; return scriptOffset ; } private findDocumentPosition ( document , taskItem ? ) { let scriptOffset = 0 ; const documentText = document . getText ( ) ; log . methodStart ( "find task definition document position" , 1 , "" , true , [ [ "task label" , taskItem ?. label ] , [ "task source" , taskItem ?. taskSource ] ] ) ; if ( ! taskItem || ! taskItem . task ) { return 0 ; } const def = taskItem . task . definition ; if ( taskItem . taskSource === "npm" || taskItem . taskSource === "Workspace" ) { scriptOffset = this . findJsonDocumentPosition ( documentText , taskItem ) ; } else { const provider = providers . get ( util . getScriptProviderType ( def . type ) ) ; scriptOffset = provider ?. getDocumentPosition ( taskItem . task . name , documentText ) || - 1 ; } if ( scriptOffset === - 1 ) { scriptOffset = 0 ; } log . methodDone ( "find task definition document position" , 1 , "" , true , [ [ "offset" , scriptOffset ] ] ) ; return scriptOffset ; } private fireTaskChangeEvents ( taskItem , logPad , logLevel ) { if ( ! this . taskTree || ! taskItem ) { log . error ( "task change event fire, invalid argument" ) ; return ; } log . methodStart ( "fire task change events" , logLevel , logPad ) ; this . _onDidChangeTreeData . fire ( taskItem . taskFile ) ; if ( configuration . get < boolean > ( "showLastTasks" ) === true ) { const lastTasks = storage . get < string [ ] > ( constants . LAST_TASKS_STORE , [ ] ) ; if ( util . existsInArray ( lastTasks , this . getTaskItemId ( taskItem ) ) !== false ) { if ( this . taskTree [ 0 ] . label === constants . LAST_TASKS_LABEL ) { this . _onDidChangeTreeData . fire ( this . taskTree [ 0 ] ) ; } } } const favTasks = storage . get < string [ ] > ( constants . FAV_TASKS_STORE , [ ] ) ; if ( util . existsInArray ( favTasks , this . getTaskItemId ( taskItem ) ) !== false ) { if ( this . taskTree [ 0 ] . label === constants . FAV_TASKS_LABEL ) { this . _onDidChangeTreeData . fire ( this . taskTree [ 0 ] ) ; } else if ( this . taskTree [ 1 ] . label === constants . FAV_TASKS_LABEL ) { this . _onDidChangeTreeData . fire ( this . taskTree [ 1 ] ) ; } } log . methodStart ( "fire task change events" , logLevel , logPad ) ; } async getChildren ( element ? , logPad = "" , logLevel = 2 ) { let waited = 0 ; let items = [ ] ; log . methodStart ( "get tree children" , logLevel , logPad , false , [ [ "task folder" , element ?. label ] , [ "all tasks need to be retrieved" , ! this . tasks ] , [ "specific tasks need to be retrieved" , ! ! this . currentInvalidation ] , [ "current invalidation" , this . currentInvalidation ] , [ "task tree needs to be built" , ! this . taskTree ] ] ) ; while ( this . treeBuilding ) { log . write ( logPad + "   waiting..." , logLevel ) ; await util . timeout ( 100 ) ; waited += 100 ; } if ( waited ) { log . write ( logPad + "   waited " + waited + " ms" , logLevel ) ; } if ( ! this . taskTree ) { if ( ! this . tasks || this . currentInvalidation === "workspace" ) { this . tasks = await tasks . fetchTasks ( ) ; } else if ( this . currentInvalidation ) { const toRemove = [ ] ; const taskItems = await tasks . fetchTasks ( { type : ! util . isScriptType ( this . currentInvalidation ) ? this . currentInvalidation : "script" } ) ; for ( const t of this . tasks ) { if ( t . source === this . currentInvalidation || t . source === "Workspace" ) { if ( t . source !== "Workspace" || t . definition . type === this . currentInvalidation ) { toRemove . push ( t ) ; } } } for ( const t of toRemove ) { util . removeFromArray ( this . tasks , t ) ; } this . tasks . push ( ... taskItems ) ; } if ( this . tasks ) { try { this . taskTree = await this . buildTaskTree ( this . tasks , logPad + "   " , logLevel ) ; } catch ( e ) { log . error ( e . toString ( ) ) ; } log . blank ( 1 ) ; if ( ! this . taskTree || this . taskTree . length === 0 ) { this . taskTree = [ noScripts ] ; } } } if ( element instanceof TaskFolder ) { log . write ( logPad + "   Get folder task files" , logLevel ) ; items = element . taskFiles ; } else if ( element instanceof TaskFile ) { log . write ( logPad + "   Get file tasks/scripts" , logLevel ) ; items = element . treeNodes ; } else if ( ! element ) { log . write ( logPad + "   Get task tree" , logLevel ) ; if ( this . taskTree ) { items = this . taskTree ; } } log . methodDone ( "get tree children" , logLevel , logPad ) ; this . currentInvalidation = undefined ; return items ; } private getGroupedId ( folder , file , label , treeLevel ) { const groupSeparator = util . getGroupSeparator ( ) ; const labelSplit = label ?. split ( groupSeparator ) ; let id = "" ; for ( let i = 0 ; i <= treeLevel ; i ++ ) { id += labelSplit [ i ] ; } if ( file . resourceUri ) { id += file . resourceUri . fsPath . replace ( /\W/gi , "" ) ; } else if ( file . fileName ) { id += file . fileName . replace ( /\W/gi , "" ) ; } return folder . label + file . taskSource + id + ( treeLevel || treeLevel === 0 ? treeLevel . toString ( ) : "" ) ; } public getParent ( element ) { if ( element instanceof TaskFolder ) { return null ; } if ( element instanceof TaskFile ) { return element . folder ; } if ( element instanceof TaskItem ) { return element . taskFile ; } if ( element instanceof NoScripts ) { return null ; } return null ; } private getSortedRoot ( folders ) { return [ ... folders . values ( ) ] ?. sort ( ( a , b ) => { const sFolders = [ constants . FAV_TASKS_LABEL , constants . LAST_TASKS_LABEL , constants . USER_TASKS_LABEL ] ; if ( a . label === constants . LAST_TASKS_LABEL ) { return - 1 ; } else if ( b . label === constants . LAST_TASKS_LABEL ) { return 1 ; } else if ( a . label === constants . FAV_TASKS_LABEL ) { if ( b . label !== constants . LAST_TASKS_LABEL ) { return - 1 ; } return 1 ; } else if ( b . label === constants . FAV_TASKS_LABEL ) { if ( a . label !== constants . LAST_TASKS_LABEL ) { return 1 ; } return - 1 ; } else if ( a . label === constants . USER_TASKS_LABEL ) { if ( b . label !== constants . LAST_TASKS_LABEL && b . label !== constants . FAV_TASKS_LABEL ) { return - 1 ; } return 1 ; } else if ( b . label === constants . USER_TASKS_LABEL ) { if ( a . label !== constants . LAST_TASKS_LABEL && a . label !== constants . FAV_TASKS_LABEL ) { return 1 ; } return - 1 ; } if ( a . label && b . label ) { return a . label . toString ( ) . localeCompare ( b . label ?. toString ( ) ) ; } return 0 ; } ) ; } private getSpecialTaskName ( taskItem ) { return taskItem . label + " (" + taskItem . taskFile . folder . label + " - " + taskItem . taskSource + ")" ; } method param taskId param logPad param executeOpenForTests public async getTaskItems ( taskId , logPad = "" , executeOpenForTests = false , logLevel = 1 ) { const me = this ; const taskMap = new Map ( ) ; let done = false ; log . methodStart ( "Get task item(s) from tree" , logLevel , logPad , false , [ [ "task id" , taskId ?? "all tasks" ] , [ "execute open" , executeOpenForTests ] ] ) ; const treeItems = await me . getChildren ( undefined , "   " , logLevel + 1 ) ; if ( ! treeItems || treeItems . length === 0 ) { window . showInformationMessage ( "No tasks found!" ) ; await storage . update ( constants . FAV_TASKS_STORE , [ ] ) ; await storage . update ( constants . LAST_TASKS_STORE , [ ] ) ; return ; } const processItem2g = async ( pItem2 ) => { const treeFiles = await me . getChildren ( pItem2 , "   " , logLevel + 1 ) ; if ( treeFiles . length > 0 ) { for ( const item2 of treeFiles ) { if ( done ) { break ; } if ( item2 instanceof TaskItem ) { const tmp = me . getParent ( item2 ) ; assert ( tmp instanceof TaskFile , "Invaid parent type, should be TaskFile for TaskItem" ) ; await processItem2 ( item2 ) ; } else if ( item2 instanceof TaskFile && item2 . isGroup ) { log . write ( "        Task File (grouped): " + item2 . path + item2 . fileName , logLevel + 2 ) ; await processItem2g ( item2 ) ; } else if ( item2 instanceof TaskFile && ! item2 . isGroup ) { log . write ( "        Task File (grouped): " + item2 . path + item2 . fileName , logLevel + 2 ) ; await processItem2 ( item2 ) ; } } } } ; const processItem2 = async ( pItem2 ) => { const treeTasks = await me . getChildren ( pItem2 , "   " , logLevel + 1 ) ; if ( treeTasks . length > 0 ) { for ( const item3 of treeTasks ) { if ( done ) { break ; } if ( item3 instanceof TaskItem ) { if ( executeOpenForTests ) { await me . open ( item3 ) ; } const tmp = me . getParent ( item3 ) ; assert ( tmp instanceof TaskFile , "Invaid parent type, should be TaskFile for TaskItem" ) ; if ( item3 . task && item3 . task . definition ) { let tPath ; if ( me . isWorkspaceFolder ( item3 ) ) { tPath = item3 . task . definition . uri ? item3 . task . definition . uri . fsPath : ( item3 . task . definition . path ? item3 . task . definition . path : "root" ) ; } else { tPath = "root" ; } log . write ( logPad + "   ✔ Processed " + item3 . task . name , logLevel + 2 ) ; log . value ( logPad + "        id" , item3 . id , logLevel + 2 ) ; log . value ( logPad + "        type" , item3 . taskSource + " @ " + tPath , logLevel + 2 ) ; if ( item3 . id ) { taskMap . set ( item3 . id , item3 ) ; if ( taskId && taskId === item3 . id ) { done = true ; } } } } else if ( item3 instanceof TaskFile && item3 . isGroup ) { await processItem2 ( item3 ) ; } } } } ; const processItem = async ( pItem ) => { let tmp ; const treeFiles = await me . getChildren ( pItem , "   " , logLevel + 2 ) ; if ( treeFiles . length > 0 ) { for ( const item2 of treeFiles ) { if ( done ) { break ; } if ( item2 instanceof TaskFile && ! item2 . isGroup ) { log . write ( logPad + "   Task File: " + item2 . path + item2 . fileName , logLevel + 2 ) ; tmp = me . getParent ( item2 ) ; assert ( tmp instanceof TaskFolder , "Invaid parent type, should be TaskFolder for TaskFile" ) ; await processItem2 ( item2 ) ; } else if ( item2 instanceof TaskFile && item2 . isGroup ) { await processItem2g ( item2 ) ; } else if ( item2 instanceof TaskItem ) { await processItem2 ( item2 ) ; } } } } ; for ( const item of treeItems ) { if ( item instanceof TaskFolder ) { const isFav = item . label ?. toString ( ) . includes ( constants . FAV_TASKS_LABEL ) ; const isLast = item . label ?. toString ( ) . includes ( constants . LAST_TASKS_LABEL ) ; const isUser = item . label ?. toString ( ) . includes ( constants . USER_TASKS_LABEL ) ; const tmp = me . getParent ( item ) ; assert ( tmp === null , "Invalid parent type, should be null for TaskFolder" ) ; log . write ( "   Task Folder " + item . label + ":  " + ( ! isFav && ! isLast && ! isUser ? item . resourceUri ?. fsPath : ( isLast ? constants . LAST_TASKS_LABEL : ( isUser ? constants . USER_TASKS_LABEL : constants . FAV_TASKS_LABEL ) ) ) , logLevel + 1 , logPad ) ; await processItem ( item ) ; } } log . methodDone ( "Get task item(s) from tree" , logLevel , logPad , false , [ [ "# of items found" , taskMap . keys . length ] ] ) ; if ( taskId ) { return taskMap . get ( taskId ) ; } return taskMap ; } private getTaskName ( script , relativePath ) { if ( relativePath && relativePath . length ) { return `${ script } - ${ relativePath . substring ( 0 , relativePath . length - 1 ) }` ; } return script ; } private getTaskFileNode ( task , folder , files , relativePath , scopeName , logPad = "" ) { let taskFile ; const relPathAdj = task . source !== "Workspace" ? relativePath : ".vscode" ; let id = task . source + ":" + path . join ( scopeName , relPathAdj ) ; if ( task . definition . fileName && ! task . definition . scriptFile ) { id = path . join ( id , task . definition . fileName ) ; } taskFile = files . get ( id ) ; if ( ! taskFile ) { log . value ( logPad + "   Add source file container" , task . source ) ; taskFile = new TaskFile ( this . extensionContext , folder , task . definition , task . source , relativePath , 0 , false , undefined , logPad ) ; folder . addTaskFile ( taskFile ) ; files . set ( id , taskFile ) ; } return taskFile ; } private getTaskItemId ( taskItem ) { return taskItem ?. id ?. replace ( constants . LAST_TASKS_LABEL + ":" , "" ) . replace ( constants . FAV_TASKS_LABEL + ":" , "" ) . replace ( constants . USER_TASKS_LABEL + ":" , "" ) ; } private getTerminal ( taskItem , logPad = "" ) { const me = this ; let checkNum = 0 ; let term ; log . write ( "Get terminal" , 1 , logPad ) ; if ( ! taskItem . task || ! taskItem . label ) { log . write ( "   no defined task on TaskItem" , 2 , logPad ) ; return ; } if ( ! window . terminals || window . terminals . length === 0 ) { log . write ( "   zero terminals alive" , 2 , logPad ) ; return term ; } if ( window . terminals . length === 1 ) { log . write ( "   return only terminal alive" , 2 , logPad ) ; return window . terminals [ 0 ] ; } const check = ( taskName ) => { let termNum = 0 , term2 ; log . value ( "   Checking possible task terminal name #" + ( ++ checkNum ) . toString ( ) , taskName , 2 ) ; taskName = taskName . toLowerCase ( ) ; for ( const t of window . terminals ) { log . value ( "      == terminal " + ( ++ termNum ) + " name" , t . name , 2 , logPad ) ; let termName = t . name . toLowerCase ( ) . replace ( "task - " , "" ) ; if ( termName . endsWith ( " Task" ) ) { termName = termName . substring ( 0 , termName . length - 5 ) ; } if ( taskName . indexOf ( termName ) !== - 1 || termName . indexOf ( taskName ) !== - 1 ) { term2 = t ; log . write ( "   found!" , 2 , logPad ) ; break ; } } return term2 ; } ; let relPath = taskItem . task . definition . path ? taskItem . task . definition . path : "" ; if ( relPath [ relPath . length - 1 ] === "/" ) { relPath = relPath . substring ( 0 , relPath . length - 1 ) ; } else if ( relPath [ relPath . length - 1 ] === "\\" ) { relPath = relPath . substring ( 0 , relPath . length - 1 ) ; } if ( taskItem . taskFile . folder . workspaceFolder ) { const lblString = taskItem . task . name ; let taskName = taskItem . taskFile . label + ": " + taskItem . label + " (" + taskItem . taskFile . folder . workspaceFolder . name + ")" ; term = check ( taskName ) ; if ( ! term && lblString . indexOf ( "(" ) !== - 1 ) { taskName = taskItem . taskSource + ": " + lblString . substring ( 0 , lblString . indexOf ( "(" ) ) . trim ( ) + ( relPath ? " - " : "" ) + taskItem . taskFile . folder . workspaceFolder . name + ")" ; term = check ( taskName ) ; } if ( ! term ) { taskName = taskItem . taskSource + ": " + lblString + ( relPath ? " - " : "" ) + relPath + " (" + taskItem . taskFile . folder . workspaceFolder . name + ")" ; term = check ( taskName ) ; } if ( ! term ) { taskName = taskItem . taskSource + ": " + lblString + " (" + taskItem . taskFile . folder . workspaceFolder . name + ")" ; term = check ( taskName ) ; } if ( ! term ) { taskName = taskItem . taskSource + ": " + lblString + ( relPath ? " - " : "" ) + relPath + " (" + taskItem . taskFile . folder . workspaceFolder . name + ")" ; term = check ( taskName ) ; } if ( ! term && taskItem . taskSource === "Workspace" ) { taskName = "npm: " + lblString + ( relPath ? " - " : "" ) + relPath + " (" + taskItem . taskFile . folder . workspaceFolder . name + ")" ; term = check ( taskName ) ; } if ( ! term && lblString . indexOf ( "(" ) !== - 1 ) { taskName = taskItem . taskSource + ": " + lblString . substring ( 0 , lblString . indexOf ( "(" ) ) . trim ( ) + ( relPath ? " - " : "" ) + relPath + " (" + taskItem . taskFile . folder . workspaceFolder . name + ")" ; term = check ( taskName ) ; } if ( ! term && lblString . indexOf ( "(" ) !== - 1 ) { taskName = taskItem . taskSource + ": " + lblString . substring ( 0 , lblString . indexOf ( "(" ) ) . trim ( ) + ( relPath ? " - " : "" ) + relPath + " (" + taskItem . taskFile . folder . workspaceFolder . name + ")" ; term = check ( taskName ) ; } if ( ! term && relPath ) { const folder = taskItem . getFolder ( ) ; if ( folder ) { taskName = folder . name + " (" + relPath + ")" ; term = check ( taskName ) ; } if ( ! term ) { const folder = taskItem . getFolder ( ) ; if ( folder ) { taskName = folder . name + " (" + path . basename ( relPath ) + ")" ; term = check ( taskName ) ; } } } } return term ; } public getTreeItem ( element ) { log . blank ( 3 ) ; log . write ( "get tree item" , 3 ) ; log . value ( "   label" , element ?. label , 3 ) ; if ( element instanceof TaskItem ) { log . write ( "   refresh task item state" , 3 ) ; element . refreshState ( ) ; } return element ; } private async handleFileWatcherEvent ( invalidate , opt ? , logPad = "" ) { log . methodStart ( "handle filewatcher / settings change / test event" , 1 , logPad ) ; if ( ( invalidate === true || invalidate === "tests" ) && ! opt ) { log . write ( "   handling 'rebuild cache' event" , 1 , logPad + "   " ) ; this . busy = true ; await rebuildCache ( logPad + "   " ) ; log . write ( "   handling 'rebuild cache' eventcomplete" , 1 , logPad + "   " ) ; this . busy = false ; } if ( invalidate !== "tests" ) { log . write ( "   handling 'invalidate tasks cache' event" , 1 , logPad ) ; await this . invalidateTasksCache ( invalidate , opt , logPad + "   " ) ; } log . methodDone ( "   handle filewatcher / settings change / test event" , 1 , logPad ) ; } param opt1 param opt2 public async invalidateTasksCache ( opt1 ? , opt2 ? , logPad = "" ) { log . methodStart ( "invalidate tasks cache" , 1 , logPad , true , [ [ "opt1" , opt1 ] , [ "opt2" , opt2 && opt2 instanceof Uri ? opt2 . fsPath : opt2 ] ] ) ; this . busy = true ; if ( opt1 && opt1 !== "tests" && opt2 instanceof Uri ) { log . write ( "   invalidate " + opt1 + " provider file " , 1 , logPad ) ; log . value ( "      file" , opt2 , 1 , logPad ) ; const provider = providers . get ( util . getScriptProviderType ( opt1 ) ) ; await provider ?. invalidateTasksCache ( opt2 , logPad + "   " ) ; } else { if ( ! opt1 ) { log . write ( "   invalidate all providers" , 1 , logPad ) ; for ( const [ key , p ] of providers ) { log . write ( "   invalidate " + key + " provider" , 1 , logPad ) ; await p . invalidateTasksCache ( undefined , logPad + "   " ) ; } } else { log . write ( "   invalidate " + opt1 + " provider" , 1 , logPad ) ; await providers . get ( opt1 ) ?. invalidateTasksCache ( undefined , logPad + "   " ) ; } } this . busy = false ; log . methodDone ( "invalidate tasks cache" , 1 , logPad , true ) ; } private isNpmInstallTask ( task ) { return task . source === "npm" && task . name === this . getTaskName ( "install" , task . definition . path ) ; } private isTaskIncluded ( task , relativePath , logPad = "" ) { if ( ! task . definition . uri && ( task . source === "gulp" || task . source === "grunt" ) ) { log . write ( `   skipping vscode provided ${ task . source } task` , 2 , logPad ) ; return false ; } if ( task . source === "tsc" && this . isWorkspaceFolder ( task . scope ) ) { if ( task . name . indexOf ( " - " ) !== - 1 && task . name . indexOf ( " - tsconfig.json" ) === - 1 ) { relativePath = path . dirname ( task . name . substring ( task . name . indexOf ( " - " ) + 3 ) ) ; if ( util . isExcluded ( path . join ( task . scope . uri . path , relativePath ) ) ) { log . write ( "   skipping this tsc task (remapped subfolder)" , 2 , logPad ) ; return false ; } return relativePath ; } } let settingName = "enable" + util . properCase ( task . source ) ; if ( settingName === "enableApp-publisher" ) { settingName = "enableAppPublisher" ; } const srcEnabled = configuration . get ( settingName ) ; const isNpmInstallTask = this . isNpmInstallTask ( task ) ; if ( ( srcEnabled || ! this . isWorkspaceFolder ( task . scope ) ) && ! isNpmInstallTask ) { return true ; } log . value ( "   enabled in settings" , configuration . get ( settingName ) , 2 , logPad ) ; log . value ( "   is npm install task" , isNpmInstallTask , 2 , logPad ) ; if ( isNpmInstallTask && srcEnabled ) { return "npm-install" ; } log . write ( "   skipping this task" , 2 , logPad ) ; return false ; } private isWorkspaceFolder ( value ) { return value && typeof value !== "number" ; } private logTask ( task , scopeName , logPad = "" ) { const definition = task . definition ; if ( ! log . isLoggingEnabled ( ) ) { return ; } log . value ( "name" , task . name , 3 , logPad ) ; log . value ( "source" , task . source , 3 , logPad ) ; log . value ( "scope name" , scopeName , 4 , logPad ) ; if ( this . isWorkspaceFolder ( task . scope ) ) { log . value ( "scope.name" , task . scope . name , 4 , logPad ) ; log . value ( "scope.uri.path" , task . scope . uri . path , 4 , logPad ) ; log . value ( "scope.uri.fsPath" , task . scope . uri . fsPath , 4 , logPad ) ; } else { log . value ( "scope.uri.path" , "N/A (User)" , 4 , logPad ) ; } log . value ( "relative Path" , definition . path ? definition . path : "" , 4 , logPad ) ; log . value ( "type" , definition . type , 4 , logPad ) ; if ( definition . scriptType ) { log . value ( "   script type" , definition . scriptType , 4 , logPad ) ; } if ( definition . scriptFile ) { log . value ( "   script file" , definition . scriptFile , 4 , logPad ) ; } if ( definition . script ) { log . value ( "script" , definition . script , 4 , logPad ) ; } if ( definition . path ) { log . value ( "path" , definition . path , 4 , logPad ) ; } if ( definition . fileName ) { log . value ( "file name" , definition . fileName , 4 , logPad ) ; } if ( definition . uri ) { log . value ( "file path" , definition . uri . fsPath , 4 , logPad ) ; } if ( definition . takesArgs ) { log . value ( "script requires args" , "true" , 4 , logPad ) ; } if ( definition . cmdLine ) { log . value ( "script cmd line" , definition . cmdLine , 4 , logPad ) ; } } private async open ( selection ) { const clickAction = configuration . get < string > ( "clickAction" ) || "Open" ; if ( clickAction === "Execute" ) { await this . run ( selection ) ; return ; } const uri = ! util . isScriptType ( selection . taskSource ) ? selection . taskFile . resourceUri : Uri . file ( selection . task ?. definition . uri . fsPath ) ; if ( uri ) { log . methodStart ( "open document at position" , 1 , "" , true , [ [ "command" , selection . command ?. command ] , [ "source" , selection . taskSource ] , [ "uri path" , uri . path ] , [ "fs path" , uri . fsPath ] ] ) ; if ( util . pathExists ( uri . fsPath ) ) { const document = await workspace . openTextDocument ( uri ) ; const offset = this . findDocumentPosition ( document , selection instanceof TaskItem ? selection : undefined ) ; const position = document . positionAt ( offset ) ; await window . showTextDocument ( document , { selection : new Selection ( position , position ) } ) ; } } } private openTerminal ( taskItem ) { const term = this . getTerminal ( taskItem ) ; if ( term ) { term . show ( ) ; } } private pause ( taskItem ) { if ( ! taskItem || this . busy ) { window . showInformationMessage ( "Busy, please wait..." ) ; return ; } log . methodStart ( "pause" , 1 , "" , true ) ; if ( taskItem . task ?. execution ) { const terminal = this . getTerminal ( taskItem , "   " ) ; if ( terminal ) { if ( taskItem . paused ) { taskItem . paused = false ; log . value ( "   send to terminal" , "Y" , 1 ) ; terminal . sendText ( "N" ) ; } else { taskItem . paused = true ; log . value ( "   send to terminal" , "\\u0003" , 1 ) ; terminal . sendText ( "\u0003" ) ; } } else { window . showInformationMessage ( "Terminal not found" ) ; } } else { window . showInformationMessage ( "Executing task not found" ) ; } log . methodDone ( "pause" , 1 ) ; } private pushToTopOfSpecialFolder ( taskItem , label , treeIndex , logPad = "" ) { let taskItem2 ; const ltFolder = this . taskTree ? this . taskTree [ treeIndex ] as TaskFolder : undefined ; const taskId = label + ":" + this . getTaskItemId ( taskItem ) ; if ( ! ltFolder || ! taskItem . task ) { return ; } for ( const t of ltFolder . taskFiles ) { if ( t instanceof TaskItem && t . id === taskId ) { taskItem2 = t ; break ; } } if ( taskItem2 ) { ltFolder . removeTaskFile ( taskItem2 ) ; } else if ( ltFolder . taskFiles . length >= configuration . get < number > ( "numLastTasks" ) ) { ltFolder . removeTaskFile ( ltFolder . taskFiles [ ltFolder . taskFiles . length - 1 ] ) ; } if ( ! taskItem2 ) { taskItem2 = new TaskItem ( this . extensionContext , taskItem . taskFile , taskItem . task ) ; taskItem2 . id = taskId ; taskItem2 . label = this . getSpecialTaskName ( taskItem2 ) ; } log . value ( logPad + "   add item" , taskItem2 . id , 2 ) ; ltFolder . insertTaskFile ( taskItem2 , 0 ) ; } param invalidate param opt public async refresh ( invalidate ? , opt ? , logPad = "" ) { log . methodStart ( "refresh task tree" , 1 , logPad , true , [ [ "from view" , this . name ] , [ "invalidate" , invalidate ] , [ "opt fsPath" , opt && opt instanceof Uri ? opt . fsPath : "n/a" ] , [ "tree is null" , ! this . taskTree ] ] ) ; if ( invalidate !== false ) { await this . handleFileWatcherEvent ( invalidate , opt , logPad + "   " ) ; } if ( opt && opt instanceof Uri ) { log . write ( `   invalidation is for type '${ invalidate }'` , 1 , logPad ) ; this . currentInvalidation = invalidate ; this . taskTree = null ; this . _onDidChangeTreeData . fire ( null ) ; } else { log . write ( "   invalidation is for all types" , 1 , logPad ) ; this . tasks = null ; this . taskTree = null ; this . _onDidChangeTreeData . fire ( null ) ; } log . methodDone ( "refresh task tree" , 1 , logPad , true ) ; return ; } private removeGroupedTasks ( folder , subfolders , logPad , logLevel ) { const taskTypesRmv = [ ] ; log . methodStart ( "remove grouped tasks" , logLevel , logPad ) ; for ( const each of folder . taskFiles ) { if ( ! ( each instanceof TaskFile ) || ! each . label ) { continue ; } const id = folder . label + each . taskSource ; const id2 = this . getGroupedId ( folder , each , each . label . toString ( ) , each . groupLevel ) ; if ( ! each . isGroup && subfolders . get ( id ) ) { taskTypesRmv . push ( each ) ; } else if ( id2 && ! each . isGroup && subfolders . get ( id2 ) ) { taskTypesRmv . push ( each ) ; } else if ( each . isGroup ) { for ( const each2 of each . treeNodes ) { this . removeScripts ( each2 as TaskFile , folder , subfolders , 0 , logPad , logLevel + 1 ) ; if ( each2 instanceof TaskFile && each2 . isGroup && each2 . groupLevel > 0 ) { for ( const each3 of each2 . treeNodes ) { if ( each3 instanceof TaskFile ) { this . removeScripts ( each3 , folder , subfolders , 0 , logPad , logLevel + 1 ) ; } } } } } else { this . removeScripts ( each , folder , subfolders , 0 , logPad , logLevel + 1 ) ; } } for ( const each of taskTypesRmv ) { folder . removeTaskFile ( each ) ; } log . methodDone ( "remove grouped tasks" , logLevel , logPad ) ; } param taskFile param folder param subfolders param level private removeScripts ( taskFile , folder , subfolders , level = 0 , logPad = "" , logLevel = 3 ) { const me = this ; const taskTypesRmv = [ ] ; const groupSeparator = util . getGroupSeparator ( ) ; log . methodStart ( "remove scripts" , logLevel , logPad , false ) ; for ( const each of taskFile . treeNodes ) { const label = each . label ?. toString ( ) ; if ( ! label ) { continue ; } const labelPart = label ?. split ( groupSeparator ) [ level ] ; const id = this . getGroupedId ( folder , taskFile , label , level ) ; if ( each instanceof TaskItem ) { if ( label . split ( groupSeparator ) . length > 1 && labelPart ) { if ( subfolders . get ( id ) ) { taskTypesRmv . push ( each ) ; } } } else { let allTasks = false ; for ( const each2 of each . treeNodes ) { if ( each2 instanceof TaskItem ) { allTasks = true ; } else { allTasks = false ; break ; } } if ( ! allTasks ) { me . removeScripts ( each , folder , subfolders , level + 1 , logPad , logLevel + 1 ) ; } } } for ( const each2 of taskTypesRmv ) { taskFile . removeTreeNode ( each2 ) ; } log . methodDone ( "remove scripts" , logLevel , logPad ) ; } private async renameGroupedTasks ( taskFile ) { if ( ! configuration . get < boolean > ( "groupStripTaskLabel" , true ) ) { return ; } const groupSeparator = util . getGroupSeparator ( ) ; let rmvLbl = taskFile . label ?. toString ( ) ; rmvLbl = rmvLbl ?. replace ( /\(/gi , "\\(" ) . replace ( /\[/gi , "\\[" ) ; rmvLbl = rmvLbl ?. replace ( /\)/gi , "\\)" ) . replace ( /\]/gi , "\\]" ) ; for ( const each2 of taskFile . treeNodes ) { if ( each2 instanceof TaskItem ) { const rgx = new RegExp ( rmvLbl + groupSeparator , "i" ) ; each2 . label = each2 . label ?. toString ( ) . replace ( rgx , "" ) ; if ( each2 . groupLevel > 0 ) { let label = "" ; const labelParts = each2 . label ?. split ( groupSeparator ) ; if ( labelParts ) { for ( let i = each2 . groupLevel ; i < labelParts . length ; i ++ ) { label += ( label ? groupSeparator : "" ) + labelParts [ i ] ; } each2 . label = label || each2 . label ; } } } else { await this . renameGroupedTasks ( each2 ) ; } } } private async restart ( taskItem ) { log . methodStart ( "restart task" , 1 , "" , true ) ; if ( ! taskItem || this . busy ) { window . showInformationMessage ( "Busy, please wait..." ) ; } else { this . stop ( taskItem ) ; await this . run ( taskItem ) ; } log . methodDone ( "restart task" , 1 ) ; } private async resumeTask ( taskItem ) { log . methodStart ( "resume task" , 1 , "" , true ) ; const term = this . getTerminal ( taskItem , "   " ) ; if ( term ) { log . value ( "   send to terminal" , "N" , 1 ) ; term . sendText ( "N" , true ) ; taskItem . paused = false ; } else { window . showInformationMessage ( "Terminal not found" ) ; } log . methodDone ( "resume task" , 1 ) ; } param taskItem param noTerminal param withArgs private async run ( taskItem , noTerminal = false , withArgs = false , args ? ) { if ( ! taskItem || this . busy ) { window . showInformationMessage ( "Busy, please wait..." ) ; return ; } log . methodStart ( "run task" , 1 , "" , true , [ [ "task name" , taskItem . label ] ] ) ; if ( withArgs === true ) { await this . runWithArgs ( taskItem , args , noTerminal ) ; } else if ( taskItem . paused ) { await this . resumeTask ( taskItem ) ; } else { let newTask = taskItem . task ; if ( noTerminal && newTask ) { const def = newTask . definition ; const p = providers . get ( util . getScriptProviderType ( def . type ) ) , folder = taskItem . getFolder ( ) ; if ( folder ) { newTask = p ?. createTask ( def . target , undefined , folder , def . uri , undefined , "   " ) ; if ( newTask ) { newTask . definition . taskItemId = def . taskItemId ; } } } if ( await this . runTask ( newTask , noTerminal ) ) { await this . saveTask ( taskItem , configuration . get < number > ( "numLastTasks" ) , false , "   " ) ; } } log . methodDone ( "run task" , 1 ) ; } private async runLastTask ( ) { if ( this . busy ) { window . showInformationMessage ( "Busy, please wait..." ) ; return ; } let lastTaskId ; const lastTasks = storage . get < string [ ] > ( constants . LAST_TASKS_STORE , [ ] ) ; if ( lastTasks && lastTasks . length > 0 ) { lastTaskId = lastTasks [ lastTasks . length - 1 ] ; } if ( ! lastTaskId ) { window . showInformationMessage ( "No saved tasks!" ) ; return ; } log . methodStart ( "Run last task" , 1 , "" , true , [ [ "last task id" , lastTaskId ] ] ) ; const taskItem = await this . getTaskItems ( lastTaskId , "   " ) ; if ( taskItem && taskItem instanceof TaskItem ) { await this . run ( taskItem ) ; } else { window . showInformationMessage ( "Task not found!  Check log for details" ) ; await util . removeFromArrayAsync ( lastTasks , lastTaskId ) ; await storage . update ( constants . LAST_TASKS_STORE , lastTasks ) ; await this . showSpecialTasks ( true ) ; } log . methodDone ( "Run last task" , 1 ) ; } private async runNpmCommand ( taskFile , command ) { const pkgMgr = util . getPackageManager ( ) ; if ( ! taskFile . resourceUri ) { return ; } const options = { cwd : path . dirname ( taskFile . resourceUri . fsPath ) } ; const kind = { type : "npm" , script : "install" , path : path . dirname ( taskFile . resourceUri . fsPath ) } ; if ( command . indexOf ( "<packagename>" ) === - 1 ) { if ( taskFile . folder . workspaceFolder ) { const execution = new ShellExecution ( pkgMgr + " " + command , options ) ; const task = new Task ( kind , taskFile . folder . workspaceFolder , command , "npm" , execution , undefined ) ; await tasks . executeTask ( task ) ; } } else { const opts = { prompt : "Enter package name to " + command } ; await window . showInputBox ( opts ) . then ( async ( str ) => { if ( str !== undefined && taskFile . folder . workspaceFolder ) { const execution = new ShellExecution ( pkgMgr + " " + command . replace ( "<packagename>" , "" ) . trim ( ) + " " + str . trim ( ) , options ) ; const task = new Task ( kind , taskFile . folder . workspaceFolder , command . replace ( "<packagename>" , "" ) . trim ( ) + str . trim ( ) , "npm" , execution , undefined ) ; await tasks . executeTask ( task ) ; } } ) ; } } private async runTask ( task , noTerminal ? ) { if ( ! task ) { return false ; } if ( noTerminal === true ) { task . presentationOptions . reveal = TaskRevealKind . Silent ; } else { task . presentationOptions . reveal = TaskRevealKind . Always ; } try { await tasks . executeTask ( task ) ; } catch ( e ) { const err = e . toString ( ) ; if ( err . indexOf ( "No workspace folder" ) !== - 1 ) { window . showErrorMessage ( "Task execution failed:  No workspace folder.  NOTE: You must " + "save your workspace first before running 'User' tasks" ) ; } else { window . showErrorMessage ( "Task execution failed: " + err ) ; } log . write ( "Task execution failed: " + err ) ; return false ; } return true ; } param taskItem param noTerminal public async runWithArgs ( taskItem , args ? , noTerminal ? ) { if ( taskItem . task && ! ( taskItem . task . execution instanceof CustomExecution ) ) { const me = this ; const opts = { prompt : "Enter command line arguments separated by spaces" } ; const _run = async ( _args ) => { if ( _args ) { let newTask = taskItem ?. task ; if ( newTask && taskItem . task ) { const def = taskItem . task . definition , folder = taskItem . getFolder ( ) ; if ( folder ) { newTask = providers . get ( "script" ) ?. createTask ( def . script , undefined , folder , def . uri , _args . trim ( ) . split ( " " ) ) ; if ( newTask ) { newTask . definition . taskItemId = def . taskItemId ; } } } if ( newTask ) { if ( await this . runTask ( newTask , noTerminal ) ) { await me . saveTask ( taskItem , configuration . get < number > ( "numLastTasks" ) ) ; } } } } ; if ( ! args ) { window . showInputBox ( opts ) . then ( async ( str ) => { _run ( str ) ; } ) ; } else { await _run ( args ) ; } } else { window . showInformationMessage ( "Custom execution tasks cannot have the cmd line altered" ) ; } } private async saveTask ( taskItem , maxTasks , isFavorite = false , logPad = "" ) { const storeName = ! isFavorite ? constants . LAST_TASKS_STORE : constants . FAV_TASKS_STORE ; const label = ! isFavorite ? constants . LAST_TASKS_LABEL : constants . FAV_TASKS_LABEL ; const cstTasks = storage . get < string [ ] > ( storeName , [ ] ) ; const taskId = this . getTaskItemId ( taskItem ) ; log . methodStart ( "save task" , 1 , logPad , false , [ [ "treenode label" , label ] , [ "max tasks" , maxTasks ] , [ "is favorite" , isFavorite ] , [ "task id" , taskId ] , [ "current saved task ids" , cstTasks . toString ( ) ] ] ) ; if ( ! taskId ) { log . write ( "   invalid task id, exit" , 1 , logPad ) ; return ; } if ( util . existsInArray ( cstTasks , taskId ) !== false ) { await util . removeFromArrayAsync ( cstTasks , taskId ) ; } if ( maxTasks > 0 ) { while ( cstTasks . length >= maxTasks ) { cstTasks . shift ( ) ; } } cstTasks . push ( taskId ) ; await storage . update ( storeName , cstTasks ) ; log . methodDone ( "save task" , 1 , logPad , false , [ [ "new saved task ids" , cstTasks . toString ( ) ] ] ) ; await this . showSpecialTasks ( true , isFavorite , taskItem , logPad ) ; } public async showSpecialTasks ( show , isFavorite = false , taskItem ? , logPad = "" ) { let changed = true ; const tree = this . taskTree ; const storeName = ! isFavorite ? constants . LAST_TASKS_STORE : constants . FAV_TASKS_STORE ; const label = ! isFavorite ? constants . LAST_TASKS_LABEL : constants . FAV_TASKS_LABEL ; const showLastTasks = configuration . get < boolean > ( "showLastTasks" ) ; const favIdx = showLastTasks ? 1 : 0 ; const treeIdx = ! isFavorite ? 0 : favIdx ; log . methodStart ( "show special tasks" , 1 , logPad , false , [ [ "is favorite" , isFavorite ] , [ "fav index" , favIdx ] , [ "tree index" , treeIdx ] , [ "show" , show ] , [ "has task item" , ! ! taskItem ] , [ "showLastTasks setting" , showLastTasks ] ] ) ; if ( ! showLastTasks && ! isFavorite ) { return ; } if ( ! tree || tree . length === 0 || ( tree . length === 1 && tree [ 0 ] . contextValue === "noscripts" ) ) { log . write ( logPad + "   no tasks found in tree" , 1 ) ; return ; } if ( show ) { if ( ! taskItem || isFavorite ) { taskItem = undefined ; if ( tree [ treeIdx ] ?. label === label ) { tree . splice ( treeIdx , 1 ) ; } changed = true ; } if ( ! isFavorite && tree [ 0 ] ?. label !== label ) { await this . createSpecialFolder ( storeName , label , 0 , false , "   " ) ; changed = true ; } else if ( isFavorite && tree [ favIdx ] ?. label !== label ) { await this . createSpecialFolder ( storeName , label , favIdx , true , "   " ) ; changed = true ; } else if ( taskItem ) { this . pushToTopOfSpecialFolder ( taskItem , label , treeIdx ) ; changed = true ; } } else { if ( ! isFavorite && tree [ 0 ] . label === constants . LAST_TASKS_LABEL ) { tree . splice ( 0 , 1 ) ; changed = true ; } else if ( isFavorite && tree [ favIdx ] . label === constants . FAV_TASKS_LABEL ) { tree . splice ( favIdx , 1 ) ; changed = true ; } } if ( changed ) { this . _onDidChangeTreeData . fire ( taskItem || null ) ; } } private showStatusMessage ( task ) { if ( task && configuration . get < boolean > ( "showRunningTask" ) === true ) { const exec = tasks . taskExecutions . find ( e => e . task . name === task . name && e . task . source === task . source && e . task . scope === task . scope && e . task . definition . path === task . definition . path ) ; if ( exec ) { if ( ! TaskTreeDataProvider . statusBarSpace ) { TaskTreeDataProvider . statusBarSpace = window . createStatusBarItem ( StatusBarAlignment . Left , - 10000 ) ; TaskTreeDataProvider . statusBarSpace . tooltip = "Task Explorer running task" ; } let statusMsg = task . name ; if ( ( task . scope as WorkspaceFolder ) . name ) { statusMsg += " (" + ( task . scope as WorkspaceFolder ) . name + ")" ; } TaskTreeDataProvider . statusBarSpace . text = "$(loading~spin) " + statusMsg ; TaskTreeDataProvider . statusBarSpace . show ( ) ; } else { if ( TaskTreeDataProvider . statusBarSpace ) { TaskTreeDataProvider . statusBarSpace . dispose ( ) ; } } } } private sortFolder ( folder , logPad , logLevel ) { this . sortTasks ( folder . taskFiles , logPad , logLevel ) ; for ( const each of folder . taskFiles ) { if ( ( each instanceof TaskFile ) ) { this . sortTasks ( each . treeNodes , logPad , logLevel ) ; } } } private sortLastTasks ( items , lastTasks , logPad , logLevel ) { log . methodStart ( "sort last tasks" , logLevel , logPad ) ; items ?. sort ( ( a , b ) => { if ( a . id && b . id ) { const aIdx = lastTasks . indexOf ( a . id . replace ( constants . LAST_TASKS_LABEL + ":" , "" ) ) ; const bIdx = lastTasks . indexOf ( b . id . replace ( constants . LAST_TASKS_LABEL + ":" , "" ) ) ; return ( aIdx < bIdx ? 1 : ( bIdx < aIdx ? - 1 : 0 ) ) ; } return 0 ; } ) ; log . methodDone ( "sort last tasks" , logLevel , logPad ) ; } private sortTasks ( items , logPad = "" , logLevel = 1 ) { log . methodStart ( "sort tasks" , logLevel , logPad ) ; items ?. sort ( ( a , b ) => { if ( a . label && b . label ) { if ( ( a instanceof TaskFile && b instanceof TaskFile || a instanceof TaskItem && b instanceof TaskItem ) ) { return a . label ?. toString ( ) ?. localeCompare ( b . label ?. toString ( ) ) ; } else if ( a instanceof TaskFile && b instanceof TaskItem ) { return - 1 ; } return 1 ; } return 0 ; } ) ; log . methodDone ( "sort tasks" , logLevel , logPad ) ; } private stop ( taskItem ) { log . methodStart ( "stop" , 1 , "" , true ) ; if ( ! taskItem || this . busy ) { window . showInformationMessage ( "Busy, please wait..." ) ; return ; } const task = taskItem . task , exec = taskItem . execution || tasks . taskExecutions . find ( e => e . task . name === task ?. name && e . task . source === task . source && e . task . scope === task . scope && e . task . definition . path === task . definition . path ) ; if ( exec ) { if ( configuration . get < boolean > ( "keepTermOnStop" ) === true ) { const terminal = this . getTerminal ( taskItem , "   " ) ; log . write ( "   keep terminal open" , 1 ) ; if ( terminal ) { if ( taskItem . paused ) { log . value ( "   send to terminal" , "Y" , 1 ) ; terminal . sendText ( "Y" ) ; } else { log . value ( "   send to terminal" , "\\u0003" , 1 ) ; terminal . sendText ( "\u0003" ) ; setTimeout ( ( ) => { log . value ( "   send to terminal" , "Y" , 1 ) ; terminal . sendText ( "Y" , true ) ; } , 500 ) ; } taskItem . paused = false ; } else { window . showInformationMessage ( "Terminal not found" ) ; } } else { log . write ( "   kill terminal" , 1 ) ; exec . terminate ( ) ; } } else { window . showInformationMessage ( "Executing task not found" ) ; } log . methodDone ( "stop" , 1 ) ; } private taskIdStartEvents : Map < string , NodeJS . Timeout > = new Map ( ) ; private taskIdStopEvents : Map < string , NodeJS . Timeout > = new Map ( ) ; private async taskStartEvent ( e ) { const task = e . execution . task , taskId = task . definition . taskItemId ; let taskTimerId ; if ( taskTimerId = this . taskIdStartEvents . get ( taskId ) ) { clearTimeout ( taskTimerId ) ; this . taskIdStartEvents . delete ( taskId ) ; } taskTimerId = setTimeout ( async ( ) => { log . methodStart ( "task started event" , 1 ) ; this . showStatusMessage ( task ) ; const taskItem = await this . getTaskItems ( taskId ) as TaskItem ; this . fireTaskChangeEvents ( taskItem , "   " , 1 ) ; log . methodDone ( "task started event" , 1 ) ; } , 50 ) ; this . taskIdStartEvents . set ( taskId , taskTimerId ) ; } private async taskFinishedEvent ( e ) { const task = e . execution . task ; const taskId = task . definition . taskItemId ; let taskTimerId ; if ( taskTimerId = this . taskIdStopEvents . get ( taskId ) ) { clearTimeout ( taskTimerId ) ; this . taskIdStopEvents . delete ( taskId ) ; } taskTimerId = setTimeout ( async ( ) => { log . methodStart ( "task finished event" , 1 ) ; this . showStatusMessage ( task ) ; const taskItem = await this . getTaskItems ( taskId , "   " , false , 2 ) as TaskItem ; this . fireTaskChangeEvents ( taskItem , "   " , 1 ) ; log . methodDone ( "task finished event" , 1 ) ; } , 50 ) ; this . taskIdStopEvents . set ( taskId , taskTimerId ) ; } }