import { assert } from "../util" ; import { Minimatch } from "minimatch" ; import { File } from "./File" ; import { EventDispatcher } from "./EventDispatcher" ; import { FileType } from "./types" ; export class Directory extends File { name : string ; children : File [ ] = [ ] ; isOpen : boolean = true ; readonly onDidChangeChildren = new EventDispatcher ( "Directory Changed " ) ; constructor ( name ) { super ( name , FileType . Directory ) ; } notifyDidChangeChildren ( file ) { let directory = this ; while ( directory ) { directory . onDidChangeChildren . dispatch ( ) ; directory = directory . parent ; } } forEachFile ( fn , excludeTransientFiles = false , recurse = false ) { if ( recurse ) { this . children . forEach ( ( file ) => { if ( excludeTransientFiles && file . isTransient ) { return false ; } if ( file instanceof Directory ) { file . forEachFile ( fn , excludeTransientFiles , recurse ) ; } else { fn ( file ) ; } } ) ; } else { this . children . forEach ( fn ) ; } } mapEachFile < T > ( fn , excludeTransientFiles = false ) { return this . children . filter ( ( file ) => { if ( excludeTransientFiles && file . isTransient ) { return false ; } return true ; } ) . map ( fn ) ; } handleNameCollision ( name , isDirectory ? ) { for ( let i = 1 ; i <= this . children . length ; i ++ ) { const nameParts = name . split ( "." ) ; const extension = nameParts . pop ( ) ; let newName ; if ( isDirectory ) { newName = `${ name }${ i + 1 }` ; } else { newName = `${ nameParts . join ( "." ) }.${ i + 1 }.${ extension }` ; } if ( ! this . getImmediateChild ( newName ) ) { return newName ; } } throw new Error ( "Name collision not handled" ) ; } addFile ( file ) { assert ( file . parent === null ) ; if ( this . getImmediateChild ( file . name ) ) { file . name = this . handleNameCollision ( file . name , file instanceof Directory ) ; } this . children . push ( file ) ; file . parent = this ; this . notifyDidChangeChildren ( file ) ; } removeFile ( file ) { assert ( file . parent === this ) ; const i = this . children . indexOf ( file ) ; assert ( i >= 0 ) ; this . children . splice ( i , 1 ) ; file . parent = null ; this . notifyDidChangeChildren ( file ) ; } newDirectory ( path ) { if ( typeof path === "string" ) { path = path . split ( "/" ) ; } let directory = this ; while ( path . length ) { const name = path . shift ( ) ; let file = directory . getImmediateChild ( name ) ; if ( file ) { directory = file as Directory ; } else { file = new Directory ( name ) ; directory . addFile ( file ) ; directory = file as Directory ; } } assert ( directory instanceof Directory ) ; return directory ; } newFile ( path , type , isTransient = false , handleNameCollision = false ) { if ( typeof path === "string" ) { path = path . split ( "/" ) ; } let directory = this ; if ( path . length > 1 ) { directory = this . newDirectory ( path . slice ( 0 , path . length - 1 ) ) ; } const name = path [ path . length - 1 ] ; let file = directory . getFile ( name ) ; if ( file && ! handleNameCollision ) { assert ( file . type === type ) ; } else { file = new File ( path [ path . length - 1 ] , type ) ; directory . addFile ( file ) ; } file . isTransient = isTransient ; return file ; } getImmediateChild ( name ) { return this . children . find ( ( file ) => { return file . name === name ; } ) ; } getFile ( path ) { if ( typeof path === "string" ) { path = path . split ( "/" ) ; } const file = this . getImmediateChild ( path [ 0 ] ) ; if ( path . length > 1 ) { if ( file && file . type === FileType . Directory ) { return ( file as Directory ) . getFile ( path . slice ( 1 ) ) ; } else { return null ; } } return file ; } list ( ) { const list = [ ] ; function recurse ( prefix , x ) { if ( prefix ) { prefix += "/" ; } x . forEachFile ( file => { const path = prefix + file . name ; if ( file instanceof Directory ) { recurse ( path , file ) ; } else { list . push ( path ) ; } } ) ; } recurse ( "" , this ) ; return list ; } glob ( pattern ) { const mm = new Minimatch ( pattern ) ; return this . list ( ) . filter ( path => mm . match ( path ) ) ; } globFiles ( pattern ) { return this . glob ( pattern ) . map ( path => this . getFile ( path ) ) ; } hasChildren ( ) { return this . children . length > 0 ; } }