@@ -64,6 +64,12 @@ export interface Configuration {
6464browse ?:Browse ;
6565}
6666
67+ export interface ConfigurationErrors {
68+ compilerPath ?:string ;
69+ includePath ?:string ;
70+ intelliSenseMode ?:string ;
71+ }
72+
6773export interface Browse {
6874path ?:string [ ] ;
6975limitSymbolsToIncludedHeaders ?:boolean | string ;
@@ -227,7 +233,9 @@ export class CppProperties {
227233// Clear out any modifications we may have made internally by parsing the json file
228234if ( this . parsePropertiesFile ( false ) ) {
229235// Update the UI with new selected configuration
230- this . settingsPanel . updateConfigUI ( this . configurationJson . configurations [ this . currentConfigurationIndex . Value ] ) ;
236+ this . settingsPanel . updateConfigUI (
237+ this . configurationJson . configurations [ this . currentConfigurationIndex . Value ] ,
238+ this . getErrorsForConfigUI ( ) ) ;
231239} else {
232240// Parse failed, open json file
233241vscode . workspace . openTextDocument ( this . propertiesFile ) ;
@@ -392,8 +400,13 @@ export class CppProperties {
392400private isCompilerIntelliSenseModeCompatible ( ) :boolean {
393401// Check if intelliSenseMode and compilerPath are compatible
394402// cl.exe and msvc mode should be used together
395- // Ignore if compiler path is not set
396- if ( this . CurrentConfiguration . compilerPath === undefined ) {
403+ // Ignore if compiler path is not set or intelliSenseMode is not set
404+ if ( this . CurrentConfiguration . compilerPath === undefined ||
405+ this . CurrentConfiguration . compilerPath === "" ||
406+ this . CurrentConfiguration . compilerPath === "${default}" ||
407+ this . CurrentConfiguration . intelliSenseMode === undefined ||
408+ this . CurrentConfiguration . intelliSenseMode === "" ||
409+ this . CurrentConfiguration . intelliSenseMode === "${default}" ) {
397410return true ;
398411}
399412let compilerPathAndArgs :util . CompilerPathAndArgs = util . extractCompilerPathAndArgs ( this . CurrentConfiguration . compilerPath ) ;
@@ -628,7 +641,9 @@ export class CppProperties {
628641this . settingsPanel . ConfigValuesChanged ( ( ) => this . saveConfigurationUI ( ) ) ;
629642this . disposables . push ( this . settingsPanel ) ;
630643}
631- this . settingsPanel . createOrShow ( this . configurationJson . configurations [ this . currentConfigurationIndex . Value ] ) ;
644+ this . settingsPanel . createOrShow (
645+ this . configurationJson . configurations [ this . currentConfigurationIndex . Value ] ,
646+ this . getErrorsForConfigUI ( ) ) ;
632647} else {
633648// Parse failed, open json file
634649vscode . workspace . openTextDocument ( this . propertiesFile ) . then ( ( document :vscode . TextDocument ) => {
@@ -646,7 +661,9 @@ export class CppProperties {
646661if ( this . parsePropertiesFile ( false ) ) {
647662// The settings UI became visible or active.
648663// Ensure settingsPanel has copy of latest current configuration
649- this . settingsPanel . updateConfigUI ( this . configurationJson . configurations [ this . currentConfigurationIndex . Value ] ) ;
664+ this . settingsPanel . updateConfigUI (
665+ this . configurationJson . configurations [ this . currentConfigurationIndex . Value ] ,
666+ this . getErrorsForConfigUI ( ) ) ;
650667} else {
651668// Parse failed, open json file
652669vscode . workspace . openTextDocument ( this . propertiesFile ) ;
@@ -660,6 +677,7 @@ export class CppProperties {
660677this . parsePropertiesFile ( false ) ; // Clear out any modifications we may have made internally.
661678let config :Configuration = this . settingsPanel . getLastValuesFromConfigUI ( ) ;
662679this . configurationJson . configurations [ this . currentConfigurationIndex . Value ] = config ;
680+ this . settingsPanel . updateErrors ( this . getErrorsForConfigUI ( ) ) ;
663681this . writeToJson ( ) ;
664682}
665683
@@ -822,6 +840,150 @@ export class CppProperties {
822840return success ;
823841}
824842
843+ private resolvePath ( path :string , isWindows :boolean ) :string {
844+ if ( ! path || path === "${default}" ) {
845+ return "" ;
846+ }
847+
848+ let result :string = "" ;
849+
850+ // first resolve variables
851+ result = util . resolveVariables ( path , this . ExtendedEnvironment ) ;
852+ if ( result . includes ( "${workspaceFolder}" ) ) {
853+ result = result . replace ( "${workspaceFolder}" , this . rootUri . fsPath ) ;
854+ }
855+ if ( result . includes ( "${workspaceRoot}" ) ) {
856+ result = result . replace ( "${workspaceRoot}" , this . rootUri . fsPath ) ;
857+ }
858+ if ( result . includes ( "${vcpkgRoot}" ) && util . getVcpkgRoot ( ) ) {
859+ result = result . replace ( "${vcpkgRoot}" , util . getVcpkgRoot ( ) ) ;
860+ }
861+ if ( result . includes ( "*" ) ) {
862+ result = result . replace ( / \* / g, "" ) ;
863+ }
864+
865+ // resolve WSL paths
866+ if ( isWindows && result . startsWith ( "/" ) ) {
867+ const mntStr :string = "/mnt/" ;
868+ if ( result . length > "/mnt/c/" . length && result . substr ( 0 , mntStr . length ) === mntStr ) {
869+ result = result . substr ( mntStr . length ) ;
870+ result = result . substr ( 0 , 1 ) + ":" + result . substr ( 1 ) ;
871+ } else if ( this . rootfs && this . rootfs . length > 0 ) {
872+ result = this . rootfs + result . substr ( 1 ) ;
873+ // TODO: Handle WSL symlinks.
874+ }
875+ }
876+
877+ return result ;
878+ }
879+
880+ private getErrorsForConfigUI ( ) :ConfigurationErrors {
881+ let errors :ConfigurationErrors = { } ;
882+ const isWindows :boolean = os . platform ( ) === 'win32' ;
883+
884+ // Validate compilerPath
885+ let resolvedCompilerPath :string = this . resolvePath ( this . CurrentConfiguration . compilerPath , isWindows ) ;
886+ let compilerPathAndArgs :util . CompilerPathAndArgs = util . extractCompilerPathAndArgs ( resolvedCompilerPath ) ;
887+ if ( resolvedCompilerPath &&
888+ // Don't error cl.exe paths because it could be for an older preview build.
889+ ! ( isWindows && compilerPathAndArgs . compilerPath . endsWith ( "cl.exe" ) ) ) {
890+ resolvedCompilerPath = resolvedCompilerPath . trim ( ) ;
891+
892+ // Error when the compiler's path has spaces without quotes but args are used.
893+ // Except, exclude cl.exe paths because it could be for an older preview build.
894+ let compilerPathNeedsQuotes :boolean =
895+ compilerPathAndArgs . additionalArgs &&
896+ ! resolvedCompilerPath . startsWith ( '"' ) &&
897+ compilerPathAndArgs . compilerPath . includes ( " " ) ;
898+
899+ let compilerPathErrors :string [ ] = [ ] ;
900+ if ( compilerPathNeedsQuotes ) {
901+ compilerPathErrors . push ( `Compiler path with spaces and arguments is missing double quotes " around the path.` ) ;
902+ }
903+
904+ // Get compiler path without arguments before checking if it exists
905+ resolvedCompilerPath = compilerPathAndArgs . compilerPath ;
906+
907+ let pathExists :boolean = true ;
908+ let existsWithExeAdded :( path :string ) => boolean = ( path :string ) => {
909+ return isWindows && ! path . startsWith ( "/" ) && fs . existsSync ( path + ".exe" ) ;
910+ } ;
911+ if ( ! fs . existsSync ( resolvedCompilerPath ) ) {
912+ if ( existsWithExeAdded ( resolvedCompilerPath ) ) {
913+ resolvedCompilerPath += ".exe" ;
914+ } else {
915+ // Check again for a relative path.
916+ const relativePath :string = this . rootUri . fsPath + path . sep + resolvedCompilerPath ;
917+ if ( ! fs . existsSync ( relativePath ) ) {
918+ if ( existsWithExeAdded ( resolvedCompilerPath ) ) {
919+ resolvedCompilerPath += ".exe" ;
920+ } else {
921+ pathExists = false ;
922+ }
923+ } else {
924+ resolvedCompilerPath = relativePath ;
925+ }
926+ }
927+ }
928+
929+ if ( ! pathExists ) {
930+ let message :string = `Cannot find:${ resolvedCompilerPath } ` ;
931+ compilerPathErrors . push ( message ) ;
932+ } else if ( ! util . checkFileExistsSync ( resolvedCompilerPath ) ) {
933+ let message :string = `Path is not a file:${ resolvedCompilerPath } ` ;
934+ compilerPathErrors . push ( message ) ;
935+ }
936+
937+ if ( compilerPathErrors . length > 0 ) {
938+ errors . compilerPath = compilerPathErrors . join ( '\n' ) ;
939+ }
940+ }
941+
942+ // Validate includePath
943+ let includePathErrors :string [ ] = [ ] ;
944+ for ( let includePath of this . CurrentConfiguration . includePath ) {
945+ let pathExists :boolean = true ;
946+ let resolvedIncludePath :string = this . resolvePath ( includePath , isWindows ) ;
947+ if ( ! resolvedIncludePath ) {
948+ continue ;
949+ }
950+
951+ // Check if resolved path exists
952+ if ( ! fs . existsSync ( resolvedIncludePath ) ) {
953+ // Check for relative path if resolved path does not exists
954+ const relativePath :string = this . rootUri . fsPath + path . sep + resolvedIncludePath ;
955+ if ( ! fs . existsSync ( relativePath ) ) {
956+ pathExists = false ;
957+ } else {
958+ resolvedIncludePath = relativePath ;
959+ }
960+ }
961+
962+ if ( ! pathExists ) {
963+ let message :string = `Cannot find:${ resolvedIncludePath } ` ;
964+ includePathErrors . push ( message ) ;
965+ continue ;
966+ }
967+
968+ // Check if path is a directory
969+ if ( ! util . checkDirectoryExistsSync ( resolvedIncludePath ) ) {
970+ let message :string = `Path is not a directory:${ resolvedIncludePath } ` ;
971+ includePathErrors . push ( message ) ;
972+ }
973+ }
974+
975+ if ( includePathErrors . length > 0 ) {
976+ errors . includePath = includePathErrors . join ( '\n' ) ;
977+ }
978+
979+ // Validate intelliSenseMode
980+ if ( isWindows && ! this . isCompilerIntelliSenseModeCompatible ( ) ) {
981+ errors . intelliSenseMode = `IntelliSense mode${ this . CurrentConfiguration . intelliSenseMode } is incompatible with compiler path.` ;
982+ }
983+
984+ return errors ;
985+ }
986+
825987private handleSquiggles ( ) :void {
826988if ( ! this . propertiesFile ) {
827989return ;
@@ -926,35 +1088,14 @@ export class CppProperties {
9261088// TODO: Add squiggles for when the C_Cpp.default.* paths are invalid.
9271089continue ;
9281090}
929- let resolvedPath :string = util . resolveVariables ( curPath , this . ExtendedEnvironment ) ;
930- if ( resolvedPath . includes ( "${workspaceFolder}" ) ) {
931- resolvedPath = resolvedPath . replace ( "${workspaceFolder}" , this . rootUri . fsPath ) ;
932- }
933- if ( resolvedPath . includes ( "${workspaceRoot}" ) ) {
934- resolvedPath = resolvedPath . replace ( "${workspaceRoot}" , this . rootUri . fsPath ) ;
935- }
936- if ( resolvedPath . includes ( "${vcpkgRoot}" ) ) {
937- resolvedPath = resolvedPath . replace ( "${vcpkgRoot}" , util . getVcpkgRoot ( ) ) ;
938- }
939- if ( resolvedPath . includes ( "*" ) ) {
940- resolvedPath = resolvedPath . replace ( / \* / g, "" ) ;
1091+
1092+ let resolvedPath :string = this . resolvePath ( curPath , isWindows ) ;
1093+ if ( ! resolvedPath ) {
1094+ continue ;
9411095}
9421096
9431097// TODO: Invalid paths created from environment variables are not detected.
9441098
945- // Handle WSL paths.
946- const isWSL :boolean = isWindows && resolvedPath . startsWith ( "/" ) ;
947- if ( isWSL ) {
948- const mntStr :string = "/mnt/" ;
949- if ( resolvedPath . length > "/mnt/c/" . length && resolvedPath . substr ( 0 , mntStr . length ) === mntStr ) {
950- resolvedPath = resolvedPath . substr ( mntStr . length ) ;
951- resolvedPath = resolvedPath . substr ( 0 , 1 ) + ":" + resolvedPath . substr ( 1 ) ;
952- } else if ( this . rootfs && this . rootfs . length > 0 ) {
953- resolvedPath = this . rootfs + resolvedPath . substr ( 1 ) ;
954- // TODO: Handle WSL symlinks.
955- }
956- }
957-
9581099let compilerPathNeedsQuotes :boolean = false ;
9591100if ( isCompilerPath ) {
9601101resolvedPath = resolvedPath . trim ( ) ;
@@ -967,6 +1108,7 @@ export class CppProperties {
9671108resolvedPath = compilerPathAndArgs . compilerPath ;
9681109}
9691110
1111+ const isWSL :boolean = isWindows && resolvedPath . startsWith ( "/" ) ;
9701112let pathExists :boolean = true ;
9711113let existsWithExeAdded :( path :string ) => boolean = ( path :string ) => {
9721114return isCompilerPath && isWindows && ! isWSL && fs . existsSync ( path + ".exe" ) ;