@@ -16,6 +16,9 @@ import { ToolkitError } from './errors'
16
16
17
17
type Workspace = Pick < typeof vscode . workspace , 'getConfiguration' | 'onDidChangeConfiguration' >
18
18
19
+ /** Used by isValid(). Must be something that's defined in our package.json. */
20
+ const testSetting = 'aws.samcli.lambdaTimeout'
21
+
19
22
/**
20
23
* A class for manipulating VS Code user settings (from all extensions).
21
24
*
@@ -86,39 +89,46 @@ export class Settings {
86
89
}
87
90
88
91
/**
89
- * Checks that the user `settings.json` works correctly . #3910
92
+ * Checks that user `settings.json` actually works . #3910
90
93
*
91
- * Note: This checks that we can actually _write_ settings. vscode notifies the user if
92
- * settings.json is complete nonsense, but is silent if there are only "recoverable" JSON syntax
93
- * errors.
94
+ * Note: This checks that we can actually "roundtrip" (read and write) settings. vscode notifies
95
+ * the user if settings.json is complete nonsense, but silently fails if there are only
96
+ * "recoverable" JSON syntax errors.
94
97
*/
95
- public async isValid ( ) : Promise < boolean > {
96
- const key = 'aws.samcli.lambdaTimeout'
98
+ public async isValid ( ) : Promise < 'ok' | 'invalid' | 'nowrite' > {
99
+ const key = testSetting
97
100
const config = this . getConfig ( )
101
+ const tempValOld = 1234 // Legacy temp value we are migrating from.
102
+ const tempVal = 91234 // Temp value used to check that read/write works.
98
103
const defaultVal = settingsProps [ key ] . default
99
104
100
105
try {
101
- const oldVal = config . get < number > ( key )
102
- // Try to write to settings.json.
103
- await config . update ( key , 1234 , this . updateTarget )
104
- // Restore the old value, if any.
105
- if ( oldVal === undefined || defaultVal === oldVal ) {
106
- // vscode will return the default even if there was no entry in settings.json.
107
- // Avoid polluting the user's settings.json unnecessarily.
106
+ const userVal = config . get < number > ( key )
107
+ // Try to write a temporary "sentinel" value to settings.json.
108
+ await config . update ( key , tempVal , this . updateTarget )
109
+ if ( userVal === undefined || [ defaultVal , tempValOld , tempVal ] . includes ( userVal ) ) {
110
+ // Avoid polluting the user's settings.json.
108
111
await config . update ( key , undefined , this . updateTarget )
109
112
} else {
110
- await config . update ( key , oldVal , this . updateTarget )
113
+ // Restore the user's actual setting value.
114
+ await config . update ( key , userVal , this . updateTarget )
111
115
}
112
116
113
- return true
117
+ return 'ok'
114
118
} catch ( e ) {
115
- getLogger ( ) . warn (
116
- 'settings: invalid "settings.json" file? failed to update "%s": %s' ,
117
- key ,
118
- ( e as Error ) . message
119
- )
119
+ const err = e as Error
120
+ // If anything tries to update an unwritable settings.json, vscode will thereafter treat
121
+ // it as an "unsaved" file. #4043
122
+ if ( err . message . includes ( 'EACCES' ) || err . message . includes ( 'the file has unsaved changes' ) ) {
123
+ const logMsg = 'settings: unwritable settings.json: %s'
124
+ getLogger ( ) . warn ( logMsg , err . message )
125
+ return 'nowrite'
126
+ }
120
127
121
- return false
128
+ const logMsg = 'settings: invalid settings.json: %s'
129
+ getLogger ( ) . error ( logMsg , err . message )
130
+
131
+ return 'invalid'
122
132
}
123
133
}
124
134
@@ -379,7 +389,9 @@ function createSettingsClass<T extends TypeDescriptor>(section: string, descript
379
389
}
380
390
381
391
for ( const key of props . filter ( isDifferent ) ) {
382
- this . log ( 'key "%s" changed' , key )
392
+ if ( `${ section } .${ key } ` !== testSetting ) {
393
+ this . log ( 'key "%s" changed' , key )
394
+ }
383
395
emitter . fire ( { key } )
384
396
}
385
397
} )
0 commit comments