77//! configure the server itself, feature flags are passed into analysis, and
88//! tweak things like automatic insertion of `()` in completions.
99
10- use std:: { ffi:: OsString , iter, path:: PathBuf } ;
10+ use std:: { ffi:: OsString , fmt , iter, path:: PathBuf } ;
1111
1212use flycheck:: FlycheckConfig ;
1313use ide:: {
@@ -19,6 +19,7 @@ use ide_db::{
1919 imports:: insert_use:: { ImportGranularity , InsertUseConfig , PrefixKind } ,
2020 SnippetCap ,
2121} ;
22+ use itertools:: Itertools ;
2223use lsp_types:: { ClientCapabilities , MarkupKind } ;
2324use project_model:: {
2425 CargoConfig , ProjectJson , ProjectJsonData , ProjectManifest , RustcSource , UnsetTestCrates ,
@@ -31,9 +32,7 @@ use crate::{
3132 caps:: completion_item_edit_resolve,
3233 diagnostics:: DiagnosticsMapConfig ,
3334 line_index:: OffsetEncoding ,
34- lsp_ext:: supports_utf8,
35- lsp_ext:: WorkspaceSymbolSearchScope ,
36- lsp_ext:: { self , WorkspaceSymbolSearchKind } ,
35+ lsp_ext:: { self , supports_utf8, WorkspaceSymbolSearchKind , WorkspaceSymbolSearchScope } ,
3736} ;
3837
3938// Defines the server-side configuration of the rust-analyzer. We generate
@@ -369,11 +368,11 @@ impl Default for ConfigData {
369368
370369#[ derive( Debug , Clone ) ]
371370pub struct Config {
372- pub caps : lsp_types:: ClientCapabilities ,
371+ pub discovered_projects : Option < Vec < ProjectManifest > > ,
372+ caps : lsp_types:: ClientCapabilities ,
373+ root_path : AbsPathBuf ,
373374 data : ConfigData ,
374375 detached_files : Vec < AbsPathBuf > ,
375- pub discovered_projects : Option < Vec < ProjectManifest > > ,
376- pub root_path : AbsPathBuf ,
377376 snippets : Vec < Snippet > ,
378377}
379378
@@ -505,6 +504,27 @@ pub struct ClientCommandsConfig {
505504 pub trigger_parameter_hints : bool ,
506505}
507506
507+ pub struct ConfigUpdateError {
508+ errors : Vec < ( String , serde_json:: Error ) > ,
509+ }
510+
511+ impl fmt:: Display for ConfigUpdateError {
512+ fn fmt ( & self , f : & mut fmt:: Formatter < ' _ > ) -> fmt:: Result {
513+ let errors = self . errors . iter ( ) . format_with ( "\n " , |( key, e) , f| {
514+ f ( key) ?;
515+ f ( & ": " ) ?;
516+ f ( e)
517+ } ) ;
518+ write ! (
519+ f,
520+ "rust-analyzer found {} invalid config value{}:\n {}" ,
521+ self . errors. len( ) ,
522+ if self . errors. len( ) == 1 { "" } else { "s" } ,
523+ errors
524+ )
525+ }
526+ }
527+
508528impl Config {
509529 pub fn new ( root_path : AbsPathBuf , caps : ClientCapabilities ) -> Self {
510530 Config {
@@ -516,10 +536,8 @@ impl Config {
516536 snippets : Default :: default ( ) ,
517537 }
518538 }
519- pub fn update (
520- & mut self ,
521- mut json : serde_json:: Value ,
522- ) -> Result < ( ) , Vec < ( String , serde_json:: Error ) > > {
539+
540+ pub fn update ( & mut self , mut json : serde_json:: Value ) -> Result < ( ) , ConfigUpdateError > {
523541 tracing:: info!( "updating config from JSON: {:#}" , json) ;
524542 if json. is_null ( ) || json. as_object ( ) . map_or ( false , |it| it. is_empty ( ) ) {
525543 return Ok ( ( ) ) ;
@@ -553,16 +571,41 @@ impl Config {
553571 None => tracing:: info!( "Invalid snippet {}" , name) ,
554572 }
555573 }
574+
575+ self . validate ( & mut errors) ;
576+
556577 if errors. is_empty ( ) {
557578 Ok ( ( ) )
558579 } else {
559- Err ( errors)
580+ Err ( ConfigUpdateError { errors } )
581+ }
582+ }
583+
584+ fn validate ( & self , error_sink : & mut Vec < ( String , serde_json:: Error ) > ) {
585+ use serde:: de:: Error ;
586+ if self . data . checkOnSave_command . is_empty ( ) {
587+ error_sink. push ( (
588+ "/checkOnSave/command" . to_string ( ) ,
589+ serde_json:: Error :: custom ( "expected a non-empty string" ) ,
590+ ) ) ;
560591 }
561592 }
562593
563594 pub fn json_schema ( ) -> serde_json:: Value {
564595 ConfigData :: json_schema ( )
565596 }
597+
598+ pub fn root_path ( & self ) -> & AbsPathBuf {
599+ & self . root_path
600+ }
601+
602+ pub fn caps ( & self ) -> & lsp_types:: ClientCapabilities {
603+ & self . caps
604+ }
605+
606+ pub fn detached_files ( & self ) -> & [ AbsPathBuf ] {
607+ & self . detached_files
608+ }
566609}
567610
568611macro_rules! try_ {
@@ -578,43 +621,31 @@ macro_rules! try_or {
578621
579622impl Config {
580623 pub fn linked_projects ( & self ) -> Vec < LinkedProject > {
581- if self . data . linkedProjects . is_empty ( ) {
582- self . discovered_projects
583- . as_ref ( )
584- . into_iter ( )
585- . flatten ( )
586- . cloned ( )
587- . map ( LinkedProject :: from)
588- . collect ( )
589- } else {
590- self . data
591- . linkedProjects
624+ match self . data . linkedProjects . as_slice ( ) {
625+ [ ] => match self . discovered_projects . as_ref ( ) {
626+ Some ( discovered_projects) => {
627+ discovered_projects. iter ( ) . cloned ( ) . map ( LinkedProject :: from) . collect ( )
628+ }
629+ None => Vec :: new ( ) ,
630+ } ,
631+ linked_projects => linked_projects
592632 . iter ( )
593- . filter_map ( |linked_project| {
594- let res = match linked_project {
595- ManifestOrProjectJson :: Manifest ( it) => {
596- let path = self . root_path . join ( it) ;
597- ProjectManifest :: from_manifest_file ( path)
598- . map_err ( |e| {
599- tracing:: error!( "failed to load linked project: {}" , e)
600- } )
601- . ok ( ) ?
602- . into ( )
603- }
604- ManifestOrProjectJson :: ProjectJson ( it) => {
605- ProjectJson :: new ( & self . root_path , it. clone ( ) ) . into ( )
606- }
607- } ;
608- Some ( res)
633+ . filter_map ( |linked_project| match linked_project {
634+ ManifestOrProjectJson :: Manifest ( it) => {
635+ let path = self . root_path . join ( it) ;
636+ ProjectManifest :: from_manifest_file ( path)
637+ . map_err ( |e| tracing:: error!( "failed to load linked project: {}" , e) )
638+ . ok ( )
639+ . map ( Into :: into)
640+ }
641+ ManifestOrProjectJson :: ProjectJson ( it) => {
642+ Some ( ProjectJson :: new ( & self . root_path , it. clone ( ) ) . into ( ) )
643+ }
609644 } )
610- . collect ( )
645+ . collect ( ) ,
611646 }
612647 }
613648
614- pub fn detached_files ( & self ) -> & [ AbsPathBuf ] {
615- & self . detached_files
616- }
617-
618649 pub fn did_save_text_document_dynamic_registration ( & self ) -> bool {
619650 let caps =
620651 try_or ! ( self . caps. text_document. as_ref( ) ?. synchronization. clone( ) ?, Default :: default ( ) ) ;
0 commit comments