@@ -64,6 +64,12 @@ export interface Configuration {
6464 browse ?: Browse ;
6565}
6666
67+ export interface ConfigurationErrors {
68+ compilerPath ?: string ;
69+ includePath ?: string ;
70+ intelliSenseMode ?: string ;
71+ }
72+
6773export interface Browse {
6874 path ?: string [ ] ;
6975 limitSymbolsToIncludedHeaders ?: boolean | string ;
@@ -227,7 +233,9 @@ export class CppProperties {
227233 // Clear out any modifications we may have made internally by parsing the json file
228234 if ( 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
233241 vscode . workspace . openTextDocument ( this . propertiesFile ) ;
@@ -392,8 +400,13 @@ export class CppProperties {
392400 private 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}" ) {
397410 return true ;
398411 }
399412 let compilerPathAndArgs : util . CompilerPathAndArgs = util . extractCompilerPathAndArgs ( this . CurrentConfiguration . compilerPath ) ;
@@ -628,7 +641,9 @@ export class CppProperties {
628641 this . settingsPanel . ConfigValuesChanged ( ( ) => this . saveConfigurationUI ( ) ) ;
629642 this . 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
634649 vscode . workspace . openTextDocument ( this . propertiesFile ) . then ( ( document : vscode . TextDocument ) => {
@@ -646,7 +661,9 @@ export class CppProperties {
646661 if ( 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
652669 vscode . workspace . openTextDocument ( this . propertiesFile ) ;
@@ -660,6 +677,7 @@ export class CppProperties {
660677 this . parsePropertiesFile ( false ) ; // Clear out any modifications we may have made internally.
661678 let config : Configuration = this . settingsPanel . getLastValuesFromConfigUI ( ) ;
662679 this . configurationJson . configurations [ this . currentConfigurationIndex . Value ] = config ;
680+ this . settingsPanel . updateErrors ( this . getErrorsForConfigUI ( ) ) ;
663681 this . writeToJson ( ) ;
664682 }
665683
@@ -822,6 +840,150 @@ export class CppProperties {
822840 return 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+
825987 private handleSquiggles ( ) : void {
826988 if ( ! this . propertiesFile ) {
827989 return ;
@@ -926,35 +1088,14 @@ export class CppProperties {
9261088 // TODO: Add squiggles for when the C_Cpp.default.* paths are invalid.
9271089 continue ;
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-
9581099 let compilerPathNeedsQuotes : boolean = false ;
9591100 if ( isCompilerPath ) {
9601101 resolvedPath = resolvedPath . trim ( ) ;
@@ -967,6 +1108,7 @@ export class CppProperties {
9671108 resolvedPath = compilerPathAndArgs . compilerPath ;
9681109 }
9691110
1111+ const isWSL : boolean = isWindows && resolvedPath . startsWith ( "/" ) ;
9701112 let pathExists : boolean = true ;
9711113 let existsWithExeAdded : ( path : string ) => boolean = ( path : string ) => {
9721114 return isCompilerPath && isWindows && ! isWSL && fs . existsSync ( path + ".exe" ) ;
0 commit comments