'use strict' ; import * as vscode from 'vscode' ; import { ITypescriptServiceClient } from '../typescriptService' ; import { loadMessageBundle } from 'vscode-nls' ; import { dirname , join } from 'path' ; import { exists } from 'fs' ; const localize = loadMessageBundle ( ) ; const selector = [ 'javascript' , 'javascriptreact' ] ; interface Option extends vscode . MessageItem { execute ( ) : void ; } interface Hint { message : string ; options : Option [ ] ; } const fileLimit = 500 ; export function create ( client , isOpen , memento ) { const toDispose = [ ] ; const projectHinted = Object . create ( null ) ; const projectHintIgnoreList = memento . get < string [ ] > ( 'projectHintIgnoreList' , [ ] ) ; for ( let path of projectHintIgnoreList ) { if ( path === null ) { path = undefined ; } projectHinted [ path ] = true ; } let currentHint ; let item = vscode . window . createStatusBarItem ( vscode . StatusBarAlignment . Right , Number . MIN_VALUE ) ; item . command = 'js.projectStatus.command' ; toDispose . push ( vscode . commands . registerCommand ( 'js.projectStatus.command' , ( ) => { let { message , options } = currentHint ; return vscode . window . showInformationMessage ( message , ... options ) . then ( selection => { if ( selection ) { return selection . execute ( ) ; } } ) ; } ) ) ; toDispose . push ( vscode . workspace . onDidChangeTextDocument ( e => { delete projectHinted [ e . document . fileName ] ; } ) ) ; function onEditor ( editor ) { if ( ! editor || ! vscode . languages . match ( selector , editor . document ) || ! client . asAbsolutePath ( editor . document . uri ) ) { item . hide ( ) ; return ; } const file = client . asAbsolutePath ( editor . document . uri ) ; isOpen ( file ) . then ( value => { if ( ! value ) { return ; } return client . execute ( 'projectInfo' , { file , needFileNameList : true } ) . then ( res => { let { configFileName , fileNames } = res . body ; if ( projectHinted [ configFileName ] === true ) { return ; } if ( ! configFileName && vscode . workspace . rootPath ) { exists ( join ( vscode . workspace . rootPath , 'jsconfig.json' ) , exists => { if ( exists ) { return ; } currentHint = { message : localize ( 'hintCreate' , "Create a jsconfig.json to enable richer IntelliSense and code navigation across the entire workspace." ) , options : [ { title : localize ( 'ignore.cmdCreate' , 'Ignore' ) , execute : ( ) => { client . logTelemetry ( 'js.hintProjectCreation.ignored' ) ; projectHinted [ configFileName ] = true ; projectHintIgnoreList . push ( configFileName ) ; memento . update ( 'projectHintIgnoreList' , projectHintIgnoreList ) ; item . hide ( ) ; } } , { title : localize ( 'cmdCreate' , "Create jsconfig.json" ) , execute : ( ) => { client . logTelemetry ( 'js.hintProjectCreation.accepted' ) ; projectHinted [ configFileName ] = true ; item . hide ( ) ; return vscode . workspace . openTextDocument ( vscode . Uri . parse ( 'untitled:' + encodeURIComponent ( join ( vscode . workspace . rootPath , 'jsconfig.json' ) ) ) ) . then ( doc => vscode . window . showTextDocument ( doc , vscode . ViewColumn . Three ) ) . then ( editor => editor . edit ( builder => builder . insert ( new vscode . Position ( 0 , 0 ) , defaultConfig ) ) ) ; } } ] } ; item . text = '$(light-bulb)' ; item . tooltip = localize ( 'hintCreate.tooltip' , "Create a jsconfig.json to enable richer IntelliSense and code navigation across the entire workspace." ) ; item . color = '#A5DF3B' ; item . show ( ) ; client . logTelemetry ( 'js.hintProjectCreation' ) ; } ) ; } else if ( fileNames . length > fileLimit ) { let largeRoots = computeLargeRoots ( configFileName , fileNames ) . map ( f => `'/${ f }/'` ) . join ( ', ' ) ; currentHint = { message : largeRoots . length > 0 ? localize ( 'hintExclude' , "For better performance exclude folders with many files, like: {0}" , largeRoots ) : localize ( 'hintExclude.generic' , "For better performance exclude folders with many files." ) , options : [ { title : localize ( 'open' , "Configure Excludes" ) , execute : ( ) => { client . logTelemetry ( 'js.hintProjectExcludes.accepted' ) ; projectHinted [ configFileName ] = true ; item . hide ( ) ; return vscode . workspace . openTextDocument ( configFileName ) . then ( vscode . window . showTextDocument ) ; } } ] } ; item . tooltip = currentHint . message ; item . text = localize ( 'large.label' , "Configure Excludes" ) ; item . tooltip = localize ( 'hintExclude.tooltip' , "For better performance exclude folders with many files." ) ; item . color = '#A5DF3B' ; item . show ( ) ; client . logTelemetry ( 'js.hintProjectExcludes' ) ; } else { item . hide ( ) ; } } ) ; } ) . catch ( err => { console . log ( err ) ; } ) ; } toDispose . push ( vscode . window . onDidChangeActiveTextEditor ( onEditor ) ) ; onEditor ( vscode . window . activeTextEditor ) ; return vscode . Disposable . from ( ... toDispose ) ; } function computeLargeRoots ( configFileName , fileNames ) { let roots = Object . create ( null ) ; let dir = dirname ( configFileName ) ; for ( let fileName of fileNames ) { if ( fileName . indexOf ( dir ) === 0 ) { let first = fileName . substring ( dir . length + 1 ) ; first = first . substring ( 0 , first . indexOf ( '/' ) ) ; if ( first ) { roots [ first ] = ( roots [ first ] || 0 ) + 1 ; } } } let data = [ ] ; for ( let key in roots ) { data . push ( { root : key , count : roots [ key ] } ) ; } data . sort ( ( a , b ) => b . count - a . count ) . filter ( s => s . root === 'src' || s . root === 'test' || s . root === 'tests' ) ; let result = [ ] ; let sum = 0 ; for ( let e of data ) { sum += e . count ; result . push ( e . root ) ; if ( fileNames . length - sum < fileLimit ) { break ; } } return result ; } const defaultConfig = `{
	${ localize ( 'jsconfig.heading' , '// See https://go.microsoft.com/fwlink/?LinkId=759670\n\t// for the documentation about the jsconfig.json format' ) }
	"compilerOptions": {
		"target": "es6",
		"module": "commonjs",
		"allowSyntheticDefaultImports": true
	},
	"exclude": [
		"node_modules",
		"bower_components",
		"jspm_packages",
		"tmp",
		"temp"
	]
}
` ;