'use strict' ; import * as path from 'path' ; import * as fs from 'fs' ; import * as vscode from 'vscode' ; import * as util from '../common' ; import * as config from './configurations' ; import * as telemetry from '../telemetry' ; const elementId = { configName : "configName" , configNameInvalid : "configNameInvalid" , configSelection : "configSelection" , addConfigBtn : "addConfigBtn" , addConfigOk : "addConfigOk" , addConfigCancel : "addConfigCancel" , addConfigName : "addConfigName" , compilerPath : "compilerPath" , compilerPathInvalid : "compilerPathInvalid" , knownCompilers : "knownCompilers" , compilerArgs : "compilerArgs" , intelliSenseMode : "intelliSenseMode" , intelliSenseModeInvalid : "intelliSenseModeInvalid" , includePath : "includePath" , includePathInvalid : "includePathInvalid" , defines : "defines" , cStandard : "cStandard" , cppStandard : "cppStandard" , windowsSdkVersion : "windowsSdkVersion" , macFrameworkPath : "macFrameworkPath" , compileCommands : "compileCommands" , mergeConfigurations : "mergeConfigurations" , configurationProvider : "configurationProvider" , forcedInclude : "forcedInclude" , browsePath : "browsePath" , limitSymbolsToIncludedHeaders : "limitSymbolsToIncludedHeaders" , databaseFilename : "databaseFilename" , showAdvancedBtn : "showAdvancedBtn" } ; export class SettingsPanel { private telemetry : { [ key ] : number ; } = { } ; private disposable ? : vscode . Disposable ; private settingsPanelActivated = new vscode . EventEmitter < void > ( ) ; private configValuesChanged = new vscode . EventEmitter < void > ( ) ; private configSelectionChanged = new vscode . EventEmitter < void > ( ) ; private addConfigRequested = new vscode . EventEmitter < string > ( ) ; private configValues : config . Configuration = { name : "" } ; private isIntelliSenseModeDefined : boolean = false ; private configIndexSelected : number = 0 ; private compilerPaths : string [ ] = [ ] ; private panel ? : vscode . WebviewPanel ; private disposablesPanel ? : vscode . Disposable ; private static readonly viewType : string = 'settingsPanel' ; private static readonly title : string = 'C/C++ Configurations' ; public initialized : boolean = false ; constructor ( ) { this . disposable = vscode . Disposable . from ( this . settingsPanelActivated , this . configValuesChanged , this . configSelectionChanged , this . addConfigRequested ) ; } public createOrShow ( configSelection , activeConfiguration , errors , viewColumn ? ) { const column = viewColumn ?? vscode . window . activeTextEditor ?. viewColumn ; if ( this . panel ) { this . panel . reveal ( column , false ) ; return ; } this . initialized = false ; this . panel = vscode . window . createWebviewPanel ( SettingsPanel . viewType , SettingsPanel . title , column || vscode . ViewColumn . One , { enableCommandUris : true , enableScripts : true , localResourceRoots : [ vscode . Uri . file ( util . extensionPath ) , vscode . Uri . file ( path . join ( util . extensionPath , 'ui' ) ) , vscode . Uri . file ( path . join ( util . extensionPath , 'out' , 'ui' ) ) ] } ) ; this . panel . iconPath = vscode . Uri . file ( util . getExtensionFilePath ( "LanguageCCPP_color_128x.png" ) ) ; this . disposablesPanel = vscode . Disposable . from ( this . panel , this . panel . onDidDispose ( this . onPanelDisposed , this ) , this . panel . onDidChangeViewState ( this . onViewStateChanged , this ) , this . panel . webview . onDidReceiveMessage ( this . onMessageReceived , this ) , vscode . window . onDidChangeWindowState ( this . onWindowStateChanged , this ) ) ; this . panel . webview . html = this . getHtml ( ) ; this . updateWebview ( configSelection , activeConfiguration , errors ) ; } public get SettingsPanelActivated ( ) : vscode . Event < void > { return this . settingsPanelActivated . event ; } public get ConfigValuesChanged ( ) : vscode . Event < void > { return this . configValuesChanged . event ; } public get ConfigSelectionChanged ( ) : vscode . Event < void > { return this . configSelectionChanged . event ; } public get AddConfigRequested ( ) : vscode . Event < string > { return this . addConfigRequested . event ; } public get selectedConfigIndex ( ) : number { return this . configIndexSelected ; } public set selectedConfigIndex ( index ) { this . configIndexSelected = index ; } public getLastValuesFromConfigUI ( ) { return this . configValues ; } public updateConfigUI ( configSelection , configuration , errors ) { if ( this . panel ) { this . updateWebview ( configSelection , configuration , errors ) ; } } public setKnownCompilers ( knownCompilers ? , pathSeparator ? ) { if ( knownCompilers && knownCompilers . length ) { for ( const compiler of knownCompilers ) { let path = compiler . path ; if ( pathSeparator === "Forward Slash" ) { path = path . replace ( /\\/g , '/' ) ; } else { path = path . replace ( /\//g , '\\' ) ; } if ( this . compilerPaths . indexOf ( path ) === - 1 ) { this . compilerPaths . push ( path ) ; } } } } public updateErrors ( errors ) { if ( this . panel ) { this . panel . webview . postMessage ( { command : 'updateErrors' , errors : errors } ) ; } } public dispose ( ) { if ( Object . keys ( this . telemetry ) . length ) { telemetry . logLanguageServerEvent ( "ConfigUI" , undefined , this . telemetry ) ; } if ( this . panel ) { this . panel . dispose ( ) ; } if ( this . disposable ) { this . disposable . dispose ( ) ; } if ( this . disposablesPanel ) { this . disposablesPanel . dispose ( ) ; } } private onPanelDisposed ( ) { if ( this . disposablesPanel ) { this . disposablesPanel . dispose ( ) ; this . panel = undefined ; } } private updateWebview ( configSelection , configuration , errors ) { this . configValues = { ... configuration } ; this . isIntelliSenseModeDefined = ( this . configValues . intelliSenseMode !== undefined ) ; if ( this . panel && this . initialized ) { this . panel . webview . postMessage ( { command : 'setKnownCompilers' , compilers : this . compilerPaths } ) ; this . panel . webview . postMessage ( { command : 'updateConfigSelection' , selections : configSelection , selectedIndex : this . configIndexSelected } ) ; this . panel . webview . postMessage ( { command : 'updateConfig' , config : this . configValues } ) ; if ( errors !== null ) { this . panel . webview . postMessage ( { command : 'updateErrors' , errors : errors } ) ; } } } private onViewStateChanged ( e ) { if ( e . webviewPanel . active ) { this . settingsPanelActivated . fire ( ) ; } } private onWindowStateChanged ( e ) { if ( e . focused ) { this . settingsPanelActivated . fire ( ) ; } } private onMessageReceived ( message ) { if ( message === null || message === undefined ) { return ; } switch ( message . command ) { case 'change' : this . updateConfig ( message ) ; break ; case 'configSelect' : this . configSelect ( message . index ) ; break ; case 'addConfig' : this . addConfig ( message . name ) ; break ; case 'knownCompilerSelect' : this . knownCompilerSelect ( ) ; break ; case "initialized" : this . initialized = true ; this . settingsPanelActivated . fire ( ) ; break ; } } private addConfig ( name ) { this . addConfigRequested . fire ( name ) ; this . logTelemetryForElement ( elementId . addConfigName ) ; } private configSelect ( index ) { this . configIndexSelected = index ; this . configSelectionChanged . fire ( ) ; this . logTelemetryForElement ( elementId . configSelection ) ; } private knownCompilerSelect ( ) { this . logTelemetryForElement ( elementId . knownCompilers ) ; if ( this . telemetry [ elementId . compilerPath ] ) { this . telemetry [ elementId . compilerPath ] -- ; } } private updateConfig ( message ) { const splitEntries = ( input ) => input . split ( "\n" ) . filter ( ( e ) => e ) ; switch ( message . key ) { case elementId . configName : this . configValues . name = message . value ; break ; case elementId . compilerPath : this . configValues . compilerPath = message . value ; break ; case elementId . compilerArgs : this . configValues . compilerArgs = splitEntries ( message . value ) ; break ; case elementId . includePath : this . configValues . includePath = splitEntries ( message . value ) ; break ; case elementId . defines : this . configValues . defines = splitEntries ( message . value ) ; break ; case elementId . intelliSenseMode : if ( message . value !== "${default}" || this . isIntelliSenseModeDefined ) { this . configValues . intelliSenseMode = message . value ; } else { this . configValues . intelliSenseMode = undefined ; } break ; case elementId . cStandard : this . configValues . cStandard = message . value ; break ; case elementId . cppStandard : this . configValues . cppStandard = message . value ; break ; case elementId . windowsSdkVersion : this . configValues . windowsSdkVersion = message . value ; break ; case elementId . macFrameworkPath : this . configValues . macFrameworkPath = splitEntries ( message . value ) ; break ; case elementId . compileCommands : this . configValues . compileCommands = message . value ; break ; case elementId . mergeConfigurations : this . configValues . mergeConfigurations = message . value ; break ; case elementId . configurationProvider : this . configValues . configurationProvider = message . value ; break ; case elementId . forcedInclude : this . configValues . forcedInclude = splitEntries ( message . value ) ; break ; case elementId . browsePath : if ( ! this . configValues . browse ) { this . configValues . browse = { } ; } this . configValues . browse . path = splitEntries ( message . value ) ; break ; case elementId . limitSymbolsToIncludedHeaders : if ( ! this . configValues . browse ) { this . configValues . browse = { } ; } this . configValues . browse . limitSymbolsToIncludedHeaders = message . value ; break ; case elementId . databaseFilename : if ( ! this . configValues . browse ) { this . configValues . browse = { } ; } this . configValues . browse . databaseFilename = message . value ; break ; } this . configValuesChanged . fire ( ) ; this . logTelemetryForElement ( message . key ) ; } private logTelemetryForElement ( elementId ) { if ( this . telemetry [ elementId ] === undefined ) { this . telemetry [ elementId ] = 0 ; } this . telemetry [ elementId ] ++ ; } private getHtml ( ) { let content ; content = fs . readFileSync ( util . getLocalizedHtmlPath ( "ui/settings.html" ) ) . toString ( ) ; if ( this . panel && this . panel . webview ) { const cppImageUri = this . panel . webview . asWebviewUri ( vscode . Uri . file ( path . join ( util . extensionPath , 'LanguageCCPP_color_128x.png' ) ) ) ; content = content . replace ( /{{cpp_image_uri}}/g , cppImageUri . toString ( ) ) ; const settingsJsUri = this . panel . webview . asWebviewUri ( vscode . Uri . file ( path . join ( util . extensionPath , 'out/ui/settings.js' ) ) ) ; content = content . replace ( /{{settings_js_uri}}/g , settingsJsUri . toString ( ) ) ; } content = content . replace ( /{{nonce}}/g , this . getNonce ( ) ) ; return content ; } private getNonce ( ) { let nonce = "" ; const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789' ; for ( let i = 0 ; i < 32 ; i ++ ) { nonce += possible . charAt ( Math . floor ( Math . random ( ) * possible . length ) ) ; } return nonce ; } }