@@ -88,22 +88,23 @@ trait ConfigTreeQueries {
88
88
fn client_config ( & self ) -> Option < PointerCmp < ConfigInput > > ;
89
89
90
90
#[ salsa:: input]
91
- fn config_parent ( & self , file_id : FileId ) -> Option < FileId > ;
91
+ fn parent ( & self , file_id : FileId ) -> Option < FileId > ;
92
92
93
93
#[ salsa:: input]
94
94
fn config_input ( & self , file_id : FileId ) -> Option < PointerCmp < ConfigInput > > ;
95
95
96
- fn compute_recursive ( & self , file_id : FileId ) -> PointerCmp < LocalConfigData > ;
96
+ fn recursive_local ( & self , file_id : FileId ) -> PointerCmp < LocalConfigData > ;
97
97
98
- fn local_config ( & self , file_id : FileId ) -> PointerCmp < LocalConfigData > ;
98
+ /// The output
99
+ fn computed_local_config ( & self , file_id : FileId ) -> PointerCmp < LocalConfigData > ;
99
100
}
100
101
101
- fn compute_recursive ( db : & dyn ConfigTreeQueries , file_id : FileId ) -> PointerCmp < LocalConfigData > {
102
+ fn recursive_local ( db : & dyn ConfigTreeQueries , file_id : FileId ) -> PointerCmp < LocalConfigData > {
102
103
let self_input = db. config_input ( file_id) ;
103
104
tracing:: trace!( ?self_input, ?file_id) ;
104
- match db. config_parent ( file_id) {
105
+ match db. parent ( file_id) {
105
106
Some ( parent) if parent != file_id => {
106
- let parent_computed = db. compute_recursive ( parent) ;
107
+ let parent_computed = db. recursive_local ( parent) ;
107
108
if let Some ( input) = self_input. as_deref ( ) {
108
109
PointerCmp :: new ( parent_computed. clone_with_overrides ( input. local . clone ( ) ) )
109
110
} else {
@@ -122,8 +123,11 @@ fn compute_recursive(db: &dyn ConfigTreeQueries, file_id: FileId) -> PointerCmp<
122
123
}
123
124
}
124
125
125
- fn local_config ( db : & dyn ConfigTreeQueries , file_id : FileId ) -> PointerCmp < LocalConfigData > {
126
- let computed = db. compute_recursive ( file_id) ;
126
+ fn computed_local_config (
127
+ db : & dyn ConfigTreeQueries ,
128
+ file_id : FileId ,
129
+ ) -> PointerCmp < LocalConfigData > {
130
+ let computed = db. recursive_local ( file_id) ;
127
131
if let Some ( client) = db. client_config ( ) {
128
132
PointerCmp :: new ( computed. clone_with_overrides ( client. local . clone ( ) ) )
129
133
} else {
@@ -149,14 +153,35 @@ impl ConfigDb {
149
153
} ;
150
154
this. set_client_config ( None ) ;
151
155
this. ensure_node ( xdg_config_file_id) ;
152
- this. set_config_parent ( xdg_config_file_id, None ) ;
153
156
this
154
157
}
155
158
159
+ /// Gets the value of LocalConfigData for a given `rust-analyzer.toml` FileId.
160
+ ///
161
+ /// The rust-analyzer.toml does not need to exist on disk. All values are the expression of
162
+ /// overriding the parent `rust-analyzer.toml`, set by adding an entry in
163
+ /// `ConfigChanges.parent_changes`.
164
+ ///
165
+ /// If the db is not aware of the given `rust-analyzer.toml` FileId, then the config is read
166
+ /// from the user's system-wide default config.
167
+ ///
168
+ /// Note that the client config overrides all configs.
169
+ pub fn local_config ( & self , ra_toml_file_id : FileId ) -> Arc < LocalConfigData > {
170
+ if self . known_file_ids . contains ( & ra_toml_file_id) {
171
+ self . computed_local_config ( ra_toml_file_id) . 0
172
+ } else {
173
+ tracing:: warn!( ?ra_toml_file_id, "called local_config with unknown file id" ) ;
174
+ self . computed_local_config ( self . xdg_config_file_id ) . 0
175
+ }
176
+ }
177
+
178
+ /// Applies a bunch of [`ConfigChanges`]. The FileIds referred to in `ConfigChanges` do not
179
+ /// need to exist. You can generate the `parent_changes` hashmap by iterating ancestors of all
180
+ /// of the [`ide::SourceRoot`]s, slapping `.map(|path| path.join("rust-analyzer.toml"))`.
156
181
pub fn apply_changes ( & mut self , changes : ConfigChanges , vfs : & Vfs ) -> Vec < ConfigTreeError > {
157
182
let mut scratch_errors = Vec :: new ( ) ;
158
183
let mut errors = Vec :: new ( ) ;
159
- let ConfigChanges { client_change, ra_toml_changes, mut parent_changes } = changes;
184
+ let ConfigChanges { client_change, ra_toml_changes, parent_changes } = changes;
160
185
161
186
if let Some ( change) = client_change {
162
187
let current = self . client_config ( ) ;
@@ -174,9 +199,6 @@ impl ConfigDb {
174
199
// turn and face the strain
175
200
match change. change_kind {
176
201
vfs:: ChangeKind :: Create | vfs:: ChangeKind :: Modify => {
177
- if change. change_kind == vfs:: ChangeKind :: Create {
178
- parent_changes. entry ( change. file_id ) . or_insert ( ConfigParent :: UserDefault ) ;
179
- }
180
202
let input = parse_toml ( change. file_id , vfs, & mut scratch_errors, & mut errors)
181
203
. map ( PointerCmp ) ;
182
204
tracing:: trace!( "updating toml for {:?} to {:?}" , change. file_id, input) ;
@@ -203,15 +225,25 @@ impl ConfigDb {
203
225
} ;
204
226
// order of children within the parent node does not matter
205
227
tracing:: trace!( "appending child {file_id:?} to {parent_node_id:?}" ) ;
206
- self . set_config_parent ( file_id, Some ( parent_node_id) )
228
+ self . set_parent ( file_id, Some ( parent_node_id) )
207
229
}
208
230
209
231
errors
210
232
}
211
233
234
+ /// Inserts default values into the salsa inputs for the given file_id
235
+ /// if it's never been seen before
212
236
fn ensure_node ( & mut self , file_id : FileId ) {
213
237
if self . known_file_ids . insert ( file_id) {
214
238
self . set_config_input ( file_id, None ) ;
239
+ self . set_parent (
240
+ file_id,
241
+ if file_id == self . xdg_config_file_id {
242
+ None
243
+ } else {
244
+ Some ( self . xdg_config_file_id )
245
+ } ,
246
+ ) ;
215
247
}
216
248
}
217
249
}
@@ -250,17 +282,15 @@ fn parse_toml(
250
282
251
283
#[ cfg( test) ]
252
284
mod tests {
253
- use std:: path:: PathBuf ;
285
+ use std:: path:: { Path , PathBuf } ;
254
286
287
+ use itertools:: Itertools ;
255
288
use vfs:: { AbsPathBuf , VfsPath } ;
256
289
257
290
fn alloc_file_id ( vfs : & mut Vfs , s : & str ) -> FileId {
258
- tracing_subscriber:: fmt ( ) . init ( ) ;
259
291
let abs_path = AbsPathBuf :: try_from ( PathBuf :: new ( ) . join ( s) ) . unwrap ( ) ;
260
292
261
293
let vfs_path = VfsPath :: from ( abs_path) ;
262
- // FIXME: the vfs should expose this functionality more simply.
263
- // We shouldn't have to clone the vfs path just to get a FileId.
264
294
let file_id = vfs. alloc_file_id ( vfs_path) ;
265
295
vfs. set_file_id_contents ( file_id, None ) ;
266
296
file_id
@@ -270,8 +300,6 @@ mod tests {
270
300
let abs_path = AbsPathBuf :: try_from ( PathBuf :: new ( ) . join ( s) ) . unwrap ( ) ;
271
301
272
302
let vfs_path = VfsPath :: from ( abs_path) ;
273
- // FIXME: the vfs should expose this functionality more simply.
274
- // We shouldn't have to clone the vfs path just to get a FileId.
275
303
let file_id = vfs. alloc_file_id ( vfs_path) ;
276
304
vfs. set_file_id_contents ( file_id, Some ( config. to_string ( ) . into_bytes ( ) ) ) ;
277
305
file_id
@@ -280,6 +308,7 @@ mod tests {
280
308
use super :: * ;
281
309
#[ test]
282
310
fn basic ( ) {
311
+ tracing_subscriber:: fmt ( ) . try_init ( ) . ok ( ) ;
283
312
let mut vfs = Vfs :: default ( ) ;
284
313
let xdg_config_file_id =
285
314
alloc_file_id ( & mut vfs, "/home/username/.config/rust-analyzer/rust-analyzer.toml" ) ;
@@ -364,9 +393,9 @@ mod tests {
364
393
let prev = local;
365
394
let local = config_tree. local_config ( crate_a) ;
366
395
// Should have been recomputed
367
- assert_ne ! ( prev, local) ;
396
+ assert ! ( ! Arc :: ptr_eq ( & prev, & local) ) ;
368
397
// But without changes in between, should give the same Arc back
369
- assert_eq ! ( local, config_tree. local_config( crate_a) ) ;
398
+ assert ! ( Arc :: ptr_eq ( & local, & config_tree. local_config( crate_a) ) ) ;
370
399
371
400
// The newly added xdg_config_file_id should affect the output if nothing else touches
372
401
// this key
@@ -379,4 +408,75 @@ mod tests {
379
408
assert_eq ! ( local. completion_autoimport_enable, false ) ;
380
409
assert_eq ! ( local. semanticHighlighting_strings_enable, false ) ;
381
410
}
411
+
412
+ #[ test]
413
+ fn generated_parent_changes ( ) {
414
+ tracing_subscriber:: fmt ( ) . try_init ( ) . ok ( ) ;
415
+ let mut vfs = Vfs :: default ( ) ;
416
+
417
+ let xdg =
418
+ alloc_file_id ( & mut vfs, "/home/username/.config/rust-analyzer/rust-analyzer.toml" ) ;
419
+ let mut config_tree = ConfigDb :: new ( xdg) ;
420
+
421
+ let project_root = Path :: new ( "/root" ) ;
422
+ let sourceroots =
423
+ [ PathBuf :: new ( ) . join ( "/root/crate_a" ) , PathBuf :: new ( ) . join ( "/root/crate_a/crate_b" ) ] ;
424
+ let sourceroot_tomls = sourceroots
425
+ . iter ( )
426
+ . map ( |dir| dir. join ( "rust-analyzer.toml" ) )
427
+ . map ( |path| AbsPathBuf :: try_from ( path) . unwrap ( ) )
428
+ . map ( |path| vfs. alloc_file_id ( path. into ( ) ) )
429
+ . collect_vec ( ) ;
430
+ let & [ crate_a, crate_b] = & sourceroot_tomls[ ..] else {
431
+ panic ! ( ) ;
432
+ } ;
433
+
434
+ let parent_changes = sourceroots
435
+ . iter ( )
436
+ . flat_map ( |path| {
437
+ path. ancestors ( )
438
+ . take_while ( |x| x. starts_with ( project_root) )
439
+ . map ( |dir| dir. join ( "rust-analyzer.toml" ) )
440
+ . map ( |path| AbsPathBuf :: try_from ( path) . unwrap ( ) )
441
+ . map ( |path| vfs. alloc_file_id ( path. into ( ) ) )
442
+ . collect_vec ( )
443
+ . into_iter ( )
444
+ . tuple_windows ( )
445
+ . map ( |( a, b) | ( a, ConfigParent :: Parent ( b) ) )
446
+ } )
447
+ . collect :: < FxHashMap < _ , _ > > ( ) ;
448
+
449
+ for ( & a, parent) in & parent_changes {
450
+ eprintln ! (
451
+ "{a:?} ({:?}): parent = {parent:?} ({:?})" ,
452
+ vfs. file_path( a) ,
453
+ match parent {
454
+ ConfigParent :: Parent ( p) => vfs. file_path( * p) . to_string( ) ,
455
+ ConfigParent :: UserDefault => "xdg" . to_string( ) ,
456
+ }
457
+ ) ;
458
+ }
459
+
460
+ vfs. set_file_id_contents (
461
+ xdg,
462
+ Some ( b"[inlayHints.discriminantHints]\n enable = \" always\" " . to_vec ( ) ) ,
463
+ ) ;
464
+ vfs. set_file_id_contents ( crate_a, Some ( b"[completion.autoself]\n enable = false" . to_vec ( ) ) ) ;
465
+ // note that crate_b's rust-analyzer.toml doesn't exist
466
+
467
+ let changes = ConfigChanges {
468
+ ra_toml_changes : dbg ! ( vfs. take_changes( ) ) ,
469
+ parent_changes,
470
+ client_change : None ,
471
+ } ;
472
+
473
+ dbg ! ( config_tree. apply_changes( changes, & vfs) ) ;
474
+ let local = config_tree. local_config ( crate_b) ;
475
+
476
+ assert_eq ! (
477
+ local. inlayHints_discriminantHints_enable,
478
+ crate :: config:: DiscriminantHintsDef :: Always
479
+ ) ;
480
+ assert_eq ! ( local. completion_autoself_enable, false ) ;
481
+ }
382
482
}
0 commit comments