'use strict' ; import * as vscode from 'vscode' ; import * as path from 'path' ; import * as glob from 'glob' ; import * as autoprefixer from 'autoprefixer' ; import * as postcss from 'postcss' ; import { FileHelper , IFileResolver } from './FileHelper' ; import { SassHelper } from './SassCompileHelper' ; import { OutputWindow } from './OuputWindow' ; import { Helper , IFormat } from './helper' ; import { StatusBarUi } from './StatubarUi' ; export class AppModel { isWatching : boolean ; constructor ( ) { this . isWatching = false ; StatusBarUi . init ( ) ; } static get basePath ( ) : string { return vscode . workspace . rootPath || path . basename ( vscode . window . activeTextEditor . document . fileName ) ; } param WatchingMode compileAllFiles ( WatchingMode = true ) { if ( this . isWatching ) { vscode . window . showInformationMessage ( 'already watching...' ) ; return ; } StatusBarUi . working ( ) ; let showOutputWindow = Helper . getConfigSettings < boolean > ( 'showOutputWindow' ) ; this . GenerateAllCssAndMap ( showOutputWindow ) . then ( ( ) => { if ( ! WatchingMode ) { this . isWatching = true ; } this . toggleStatusUI ( ) ; } ) ; } openOutputWindow ( ) { OutputWindow . Show ( null , null , true ) ; } async compileOnSave ( ) { if ( ! this . isWatching ) return ; let currentFile = vscode . window . activeTextEditor . document . fileName ; if ( ! this . isASassFile ( currentFile , true ) ) return ; OutputWindow . Show ( 'Change Detected...' , [ path . basename ( currentFile ) ] ) ; if ( ! this . isASassFile ( currentFile ) ) { this . GenerateAllCssAndMap ( false ) . then ( ( ) => { OutputWindow . Show ( 'Watching...' , null ) ; } ) ; } else { let formats = Helper . getConfigSettings < IFormat [ ] > ( 'formats' ) ; let sassPath = currentFile ; formats . forEach ( format => { let options = this . getCssStyle ( format . format ) ; let cssMapPath = this . generateCssAndMapUri ( sassPath , format . savePath , format . extensionName ) ; this . GenerateCssAndMap ( sassPath , cssMapPath . css , cssMapPath . map , options ) . then ( ( ) => { OutputWindow . Show ( 'Watching...' , null ) ; } ) ; } ) ; } } StopWaching ( ) { if ( this . isWatching ) { this . toggleStatusUI ( ) ; } else { vscode . window . showInformationMessage ( 'not watching...' ) ; } } private toggleStatusUI ( ) { this . isWatching = ! this . isWatching ; let showOutputWindow = Helper . getConfigSettings < boolean > ( 'showOutputWindow' ) ; if ( ! this . isWatching ) { StatusBarUi . notWatching ( ) ; OutputWindow . Show ( 'Not Watching...' , null , showOutputWindow ) ; } else { StatusBarUi . watching ( ) ; OutputWindow . Show ( 'Watching...' , null , showOutputWindow ) ; } } async isSassFileIncluded ( sassPath , queryPatten = '**/[^_]*.s[a|c]ss' ) { let files = await this . getSassFiles ( queryPatten ) ; return files . find ( e => e === sassPath ) ? true : false ; } isASassFile ( pathUrl , partialSass = false ) { const filename = path . basename ( pathUrl ) ; return ( partialSass || ! filename . startsWith ( '_' ) ) && ( filename . endsWith ( 'sass' ) || filename . endsWith ( 'scss' ) ) ; } getSassFiles ( queryPatten = '**/[^_]*.s[a|c]ss' ) { let excludedList = Helper . getConfigSettings < string [ ] > ( 'excludeList' ) ; let includeItems = Helper . getConfigSettings < string [ ] | null > ( 'includeItems' ) ; let options = { ignore : excludedList , mark : true , cwd : AppModel . basePath } ; if ( includeItems && includeItems . length ) { if ( includeItems . length === 1 ) { queryPatten = includeItems [ 0 ] ; } else { queryPatten = `{${ includeItems . join ( ',' ) }}` ; } } return new Promise ( resolve => { glob ( queryPatten , options , ( err , files ) => { if ( err ) { OutputWindow . Show ( 'Error To Seach Files' , err , true ) ; resolve ( [ ] ) ; return ; } const filePaths = files . filter ( file => this . isASassFile ( file ) ) . map ( file => path . join ( AppModel . basePath , file ) ) ; return resolve ( filePaths || [ ] ) ; } ) ; } ) ; } param callback private findAllSaasFilesAsync ( callback ) { this . getSassFiles ( ) . then ( files => callback ( files ) ) ; } param SassPath param targetCssUri param mapFileUri param options private GenerateCssAndMap ( SassPath , targetCssUri , mapFileUri , options ) { let generateMap = Helper . getConfigSettings < boolean > ( 'generateMap' ) ; let autoprefixerTarget = Helper . getConfigSettings < Array < string > > ( 'autoprefix' ) ; let showOutputWindow = Helper . getConfigSettings < boolean > ( 'showOutputWindow' ) ; return new Promise ( resolve => { SassHelper . instance . compileOne ( SassPath , options ) . then ( async ( result ) => { if ( result . status !== 0 ) { OutputWindow . Show ( 'Compilation Error' , [ result . formatted ] , showOutputWindow ) ; StatusBarUi . compilationError ( this . isWatching ) ; if ( ! showOutputWindow ) { vscode . window . setStatusBarMessage ( result . formatted . split ( '\n' ) [ 0 ] , 4500 ) ; } resolve ( true ) ; } else { let promises = [ ] ; let mapFileTag = `/*# sourceMappingURL=${ path . basename ( targetCssUri ) }.map */` ; if ( autoprefixerTarget ) { result . text = await this . autoprefix ( result . text , autoprefixerTarget ) ; } if ( ! generateMap ) { promises . push ( FileHelper . Instance . writeToOneFile ( targetCssUri , `${ result . text }` ) ) ; } else { promises . push ( FileHelper . Instance . writeToOneFile ( targetCssUri , `${ result . text }${ mapFileTag }` ) ) ; let map = this . GenerateMapObject ( result . map , targetCssUri ) ; promises . push ( FileHelper . Instance . writeToOneFile ( mapFileUri , JSON . stringify ( map , null , 4 ) ) ) ; } Promise . all ( promises ) . then ( fileResolvers => { OutputWindow . Show ( 'Generated :' , null , false , false ) ; StatusBarUi . compilationSuccess ( this . isWatching ) ; fileResolvers . forEach ( fileResolver => { if ( fileResolver . Exception ) { OutputWindow . Show ( 'Error:' , [ fileResolver . Exception . errno . toString ( ) , fileResolver . Exception . path , fileResolver . Exception . message ] , true ) ; console . error ( 'error :' , fileResolver ) ; } else { OutputWindow . Show ( null , [ fileResolver . FileUri ] , false , false ) ; } } ) ; OutputWindow . Show ( null , null , false , true ) ; resolve ( true ) ; } ) ; } } ) ; } ) ; } param popUpOutputWindow private GenerateAllCssAndMap ( popUpOutputWindow ) { let formats = Helper . getConfigSettings < IFormat [ ] > ( 'formats' ) ; return new Promise ( ( resolve ) => { this . findAllSaasFilesAsync ( ( sassPaths ) => { OutputWindow . Show ( 'Compiling Sass/Scss Files: ' , sassPaths , popUpOutputWindow ) ; let promises = [ ] ; sassPaths . forEach ( ( sassPath ) => { formats . forEach ( format => { let options = this . getCssStyle ( format . format ) ; let cssMapUri = this . generateCssAndMapUri ( sassPath , format . savePath , format . extensionName ) ; promises . push ( this . GenerateCssAndMap ( sassPath , cssMapUri . css , cssMapUri . map , options ) ) ; } ) ; } ) ; Promise . all ( promises ) . then ( ( e ) => resolve ( e ) ) ; } ) ; } ) ; } param mapObject param targetCssUri private GenerateMapObject ( mapObject , targetCssUri ) { let map = { 'version' : 3 , 'mappings' : '' , 'sources' : [ ] , 'names' : [ ] , 'file' : '' } ; map . mappings = mapObject . mappings ; map . file = path . basename ( targetCssUri ) ; mapObject . sources . forEach ( ( source ) => { if ( source . startsWith ( '../sass/' ) ) { source = source . substring ( '../sass/' . length ) ; } else if ( source . startsWith ( '../' ) ) { source = source . substring ( '../' . length ) ; } if ( process . platform !== 'win32' ) { source = '/' + source ; } let testpath = path . relative ( path . dirname ( targetCssUri ) , source ) ; testpath = testpath . replace ( /\\/gi , '/' ) ; map . sources . push ( testpath ) ; } ) ; return map ; } private generateCssAndMapUri ( filePath , savePath , _extensionName ? ) { let extensionName = _extensionName || '.css' ; if ( savePath ) { try { let workspaceRoot = vscode . workspace . rootPath ; let generatedUri = null ; if ( savePath . startsWith ( '~' ) ) generatedUri = path . join ( path . dirname ( filePath ) , savePath . substring ( 1 ) ) ; else generatedUri = path . join ( workspaceRoot , savePath ) ; FileHelper . Instance . MakeDirIfNotAvailable ( generatedUri ) ; filePath = path . join ( generatedUri , path . basename ( filePath ) ) ; } catch ( err ) { console . log ( err ) ; OutputWindow . Show ( 'Error:' , [ err . errno . toString ( ) , err . path , err . message ] , true ) ; throw Error ( 'Something Went Wrong.' ) ; } } let cssUri = filePath . substring ( 0 , filePath . lastIndexOf ( '.' ) ) + extensionName ; return { css : cssUri , map : cssUri + '.map' } ; } private getCssStyle ( format ? ) { let outputStyleFormat = format || 'expanded' ; return SassHelper . targetCssFormat ( outputStyleFormat ) ; } param css param target private async autoprefix ( css , browsers ) { let showOutputWindow = Helper . getConfigSettings < boolean > ( 'showOutputWindow' ) ; const prefixer = postcss ( [ autoprefixer ( { browsers , grid : true } ) ] ) ; return await prefixer . process ( css ) . then ( res => { res . warnings ( ) . forEach ( warn => { OutputWindow . Show ( 'Autoprefix Error' , [ warn . text ] , showOutputWindow ) ; } ) ; return res . css ; } ) ; } dispose ( ) { StatusBarUi . dispose ( ) ; OutputWindow . dispose ( ) ; } }